diff options
Diffstat (limited to 'main.cpp')
-rw-r--r-- | main.cpp | 359 |
1 files changed, 7 insertions, 352 deletions
@@ -1,358 +1,13 @@ #include <iostream> -#include <sstream> -#include <memory> -#include <vector> -#include <unordered_map> -#include <variant> -#include <stdexcept> -#include <functional> #include <filesystem> #include <cstring> -#include <cassert> -#include <unistd.h> -#include <X11/Xlib.h> #include <X11/Xutil.h> -#include <X11/XKBlib.h> -#include "command.h" +#include "seqmatcher.h" +#include "got.h" +#include "gui.h" +#include "xutil.h" -std::string trim(const std::string &s) { - size_t left = 0; - while (left < s.size() && isspace(s[left])) left++; - size_t right = s.size() - 1; - while (right >= left && isspace(s[right])) right--; - return s.substr(left, right - left + 1); -} - -class X_keycode { -public: - X_keycode() : code{0} {} - X_keycode(unsigned int code) : code{code} {} - explicit operator unsigned int() const { return code; } - bool operator==(X_keycode other) const { return code == other.code; } -private: unsigned int code; -}; - -template <> -struct std::hash<X_keycode> { - size_t operator()(X_keycode code) const { - return std::hash<unsigned int>{}((unsigned int)code); - } -}; - -class X_keysym { -public: - X_keysym() : sym{0} {} - X_keysym(unsigned int sym) : sym{sym} {} - explicit operator unsigned int() const { return sym; } - X_keycode toCode(Display *dpy) const { return XKeysymToKeycode(dpy, sym); } - bool operator==(X_keysym other) const { return sym == other.sym; } -private: unsigned int sym; -}; - -template <> -struct std::hash<X_keysym> { - size_t operator()(X_keysym sym) const { - return std::hash<unsigned int>{}((unsigned int)sym); - } -}; - -void bel(Display *dpy) { - XkbBell(dpy, None, 100, None); -} - -template <typename Cleanup> -class UponExit { -public: - UponExit(Cleanup cleanup) : cleanup{cleanup} {} - ~UponExit() { - if (cleanup) (*cleanup)(); - } - UponExit(const UponExit&) = delete; - UponExit(UponExit &&other) : cleanup{move(other.cleanup)} { - other.cleanup.reset(); - } - UponExit& operator=(const UponExit&) = delete; - UponExit& operator=(UponExit &&other) { - cleanup = move(other.cleanup); - other.cleanup.reset(); - } - -private: - std::optional<Cleanup> cleanup; -}; - -auto XGrabKeyRAII(Display *dpy, X_keycode code, int modifier, Window win) { - XGrabKey(dpy, (unsigned int)code, modifier, win, False, GrabModeAsync, GrabModeAsync); - return UponExit{[dpy, code, modifier, win]() { - XUngrabKey(dpy, (unsigned int)code, modifier, win); - XSync(dpy, False); - }}; -} - -auto XGrabKeyboardRAII(Display *dpy, Window win) { - int ret = XGrabKeyboard(dpy, win, False, GrabModeAsync, GrabModeAsync, CurrentTime); - if (ret == AlreadyGrabbed) { - XUngrabKeyboard(dpy, CurrentTime); - XSync(dpy, False); - throw std::runtime_error("Cannot grab keyboard: already grabbed"); - } - return UponExit{[dpy]() { - XUngrabKeyboard(dpy, CurrentTime); - XSync(dpy, False); - }}; -} - -auto XOpenDisplayRAII(const char *name) { - Display *dpy = XOpenDisplay(name); - if (dpy == nullptr) { - std::cerr << "Cannot open X display" << std::endl; - exit(1); - } - return std::make_pair(dpy, UponExit{[dpy]() { - XCloseDisplay(dpy); - }}); -} - -template <typename F> // return true to stop watch and loop -void globalKeyWatch(Display *dpy, X_keysym headerSym, F callback) { - const Window root = DefaultRootWindow(dpy); - const X_keycode headerCode = headerSym.toCode(dpy); - - auto guard = XGrabKeyRAII(dpy, headerCode, AnyModifier, root); - - XSelectInput(dpy, root, KeyPressMask); - while (true) { - XEvent ev; - XNextEvent(dpy, &ev); - if (ev.type == KeyPress && ev.xkey.keycode == (unsigned int)headerCode) { - if (callback(ev.xkey)) return; - } - } -} - -template <typename F> // return true to lose grab and loop -void globalKeyboardGrab(Display *dpy, F callback) { - const Window root = DefaultRootWindow(dpy); - - try { - auto guard = XGrabKeyboardRAII(dpy, root); - - while (true) { - XEvent ev; - XNextEvent(dpy, &ev); - if (ev.type == KeyPress) { - if (callback(ev.xkey)) return; - } - } - } catch (std::exception &e) { - std::cerr << e.what() << std::endl; - } -} - -class SeqMatcher { -public: - using Callback = std::function<void()>; - - struct SymSequence { - std::vector<X_keysym> syms; - Callback callback; - }; - - SeqMatcher(Display *dpy) : dpy{dpy} {} - - SeqMatcher(Display *dpy, std::vector<SymSequence> seqs) - : dpy{dpy} { - for (const auto &seq : seqs) addSequence(seq.syms, seq.callback); - } - - void addSequence(const std::vector<X_keysym> &syms, Callback callback) { - if (syms.empty()) { - throw std::logic_error("Cannot register empty key sequence"); - } - - Node *current = &rootNode; - for (X_keysym sym : syms) { - if (std::holds_alternative<Callback>(current->v)) { - throw std::logic_error("Overlapping key sequences (second is longer)"); - } else { - if (!std::holds_alternative<NodeMap>(current->v)) { - current->v.emplace<NodeMap>(); - } - NodeMap &map = std::get<NodeMap>(current->v); - X_keycode code = sym.toCode(dpy); - auto it = map.find(code); - if (it != map.end()) { - current = it->second.get(); - } else { - current = map.emplace(sym.toCode(dpy), std::make_unique<Node>()).first->second.get(); - } - } - } - - if (auto *map = std::get_if<NodeMap>(¤t->v)) { - if (!map->empty()) { - throw std::logic_error("Overlapping key sequences (second is shorter)"); - } - } - if (std::holds_alternative<Callback>(current->v)) { - throw std::logic_error("Overlapping key sequences (equally long)"); - } - current->v.emplace<Callback>(callback); - } - - // Returns bel()-running callback if unrecognised sequence is given - std::optional<Callback> observe(const XKeyEvent &ev) { - auto *map = std::get_if<NodeMap>(&curNode->v); - assert(map); - - auto it = map->find(X_keycode{ev.keycode}); - if (it == map->end()) { - // Sequence not found - reset(); - return [dpy = dpy]() { bel(dpy); }; - } - - curNode = it->second.get(); - - if (auto *cb = std::get_if<Callback>(&curNode->v)) { - // Sequence completed - reset(); - return *cb; - } - - // Need more keys - return std::nullopt; - } - - void reset() { - curNode = &rootNode; - } - -private: - struct Node; - using NodeMap = std::unordered_map<X_keycode, std::unique_ptr<Node>>; - struct Node { - std::variant<NodeMap, Callback> v; - }; - - Display *const dpy; - Node rootNode; - Node *curNode = &rootNode; -}; - -namespace sqlite { - std::vector<std::vector<std::string>> parseCSV(std::string output) { - std::istringstream ss{output}; - std::vector<std::vector<std::string>> table; - - std::string line; - while (std::getline(ss, line)) { - while (!line.empty() && strchr("\r\n", line.back()) != nullptr) - line.pop_back(); - - table.emplace_back(); - - if (line.empty()) continue; - - std::vector<std::string> &row = table.back(); - row.emplace_back(); - bool inString = false; - for (size_t i = 0; i < line.size(); i++) { - switch (line[i]) { - case '"': - if (inString) { - if (i + 1 < line.size() && line[i+1] == '"') { - row.back().push_back('"'); - i++; - } else { - inString = false; - } - } else { - inString = true; - } - break; - - case ',': - if (inString) { - row.back().push_back(','); - } else { - row.emplace_back(); - } - break; - - default: - row.back().push_back(line[i]); - break; - } - } - } - - return table; - } -} - -namespace got { - std::string getDBpath() { - return std::string{getenv("HOME")} + "/.timetrap.db"; - } - - // Returns {sheet, note} - std::optional<std::pair<std::string, std::string>> getRunning() { - std::string output = runCommand({"sqlite3", getDBpath(), ".mode csv", "select sheet, note from entries where end is null"}); - auto table = sqlite::parseCSV(move(output)); - if (table.empty()) return std::nullopt; - else return std::make_pair(table[0][0], table[0][1]); - } - - std::vector<std::string> getSheets() { - std::string output = runCommand({"sqlite3", getDBpath(), "select distinct sheet from entries"}); - std::istringstream ss{output}; - std::vector<std::string> lines; - std::string line; - while (std::getline(ss, line)) { - if (line.size() > 0) lines.push_back(move(line)); - } - std::sort(lines.begin(), lines.end()); - return lines; - } - - void editRunning(const std::string &descr) { - runCommand({"got", "edit", descr}); - } - - void checkOut() { - runCommand({"got", "out"}); - } - - void checkIn(const std::string &sheet) { - runCommand({"got", "sheet", sheet}); - runCommand({"got", "in"}); - } -} - -namespace gui { - void showNotification(const std::string &message) { - runCommand({"zenity", "--notification", "--text", message}); - } - - std::optional<std::string> promptText(const std::string &message) { - auto result = readCommand({"zenity", "--entry", "--text", message}); - if (result.first == 0) return result.second; - else return std::nullopt; - } - - std::optional<std::string> chooseList(const std::string &message, const std::string &header, const std::vector<std::string> &options) { - std::vector<std::string> args{ - "zenity", "--list", "--text", message, "--column", header - }; - args.insert(args.end(), options.begin(), options.end()); - auto result = readCommand(args); - if (result.first == 0) return trim(result.second); - else return std::nullopt; - } -} - int main(int argc, char **argv) { if (argc >= 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) { std::cout << @@ -374,7 +29,7 @@ int main(int argc, char **argv) { return 1; } - auto dpy_pair = XOpenDisplayRAII(nullptr); + auto dpy_pair = x::XOpenDisplayRAII(nullptr); Display *dpy = dpy_pair.first; bool quitRequested = false; @@ -438,14 +93,14 @@ int main(int argc, char **argv) { quitRequested = true; }); - globalKeyWatch( + x::globalKeyWatch( dpy, XK_Break, [dpy, &matcher, &quitRequested](const XKeyEvent&) -> bool { matcher.reset(); SeqMatcher::Callback cb; - globalKeyboardGrab(dpy, [&matcher, &cb](const XKeyEvent &ev) -> bool { + x::globalKeyboardGrab(dpy, [&matcher, &cb](const XKeyEvent &ev) -> bool { auto opt_cb = matcher.observe(ev); if (opt_cb) { cb = *opt_cb; |