diff options
-rw-r--r-- | command.h | 5 | ||||
-rw-r--r-- | got.cpp | 45 | ||||
-rw-r--r-- | got.h | 26 | ||||
-rw-r--r-- | gui.cpp | 35 | ||||
-rw-r--r-- | gui.h | 17 | ||||
-rw-r--r-- | main.cpp | 359 | ||||
-rw-r--r-- | seqmatcher.cpp | 72 | ||||
-rw-r--r-- | seqmatcher.h | 44 | ||||
-rw-r--r-- | sqliteutil.cpp | 55 | ||||
-rw-r--r-- | sqliteutil.h | 9 | ||||
-rw-r--r-- | xutil.cpp | 113 | ||||
-rw-r--r-- | xutil.h | 94 |
12 files changed, 522 insertions, 352 deletions
@@ -5,5 +5,10 @@ #include <utility> +// Run a command and wait for completion; if successful (exit code 0), return +// the stdout output, else throw an exception. std::string runCommand(const std::vector<std::string> &args); + +// Run a command and wait for completion, returning the exit code and the +// stdout output. std::pair<int, std::string> readCommand(const std::vector<std::string> &args); @@ -0,0 +1,45 @@ +#include <sstream> +#include <algorithm> +#include "command.h" +#include "got.h" +#include "sqliteutil.h" + + +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"}); + } +} @@ -0,0 +1,26 @@ +#pragma once + +#include <vector> +#include <string> +#include <optional> + + +namespace got { + // The path to the database + std::string getDBpath(); + + // Returns {sheet, note} + std::optional<std::pair<std::string, std::string>> getRunning(); + + // Returns names of sheets occurring in the database + std::vector<std::string> getSheets(); + + // Change the description of the currently running task to the given string + void editRunning(const std::string &descr); + + // Check out of any current task + void checkOut(); + + // Check into the given sheet + void checkIn(const std::string &sheet); +} @@ -0,0 +1,35 @@ +#include "command.h" +#include "gui.h" + + +namespace { + 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); + } +} + +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; + } +} @@ -0,0 +1,17 @@ +#pragma once + +#include <vector> +#include <string> +#include <optional> + + +namespace gui { + // Show a dialog with the given message + void showNotification(const std::string &message); + + // Request a line of text from the user + std::optional<std::string> promptText(const std::string &message); + + // Let the user choose from a list of items + std::optional<std::string> chooseList(const std::string &message, const std::string &header, const std::vector<std::string> &options); +} @@ -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; diff --git a/seqmatcher.cpp b/seqmatcher.cpp new file mode 100644 index 0000000..f0abedc --- /dev/null +++ b/seqmatcher.cpp @@ -0,0 +1,72 @@ +#include "seqmatcher.h" +#include <cassert> + + +SeqMatcher::SeqMatcher(Display *dpy) : dpy{dpy} {} + +SeqMatcher::SeqMatcher(Display *dpy, std::vector<SymSequence> seqs) + : dpy{dpy} { + for (const auto &seq : seqs) addSequence(seq.syms, seq.callback); +} + +void SeqMatcher::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); +} + +std::optional<SeqMatcher::Callback> SeqMatcher::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]() { x::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 SeqMatcher::reset() { + curNode = &rootNode; +} diff --git a/seqmatcher.h b/seqmatcher.h new file mode 100644 index 0000000..7ce6bf4 --- /dev/null +++ b/seqmatcher.h @@ -0,0 +1,44 @@ +#pragma once + +#include <functional> +#include <vector> +#include <variant> +#include <unordered_map> +#include <memory> +#include "xutil.h" + + +class SeqMatcher { +public: + using Callback = std::function<void()>; + + struct SymSequence { + std::vector<x::Keysym> syms; + Callback callback; + }; + + SeqMatcher(Display *dpy); + + // Initialise with an initial list of sequences. + SeqMatcher(Display *dpy, std::vector<SymSequence> seqs); + + // Register a key sequence + void addSequence(const std::vector<x::Keysym> &syms, Callback callback); + + // Returns bel()-running callback if unrecognised sequence is given + std::optional<Callback> observe(const XKeyEvent &ev); + + // Cancel any running sequence and restart from the beginning + void reset(); + +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; +}; diff --git a/sqliteutil.cpp b/sqliteutil.cpp new file mode 100644 index 0000000..93b91de --- /dev/null +++ b/sqliteutil.cpp @@ -0,0 +1,55 @@ +#include <sstream> +#include <cstring> +#include "sqliteutil.h" + + +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; + } +} diff --git a/sqliteutil.h b/sqliteutil.h new file mode 100644 index 0000000..2971474 --- /dev/null +++ b/sqliteutil.h @@ -0,0 +1,9 @@ +#pragma once +#include <vector> +#include <string> + + +namespace sqlite { + // Parse sqlite's CSV output, producing the query's result table as strings + std::vector<std::vector<std::string>> parseCSV(std::string output); +} diff --git a/xutil.cpp b/xutil.cpp new file mode 100644 index 0000000..e5eb8c2 --- /dev/null +++ b/xutil.cpp @@ -0,0 +1,113 @@ +#include <iostream> +#include <functional> +#include <X11/XKBlib.h> +#include "xutil.h" + + +namespace x { + + Keycode::Keycode() + : code{0} {} + + Keycode::Keycode(unsigned int code) + : code{code} {} + + Keycode::operator unsigned int() const { + return code; + } + + bool Keycode::operator==(Keycode other) const { + return code == other.code; + } + + Keysym::Keysym() + : sym{0} {} + + Keysym::Keysym(unsigned int sym) + : sym{sym} {} + + Keysym::operator unsigned int() const { + return sym; + } + + Keycode Keysym::toCode(Display *dpy) const { + return XKeysymToKeycode(dpy, sym); + } + + bool Keysym::operator==(Keysym other) const { + return sym == other.sym; + } + + void bel(Display *dpy) { + XkbBell(dpy, None, 100, None); + } + + using UponExitF = UponExit<std::function<void()>>; + + UponExitF XGrabKeyRAII(Display *dpy, Keycode code, int modifier, Window win) { + XGrabKey(dpy, (unsigned int)code, modifier, win, False, GrabModeAsync, GrabModeAsync); + return UponExitF{[dpy, code, modifier, win]() { + XUngrabKey(dpy, (unsigned int)code, modifier, win); + XSync(dpy, False); + }}; + } + + UponExitF 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 UponExitF{[dpy]() { + XUngrabKeyboard(dpy, CurrentTime); + XSync(dpy, False); + }}; + } + + std::pair<Display*, UponExitF> 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, UponExitF{[dpy]() { + XCloseDisplay(dpy); + }}); + } + + void globalKeyWatch(Display *dpy, Keysym keysym, std::function<bool(const XKeyEvent&)> callback) { + const Window root = DefaultRootWindow(dpy); + const Keycode keycode = keysym.toCode(dpy); + + auto guard = XGrabKeyRAII(dpy, keycode, AnyModifier, root); + + XSelectInput(dpy, root, KeyPressMask); + while (true) { + XEvent ev; + XNextEvent(dpy, &ev); + if (ev.type == KeyPress && ev.xkey.keycode == (unsigned int)keycode) { + if (callback(ev.xkey)) return; + } + } + } + + void globalKeyboardGrab(Display *dpy, std::function<bool(const XKeyEvent&)> 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; + } + } + +} @@ -0,0 +1,94 @@ +#pragma once + +#include <functional> +#include <X11/Xlib.h> + + +namespace x { + + // Typed wrapper for an X keycode + class Keycode { + public: + Keycode(); + Keycode(unsigned int code); + explicit operator unsigned int() const; + bool operator==(Keycode other) const; + private: + unsigned int code; + }; + + // Typed wrapper for an X keysym + class Keysym { + public: + Keysym(); + Keysym(unsigned int sym); + explicit operator unsigned int() const; + Keycode toCode(Display *dpy) const; + bool operator==(Keysym other) const; + private: + unsigned int sym; + }; + + // Play the system bell + void bel(Display *dpy); + + // Run a handler on scope exit + 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; + }; + + // Grab a single key (code + modifier) on the X server in the given window. + // Drop the UponExit to ungrab. For a global grab, use + // DefaultRootWindow(dpy) as the window. + UponExit<std::function<void()>> XGrabKeyRAII(Display *dpy, Keycode code, int modifier, Window win); + + // Grab a the whole keyboard on the X server in the given window. Drop the + // UponExit to ungrab. For a global grab, use DefaultRootWindow(dpy) as the + // window. + UponExit<std::function<void()>> XGrabKeyboardRAII(Display *dpy, Window win); + + // Open the specified display (pass nullptr to use $DISPLAY). Drop the UponExit to close. + std::pair<Display*, UponExit<std::function<void()>>> XOpenDisplayRAII(const char *name); + + // Grab the specified key globally for any modifier combination, and run + // the callback on every hit of that key. Ungrabs and returns when the + // callback returns true. + void globalKeyWatch(Display *dpy, Keysym keysym, std::function<bool(const XKeyEvent&)> callback); + + // Grab the whole keyboard globally, and run the callback on every keyboard + // key event. Ungrabs and returns when the callback returns true. + void globalKeyboardGrab(Display *dpy, std::function<bool(const XKeyEvent&)> callback); + +} + +template <> +struct std::hash<x::Keycode> { + size_t operator()(x::Keycode code) const { + return std::hash<unsigned int>{}((unsigned int)code); + } +}; + +template <> +struct std::hash<x::Keysym> { + size_t operator()(x::Keysym sym) const { + return std::hash<unsigned int>{}((unsigned int)sym); + } +}; + |