diff options
| author | BossCode45 <human.cyborg42@gmail.com> | 2024-10-01 14:54:37 +1300 |
|---|---|---|
| committer | BossCode45 <human.cyborg42@gmail.com> | 2024-10-01 14:54:37 +1300 |
| commit | 915532bf8fbda9ba2a36e04fcd6acc67c6c68fa5 (patch) | |
| tree | 0d7a7569ab5fc30c90d5df91a54d312c764cf328 /src | |
| parent | f998705c5a0e50021875a811537962083b73ed26 (diff) | |
| download | YATwm-915532bf8fbda9ba2a36e04fcd6acc67c6c68fa5.tar.gz YATwm-915532bf8fbda9ba2a36e04fcd6acc67c6c68fa5.zip | |
Restructure
Diffstat (limited to 'src')
| -rw-r--r-- | src/IPC.cpp | 86 | ||||
| -rw-r--r-- | src/IPC.h | 26 | ||||
| -rw-r--r-- | src/commands.cpp | 335 | ||||
| -rw-r--r-- | src/commands.h | 89 | ||||
| -rw-r--r-- | src/config.cpp | 132 | ||||
| -rw-r--r-- | src/config.h | 53 | ||||
| -rw-r--r-- | src/error.h | 26 | ||||
| -rw-r--r-- | src/ewmh.cpp | 101 | ||||
| -rw-r--r-- | src/ewmh.h | 25 | ||||
| -rw-r--r-- | src/keybinds.cpp | 282 | ||||
| -rw-r--r-- | src/keybinds.h | 59 | ||||
| -rw-r--r-- | src/main.cpp | 1159 | ||||
| -rw-r--r-- | src/structs.h | 48 | ||||
| -rw-r--r-- | src/util.cpp | 32 | ||||
| -rw-r--r-- | src/util.h | 19 |
15 files changed, 2472 insertions, 0 deletions
diff --git a/src/IPC.cpp b/src/IPC.cpp new file mode 100644 index 0000000..0aed97e --- /dev/null +++ b/src/IPC.cpp @@ -0,0 +1,86 @@ +#include "IPC.h" +#include "ewmh.h" + +#include <cstring> +#include <string> +#include <sys/socket.h> +#include <iostream> +#include <unistd.h> + +using std::cout, std::endl; + +static const char* path = "/tmp/YATwm.sock"; + +IPCModule::IPCModule(CommandsModule& commandsModule, Config& cfg, Globals& globals) + :commandsModule(commandsModule), + cfg(cfg), + globals(globals) +{ + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + address.sun_family = AF_UNIX; + strcpy(address.sun_path, path); + unlink(address.sun_path); + len = strlen(address.sun_path) + sizeof(address.sun_family); + + if(bind(sockfd, (sockaddr*)&address, len) == -1) + { + cout << "ERROR " << errno << endl; + } + cout << "SOCKETED" << endl; +} + +void IPCModule::init() +{ + setIPCPath((unsigned char*)path, strlen(path)); +} + +void IPCModule::doListen() +{ + if(listen(sockfd, 1) != 0) + { + cout << "ERROR 2" << endl; + return; + } + if(first) + { + first = false; + return; + } + cout << "DOLISTEN" << endl; + unsigned int socklen = 0; + sockaddr_un remote; + int newsock = accept(sockfd, (sockaddr*)&remote, &socklen); + cout << "LISTENING" << endl; + char buffer[256]; + memset(buffer, 0, 256); + read(newsock, buffer, 256); + std::string command(buffer); + while(command[command.size() - 1] == 0 || command[command.size() - 1] == '\n') + command = command.substr(0, command.size() - 1); + //cout << '"' << command << '"' << endl; + try + { + commandsModule.runCommand(command); + } + catch(Err e) + { + cout << e.code << " " << e.message << endl; + } + char* message = "RAN COMMAND"; + send(newsock, message, strlen(message), 0); + cout << "RAN COMMAND" << endl; + shutdown(newsock, SHUT_RDWR); + close(newsock); +} + +void IPCModule::quitIPC() +{ + close(sockfd); +} + +int IPCModule::getFD() +{ + if(sockfd > 0) + return sockfd; + return -1; +} diff --git a/src/IPC.h b/src/IPC.h new file mode 100644 index 0000000..89e67e5 --- /dev/null +++ b/src/IPC.h @@ -0,0 +1,26 @@ +#pragma once + +#include <sys/socket.h> +#include <sys/un.h> + +#include "commands.h" +#include "config.h" +#include "util.h" + +class IPCModule +{ +public: + IPCModule(CommandsModule& commandsModule, Config& cfg, Globals& globals); + void init(); + void doListen(); + void quitIPC(); + int getFD(); +private: + CommandsModule& commandsModule; + Config& cfg; + Globals& globals; + int sockfd; + int len; + bool first = true; + sockaddr_un address; +}; diff --git a/src/commands.cpp b/src/commands.cpp new file mode 100644 index 0000000..57cfd0b --- /dev/null +++ b/src/commands.cpp @@ -0,0 +1,335 @@ +#include "commands.h" +#include "error.h" +#include "util.h" + +#include <cctype> +#include <iostream> +#include <algorithm> +#include <iterator> +#include <stdexcept> +#include <string> +#include <utility> +#include <vector> +#include <regex> +#include <cstring> + +using std::cout, std::endl, std::string, std::vector; + +const void CommandsModule::echo(const CommandArg* argv) +{ + cout << argv[0].str << endl; +} + +CommandsModule::CommandsModule() +{ + addCommand("echo", &CommandsModule::echo, 1, {STR_REST}, this); +} +CommandsModule::~CommandsModule() +{ + for(Command c : commandList) + { + if(c.argc > 0) + delete[] c.argTypes; + } +} + +void CommandsModule::addCommand(Command c) +{ + if(lookupCommand(c.name) != nullptr) + { + cout << "Duplicate command: " << c.name << endl; + } + commandList.push_back(c); +} +void CommandsModule::addCommand(std::string name, const void (*func)(const CommandArg *), const int argc, CommandArgType *argTypes) +{ + Command c = {name, nullptr, func, argc, argTypes, nullptr}; + addCommand(c); +} +void CommandsModule::addCommand(std::string name, const void(*func)(const CommandArg*), const int argc, std::vector<CommandArgType> argTypes) +{ + CommandArgType* argTypesArr = new CommandArgType[argc]; + for(int i = 0; i < argc; i++) + { + argTypesArr[i] = argTypes[i]; + } + addCommand(name, func, argc, argTypesArr); +} + +struct NameMatches +{ + NameMatches(string s): s_{s} {} + bool operator()(Command c) { return (c.name == s_); } + string s_; +}; + +Command* CommandsModule::lookupCommand(string name) +{ + auto elem = std::find_if(commandList.begin(), commandList.end(), NameMatches(name)); + if (elem != commandList.end()) + { + int i = elem - commandList.begin(); + return &(commandList[i]); + } + else + { + return nullptr; + } +} + +vector<string> CommandsModule::splitCommand(string command) +{ + vector<string> v; + string arg = ""; + bool inQuotes = false; + bool escapeNext = true; + char quoteType; + for(int i = 0; i < command.size(); i++) + { + if(escapeNext) + { + arg += command[i]; + escapeNext = false; + } + else if(command[i] == '\\') + { + escapeNext = true; + } + else if(inQuotes) + { + if(command[i] == quoteType) + { + if(arg != "") + { + v.push_back(arg); + arg = ""; + } + inQuotes = false; + } + else + { + arg += command[i]; + } + } + else + { + if(command[i] == ' ') + { + if(arg != "") + { + v.push_back(arg); + arg = ""; + } + } + else if(command[i] == '"' || command[i] == '\'') + { + inQuotes = true; + quoteType = command[i]; + } + else + { + arg += command[i]; + } + } + } + if(arg != "") + v.push_back(arg); + return v; +} + +CommandArg* CommandsModule::getCommandArgs(vector<string>& split, const CommandArgType* argTypes, const int argc) +{ + CommandArg* args = new CommandArg[argc]; + for(int i = 1; i < argc + 1; i++) + { + switch(argTypes[i-1]) + { + case STR: args[i-1].str = (char*)split[i].c_str(); break; + case NUM: + { + try + { + args[i-1].num = std::stoi(split[i]); + break; + } + catch(std::invalid_argument e) + { + delete[] args; + throw Err(CMD_ERR_WRONG_ARGS, split[i] + " is not a number!"); + } + } + case MOVDIR: + { + if(lowercase(split[i]) == "up") + args[i-1].dir = UP; + else if(lowercase(split[i]) == "down") + args[i-1].dir = DOWN; + else if(lowercase(split[i]) == "left") + args[i-1].dir = LEFT; + else if(lowercase(split[i]) == "right") + args[i-1].dir = RIGHT; + else + { + delete[] args; + throw Err(CMD_ERR_WRONG_ARGS, split[i] + " is not a direction!"); + } + break; + } + case STR_REST: + { + string rest = ""; + for(int j = i; j < split.size(); j++) + { + rest += split[j]; + if(j != split.size() - 1) + rest += " "; + } + args[i-1].str = new char[rest.size()]; + strcpy(args[i-1].str, rest.c_str()); + return args; + } + case NUM_ARR_REST: + { + int* rest = new int[split.size() - i]; + for(int j = 0; j < split.size() - i; j++) + { + try + { + rest[j] = std::stoi(split[j + i]); + } + catch(std::invalid_argument e) + { + delete[] rest; + delete[] args; + throw Err(CMD_ERR_WRONG_ARGS, split[i] + " is not a number!"); + } + } + args[i-1].numArr = {rest, (int) split.size() - i}; + return args; + } + default: cout << "UH OH SOMETHING IS VERY WRONG" << endl; + } + } + return args; +} + +void CommandsModule::runCommand(string command) +{ + vector<string> split = splitCommand(command); + vector<string>::const_iterator start = split.begin(); + int count = 0; + for(string s : split) + { + if(s == ";") + { + vector<string>::const_iterator end = start + count; + vector<string> partialCmd(start, end); + runCommand(partialCmd); + count = 0; + start = end + 1; + } + else + { + count++; + } + } + if(start != split.end()) + { + vector<string> partialCmd(start, (vector<string>::const_iterator)split.end()); + runCommand(partialCmd); + } +} +void CommandsModule::runCommand(vector<string> split) +{ + Command* cmd = lookupCommand(split[0]); + if(cmd == nullptr) + throw Err(CMD_ERR_NOT_FOUND, split[0] + " is not a valid command name"); + if(cmd->argc > split.size() - 1) + throw Err(CMD_ERR_WRONG_ARGS, "wrong number of arguments"); + CommandArg* args; + try + { + args = getCommandArgs(split, cmd->argTypes, cmd->argc); + } + catch(Err e) + { + throw e; + } + try + { + if(cmd->module == nullptr) + cmd->staticFunc(args); + else + cmd->func(*cmd->module, args); + } + catch (Err e) + { + for(int i = 0; i < cmd->argc; i++) + { + if(cmd->argTypes[i] == STR_REST) + delete[] args[i].str; + } + delete[] args; + throw e; + } + for(int i = 0; i < cmd->argc; i++) + { + if(cmd->argTypes[i] == STR_REST) + delete[] args[i].str; + } + delete[] args; +} + +vector<Err> CommandsModule::checkCommand(string command) +{ + vector<Err> errs; + vector<string> split = splitCommand(command); + vector<string>::const_iterator start = split.begin(); + int count = 0; + for(string s : split) + { + if(s == ";") + { + vector<string>::const_iterator end = start + count; + vector<string> partialCmd(start, end); + errs.push_back(checkCommand(partialCmd)); + count = 0; + start = end + 1; + } + else + { + count++; + } + } + if(start != split.end()) + { + vector<string> partialCmd(start, (vector<string>::const_iterator)split.end()); + errs.push_back(checkCommand(partialCmd)); + } + return errs; +} + +Err CommandsModule::checkCommand(vector<string> split) +{ + Command* cmd = lookupCommand(split[0]); + if(cmd == nullptr) + return Err(CMD_ERR_NOT_FOUND, split[0] + " is not a valid command name"); + if(cmd->argc > split.size()) + return Err(CMD_ERR_WRONG_ARGS, "wrong number of arguments"); + CommandArg* args; + try + { + args = getCommandArgs(split, cmd->argTypes, cmd->argc); + } + catch(Err e) + { + return e; + } + for(int i = 0; i < cmd->argc; i++) + { + if(cmd->argTypes[i] == STR_REST || cmd->argTypes[i] == NUM_ARR_REST) + delete[] args[i].str; + } + delete[] args; + return Err(NOERR, ""); +} diff --git a/src/commands.h b/src/commands.h new file mode 100644 index 0000000..af4a4a0 --- /dev/null +++ b/src/commands.h @@ -0,0 +1,89 @@ +#pragma once + +#include "error.h" + +#include <vector> +#include <string> +#include <any> +#include <functional> + +enum MoveDir + { + UP, + RIGHT, + DOWN, + LEFT + }; +enum CommandArgType + { + STR, + NUM, + MOVDIR, + STR_REST, + NUM_ARR_REST + }; + +struct NumArr +{ + int* arr; + int size; +}; +typedef union +{ + char* str; + int num; + NumArr numArr; + MoveDir dir; +} CommandArg; + +struct Command +{ + const std::string name; + const std::function<void(std::any&, const CommandArg* argv)> func; + const std::function<void(const CommandArg* argv)> staticFunc; + const int argc; + CommandArgType* argTypes; + std::any* module; +}; +class CommandsModule +{ +private: + std::vector<Command> commandList; + std::vector<std::string> splitCommand(std::string command); + CommandArg* getCommandArgs(std::vector<std::string>& args, const CommandArgType* argTypes, const int argc); + const void echo(const CommandArg* argv); +public: + CommandsModule(); + ~CommandsModule(); + template <class T> + void addCommand(std::string name, const void(T::*func)(const CommandArg*), const int argc, CommandArgType* argTypes, T* module); + void addCommand(std::string name, const void(*func)(const CommandArg*), const int argc, CommandArgType* argTypes); + template <class T> + void addCommand(std::string name, const void(T::*func)(const CommandArg*), const int argc, std::vector<CommandArgType> argTypes, T* module); + void addCommand(std::string name, const void(*func)(const CommandArg*), const int argc, std::vector<CommandArgType> argTypes); + void addCommand(Command c); + Command* lookupCommand(std::string name); + void runCommand(std::string command); + void runCommand(std::vector<std::string> split); + std::vector<Err> checkCommand(std::string command); + Err checkCommand(std::vector<std::string> split); +}; + +// YES I KNOW THIS IS BAD +// but it needs to be done this way +template <class T> +void CommandsModule::addCommand(std::string name, const void(T::*func)(const CommandArg*), const int argc, CommandArgType* argTypes, T* module) +{ + Command c = {name, (const void*(std::any::*)(const CommandArg* argv)) func, nullptr, argc, argTypes, (std::any*)module}; + addCommand(c); +} +template <class T> +void CommandsModule::addCommand(std::string name, const void(T::*func)(const CommandArg*), const int argc, std::vector<CommandArgType> argTypes, T* module) +{ + CommandArgType* argTypesArr = new CommandArgType[argc]; + for(int i = 0; i < argc; i++) + { + argTypesArr[i] = argTypes[i]; + } + addCommand(name, func, argc, argTypesArr, module); +} diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..3af2491 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,132 @@ +#include "config.h" +#include "commands.h" +#include "error.h" + +#include <X11/Xlib.h> + +#include <cstdio> +#include <cstring> +#include <fstream> +#include <ios> +#include <string> +#include <vector> +#include <sstream> +#include <unistd.h> +#include <fcntl.h> + +//Just for testing +#include <iostream> + +using std::string; + +// For testing +using std::cout, std::endl; + +const void Config::gapsCmd(const CommandArg* argv) +{ + gaps = argv[0].num; +} + +const void Config::outerGapsCmd(const CommandArg* argv) +{ + outerGaps = argv[0].num; +} + +const void Config::logFileCmd(const CommandArg* argv) +{ + logFile = argv[0].str; +} + +const void Config::addWorkspaceCmd(const CommandArg* argv) +{ + int* prefs = new int[argv[1].numArr.size]; + for(int i = 0; i < argv[1].numArr.size; i++) + { + prefs[i] = argv[1].numArr.arr[i] - 1; + } + workspaces.push_back({argv[0].str, prefs, argv[1].numArr.size}); + numWS++; +} + +const void Config::swapSuperAltCmd(const CommandArg* argv) +{ + swapSuperAlt ^= true; +} + +Config::Config(CommandsModule& commandsModule) + : commandsModule(commandsModule) +{ + //Register commands for config + commandsModule.addCommand("gaps", &Config::gapsCmd, 1, {NUM}, this); + commandsModule.addCommand("outergaps", &Config::outerGapsCmd, 1, {NUM}, this); + commandsModule.addCommand("logfile", &Config::logFileCmd, 1, {STR_REST}, this); + commandsModule.addCommand("addworkspace", &Config::addWorkspaceCmd, 2, {STR, NUM_ARR_REST}, this); + commandsModule.addCommand("swapmods", &Config::swapSuperAltCmd, 0, {}, this); +} + +std::vector<Err> Config::reloadFile() +{ + if(!loaded) + return {{CFG_ERR_NON_FATAL, "Not loaded config yet"}}; + return loadFromFile(file); +} + +std::vector<Err> Config::loadFromFile(std::string path) +{ + std::vector<Err> errs; + + file = path; + + std::ifstream config(path); + if(!config.good()) + { + config = std::ifstream("/etc/YATwm/config"); + errs.push_back({CFG_ERR_FATAL, "Using default config: /etc/YATwm/config"}); + } + + //Set defaults + gaps = 10; + outerGaps = 10; + logFile = "/tmp/yatlog.txt"; + numWS = 0; + swapSuperAlt = false; + workspaces = std::vector<Workspace>(); + + //Probably need something for workspaces and binds too... + + string cmd; + int line = 0; + while(getline(config, cmd)) + { + line++; + if(cmd.size() == 0) + continue; + if(cmd.at(0) == '#') + continue; + try + { + commandsModule.runCommand(cmd); + } + catch (Err e) + { + errs.push_back({e.code, "Error in config (line " + std::to_string(line) + "): " + std::to_string(e.code) + "\n\tMessage: " + e.message}); + + } + } + loaded = true; + return errs; +} + +Config::~Config() +{ + free(); +} +void Config::free() +{ + if(!loaded) + return; + for(Workspace w : workspaces) + { + delete [] w.screenPreferences; + } +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..452db9c --- /dev/null +++ b/src/config.h @@ -0,0 +1,53 @@ +#pragma once + +#include "commands.h" +#include <X11/X.h> +#include <X11/keysym.h> + +#include <string> +#include <vector> + +struct Workspace +{ + std::string name; + int* screenPreferences; + int screenPreferencesc; +}; + +#define COMMAND(X) \ + const void X (const CommandArg* argv) + +class Config +{ +public: + Config(CommandsModule& commandsModule); + ~Config(); + void free(); + + std::vector<Err> loadFromFile(std::string path); + std::vector<Err> reloadFile(); + + // Main + int gaps; + int outerGaps; + std::string logFile; + + // Workspaces + std::vector<Workspace> workspaces; + int numWS; + bool loaded = false; + + // Binds + bool swapSuperAlt; + + // Config Commands + COMMAND(gapsCmd); + COMMAND(outerGapsCmd); + COMMAND(logFileCmd); + COMMAND(addWorkspaceCmd); + COMMAND(swapSuperAltCmd); + +private: + CommandsModule& commandsModule; + std::string file; +}; diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..f0a67a5 --- /dev/null +++ b/src/error.h @@ -0,0 +1,26 @@ +#pragma once + +#include <string> + +typedef unsigned int ErrCode; + +#define NOERR 0 +#define ERR_NON_FATAL 110 +#define ERR_FATAL 120 +#define CFG_ERR_NON_FATAL 210 +#define CFG_ERR_KEYBIND 211 +#define CFG_ERR_FATAL 220 +#define CMD_ERR_NON_FATAL 310 +#define CMD_ERR_NOT_FOUND 311 +#define CMD_ERR_WRONG_ARGS 312 +#define CMD_ERR_FATAL 320 +struct Err +{ + ErrCode code; + std::string message; + Err(ErrCode code, std::string message) + { + this->code = code; + this->message = message; + } +}; diff --git a/src/ewmh.cpp b/src/ewmh.cpp new file mode 100644 index 0000000..a3cc505 --- /dev/null +++ b/src/ewmh.cpp @@ -0,0 +1,101 @@ +#include "ewmh.h" +#include <X11/X.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <iostream> +#include <ostream> +#include <string> + +Display** dpy_; +Window* root_; + +void initEWMH(Display** dpy, Window* root, int numWS, std::vector<Workspace> workspaces) +{ + dpy_ = dpy; + root_ = root; + + Atom supported[] = {XInternAtom(*dpy_, "_NET_NUMBER_OF_DESKTOPS", false), XInternAtom(*dpy_, "_NET_DESKTOP_NAMES", false), XInternAtom(*dpy_, "_NET_CLIENT_LIST", false), XInternAtom(*dpy_, "_NET_CURRENT_DESKTOP", false)}; + int wsNamesLen = numWS; //For null bytes + for(int i = 0; i < numWS; i++) + { + wsNamesLen += workspaces[i].name.length(); + } + char wsNames[wsNamesLen]; + int pos = 0; + for(int i = 0; i < numWS; i++) + { + for(char toAdd : workspaces[i].name) + { + wsNames[pos++] = toAdd; + } + wsNames[pos++] = '\0'; + } + unsigned long numDesktops = numWS; + Atom netSupportedAtom = XInternAtom(*dpy_, "_NET_SUPPORTED", false); + Atom netNumDesktopsAtom = XInternAtom(*dpy_, "_NET_NUMBER_OF_DESKTOPS", false); + Atom netDesktopNamesAtom = XInternAtom(*dpy_, "_NET_DESKTOP_NAMES", false); + Atom XA_UTF8STRING = XInternAtom(*dpy_, "UTF8_STRING", false); + XChangeProperty(*dpy_, *root_, netSupportedAtom, XA_ATOM, 32, PropModeReplace, (unsigned char*)supported, 3); + XChangeProperty(*dpy_, *root_, netDesktopNamesAtom, XA_UTF8STRING, 8, PropModeReplace, (unsigned char*)&wsNames, wsNamesLen); + XChangeProperty(*dpy_, *root_, netNumDesktopsAtom, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&numDesktops, 1); + + +} + +void updateClientList(std::map<int, Client> clients) +{ + Atom netClientList = XInternAtom(*dpy_, "_NET_CLIENT_LIST", false); + XDeleteProperty(*dpy_, *root_, netClientList); + + std::map<int, Client>::iterator cItr; + for(cItr = clients.begin(); cItr != clients.end(); cItr++) + { + XChangeProperty(*dpy_, *root_, netClientList, XA_WINDOW, 32, PropModeAppend, (unsigned char*)&cItr->second.w, 1); + } + +} + +void setWindowDesktop(Window w, int desktop) +{ + unsigned long currDesktop = desktop - 1; + Atom netWMDesktop = XInternAtom(*dpy_, "_NET_WM_DESKTOP", false); + XChangeProperty(*dpy_, w, netWMDesktop, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&currDesktop, 1); +} + +void setCurrentDesktop(int desktop) +{ + unsigned long currDesktop = desktop - 1; + Atom netCurrentDesktop = XInternAtom(*dpy_, "_NET_CURRENT_DESKTOP", false); + XChangeProperty(*dpy_, *root_, netCurrentDesktop, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&currDesktop, 1); +} + +void setFullscreen(Window w, bool fullscreen) +{ + Atom netWMState = XInternAtom(*dpy_, "_NET_WM_STATE", false); + Atom netWMStateVal; + if(fullscreen) + netWMStateVal = XInternAtom(*dpy_, "_NET_WM_STATE_FULLSCREEN", false); + else + netWMStateVal = XInternAtom(*dpy_, "", false); + XChangeProperty(*dpy_, w, netWMState, XA_ATOM, 32, PropModeReplace, (unsigned char*)&netWMStateVal, 1); + +} + +void setIPCPath(unsigned char* path, int len) +{ + Atom socketPathAtom = XInternAtom(*dpy_, "YATWM_SOCKET_PATH", false); + XChangeProperty(*dpy_, *root_, socketPathAtom, XA_STRING, 8, PropModeReplace, path, len); +} + +int getProp(Window w, char* propName, Atom* type, unsigned char** data) +{ + Atom prop_type = XInternAtom(*dpy_, propName, false); + int format; + unsigned long length; + unsigned long after; + int status = XGetWindowProperty(*dpy_, w, prop_type, + 0L, 1L, False, + AnyPropertyType, type, &format, + &length, &after, data); + return(status); +} diff --git a/src/ewmh.h b/src/ewmh.h new file mode 100644 index 0000000..9473d5d --- /dev/null +++ b/src/ewmh.h @@ -0,0 +1,25 @@ +#pragma once + +#include <X11/X.h> +#include <X11/Xatom.h> + +#include <map> +#include <string> +#include <vector> + +#include "structs.h" +#include "config.h" + +void initEWMH(Display** dpy, Window* root, int numWS, std::vector<Workspace> workspaces); + +void updateClientList(std::map<int, Client> clients); + +void setWindowDesktop(Window w, int desktop); + +void setCurrentDesktop(int desktop); + +void setFullscreen(Window w, bool fullscreen); + +void setIPCPath(unsigned char* path, int len); + +int getProp(Window w, char* propName, Atom* type, unsigned char** data); diff --git a/src/keybinds.cpp b/src/keybinds.cpp new file mode 100644 index 0000000..c41452f --- /dev/null +++ b/src/keybinds.cpp @@ -0,0 +1,282 @@ +#include <X11/X.h> +#include <X11/Xlib.h> +#include <cctype> +#include <iostream> +#include <sstream> +#include <utility> +#include <vector> +#include <regex> + +#include "commands.h" +#include "error.h" +#include "keybinds.h" +#include "util.h" + +using std::string; + +bool Keybind::operator<(const Keybind &o) const { + if(key != o.key) + { + return key < o.key; + } + else return modifiers < o.modifiers; +} +bool Keybind::operator==(const Keybind &o) const { + return (key == o.key && modifiers == o.modifiers); +} + +KeybindsModule::KeybindsModule(CommandsModule& commandsModule, Config& cfg, Globals& globals, void (*updateMousePos)()) + :commandsModule(commandsModule), + globals(globals), + cfg(cfg) +{ + commandsModule.addCommand("bind", &KeybindsModule::bind, 2, {STR, STR_REST}, this); + commandsModule.addCommand("quitkey", &KeybindsModule::quitKey, 1, {STR}, this); + commandsModule.addCommand("bindmode", &KeybindsModule::bindMode, 1, {STR}, this); + + bindModes = { + {"normal", &KeybindsModule::normalBindMode}, + {"emacs", &KeybindsModule::emacsBindMode} + }; + bindFunc = &KeybindsModule::normalBindMode; + + this->updateMousePos = updateMousePos; + keyMaps.insert({0, std::map<Keybind, KeyFunction>()}); +} + +void KeybindsModule::changeMap(int newMapID) +{ + if(currentMapID == newMapID) + return; + if(currentMapID != 0) + XUngrabKeyboard(globals.dpy, CurrentTime); + XUngrabButton(globals.dpy, AnyKey, AnyModifier, globals.root); + currentMapID = newMapID; + if(newMapID == 0) + { + for(std::pair<Keybind, KeyFunction> pair : getKeymap(currentMapID)) + { + Keybind bind = pair.first; + XGrabKey(globals.dpy, bind.key, bind.modifiers, globals.root, false, GrabModeAsync, GrabModeAsync); + } + } + else + { + XGrabKeyboard(globals.dpy, globals.root, false, GrabModeAsync, GrabModeAsync, CurrentTime); + } +} + +const void KeybindsModule::handleKeypress(XKeyEvent e) +{ + if(e.same_screen!=1) return; + // cout << "Key Pressed" << endl; + // cout << "\tState: " << e.state << endl; + // cout << "\tCode: " << XKeysymToString(XKeycodeToKeysym(globals.dpy, e.keycode, 0)) << endl; + updateMousePos(); + + const unsigned int masks = ShiftMask | ControlMask | Mod1Mask | Mod4Mask; + Keybind k = {(KeyCode)e.keycode, e.state & masks}; + if(k == exitBind) + { + changeMap(0); + } + else if(getKeymap(currentMapID).count(k) > 0) + { + KeyFunction& c = getKeymap(currentMapID).find(k)->second; + if(getKeymap(c.mapID).size() == 0) + { + commandsModule.runCommand(c.command); + changeMap(0); + } + else + { + XUngrabButton(globals.dpy, AnyKey, AnyModifier, globals.root); + changeMap(c.mapID); + } + } + else if(std::find(std::begin(ignoredKeys), std::end(ignoredKeys), e.keycode) == std::end(ignoredKeys)) + { + changeMap(0); + } +} + +bool isUpper(const std::string& s) { + return std::all_of(s.begin(), s.end(), [](unsigned char c){ return std::isupper(c); }); +} + +const void KeybindsModule::bindMode(const CommandArg* argv) +{ + if(bindModes.count(argv[0].str) < 1) + { + throw Err(CFG_ERR_KEYBIND, "Bind mode: " + string(argv[0].str) + " does not exist"); + } + else + { + bindFunc = bindModes.find(argv[0].str)->second; + } +} + +Keybind KeybindsModule::getKeybind(std::string bindString) +{ + return (this->*bindFunc)(bindString); +} + +const Keybind KeybindsModule::normalBindMode(string bindString) +{ + std::vector<string> keys = split(bindString, '+'); + Keybind bind; + bind.modifiers = 0; + for(string key : keys) + { + if(key == "mod") + { + bind.modifiers |= Mod4Mask >> 3 * cfg.swapSuperAlt; + } + else if(key == "alt") + { + bind.modifiers |= Mod1Mask << 3 * cfg.swapSuperAlt; + } + else if(key == "shift") + { + bind.modifiers |= ShiftMask; + } + else if(key == "control") + { + bind.modifiers |= ControlMask; + } + else + { + if(isUpper(key)) + { + bind.modifiers |= ShiftMask; + } + KeySym s = XStringToKeysym(key.c_str()); + if(s == NoSymbol) + { + throw Err(CFG_ERR_KEYBIND, "Keybind '" + bindString + "' is invalid!"); + } + bind.key = XKeysymToKeycode(globals.dpy, s); + } + } + if(!bind.key) + throw Err(CFG_ERR_KEYBIND, "Keybind '" + bindString + "' is invalid!"); + return bind; +} + +const Keybind KeybindsModule::emacsBindMode(string bindString) +{ + Keybind bind; + bind.modifiers = 0; + + const std::regex keyRegex("^(?:([CMs])-)?(?:([CMs])-)?(?:([CMs])-)?([^\\s]|(SPC|ESC|RET|))$"); + std::smatch keyMatch; + if(std::regex_match(bindString, keyMatch, keyRegex)) + { + for (int i = 1; i < 3; i++) + { + std::ssub_match modifierMatch = keyMatch[i]; + if(modifierMatch.matched) + { + std::string modifier = modifierMatch.str(); + if(modifier == "s") + { + bind.modifiers |= Mod4Mask >> 3 * cfg.swapSuperAlt; + } + else if(modifier == "M") + { + bind.modifiers |= Mod1Mask << 3 * cfg.swapSuperAlt; + } + else if(modifier == "C") + { + bind.modifiers |= ControlMask; + } + } + } + } + KeySym keySym = XStringToKeysym(keyMatch[4].str().c_str()); + + if(isUpper(keyMatch[4].str().c_str()) && keySym != NoSymbol) + { + bind.modifiers |= ShiftMask; + } + if(keySym == NoSymbol) + { + if(keyMatch[4].str() == "RET") + keySym = XK_Return; + else if(keyMatch[4].str() == "ESC") + keySym = XK_Escape; + else if(keyMatch[4].str() == "SPC") + keySym = XK_space; + else if(keyMatch[4].str() == "-") + keySym = XK_minus; + else if(keyMatch[4].str() == "+") + keySym = XK_plus; + else + throw Err(CFG_ERR_KEYBIND, "Keybind '" + bindString + "' is invalid"); + } + bind.key = XKeysymToKeycode(globals.dpy, keySym); + + return bind; +} + +const void KeybindsModule::bind(const CommandArg* argv) +{ + std::vector<Err> errs = commandsModule.checkCommand(argv[1].str); + for(Err e : errs) + { + if(e.code != NOERR) + { + e.message = "Binding fail - " + e.message; + throw e; + } + } + std::vector<string> keys = split(argv[0].str, ' '); + int currentBindingMap = 0; + for(int i = 0; i < keys.size() - 1; i++) + { + Keybind bind = getKeybind(keys[i]); + if(getKeymap(currentBindingMap).count(bind) > 0) + { + currentBindingMap = getKeymap(currentBindingMap).find(bind)->second.mapID; + } + else + { + KeyFunction newMap = {"", nextKeymapID}; + keyMaps.insert({nextKeymapID, std::map<Keybind, KeyFunction>()}); + nextKeymapID++; + getKeymap(currentBindingMap).insert({bind, newMap}); + currentBindingMap = getKeymap(currentBindingMap).find(bind)->second.mapID; + } + } + Keybind bind = getKeybind(keys[keys.size() - 1]); + if(getKeymap(currentBindingMap).count(bind) <= 0) + { + KeyFunction function = {argv[1].str, nextKeymapID}; + keyMaps.insert({nextKeymapID, std::map<Keybind, KeyFunction>()}); + nextKeymapID++; + getKeymap(currentBindingMap).insert({bind, function}); + if(currentBindingMap == currentMapID) + { + KeyCode c = XKeysymToKeycode(globals.dpy, bind.key); + XGrabKey(globals.dpy, c, bind.modifiers, globals.root, false, GrabModeAsync, GrabModeAsync); + } + } + else + { + throw Err(CFG_ERR_KEYBIND, "Bind is a keymap already!"); + } + // cout << "Added bind" << endl; + // cout << "\t" << argv[0].str << endl; +} + +const void KeybindsModule::quitKey(const CommandArg* argv) +{ + exitBind = getKeybind(argv[0].str); +} + +const void KeybindsModule::clearKeybinds() +{ + XUngrabButton(globals.dpy, AnyKey, AnyModifier, globals.root); + keyMaps = std::map<int, std::map<Keybind, KeyFunction>>(); + keyMaps.insert({0, std::map<Keybind, KeyFunction>()}); +} diff --git a/src/keybinds.h b/src/keybinds.h new file mode 100644 index 0000000..a742240 --- /dev/null +++ b/src/keybinds.h @@ -0,0 +1,59 @@ +#pragma once + +#include <X11/X.h> +#include <X11/Xlib.h> + +#include <map> +#include <string> +#include <X11/keysym.h> +#include <vector> + +#include "commands.h" +#include "config.h" +#include "util.h" + +struct Keybind { + KeyCode key; + unsigned int modifiers; + bool operator<(const Keybind &o) const; + bool operator==(const Keybind &o) const; +}; + +struct KeyFunction +{ + std::string command; + int mapID; +}; + +#define getKeymap(X) \ + keyMaps.find(X)->second + + +class KeybindsModule +{ +public: + KeybindsModule(CommandsModule& commandsModule, Config& cfg, Globals& globals, void (*updateMousePos)()); + ~KeybindsModule() = default; + const void bind(const CommandArg* argv); + const void quitKey(const CommandArg* argv); + const void bindMode(const CommandArg* argv); + const void handleKeypress(XKeyEvent e); + const void clearKeybinds(); +private: + Keybind getKeybind(std::string bindString); + void changeMap(int newMapID); + std::map<int, std::map<Keybind, KeyFunction>> keyMaps; + const Keybind emacsBindMode(std::string bindString); + const Keybind normalBindMode(std::string bindString); + std::map<std::string, const Keybind(KeybindsModule::*)(std::string bindString)> bindModes; + const Keybind(KeybindsModule::* bindFunc)(std::string bindString); + // Modifier keys to ignore when canceling a keymap + KeyCode ignoredKeys[8] = {50, 37, 133, 64, 62, 105, 134, 108}; + int currentMapID = 0; + int nextKeymapID = 1; + Keybind exitBind = {42, 0x40}; + CommandsModule& commandsModule; + Config& cfg; + Globals& globals; + void (*updateMousePos)(); +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1f3edc2 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,1159 @@ +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/cursorfont.h> +#include <X11/Xutil.h> +#include <X11/extensions/Xrandr.h> + +#include <libnotify/notification.h> +#include <libnotify/notify.h> + +#include <chrono> +#include <cstdio> +#include <cstdlib> +#include <ctime> +#include <fstream> +#include <ios> +#include <iostream> +#include <map> +#include <ostream> +#include <string> +#include <sys/poll.h> +#include <sys/select.h> +#include <vector> +#include <unistd.h> +#include <cstring> +#include <algorithm> +#include <fcntl.h> +#include <poll.h> + +#include "IPC.h" +#include "commands.h" +#include "keybinds.h" +#include "structs.h" +#include "config.h" +#include "util.h" +#include "ewmh.h" +#include "error.h" + +using std::cout; +using std::string; +using std::endl; +using std::map; +using std::pair; +using std::vector; + +std::ofstream yatlog; +std::time_t timeNow; +std::tm *now; +char nowString[80]; + +#define log(x) \ + updateTime(); \ + yatlog << nowString << x << std::endl + +Display* dpy; +Window root; + +Globals globals = {dpy, root}; + +void updateMousePos(); + +CommandsModule commandsModule; +Config cfg(commandsModule); +KeybindsModule keybindsModule(commandsModule, cfg, globals, &updateMousePos); +IPCModule ipc(commandsModule, cfg, globals); + +int sW, sH; +int bH; +TileDir nextDir = horizontal; + +bool keepGoing = true; + +map<int, Client> clients; +int currClientID = 0; +map<int, Frame> frames; +int currFrameID = 1; +map<Window, int> frameIDS; + +ScreenInfo* screens; +int* focusedWorkspaces; +int focusedScreen = 0; +int nscreens; +int mX, mY; + +#define getClient(c) clients.find(c)->second +#define getFrame(f) frames.find(f)->second +#define getFrameID(w) frameIDS.find(w)->second + +Window bar; + +int currWS = 1; + + +// Usefull functions +int FFCF(int sID); +void detectScreens(); +void focusRoot(int root); +void handleConfigErrs(vector<Err> cfgErrs); +void updateTime(); + +void configureRequest(XConfigureRequestEvent e); +void mapRequest(XMapRequestEvent e); +void destroyNotify(XDestroyWindowEvent e); +void enterNotify(XEnterWindowEvent e); +void clientMessage(XClientMessageEvent e); + +static int OnXError(Display* display, XErrorEvent* e); + +// Tiling +// Call this one to tile everything (it does all the fancy stuff trust me just call this one) +void tileRoots(); +// Call this one to until everything (it handles multiple monitors) +void untileRoots(); +// This is to be called by tileRoots, it takes in the x, y, w, and h of where it's allowed to tile windows to, and returns the ID of a fullscreen client if one is found, or noID (-1) if none are found +int tile(int frameID, int x, int y, int w, int h); +// This is to be called by tileRoots, it takes in a frameID and recursively unmaps all its children +void untile(int frameID); + +// Usefull functions +int FFCF(int sID) +{ + if(frames.find(sID)->second.isClient) + return sID; + return FFCF(frames.find(sID)->second.subFrameIDs[0]); +} +void detectScreens() +{ + delete[] screens; + delete[] focusedWorkspaces; + log("Detecting screens: "); + XRRMonitorInfo* monitors = XRRGetMonitors(dpy, root, true, &nscreens); + log("\t" << nscreens << " monitors"); + screens = new ScreenInfo[nscreens]; + focusedWorkspaces = new int[nscreens]; + for(int i = 0; i < nscreens; i++) + { + focusedWorkspaces[i] = i * 5 + 1; + char* name = XGetAtomName(dpy, monitors[i].name); + screens[i] = {name, monitors[i].x, monitors[i].y, monitors[i].width, monitors[i].height}; + log("\tMonitor " << i + 1 << " - " << screens[i].name); + log("\t\tx: " << screens[i].x << ", y: " << screens[i].y); + log("\t\tw: " << screens[i].w << ", h: " << screens[i].h); + XFree(name); + } + for(int i = 0; i < cfg.workspaces.size(); i++) + { + if(cfg.workspaces[i].screenPreferences[0] < nscreens && focusedWorkspaces[cfg.workspaces[i].screenPreferences[0]] == 0) + { + //focusedWorkspaces[screenPreferences[i][0]] = i+1; + } + } + XFree(monitors); +} +void updateMousePos() +{ + Window rootRet, childRet; + int rX, rY, cX, cY; + unsigned int maskRet; + XQueryPointer(dpy, root, &rootRet, &childRet, &rX, &rY, &cX, &cY, &maskRet); + mX = rX; + mY = rY; +} +int getClientChild(int fID) +{ + if(getFrame(fID).isClient) + return fID; + else + return getClientChild(getFrame(fID).subFrameIDs[0]); +} +void focusRoot(int root) +{ + //log("Focusing root: " << root); + if(getFrame(root).subFrameIDs.size() == 0) + { + //log("\tRoot has no children"); + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + return; + } + int client = getFrame(getClientChild(root)).cID; + Window w = getClient(client).w; + //log("\tFocusing window: " << w); + XSetInputFocus(dpy, w, RevertToPointerRoot, CurrentTime); +} +void handleConfigErrs(vector<Err> cfgErrs) +{ + for(Err cfgErr : cfgErrs) + { + if(cfgErr.code == CFG_ERR_FATAL) + { + log("YATwm fatal error (Code " << cfgErr.code << ")\n" << cfgErr.message); + std::string title = "YATwm fatal config error (Code " + std::to_string(cfgErr.code) + ")"; + std::string body = cfgErr.message; + NotifyNotification* n = notify_notification_new(title.c_str(), + body.c_str(), + 0); + notify_notification_set_timeout(n, 10000); + if(!notify_notification_show(n, 0)) + { + log("notification failed"); + } + } + else + { + log("YATwm non fatal error (Code " << cfgErr.code << ")\n" << cfgErr.message); + std::string title = "YATwm non fatal config error (Code " + std::to_string(cfgErr.code) + ")"; + std::string body = "Check logs for more information"; + NotifyNotification* n = notify_notification_new(title.c_str(), + body.c_str(), + 0); + notify_notification_set_timeout(n, 10000); + if(!notify_notification_show(n, 0)) + { + log("notification failed"); + } + } + } +} +void updateTime() +{ + timeNow = std::time(0); + now = std::localtime(&timeNow); + strftime(nowString, sizeof(nowString), "[%H:%M:%S] ", now); +} + +//Keybind commands +const void exit(const CommandArg* argv) +{ + keepGoing = false; +} +const void spawn(const CommandArg* argv) +{ + if(fork() == 0) + { + const std::string argsStr = argv[0].str; + vector<std::string> args = split(argsStr, ' '); + char** execvpArgs = new char*[args.size()]; + for(int i = 0; i < args.size(); i++) + { + execvpArgs[i] = strdup(args[i].c_str()); + } + int null = open("/dev/null", O_WRONLY); + dup2(null, 0); + dup2(null, 1); + dup2(null, 2); + execvp(execvpArgs[0], execvpArgs); + exit(0); + } +} +const void spawnOnce(const CommandArg* argv) +{ + if(cfg.loaded) + return; + else spawn(argv); +} +const void toggle(const CommandArg* argv) +{ + nextDir = nextDir = (nextDir==horizontal)? vertical : horizontal; +} +const void kill(const CommandArg* argv) +{ + Window w; + int revertToReturn; + XGetInputFocus(dpy, &w, &revertToReturn); + Atom* supported_protocols; + int num_supported_protocols; + if (XGetWMProtocols(dpy, + w, + &supported_protocols, + &num_supported_protocols) && + (std::find(supported_protocols, + supported_protocols + num_supported_protocols, + XInternAtom(dpy, "WM_DELETE_WINDOW", false)) != + supported_protocols + num_supported_protocols)) { + // 1. Construct message. + XEvent msg; + memset(&msg, 0, sizeof(msg)); + msg.xclient.type = ClientMessage; + msg.xclient.message_type = XInternAtom(dpy, "WM_PROTOCOLS", false); + msg.xclient.window = w; + msg.xclient.format = 32; + msg.xclient.data.l[0] = XInternAtom(dpy, "WM_DELETE_WINDOW", false); + // 2. Send message to window to be closed. + cout << "Nice kill\n"; + XSendEvent(dpy, w, false, 0, &msg); + } else { + cout << "Mean kill\n"; + XKillClient(dpy, w); + } +} +// Took this out as it is used commonly +void cWS(int newWS) +{ + int prevWS = currWS; + + currWS = newWS; + if(prevWS == currWS) + return; + untileRoots(); + + //log("Changing WS with keybind"); + + for(int i = 0; i < cfg.workspaces[newWS - 1].screenPreferencesc; i++) + { + if(nscreens > cfg.workspaces[newWS - 1].screenPreferences[i]) + { + int screen = cfg.workspaces[newWS - 1].screenPreferences[i]; + //log("Found screen (screen " << screenPreferences[arg.num - 1][i] << ")"); + prevWS = focusedWorkspaces[screen]; + //log("Changed prevWS"); + focusedWorkspaces[screen] = newWS; + //log("Changed focusedWorkspaces"); + if(focusedScreen != screen) + { + focusedScreen = screen; + XWarpPointer(dpy, root, root, 0, 0, 0, 0, screens[screen].x + screens[screen].w/2, screens[screen].y + screens[screen].h/2); + } + //log("Changed focusedScreen"); + break; + } + } + + //log("Finished changes"); + + //log(prevWS); + // LOOK: what is this for????? + if(prevWS < 1 || prevWS > cfg.workspaces.size()) + { + //untile(prevWS); + } + //log("Untiled"); + //tile(currWS, outerGaps, outerGaps, sW - outerGaps*2, sH - outerGaps*2 - bH); + tileRoots(); + //log("Roots tiled"); + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + + cout << focusedWorkspaces[0] << endl; + + //EWMH + setCurrentDesktop(currWS); +} +const void changeWS(const CommandArg* argv) +{ + cWS(argv[0].num); +} +const void wToWS(const CommandArg* argv) +{ + Window focusedWindow; + int revertToReturn; + XGetInputFocus(dpy, &focusedWindow, &revertToReturn); + if(focusedWindow == root) + return; + + int fID = frameIDS.find(focusedWindow)->second; + //TODO: make floating windows move WS + if(clients.find(frames.find(fID)->second.cID)->second.floating) + return; + vector<int>& pSF = frames.find(frames.find(fID)->second.pID)->second.subFrameIDs; + for(int i = 0; i < pSF.size(); i++) + { + if(pSF[i] == fID) + { + //Frame disolve + pSF.erase(pSF.begin() + i); + int pID = frames.find(fID)->second.pID; + if(pSF.size() < 2 && !frames.find(pID)->second.isRoot) + { + //Erase parent frame + int lastChildID = frames.find(frames.find(pID)->second.subFrameIDs[0])->second.ID; + int parentParentID = frames.find(pID)->second.pID; + vector<int>& parentParentSubFrameIDs = frames.find(parentParentID)->second.subFrameIDs; + for(int j = 0; j < parentParentSubFrameIDs.size(); j++) + { + if(parentParentSubFrameIDs[j] == pID) + { + parentParentSubFrameIDs[j] = lastChildID; + frames.find(lastChildID)->second.pID = parentParentID; + frames.erase(pID); + break; + } + } + } + + break; + } + } + frames.find(fID)->second.pID = argv[0].num; + frames.find(argv[0].num)->second.subFrameIDs.push_back(fID); + + //EWMH + setWindowDesktop(focusedWindow, argv[0].num); + + XUnmapWindow(dpy, focusedWindow); + //tile(currWS, outerGaps, outerGaps, sW - outerGaps*2, sH - outerGaps*2 - bH); + untileRoots(); + tileRoots(); + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); +} +int dirFind(int fID, MoveDir dir) +{ + vector<int>& pSF = frames.find(frames.find(fID)->second.pID)->second.subFrameIDs; + TileDir pDir = frames.find(frames.find(fID)->second.pID)->second.dir; + + int i = 0; + for(int f : pSF) + { + if(f == fID) + { + break; + } + i++; + } + + if(pDir == vertical) + { + switch(dir) + { + case UP: i--; break; + case DOWN: i++; break; + case LEFT: return (frames.find(fID)->second.pID > cfg.numWS)? dirFind(frames.find(fID)->second.pID, dir) : fID; + case RIGHT: return (frames.find(fID)->second.pID > cfg.numWS)? dirFind(frames.find(fID)->second.pID, dir) : fID; + } + } + else if(pDir == horizontal) + { + switch(dir) + { + case LEFT: i--; break; + case RIGHT: i++; break; + case UP: return (frames.find(fID)->second.pID > cfg.numWS)? dirFind(frames.find(fID)->second.pID, dir) : fID; + case DOWN: return (frames.find(fID)->second.pID > cfg.numWS)? dirFind(frames.find(fID)->second.pID, dir) : fID; + } + } + if(i < 0) + i = pSF.size() - 1; + if(i == pSF.size()) + i = 0; + + return pSF[i]; +} +const void focChange(const CommandArg* argv) +{ + Window focusedWindow; + int revertToReturn; + XGetInputFocus(dpy, &focusedWindow, &revertToReturn); + if(focusedWindow == root) + return; + + int fID = frameIDS.find(focusedWindow)->second; + int nID = dirFind(fID, argv[0].dir); + int fNID = FFCF(nID); + Window w = clients.find(frames.find(fNID)->second.cID)->second.w; + XSetInputFocus(dpy, w, RevertToPointerRoot, CurrentTime); +} +const void wMove(const CommandArg* argv) +{ + Window focusedWindow; + int revertToReturn; + XGetInputFocus(dpy, &focusedWindow, &revertToReturn); + if(focusedWindow == root) + return; + + int fID = frameIDS.find(focusedWindow)->second; + if(clients.find(frames.find(fID)->second.cID)->second.floating) + return; + int nID = dirFind(fID, argv[0].dir); + int fNID = FFCF(nID); + int pID = frames.find(fNID)->second.pID; + int oPID = frames.find(fID)->second.pID; + + vector<int>& pSF = frames.find(pID)->second.subFrameIDs; + vector<int>& oPSF = frames.find(oPID)->second.subFrameIDs; + + for(int i = 0; i < frames.find(oPID)->second.subFrameIDs.size(); i++) + { + if(oPSF[i] != fID) + continue; + + if(pID!=oPID) + { + //Frame dissolve + oPSF.erase(oPSF.begin() + i); + if(oPSF.size() < 2 && !frames.find(oPID)->second.isRoot) + { + //Erase parent frame + int lastChildID = frames.find(frames.find(oPID)->second.subFrameIDs[0])->second.ID; + int parentParentID = frames.find(oPID)->second.pID; + vector<int>& parentParentSubFrameIDs = frames.find(parentParentID)->second.subFrameIDs; + for(int j = 0; j < parentParentSubFrameIDs.size(); j++) + { + if(parentParentSubFrameIDs[j] == oPID) + { + parentParentSubFrameIDs[j] = lastChildID; + frames.find(lastChildID)->second.pID = parentParentID; + frames.erase(oPID); + break; + } + } + } + + frames.find(fID)->second.pID = pID; + pSF.push_back(fID); + } + else + { + if(frames.find(pID)->second.dir == vertical) + { + if(argv[0].dir == LEFT || argv[0].dir == RIGHT) + return; + } + else + { + if(argv[0].dir == UP || argv[0].dir == DOWN) + return; + } + + int offset; + if(argv[0].dir == UP || argv[0].dir == LEFT) + offset = -1; + else + offset = 1; + + int swapPos = i + offset; + + if(swapPos == pSF.size()) + swapPos = 0; + else if(swapPos == -1) + swapPos = pSF.size() - 1; + + std::swap(pSF[i], pSF[swapPos]); + } + untileRoots(); + tileRoots(); + //tile(currWS, outerGaps, outerGaps, sW - outerGaps*2, sH - outerGaps*2 - bH); + XSetInputFocus(dpy, focusedWindow, RevertToPointerRoot, CurrentTime); + return; + } + XSetInputFocus(dpy, focusedWindow, RevertToPointerRoot, CurrentTime); +} +const void bashSpawn(const CommandArg* argv) +{ + if(fork() == 0) + { + int null = open("/dev/null", O_WRONLY); + dup2(null, 0); + dup2(null, 1); + dup2(null, 2); + system(argv[0].str); + exit(0); + } +} +const void bashSpawnOnce(const CommandArg* argv) +{ + if(cfg.loaded) + return; + else bashSpawn(argv); +} +const void reload(const CommandArg* argv) +{ + detectScreens(); + + //Clear keybinds + keybindsModule.clearKeybinds(); + + //Load config again + vector<Err> cfgErr = cfg.reloadFile(); + //Error check + handleConfigErrs(cfgErr); + + //Re tile + untileRoots(); + tileRoots(); +} +const void wsDump(const CommandArg* argv) +{ + log("Workspace dump:"); + for(int i = 1; i < currFrameID; i++) + { + if(getFrame(i).isClient) + { + int id = i; + while(!getFrame(id).isRoot) + { + id=getFrame(id).pID; + } + log("\tClient with ID: " << getClient(getFrame(i).cID).w << ", on worskapce " << id); + } + } +} +const void nextMonitor(const CommandArg* argv) +{ + focusedScreen++; + if(focusedScreen >= nscreens) + focusedScreen = 0; + + XWarpPointer(dpy, root, root, 0, 0, 0, 0, screens[focusedScreen].x + screens[focusedScreen].w/2, screens[focusedScreen].y + screens[focusedScreen].h/2); + focusRoot(focusedWorkspaces[focusedScreen]); +} +const void fullscreen(const CommandArg* arg) +{ + Window focusedWindow; + int focusedRevert; + XGetInputFocus(dpy, &focusedWindow, &focusedRevert); + + int fID = getFrameID(focusedWindow); + int cID = getFrame(fID).cID; + getClient(cID).fullscreen ^= true; + tileRoots(); + setFullscreen(focusedWindow, getClient(cID).fullscreen); +} + +void configureRequest(XConfigureRequestEvent e) +{ + XWindowChanges changes; + changes.x = e.x; + changes.y = e.y; + changes.width = e.width; + changes.height = e.height; + changes.border_width = e.border_width; + changes.sibling = e.above; + changes.stack_mode = e.detail; + XConfigureWindow(dpy, e.window, (unsigned int) e.value_mask, &changes); + log("Configure request: " << e.window); + //XSetInputFocus(dpy, e.window, RevertToNone, CurrentTime); + //tileRoots(); +} + +void mapRequest(XMapRequestEvent e) +{ + XMapWindow(dpy, e.window); + + XTextProperty name; + bool gotName = XGetWMName(dpy, e.window, &name); + XWindowAttributes attr; + XGetWindowAttributes(dpy, e.window, &attr); + if(gotName) + { + log("Mapping window: " << name.value); + } + else + { + log("Mapping window with unknown name (its probably mpv, mpv is annoying)"); + } + log("\tWindow ID: " << e.window); + + Window focusedWindow; + int revertToReturn; + int pID; + XGetInputFocus(dpy, &focusedWindow, &revertToReturn); + if(focusedWindow && focusedWindow != root && frameIDS.count(focusedWindow)>0) + { + //Use focused to determine parent + pID = frames.find(frameIDS.find(focusedWindow)->second)->second.pID; + } + else + { + Window rootRet, childRet; + int rX, rY, cX, cY; + unsigned int maskRet; + XQueryPointer(dpy, root, &rootRet, &childRet, &rX, &rY, &cX, &cY, &maskRet); + mX = rX; + mY = rY; + int monitor = 0; + for(int i = 0; i < nscreens; i++) + { + if(screens[i].x <= mX && mX < screens[i].x + screens[i].w) + { + if(screens[i].y <= mY && mY < screens[i].y + screens[i].h) + { + monitor = i; + } + } + } + pID = focusedWorkspaces[monitor]; + focusedScreen = monitor; + /* + if(mX == rX && mY == rY) + { + //Use focused screen + log("\tFocused screen is: " << focusedScreen); + } + else + { + //Use mouse + //TODO: Make this find the monitor + log("\tMouse is at x: " << rX << ", y: " << rY); + mX = rX; + mY = rY; + } + */ + } + + unsigned char* data; + Atom type; + int status = getProp(e.window, "_NET_WM_WINDOW_TYPE", &type, &data); + if (status == Success && type != None && ((Atom*)data)[0] == XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", false)) + { + log("\tWindow was bar"); + bH = attr.height; + bar = e.window; + XFree(data); + return; + } + XFree(data); + + + + + XSelectInput(dpy, e.window, EnterWindowMask); + + //Make client + Client c = {currClientID, e.window, false, false}; + currClientID++; + + //Add to clients map + clients.insert(pair<int, Client>(c.ID, c)); + + //Make frame + //pID = (frameIDS.count(focusedWindow)>0)? frames.find(frameIDS.find(focusedWindow)->second)->second.pID : currWS; + vector<int> v; + vector<int> floating; + Frame f = {currFrameID, pID, true, c.ID, noDir, v, false, floating}; + currFrameID++; + + + //Add ID to frameIDS map + frameIDS.insert(pair<Window, int>(e.window, f.ID)); + + status = getProp(e.window, "_NET_WM_STATE", &type, &data); + if(status == Success && type!=None && (((Atom*)data)[0] == XInternAtom(dpy, "_NET_WM_STATE_MODAL", false) || ((Atom*)data)[0] == XInternAtom(dpy, "_NET_WM_STATE_ABOVE", false))) + { + cout << "Floating" << endl; + clients.find(c.ID)->second.floating = true; + frames.find(pID)->second.floatingFrameIDs.push_back(f.ID); + frames.insert(pair<int, Frame>(f.ID, f)); + setWindowDesktop(e.window, currWS); + updateClientList(clients); + XFree(data); + //tile(currWS, outerGaps, outerGaps, sW - outerGaps*2, sH - outerGaps*2 - bH); + tileRoots(); + return; + } + XFree(data); + + //Check how to add + if(nextDir == frames.find(pID)->second.dir || frameIDS.count(focusedWindow)==0) + { + //Add to focused parent + frames.find(pID)->second.subFrameIDs.push_back(f.ID); + } + else + { + //Get parent sub frames for later use + vector<int>& pS = frames.find(pID)->second.subFrameIDs; + + //Get index of focused frame in parent sub frames + int index; + for(index = 0; index < pS.size(); index++) + { + if(pS[index] == frames.find(frameIDS.find(focusedWindow)->second)->second.ID) + break; + } + + //Make new frame + vector<int> v; + v.push_back(frames.find(frameIDS.find(focusedWindow)->second)->second.ID); + v.push_back(f.ID); + Frame pF = {currFrameID, pID, false, noID, nextDir, v, false, floating}; + + //Update the IDS + f.pID = currFrameID; + frames.find(frames.find(frameIDS.find(focusedWindow)->second)->second.ID)->second.pID = currFrameID; + pS[index] = currFrameID; + + currFrameID++; + + //Insert the new frame into the frames map + frames.insert(pair<int, Frame>(pF.ID, pF)); + } + + //Add to frames map + frames.insert(pair<int, Frame>(f.ID, f)); + + setWindowDesktop(e.window, currWS); + updateClientList(clients); + + //tile(currWS, outerGaps, outerGaps, sW - outerGaps*2, sH - outerGaps*2 - bH); + XSetInputFocus(dpy, e.window, RevertToNone, CurrentTime); + tileRoots(); +} + +void destroyNotify(XDestroyWindowEvent e) +{ + if(frameIDS.count(e.window)<1) + return; + log("Destroy notif"); + log("\tWindow ID: " << e.window); + int fID = frameIDS.find(e.window)->second; + int pID = frames.find(fID)->second.pID; + vector<int>& pS = frames.find(pID)->second.subFrameIDs; + if(clients.find(frames.find(fID)->second.cID)->second.floating) + { + pS = frames.find(pID)->second.floatingFrameIDs; + } + for(int i = 0; i < pS.size(); i++) + { + if(frames.find(pS[i])->second.ID == fID) + { + pS.erase(pS.begin() + i); + clients.erase(frames.find(fID)->second.cID); + frames.erase(fID); + frameIDS.erase(e.window); + + if(pS.size() < 2 && !frames.find(pID)->second.isRoot) + { + //Erase parent frame + int lastChildID = frames.find(frames.find(pID)->second.subFrameIDs[0])->second.ID; + int parentParentID = frames.find(pID)->second.pID; + vector<int>& parentParentSubFrameIDs = frames.find(parentParentID)->second.subFrameIDs; + for(int j = 0; j < parentParentSubFrameIDs.size(); j++) + { + if(parentParentSubFrameIDs[j] == pID) + { + parentParentSubFrameIDs[j] = lastChildID; + frames.find(lastChildID)->second.pID = parentParentID; + frames.erase(pID); + break; + } + } + } + break; + } + } + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + //tile(currWS, outerGaps, outerGaps, sW - outerGaps*2, sH - outerGaps*2 - bH); + tileRoots(); + + updateClientList(clients); +} +void enterNotify(XEnterWindowEvent e) +{ + //log(e.xcrossing.x); + //Cancel if crossing into root + if(e.window == root) + return; + + XWindowAttributes attr; + XGetWindowAttributes(dpy, e.window, &attr); + int monitor = 0; + for(int i = 0; i < nscreens; i++) + { + if(screens[i].x <= attr.x && attr.x < screens[i].x + screens[i].w) + { + if(screens[i].y <= attr.y && attr.y < screens[i].y + screens[i].h) + { + monitor = i; + } + } + } + focusedScreen = monitor; + XSetInputFocus(dpy, e.window, RevertToNone, CurrentTime); +} +void clientMessage(XClientMessageEvent e) +{ + char* name = XGetAtomName(dpy, e.message_type); + log("Client message: " << name); + if(e.message_type == XInternAtom(dpy, "_NET_CURRENT_DESKTOP", false)) + { + cWS(e.data.l[0] + 1); + /* + //Change desktop + int nextWS = (long)e.data.l[0] + 1; + int prevWS = currWS; + currWS = nextWS; + + if(prevWS == currWS) + return; + + untile(prevWS); + tile(currWS, outerGaps, outerGaps, sW - outerGaps*2, sH - outerGaps*2 - bH); + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + + //EWMH + setCurrentDesktop(currWS); + */ + } + else if(e.message_type == XInternAtom(dpy, "_NET_WM_STATE", false)) + { + if((Atom)e.data.l[0] == 0) + log("\tremove"); + if((Atom)e.data.l[0] == 1) + log("\ttoggle"); + if((Atom)e.data.l[0] == 2) + log("\tadd"); + char* prop1 = XGetAtomName(dpy, (Atom)e.data.l[1]); + if((Atom)e.data.l[1] == XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", false)) + { + int fID = getFrameID(e.window); + int cID = getFrame(fID).cID; + getClient(cID).fullscreen = (Atom) e.data.l[0] > 0; + setFullscreen(e.window, (Atom) e.data.l[0] > 0); + tileRoots(); + } + XFree(prop1); + } + XFree(name); +} + +static int OnXError(Display* display, XErrorEvent* e) +{ + char* error = new char[50]; + XGetErrorText(dpy, e->type, error, 50); + log("XError " << error); + delete[] error; + return 0; +} + +void tileRoots() +{ + for(int i = 0; i < nscreens; i++) + { + int fullscreenClientID = tile(focusedWorkspaces[i], screens[i].x + cfg.outerGaps, screens[i].y + cfg.outerGaps, screens[i].w - cfg.outerGaps*2, screens[i].h - cfg.outerGaps*2 - bH); + if(fullscreenClientID!=noID) + { + untile(focusedWorkspaces[i]); + Client c = getClient(fullscreenClientID); + XMapWindow(dpy, c.w); + XMoveWindow(dpy, c.w, + screens[i].x, screens[i].y); + XResizeWindow(dpy, c.w, + screens[i].w, screens[i].h); + } + } +} +void untileRoots() +{ + for(int i = 0; i < nscreens; i++) + { + untile(focusedWorkspaces[i]); + } +} +int tile(int frameID, int x, int y, int w, int h) +{ + for(int fID : frames.find(frameID)->second.floatingFrameIDs) + { + Window w = clients.find(frames.find(fID)->second.cID)->second.w; + XMapWindow(dpy, w); + } + TileDir dir = frames.find(frameID)->second.dir; + int i = 0; + vector<int>& subFrameIDs = frames.find(frameID)->second.subFrameIDs; + for(int fID : subFrameIDs) + { + Frame f = frames.find(fID)->second; + int wX = (dir==horizontal) ? x + i * (w/subFrameIDs.size()) : x; + int wY = (dir==vertical) ? y + i * (h/subFrameIDs.size()) : y; + int wW = (dir==horizontal) ? w/subFrameIDs.size() : w; + int wH = (dir==vertical) ? h/subFrameIDs.size() : h; + i++; + if(i==subFrameIDs.size()) + { + wW = (dir==horizontal) ? w - (wX - x) : w; + wH = (dir==vertical) ? h - (wY - y) : h; + } + if(!f.isClient) + { + int fullscreenClientID = tile(fID, wX, wY, wW, wH); + if(fullscreenClientID == noID) + return fullscreenClientID; + continue; + } + Client c = clients.find(f.cID)->second; + if(c.fullscreen) + return c.ID; + wX += cfg.gaps; + wY += cfg.gaps; + wW -= cfg.gaps * 2; + wH -= cfg.gaps * 2; + XMapWindow(dpy, c.w); + XMoveWindow(dpy, c.w, + wX, wY); + XResizeWindow(dpy, c.w, + wW, wH); + } + return noID; +} + +void untile(int frameID) +{ + for(int fID : frames.find(frameID)->second.floatingFrameIDs) + { + Window w = clients.find(frames.find(fID)->second.cID)->second.w; + XUnmapWindow(dpy, w); + } + vector<int>& subFrameIDs = frames.find(frameID)->second.subFrameIDs; + TileDir dir = frames.find(frameID)->second.dir; + for(int fID : subFrameIDs) + { + Frame f = frames.find(fID)->second; + if(!f.isClient) + { + untile(fID); + continue; + } + Client c = clients.find(f.cID)->second; + XUnmapWindow(dpy, c.w); + } +} + +int main(int argc, char** argv) +{ + if(argc > 1) + { + if(strcmp(argv[1], "--version") == 0) + { + const char* version = + "YATwm for X\n" + "version 0.1.0"; + cout << version << endl; + return 0; + } + } + //Important init stuff + mX = mY = 0; + dpy = XOpenDisplay(nullptr); + root = Window(DefaultRootWindow(dpy)); + + // Adding commands + commandsModule.addCommand("exit", exit, 0, {}); + commandsModule.addCommand("spawn", spawn, 1, {STR_REST}); + commandsModule.addCommand("spawnOnce", spawnOnce, 1, {STR_REST}); + commandsModule.addCommand("toggle", toggle, 0, {}); + commandsModule.addCommand("kill", kill, 0, {}); + commandsModule.addCommand("changeWS", changeWS, 1, {NUM}); + commandsModule.addCommand("wToWS", wToWS, 1, {NUM}); + commandsModule.addCommand("focChange", focChange, 1, {MOVDIR}); + commandsModule.addCommand("bashSpawn", bashSpawn, 1, {STR_REST}); + commandsModule.addCommand("bashSpawnOnce", bashSpawnOnce, 1, {STR_REST}); + commandsModule.addCommand("reload", reload, 0, {}); + commandsModule.addCommand("wsDump", wsDump, 0, {}); + commandsModule.addCommand("nextMonitor", nextMonitor, 0, {}); + commandsModule.addCommand("fullscreen", fullscreen, 0, {}); + + //Config + std::vector<Err> cfgErr; + + char* confDir = getenv("XDG_CONFIG_HOME"); + if(confDir != NULL) + { + cfgErr = cfg.loadFromFile(string(confDir) + "/YATwm/config"); + } + else + { + string home = getenv("HOME"); + cfgErr = cfg.loadFromFile(home + "/.config/YATwm/config"); + } + + //Log + yatlog.open(cfg.logFile, std::ios_base::app); + yatlog << "\n" << endl; + + //Print starting message + log("-------- YATWM STARTING --------"); + + //Notifications + notify_init("YATwm"); + + //Error check config + handleConfigErrs(cfgErr); + + screens = new ScreenInfo[1]; + focusedWorkspaces = new int[1]; + detectScreens(); + + int screenNum = DefaultScreen(dpy); + sW = DisplayWidth(dpy, screenNum); + sH = DisplayHeight(dpy, screenNum); + + //XSetErrorHandler(OnXError); + XSelectInput(dpy, root, SubstructureRedirectMask | SubstructureNotifyMask | KeyPressMask | EnterWindowMask); + + XDefineCursor(dpy, root, XCreateFontCursor(dpy, XC_top_left_arrow)); + //EWMH + initEWMH(&dpy, &root, cfg.workspaces.size(), cfg.workspaces); + setCurrentDesktop(1); + + ipc.init(); + + for(int i = 1; i < cfg.numWS + 1; i++) + { + vector<int> v; + Frame rootFrame = {i, noID, false, noID, horizontal, v, true}; + frames.insert(pair<int, Frame>(i, rootFrame)); + currFrameID++; + } + + XSetInputFocus(dpy, root, RevertToNone, CurrentTime); + XWarpPointer(dpy, root, root, 0, 0, 0, 0, 960, 540); + + fd_set fdset; + int x11fd = ConnectionNumber(dpy); + FD_ZERO(&fdset); + FD_SET(x11fd, &fdset); + FD_SET(ipc.getFD(), &fdset); + + log("Begin mainloop"); + while(keepGoing) + { + FD_ZERO(&fdset); + FD_SET(x11fd, &fdset); + FD_SET(ipc.getFD(), &fdset); + int ready = select(x11fd + 1, &fdset, NULL, NULL, NULL); + if(FD_ISSET(ipc.getFD(), &fdset)) + { + ipc.doListen(); + } + if(FD_ISSET(x11fd, &fdset)) + { + XEvent e; + while(XPending(dpy)) + { + XNextEvent(dpy, &e); + + switch(e.type) + { + case KeyPress: + keybindsModule.handleKeypress(e.xkey); + break; + case ConfigureRequest: + configureRequest(e.xconfigurerequest); + break; + case MapRequest: + mapRequest(e.xmaprequest); + break; + case DestroyNotify: + destroyNotify(e.xdestroywindow); + break; + case EnterNotify: + enterNotify(e.xcrossing); + break; + case ClientMessage: + clientMessage(e.xclient); + break; + default: + // cout << "Unhandled event: " << getEventName(e.type) << endl; + break; + } + } + } + if(ready == -1) + { + cout << "E" << endl; + log("ERROR"); + } + } + + //Kill children + ipc.quitIPC(); + XCloseDisplay(dpy); +} diff --git a/src/structs.h b/src/structs.h new file mode 100644 index 0000000..8215bf5 --- /dev/null +++ b/src/structs.h @@ -0,0 +1,48 @@ +#pragma once + +#include <X11/Xlib.h> + +#include <X11/extensions/Xrandr.h> +#include <string> +#include <vector> + +#define noID -1 + +struct Client +{ + int ID; + Window w; + bool floating; + bool fullscreen; +}; + +enum TileDir +{ + horizontal, + vertical, + noDir +}; + +struct Frame +{ + int ID; + int pID; + + bool isClient; + + //If its a client (window) + int cID; + + //If it isn't a client + TileDir dir; + std::vector<int> subFrameIDs; + bool isRoot; + std::vector<int> floatingFrameIDs; + //int whichChildFocused = 0; +}; + +struct ScreenInfo +{ + std::string name; + int x, y, w, h; +}; diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..58116d0 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,32 @@ +#include "util.h" + +#include <sstream> +#include <algorithm> + +using std::string; + +std::vector<string> split (const string &s, char delim) { + std::vector<string> result; + std::stringstream ss (s); + string item; + + while (getline (ss, item, delim)) { + result.push_back (item); + } + + return result; +} + +const string evNames[] = {"", "", "KeyPress", "KeyRelease", "ButtonPress", "ButtonRelease", "MotionNotify", "EnterNotify", "LeaveNotify", "FocusIn", "FocusOut", "KeymapNotify", "Expose", "GraphicsExpose", "NoExpose", "VisibilityNotify", "CreateNotify", "DestroyNotify", "UnmapNotify", "MapNotify", "MapRequest", "ReparentNotify", "ConfigureNotify", "ConfigureRequest", "GravityNotify", "ResizeRequest", "CirculateNotify", "CirculateRequest", "PropertyNotify", "SelectionClear", "SelectionRequest", "SelectionNotify", "ColormapNotify", "ClientMessage", "MappingNotify", "GenericEvent", "LASTEvent"}; + +string getEventName(int e) +{ + return evNames[e]; +} + +string lowercase(string s) +{ + string s2 = s; + std::transform(s2.begin(), s2.end(), s2.begin(), [](unsigned char c){ return std::tolower(c); }); + return s2; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..66ecf76 --- /dev/null +++ b/src/util.h @@ -0,0 +1,19 @@ +#pragma once + +#include <vector> +#include <string> +#include <X11/Xlib.h> + + +std::string getEventName(int e); + + +std::vector<std::string> split (const std::string &s, char delim); + +std::string lowercase(std::string s); + + +struct Globals { + Display*& dpy; + Window& root; +}; |
