summaryrefslogtreecommitdiff
path: root/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'main.cpp')
-rw-r--r--main.cpp359
1 files changed, 7 insertions, 352 deletions
diff --git a/main.cpp b/main.cpp
index 45eef83..f2d6a9a 100644
--- a/main.cpp
+++ b/main.cpp
@@ -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>(&current->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;