diff options
| author | BossCode45 <human.cyborg42@gmail.com> | 2023-02-01 20:07:40 +1300 |
|---|---|---|
| committer | BossCode45 <human.cyborg42@gmail.com> | 2023-02-01 20:07:40 +1300 |
| commit | 4b2507efc3721fbeb8282e1831273d0a9c445ae6 (patch) | |
| tree | 6ab48529bb69ac460bd47f55839f215a6aa3cd8c | |
| parent | 585af104691e91ad12adced578eabe8cfbfba9b8 (diff) | |
| download | YATwm-4b2507efc3721fbeb8282e1831273d0a9c445ae6.tar.gz YATwm-4b2507efc3721fbeb8282e1831273d0a9c445ae6.zip | |
Better error checking for config
| -rw-r--r-- | compile_flags.txt | 4 | ||||
| -rw-r--r-- | config.cpp | 256 | ||||
| -rw-r--r-- | config.h | 20 | ||||
| -rw-r--r-- | main.cpp | 84 | ||||
| -rw-r--r-- | makefile | 5 | ||||
| -rw-r--r-- | readme.org | 7 |
6 files changed, 320 insertions, 56 deletions
diff --git a/compile_flags.txt b/compile_flags.txt deleted file mode 100644 index d7cdb32..0000000 --- a/compile_flags.txt +++ /dev/null @@ -1,4 +0,0 @@ --xc++ --std=c++17 --I -include @@ -1,7 +1,10 @@ #include "config.h" +#include "error.h" +#include "toml++/toml.hpp" #include "util.h" +#include <X11/X.h> #include <X11/Xlib.h> #include <string> @@ -9,10 +12,9 @@ //Just for testing #include <iostream> +#include <vector> -#include <toml++/toml.hpp> - -using std::map, std::string; +using std::map, std::string, std::to_string; // For testing using std::cout, std::endl, std::cerr; @@ -37,43 +39,190 @@ Config::Config() { } -void Config::loadFromFile(string path) +string to_string(string s) { - //free(); - toml::table tbl; - try + return s; +} +string to_string(bool b) +{ + if(b) + return "true"; + else + return "false"; +} + +template <typename T> +T Config::getValue(string path, Err* err) +{ + std::optional<T> tblVal = tbl.at_path(path).value<T>(); + if(tblVal) + return *tblVal; + else { - tbl = toml::parse_file(path); + err->code = ERR_CFG_NON_FATAL; + T val = *defaults.at_path(path).value<T>(); + err->errorMessage += "\n\tValue for " + path + " is invalid, using default (" + to_string(val) + ")"; + return val; + } +} + +void Config::loadWorkspaceArrays(toml::table tbl, toml::table defaults, Err* err) +{ + if(!tbl["Workspaces"]["workspaceNames"].as_array()) + { + err->code = ERR_CFG_NON_FATAL; + err->errorMessage += "\n\tworkspaceNames invalid array, using defaults"; + return loadWorkspaceArrays(defaults, defaults, err); + } + workspaceNamesc = tbl["Workspaces"]["workspaceNames"].as_array()->size(); + workspaceNames = new string[workspaceNamesc]; + for(int i = 0; i < workspaceNamesc; i++) + { + auto element = tbl["Workspaces"]["workspaceNames"][i].value<string>(); + if(element) + workspaceNames[i] = *element; + else + { + err->code = ERR_CFG_NON_FATAL; + err->errorMessage += "\nelement " + to_string(i) + " in workspaceNames invalid, using defaults"; + delete[] workspaceNames; + return loadWorkspaceArrays(defaults, defaults, err); + } + } + if(!tbl["Workspaces"]["screenPreferences"].as_array()) + { + err->code = ERR_CFG_NON_FATAL; + err->errorMessage += "\nscreenPreferences invalid array, using default"; + delete[] workspaceNames; + return loadWorkspaceArrays(defaults, defaults, err); + } + screenPreferencesc = tbl["Workspaces"]["screenPreferences"].as_array()->size(); + if(screenPreferencesc != workspaceNamesc) + { + err->code = ERR_CFG_NON_FATAL; + err->errorMessage += "\nworkspaceNames and screenPreferences aren't the same length, using defaults"; + delete[] workspaceNames; + return loadWorkspaceArrays(defaults, defaults, err); } - catch (const toml::parse_error& err) + screenPreferences = new int*[screenPreferencesc]; + for(int i = 0; i < screenPreferencesc; i++) { - throw err; + if(!tbl["Workspaces"]["screenPreferences"][i].as_array()) + { + err->code = ERR_CFG_NON_FATAL; + err->errorMessage += "\telement " + to_string(i) + " in screenPreferences in invalid, using defaults"; + delete[] workspaceNames; + for(int k = 0; k < i; k++) + { + delete[] screenPreferences[k]; + } + delete[] screenPreferences; + return loadWorkspaceArrays(defaults, defaults, err); + } + int* wsScreens = new int[maxMonitors]; + for(int j = 0; j < maxMonitors; j++) + { + if(tbl["Workspaces"]["screenPreferences"][i].as_array()->size() <= j) + { + wsScreens[j] = 0; + continue; + } + auto element = tbl["Workspaces"]["screenPreferences"][i][j].value<int>(); + if(element) + wsScreens[j] = *element; + else + { + err->code = ERR_CFG_NON_FATAL; + err->errorMessage += "\telement " + to_string(i) + " " + to_string(j) + " in screenPreferences in invalid, using defaults"; + delete[] workspaceNames; + for(int k = 0; k <= i; k++) + { + delete[] screenPreferences[k]; + } + delete[] screenPreferences; + return loadWorkspaceArrays(defaults, defaults, err); + } + } + screenPreferences[i] = wsScreens; } +} - //Startup +void Config::loadStartupBash(toml::table tbl, toml::table defaults, Err* err) +{ + if(!tbl["Startup"]["startupBash"].as_array()) + { + err->code = ERR_CFG_NON_FATAL; + err->errorMessage += "\n\tstartupBash array invalid, using default"; + return loadStartupBash(defaults, defaults, err); + } startupBashc = tbl["Startup"]["startupBash"].as_array()->size(); - startupBash = new string[startupBashc]; + std::vector<string> startupBash; for(int i = 0; i < startupBashc; i++) { auto element = tbl["Startup"]["startupBash"][i].value<string>(); if(element) - { - startupBash[i] = *element; - } + startupBash.push_back(*element); else { - cerr << "Element " << i << " in `startupBash` is not a string" << endl; - startupBash[i] = ""; + err->code = ERR_CFG_NON_FATAL; + err->errorMessage += "\n\tstartupBash element " + to_string(i) + " invalid, skipping"; } } + startupBashc = startupBash.size(); + this->startupBash = new string[startupBashc]; + for(int i = 0; i < startupBash.size(); i++) + { + this->startupBash[i] = startupBash[i]; + } +} + +Err Config::reload() +{ + if(!loaded) + return {ERR_CFG_FATAL, "Path not set yet, call loadFromFile before reload"}; + return loadFromFile(path); +} + +Err Config::loadFromFile(string path) +{ + if(loaded) + { + free(); + } + loaded = true; + this->path = path; + Err err; + err.code = NOERR; + err.errorMessage = ""; + defaults = toml::parse_file("/etc/YATwm/config.toml"); + try + { + tbl = toml::parse_file(path); + } + catch (const toml::parse_error& parseErr) + { + err.code = ERR_CFG_FATAL; + string description = (string) parseErr.description(); + string startCol = std::to_string(parseErr.source().begin.column); + string startLine = std::to_string(parseErr.source().begin.line); + string endCol = std::to_string(parseErr.source().end.column); + string endLine = std::to_string(parseErr.source().end.line); + string pos = "Line " + startLine; + string what = parseErr.what(); + err.errorMessage += "\n\t" + description + "\t(" + pos + ")" + "\n\tUsing /etc/YATwm/config.toml instead"; + tbl = defaults; + } + + //Startup + loadStartupBash(tbl, defaults, &err); //Main - gaps = tbl["Main"]["gaps"].value_or<int>(3); - outerGaps = tbl["Main"]["gaps"].value_or<int>(3); - logFile = tbl["Main"]["gaps"].value_or<string>("/tmp/yatlog.txt"); + gaps = getValue<int>("Main.gaps", &err); + outerGaps = getValue<int>("Main.outerGaps", &err); + logFile = getValue<string>("Main.logFile", &err); //Workspaces - numWS = tbl["Workspaces"]["numWS"].value_or<int>(10); + numWS = getValue<int>("Workspaces.numWS", &err); workspaceNamesc = tbl["Workspaces"]["workspaceNames"].as_array()->size(); workspaceNames = new string[workspaceNamesc]; for(int i = 0; i < workspaceNamesc; i++) @@ -89,7 +238,7 @@ void Config::loadFromFile(string path) workspaceNames[i] = ""; } } - maxMonitors = tbl["Workspaces"]["maxMonitors"].value_or<int>(2); + maxMonitors = getValue<int>("Workspaces.maxMonitors", &err); screenPreferencesc = tbl["Workspaces"]["screenPreferences"].as_array()->size(); screenPreferences = new int*[screenPreferencesc]; for(int i = 0; i < screenPreferencesc; i++) @@ -115,14 +264,31 @@ void Config::loadFromFile(string path) } //Keybinds - bool swapSuperAlt = tbl["Keybinds"]["swapSuperAlt"].value_or<bool>(false); - bindsc = tbl["Keybinds"]["key"].as_array()->size(); - binds = new KeyBind[bindsc]; + bool swapSuperAlt = getValue<bool>("Keybinds.swapSuperAlt", &err); + + toml::node_view<toml::node> bindsArr = tbl["Keybinds"]["key"]; + if(!bindsArr.is_array()) + { + err.code = ERR_CFG_NON_FATAL; + err.errorMessage += "\n\tBinds array not valid, using default"; + bindsArr = defaults["Keybinds"]["key"]; + } + std::vector<KeyBind> keyBinds; + bindsc = bindsArr.as_array()->size(); for(int i = 0; i < bindsc; i++) { KeyBind bind; bind.modifiers = 0; - const string bindString = *tbl["Keybinds"]["key"][i]["bind"].value<string>(); + const std::optional<string> potentialBindString = bindsArr[i]["bind"].value<string>(); + string bindString; + if(potentialBindString) + bindString = *potentialBindString; + else + { + err.code = ERR_CFG_NON_FATAL; + err.errorMessage += "\n\tSkipping element " + to_string(i) + " of binds as the bind string is invalid"; + continue; + } std::vector<string> keys = split(bindString, '+'); for(string key : keys) { @@ -151,13 +317,34 @@ void Config::loadFromFile(string path) else { bind.keysym = XStringToKeysym(key.c_str()); + if(bind.keysym == NoSymbol) + { + err.code = ERR_CFG_NON_FATAL; + err.errorMessage += "\n\tSkipping element " + to_string(i) + " of binds as the bind string is invalid"; + continue; + } } } - string funcString = *tbl["Keybinds"]["key"][i]["func"].value<string>(); + std::optional<string> potentialFuncString = bindsArr[i]["func"].value<string>(); + string funcString; + if(potentialFuncString) + funcString = *potentialFuncString; + else + { + err.code = ERR_CFG_NON_FATAL; + err.errorMessage += "\n\tSkipping element " + to_string(i) + " of binds as the func string is invalid"; + continue; + } + if(funcNameMap.count(funcString) == 0) + { + err.code = ERR_CFG_NON_FATAL; + err.errorMessage += "\n\tSkipping element " + to_string(i) + " of binds as the func string is invalid"; + continue; + } void(* func) (const KeyArg arg) = funcNameMap.find(funcString)->second; bind.func = func; - auto args = tbl["Keybinds"]["key"][i]["args"]; + auto args = bindsArr[i]["args"]; if(args.is<int64_t>()) { int num = *args.value<int>(); @@ -183,8 +370,17 @@ void Config::loadFromFile(string path) { bind.args = {NULL}; } - binds[i] = bind; + keyBinds.push_back(bind); + } + bindsc = keyBinds.size(); + binds = new KeyBind[bindsc]; + for(int i = 0; i < bindsc; i++) + { + binds[i] = keyBinds[i]; } + if(err.code != NOERR) + err.errorMessage = err.errorMessage.substr(1); + return err; } Config::~Config() @@ -200,4 +396,6 @@ void Config::free() delete[] screenPreferences[i]; } delete[] screenPreferences; + delete[] binds; + loaded = false; } @@ -1,5 +1,9 @@ #pragma once +#include "error.h" + +#include <toml++/toml.hpp> + #include <X11/X.h> #include <X11/keysym.h> @@ -51,7 +55,9 @@ class Config ~Config(); void free(); - void loadFromFile(std::string path); + Err loadFromFile(std::string path); + Err reload(); + // Startup std::string* startupBash; int startupBashc; @@ -72,4 +78,16 @@ class Config // Keybinds KeyBind* binds; int bindsc; + private: + template <typename T> + T getValue(std::string path, Err* err); + + void loadWorkspaceArrays(toml::table tbl, toml::table defaults, Err* err); + void loadStartupBash(toml::table tbl, toml::table defaults, Err* err); + + toml::table tbl; + toml::table defaults; + + bool loaded = false; + std::string path; }; @@ -3,8 +3,11 @@ #include <X11/Xatom.h> #include <X11/cursorfont.h> +#include <libnotify/notification.h> #include <toml++/toml.hpp> +#include <libnotify/notify.h> + #include <X11/Xutil.h> #include <X11/extensions/Xrandr.h> #include <chrono> @@ -72,6 +75,7 @@ int FFCF(int sID); void detectScreens(); void updateMousePos(); void focusRoot(int root); +void handleConfigErrs(Err cfgErr); void keyPress(XKeyEvent e); void configureRequest(XConfigureRequestEvent e); @@ -152,6 +156,40 @@ void focusRoot(int root) //log("\tFocusing window: " << w); XSetInputFocus(dpy, w, RevertToPointerRoot, CurrentTime); } +void handleConfigErrs(Err cfgErr) +{ + if(cfgErr.code!=NOERR) + { + if(cfgErr.code == ERR_CFG_FATAL) + { + log("YATwm fatal error (Code " << cfgErr.code << ")\n" << cfgErr.errorMessage); + std::string title = "YATwm fatal config error (Code " + std::to_string(cfgErr.code) + ")"; + std::string body = cfgErr.errorMessage; + 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.errorMessage); + 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"); + } + } + } +} //Keybind commands void exit(const KeyArg arg) @@ -467,6 +505,14 @@ void bashSpawn(const KeyArg arg) void reload(const KeyArg arg) { detectScreens(); + + //Load config again + Err cfgErr = cfg.reload(); + //Error check + handleConfigErrs(cfgErr); + + //Re tile + tileRoots(); } void wsDump(const KeyArg arg) { @@ -498,12 +544,12 @@ void keyPress(XKeyEvent e) { if(e.same_screen!=1) return; updateMousePos(); - cout << "Keypress recieved\n"; + //cout << "Keypress recieved\n"; KeySym keysym = XLookupKeysym(&e, 0); - cout << "\t" << XKeysymToString(keysym) << " super: " << ((e.state & Mod4Mask) == Mod4Mask) << " alt: " << ((e.state & Mod1Mask) == Mod1Mask) << " shift: " << ((e.state & ShiftMask) == ShiftMask) << std::endl; + //cout << "\t" << XKeysymToString(keysym) << " super: " << ((e.state & Mod4Mask) == Mod4Mask) << " alt: " << ((e.state & Mod1Mask) == Mod1Mask) << " shift: " << ((e.state & ShiftMask) == ShiftMask) << std::endl; for(int i = 0; i < cfg.bindsc; i++) { - if(cfg.binds[i].keysym == keysym)// && e.state == cfg.binds[i].modifiers) + if(cfg.binds[i].keysym == keysym && (e.state & cfg.binds[i].modifiers) == cfg.binds[i].modifiers) { cfg.binds[i].func(cfg.binds[i].args); } @@ -862,29 +908,33 @@ void untile(int frameID) int main(int argc, char** argv) { - std::string home = getenv("HOME"); - std::string pathAfterHome = "/.config/YATwm/config.toml"; - std::string file = home + pathAfterHome; - try - { - cfg.loadFromFile(file); - } - catch (const toml::parse_error& err) - { - std::cerr << "Parsing failed:\n" << err << "\n"; - return 1; - } - + //Important init stuff mX = mY = 0; dpy = XOpenDisplay(nullptr); root = Window(DefaultRootWindow(dpy)); + + //Config + std::string home = getenv("HOME"); + std::string pathAfterHome = "/.config/YATwm/config.toml"; + std::string file = home + pathAfterHome; + Err cfgErr = cfg.loadFromFile(file); + + //Log yatlog.open(cfg.logFile, std::ios_base::app); + //Print starting message auto timeUnformatted = std::chrono::system_clock::now(); std::time_t time = std::chrono::system_clock::to_time_t(timeUnformatted); log("\nYAT STARTING: " << std::ctime(&time) << "--------------------------------------"); + //Notifications + notify_init("YATwm"); + + //Error check config + handleConfigErrs(cfgErr); + + screens = new ScreenInfo[1]; focusedWorkspaces = new int[1]; detectScreens(); @@ -924,8 +974,8 @@ int main(int argc, char** argv) XSetInputFocus(dpy, root, RevertToNone, CurrentTime); XWarpPointer(dpy, root, root, 0, 0, 0, 0, 960, 540); - cout << "Begin mainloop\n"; + log("Begin mainloop"); while(keepGoing) { XEvent e; @@ -1,6 +1,6 @@ .PHONY: clean CXX := g++ -CXXFLAGS := -std=c++17 -Iinclude #-g -fsanitize=address -fno-omit-frame-pointer +CXXFLAGS := -std=c++17 -Iinclude `pkg-config --cflags --libs libnotify`# -g -fsanitize=address -fno-omit-frame-pointer LINKFLAGS := -lX11 -lXrandr OBJS_DIR := . OUT_DIR := . @@ -19,10 +19,13 @@ $(OBJS_DIR)/%.o : $(SOURCE_DIR)/%.cpp i: $(EXEC) sudo mv $(EXEC) /usr/bin sudo cp yat.desktop /usr/share/xsessions + sudo mkdir /etc/YATwm + sudo cp config.toml /etc/YATwm install: i r: sudo rm /usr/bin/$(EXEC) sudo rm /usr/share/xsessions/yat.desktop + sudo rm -rf /etc/YATwm remove: r #Files to be compiled @@ -8,7 +8,7 @@ This only just works, multiple monitors aren't supported and floating windows ca * Usage instructions ** Installation *** Pre reqs -- ~Xlib~ and ~g++~ to build the program +- ~Xlib~ and ~g++~ and ~libnotify~ to build the program - ~Xephyr~ for the test script - ~rofi~, ~alacritty~, ~picom~, ~feh~, ~polybar~, ~qutebrowser~ and ~i3lock~ for the default config - The current default config is currently just the configuration that I want to use (this will likely change) @@ -16,10 +16,9 @@ This only just works, multiple monitors aren't supported and floating windows ca - ~make i~ or ~make install~ to install - ~make r~ or ~make remove~ to remove - ~./test~ to test -** Config (YOU MUST MAKE A WORKING CONFIG FILE OR ELSE YATwm WILL NOT RUN) +** Config You can configure YATwm with the config file in ~$HOME/.config/YATwm/config.toml~. I have provided an example config file in this directory that has all the variables set to their defaults. -*** Problems with the config file -The arrays (this includes the keybinds) must have values or else stuff breaks as they do not currently have defaults. +It should alert you with a notification if you have an error, and put the error your log file. It will just use default values if you didn't provide them or provided invalid values (apart from keybinds and startupBash entries, which will just be skipped if invalid (but defaults will be used if the whole array is missing). *** Startup Add a bash command to the ~startupBash~ string array in your config and it will execute on startup. *** General |
