diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4753a05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# C++ generated headers +*.gch diff --git a/install b/install index 3b1cd86..d81d328 100755 --- a/install +++ b/install @@ -9,8 +9,6 @@ OLD_CONF_PATH=${XDG_CONFIG_HOME:-$HOME/.config}/comfortable-swipe.conf if [ -x "$(command -v $PROGRAM)" ]; then # stop any running comfortable-swipe if it exists $PROGRAM stop - # remove existing comfortable-swipe - rm $(which comfortable-swipe) fi #copy config file @@ -43,11 +41,19 @@ else fi fi fi -echo "Installing..." -# mkdir -p ~/.local/bin -sudo g++ -std=c++11 -O2 $DIR/src/comfortable-swipe.cpp -lxdo -o $PROGRAM || exec echo "Installation aborted" -GROUP=$(ls -l /dev/input/event* | awk '{print $4}' | head --line=1) || abort +echo "Installing..." + +# remove existing comfortable-swipe +if [ -x "$(command -v $PROGRAM)" ]; then + sudo rm -f $(which comfortable-swipe) +fi + +# compile library +sudo $DIR/src/compile $PROGRAM || abort + +# add permissions to input group (defer) +# GROUP=$(ls -l /dev/input/event* | awk '{print $4}' | head --line=1) || abort # toggle autostart twice to refresh any changes $PROGRAM autostart > /dev/null || abort @@ -56,4 +62,4 @@ $PROGRAM autostart > /dev/null || abort echo "Successfully installed comfortable-swipe." echo "Configuration file is located at $CONF_PATH" echo "" -echo "Try running 'comfortable-swipe start' to test." \ No newline at end of file +echo "Try running 'comfortable-swipe start' to test." diff --git a/src/comfortable-swipe.cpp b/src/comfortable-swipe.cpp deleted file mode 100644 index 18203a1..0000000 --- a/src/comfortable-swipe.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "comfortable-swipe.hpp" -/* MAIN DRIVER FUNCTION */ - -int main(int argc, char** args) { - if (argc > 1) { - string arg = args[1]; - // select based on argument - if (arg == "start") service::start(); - else if (arg == "stop") service::stop(); - else if (arg == "restart") service::restart(); - else if (arg == "buffer") service::buffer(); - else if (arg == "autostart") service::autostart(); - else service::help(); - } else { - service::help(); - } -} diff --git a/src/comfortable-swipe.hpp b/src/comfortable-swipe.hpp deleted file mode 100644 index 9b87052..0000000 --- a/src/comfortable-swipe.hpp +++ /dev/null @@ -1,439 +0,0 @@ -/* -Comfortable Swipe -by Rico Tiongson - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#define cstr const string& -#define PROGRAM "/usr/local/bin/comfortable-swipe" -#define CONFIG "/usr/local/share/comfortable-swipe/comfortable-swipe.conf" -using namespace std; - -extern "C" { - // sudo apt install libxdo-dev - #include -} - -/* MASKS FOR GESTURES */ - -#define MSK_THREE_FINGERS 0 -#define MSK_FOUR_FINGERS 1 -#define MSK_NEGATIVE 0 -#define MSK_POSITIVE 2 -#define MSK_HORIZONTAL 0 -#define MSK_VERTICAL 4 - -/* GESTURE MNEMONYMS */ -#define FRESH -1 -#define OPPOSITE (mask ^ MSK_POSITIVE) - -/* FORWARD DECLARATIONS */ - -namespace util { - extern const char* GESTURE_SWIPE_BEGIN_REGEX_PATTERN; - extern const char* GESTURE_SWIPE_UPDATE_REGEX_PATTERN; - extern const char* GESTURE_SWIPE_END_REGEX_PATTERN; - map read_config_file(const char*); -} - -namespace service { - void buffer(); - void start(); - void stop(); - void restart(); - void autostart(); - void help(); -} - -struct swipe_gesture { - string fingers; - string dx, dy, udx, udy; - xdo_t* xdo; - virtual void on_update() = 0; - virtual void on_begin() = 0; - virtual void on_end() = 0; - swipe_gesture(): xdo(xdo_new(NULL)) {} - ~swipe_gesture() {xdo_free(xdo);} -}; - -const char* const command_map[] = { - "left 3", - "left 4", - "right 3", - "right 4", - "up 3", - "up 4", - "down 3", - "down 4" -}; - -struct swipe_gesture_impl : swipe_gesture { - int screen_num, ix, iy; - float x, y, threshold_squared; - int previous_gesture; - const char** commands; - swipe_gesture_impl( - const float threshold, - const char* left3 /* 000 */, - const char* left4 /* 001 */, - const char* right3 /* 010 */, - const char* right4 /* 011 */, - const char* up3 /* 100 */, - const char* up4 /* 101 */, - const char* down3 /* 110 */, - const char* down4 /* 111 */ - ): swipe_gesture(), threshold_squared(threshold*threshold) { - commands = new const char*[8]; - commands[0] = left3; - commands[1] = left4; - commands[2] = right3; - commands[3] = right4; - commands[4] = up3; - commands[5] = up4; - commands[6] = down3; - commands[7] = down4; - } - ~swipe_gesture_impl() { - delete[] commands; - } - void key(const char* cmd) const { - xdo_send_keysequence_window(xdo, CURRENTWINDOW, cmd, 0); - } - void on_begin() override { - xdo_get_mouse_location(xdo, &ix, &iy, &screen_num); - previous_gesture = FRESH; - x = 0; - y = 0; - } - void on_update() override { - x += stof(dx); - y += stof(dy); - // scale threshold to 1/10 when gesture is not fresh - float scale = previous_gesture == FRESH - ? 1.00f - : 0.01f; // square root of 1/10 - if (x*x + y*y > threshold_squared*scale) { - int mask = 0; - if (fingers[0] == '3') mask |= MSK_THREE_FINGERS; else - if (fingers[0] == '4') mask |= MSK_FOUR_FINGERS; - if (abs(x) > abs(y)) { - mask |= MSK_HORIZONTAL; - if (x < 0) mask |= MSK_NEGATIVE; - else mask |= MSK_POSITIVE; - } else { - mask |= MSK_VERTICAL; - if (y < 0) mask |= MSK_NEGATIVE; - else mask |= MSK_POSITIVE; - } - // send command on fresh OR opposite gesture - if (previous_gesture == FRESH or previous_gesture == OPPOSITE) { - x = y = 0; - previous_gesture = mask; - cout << "SWIPE " << command_map[mask] << endl; - key(commands[mask]); - } - } - } - void on_end() override { - } -}; - -// path services -namespace service { - // get the full path of the .conf file - const char* conf_filename() { - return CONFIG; - } - // get the full path of the .desktop file associated - // with the autostart feature - string autostart_filename() { - static string *filename = NULL; - if (filename == NULL) { - const char* xdg_config = getenv("XDG_CONFIG_HOME"); - string config( - xdg_config == NULL - ? string(getenv("HOME")) + "/.config" - : xdg_config - ); - filename = new string(config - + "/autostart/comfortable-swipe.desktop"); - } - return *filename; - } -} - -namespace service { - - // process regex at compile time - const regex gesture_begin(util::GESTURE_SWIPE_BEGIN_REGEX_PATTERN); - const regex gesture_update(util::GESTURE_SWIPE_UPDATE_REGEX_PATTERN); - const regex gesture_end(util::GESTURE_SWIPE_END_REGEX_PATTERN); - - // parses output from libinput debug-events - void buffer() { - int c = clock(); - // check first if $user - ios::sync_with_stdio(false); - cin.tie(0); cout.tie(0); - // read config file - auto config = util::read_config_file(conf_filename()); - // initialize gesture handler - swipe_gesture_impl swipe( - config.count("threshold") ? stof(config["threshold"]) : 0.0, - config["left3"].c_str(), - config["left4"].c_str(), - config["right3"].c_str(), - config["right4"].c_str(), - config["up3"].c_str(), - config["up4"].c_str(), - config["down3"].c_str(), - config["down4"].c_str() - ); - // start reading lines from input one by one - static const int MAX_LINE_LENGTH = 256; - static char data[MAX_LINE_LENGTH]; - static cmatch matches; - bool flag_begin = false; - while (fgets_unlocked(data, MAX_LINE_LENGTH, stdin) != NULL) { - if (!flag_begin) { - if (regex_match(data, matches, gesture_begin)) { - swipe.fingers = matches[1]; - swipe.on_begin(); - flag_begin = true; - } - } else { - if (regex_match(data, matches, gesture_update)) { - swipe.fingers = matches[1]; - swipe.dx = matches[2]; - swipe.dy = matches[3]; - swipe.udx = matches[4]; - swipe.udy = matches[5]; - swipe.on_update(); - } else if (regex_match(data, matches, gesture_end)) { - swipe.fingers = matches[1]; - swipe.on_end(); - flag_begin = false; - } - } - } - } - // starts service - void start() { - int x = system("stdbuf -oL -e0 libinput debug-events | " PROGRAM " buffer"); - } - // stops service - void stop() { - // kill all comfortable-swipe, except self - char* buffer = new char[20]; - FILE* pipe = popen("pgrep -f comfortable-swipe", "r"); - if (!pipe) throw std::runtime_error("stop command failed"); - string kill = "kill"; - while (!feof(pipe)) { - if (fgets(buffer, 20, pipe) != NULL) { - int pid = atoi(buffer); - if (pid != getpid()) { - kill += " " + to_string(pid); - } - } - } - int result = system(kill.data()); - delete[] buffer; - pclose(pipe); - } - // stops then starts service - void restart() { - service::stop(); - service::start(); - } - // toggle automatically start application on startup - void autostart() { - cstr path = autostart_filename(); - if (ifstream(path.data()).good()) { - // file found, delete it - if (remove(path.data()) != 0) - cerr << "Error: failed to switch off autostart. " - << "Maybe the autostart file is in use?" - << endl; - else - cout << "Autostart switched off" << endl; - } else { - // file not found, create it - int result = system(("mkdir -p $(dirname " + path + ")").data()); - ofstream fout(path.data()); - if (result != 0 || !fout.good()) - cerr << "Error: failed to switch on autostart. " - << "Are you sure you have the permissions?" - << endl; - else { - fout << - "[Desktop Entry]\n" - "Type=Application\n" - "Exec=bash -c \"" PROGRAM " start\"\n" - "Hidden=false\n" - "NoDisplay=false\n" - "X-GNOME-Autostart-enabled=true\n" - "Name=Comfortable Swipe\n" - "Comment=3 or 4 touchpad gestures\n"; - cout << "Autostart switched on" << endl; - } - } - } - // shows help - void help() { - puts("comfortable-swipe [start|stop|restart|autostart|buffer|help]"); - puts(""); - puts("start - starts 3/4-finger gesture service"); - puts("stop - stops 3/4-finger gesture service"); - puts("restart - stops then starts 3/4-finger gesture service"); - puts("autostart - automatically run on startup (toggleable)"); - puts("buffer - parses output of libinput debug-events"); - puts("help - shows the help dialog"); - puts(""); - printf("Configuration file can be found in %s", conf_filename()); - } -} - -/* UTILITY FUNCTIONS */ - -namespace util { - - - /** - * Regex pattern for the libinput entry for start of swipe. - * Extracts one match for the number of fingers used during the swipe. - * - * eg. event15 GESTURE_SWIPE_BEGIN +34.33s 3 - * ^ - * fingers - */ - const char* GESTURE_SWIPE_BEGIN_REGEX_PATTERN = - "^" // start of string - "[ -]event\\d+" // event - "\\s+GESTURE_SWIPE_BEGIN" // gesture - "\\s+\\S+" // timestamp - "\\s+(\\d+)" // fingers - "\\s*$" // end of string - ; - - /** - * Regex pattern for the libinput entry for the end of swipe. - * Extracts one match for the number of fingers used during the swipe. - * - * eg. event15 GESTURE_SWIPE_END +35.03s 3 - * ^ - * fingers - */ - const char* GESTURE_SWIPE_END_REGEX_PATTERN = - "^" // start of string - "[ -]event\\d+" // event - "\\s+GESTURE_SWIPE_END" // gesture - "\\s+\\S+" // timestamp - "\\s+(\\d+)" // fingers - "\\s*$" // end of string - ; - - // matches signed decimal numbers (eg. "6.02" "-1.1") - #define CF_NUMBER_REGEX "-?\\d+(?:\\.\\d+)" - - // matches and extracts a space-prefixed signed fraction (eg. "-3.00/ 5.12") - #define CF_NUMBER_DIVISION "\\s*(" CF_NUMBER_REGEX ")/\\s*(" CF_NUMBER_REGEX ")" - - /** - * Regex pattern for the libinput entry for during a swipe. - * Extracts number of fingers used and the speed (normal and accelerated) of the swipe. - * - * eg. event15 GESTURE_SWIPE_UPDATE +34.70s 3 -0.12/ 4.99 (-0.33/13.50 unaccelerated) - * ^ ^ ^ ^ ^ - * fingers dx dy udx udy - */ - const char* GESTURE_SWIPE_UPDATE_REGEX_PATTERN = - "^" // start of string - "[ -]event\\d+" // event - "\\s+GESTURE_SWIPE_UPDATE" // gesture - "\\s+\\S+" // timestamp - "\\s+(\\d+)" // fingers - "\\s+" CF_NUMBER_DIVISION // speed (dx/dy) - "\\s+\\(" CF_NUMBER_DIVISION "\\s+unaccelerated\\)" // unaccelerated speed (udx/udy) - "\\s*$" // end of string - ; - - // delete macros - #undef CF_NUMBER_DIVISION - #undef CF_NUMBER_EXTRACT - #undef CF_NUMBER_REGEX - - /** - * A utility method for reading the config file. - * - * @param filename (const char*) the path of the config file. - */ - map read_config_file(const char* filename) { - map conf; - ifstream fin(filename); - if (!fin.is_open()) { - cerr << "file \"" << filename << "\" does not exist!" << endl; - exit(1); - } - static string line, token[2]; - int line_number = 0; - while (getline(fin, line)) { - ++line_number; - token[0].clear(); - token[1].clear(); - int length = line.length(); - int equal_flag = 0; - for (int i = 0; i < length; ++i) { - if (line[i] == '#') - break; - if (line[i] == '=') { - if (++equal_flag > 1) { - cerr << "error in conf file " << filename << endl; - cerr << "multiple equal signs in line " << line_number << endl; - exit(1); - } - } else if (!isspace(line[i])) { - token[equal_flag].push_back(line[i]); - } - } - // ignore empty lines - if (equal_flag == 0 && token[0].length() == 0) - continue; - if (equal_flag == 0) { - cerr << "error in conf file: " << filename << endl; - cerr << "equal sign expected in line " << line_number << endl; - exit(1); - } - if (equal_flag == 1 && token[1].length() > 0) { - conf[token[0]] = token[1]; - } - } - return conf; - } - -} diff --git a/src/compile b/src/compile new file mode 100755 index 0000000..d426b80 --- /dev/null +++ b/src/compile @@ -0,0 +1,2 @@ +#!/bin/sh +g++ $(dirname $0)/main.cpp -std=c++11 -O2 -lxdo -Wno-unused-result -o $1 diff --git a/src/lib/comfortable_swipe b/src/lib/comfortable_swipe new file mode 100644 index 0000000..1c30917 --- /dev/null +++ b/src/lib/comfortable_swipe @@ -0,0 +1,40 @@ +#ifndef __COMFORTABLE_SWIPE__ +#define __COMFORTABLE_SWIPE__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "index.hpp" + +/** + * Make sure to include all implementation (.cpp) files below to be ready for export. + */ + +#include "gesture/swipe_gesture.cpp" +#include "service/autostart.cpp" +#include "service/buffer.cpp" +#include "service/help.cpp" +#include "service/restart.cpp" +#include "service/start.cpp" +#include "service/stop.cpp" +#include "util/autostart_filename.cpp" +#include "util/conf_filename.cpp" +#include "util/read_config_file.cpp" +#include "util/regex.cpp" + +#endif /* __COMFORTABLE_SWIPE__ */ diff --git a/src/lib/gesture/swipe_gesture.cpp b/src/lib/gesture/swipe_gesture.cpp new file mode 100644 index 0000000..ca123db --- /dev/null +++ b/src/lib/gesture/swipe_gesture.cpp @@ -0,0 +1,150 @@ +#ifndef __COMFORTABLE_SWIPE__gesture_swipe_gesture__ +#define __COMFORTABLE_SWIPE__gesture_swipe_gesture__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include // std::cout, std::endl +#include "../index.hpp" + +extern "C" +{ + #include // xdo, xdo_new, xdo_free, + // xdo_get_mouse_location + // CURRENT_WINDOW +} + +namespace comfortable_swipe +{ + namespace gesture + { + /** + * Constructs a new swipe gesture with xdo. + */ + swipe_gesture::swipe_gesture + ( + const float threshold, + const char* left3 /* 000 */, + const char* left4 /* 001 */, + const char* right3 /* 010 */, + const char* right4 /* 011 */, + const char* up3 /* 100 */, + const char* up4 /* 101 */, + const char* down3 /* 110 */, + const char* down4 /* 111 */ + ): + xdo(xdo_new(NULL)), + commands(new const char*[8]{left3, left4, right3, right4, up3, up4, down3, down4}) + { } + + /** + * Constructs a new swipe gesture with xdo. + */ + swipe_gesture::~swipe_gesture() + { + xdo_free(this->xdo); + delete[] commands; + } + + /** + * Hook on begin of swipe gesture. + */ + void swipe_gesture::begin() + { + xdo_get_mouse_location(this->xdo, &this->ix, &this->iy, &this->screen_num); + this->previous_gesture = swipe_gesture::FRESH; + this->x = 0; + this->y = 0; + } + + /** + * Hook on update of swipe gesture. + */ + void swipe_gesture::update() + { + this->x += this->dx; + this->y += this->dy; + // scale threshold to 1/10 when gesture is not fresh + float scale = this->previous_gesture == swipe_gesture::FRESH + ? 1.00f + : 0.01f; // square root of 1/10 + if (this->x * this->x + this->y * this->y > this->threshold_squared * scale) + { + int mask = 0; + if (this->fingers == 3) mask |= swipe_gesture::MSK_THREE_FINGERS; + else if (this->fingers == 4) mask |= swipe_gesture::MSK_FOUR_FINGERS; + + const float absx = x >= 0 ? x : -x; + const float absy = y >= 0 ? y : -y; + if (absx > absy) + { // horizontal + mask |= swipe_gesture::MSK_HORIZONTAL; + if (x < 0) + mask |= swipe_gesture::MSK_NEGATIVE; + else + mask |= swipe_gesture::MSK_POSITIVE; + } + else /* std::abs(x) <= std::abs(y) */ + { // vertical + mask |= swipe_gesture::MSK_VERTICAL; + if (y < 0) + mask |= swipe_gesture::MSK_NEGATIVE; + else + mask |= swipe_gesture::MSK_POSITIVE; + } + + // send command on fresh OR opposite gesture + if (this->previous_gesture == swipe_gesture::FRESH + || this->previous_gesture == (mask ^ swipe_gesture::MSK_POSITIVE)) + { + this->x = this->y = 0; + this->previous_gesture = mask; + std::cout << "SWIPE " << swipe_gesture::command_map[mask] << std::endl; + xdo_send_keysequence_window(xdo, CURRENTWINDOW, swipe_gesture::commands[mask], 0); + } + } + } + + /** + * Hook on end of swipe gesture. + */ + void swipe_gesture::end() + { } + + /* STATICS DEFINITIONS */ + const int swipe_gesture::MSK_THREE_FINGERS = 0; + const int swipe_gesture::MSK_FOUR_FINGERS = 1; + const int swipe_gesture::MSK_NEGATIVE = 0; + const int swipe_gesture::MSK_POSITIVE = 2; + const int swipe_gesture::MSK_HORIZONTAL = 0; + const int swipe_gesture::MSK_VERTICAL = 4; + const int swipe_gesture::FRESH = -1; + const char * const swipe_gesture::command_map[8] = { + "left 3", + "left 4", + "right 3", + "right 4", + "up 3", + "up 4", + "down 3", + "down 4" + }; + } +} + +#endif /* __COMFORTABLE_SWIPE__gesture_swipe_gesture__ */ \ No newline at end of file diff --git a/src/lib/gesture/swipe_gesture.h b/src/lib/gesture/swipe_gesture.h new file mode 100644 index 0000000..394962d --- /dev/null +++ b/src/lib/gesture/swipe_gesture.h @@ -0,0 +1,87 @@ +#ifndef __COMFORTABLE_SWIPE__gesture_swipe_gesture_h__ +#define __COMFORTABLE_SWIPE__gesture_swipe_gesture_h__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +extern "C" +{ + #include // xdo_t +} + +#ifdef __cplusplus +extern "C" { +#endif + +namespace comfortable_swipe +{ + namespace gesture + { + struct swipe_gesture + { + // constructor + swipe_gesture( + const float, + const char*, + const char*, + const char*, + const char*, + const char*, + const char*, + const char*, + const char* + ); + + ~swipe_gesture(); + + // fields for xdo + int fingers; + float dx, dy, udx, udy; + xdo_t * xdo; + + // location of mouse + int screen_num, ix, iy; + + // current location + float x, y, threshold_squared; + int previous_gesture; + const char ** commands; + + // hooks + void update(); + void begin(); + void end(); + + // statics + static const int MSK_THREE_FINGERS; + static const int MSK_FOUR_FINGERS; + static const int MSK_NEGATIVE; + static const int MSK_POSITIVE; + static const int MSK_HORIZONTAL; + static const int MSK_VERTICAL; + static const int FRESH; + static const char * const command_map[8]; + }; + } +} + +#ifdef __cplusplus +} +#endif + +#endif /* __COMFORTABLE_SWIPE__gesture_swipe_gesture_h__ */ diff --git a/src/lib/index.hpp b/src/lib/index.hpp new file mode 100644 index 0000000..ba2b232 --- /dev/null +++ b/src/lib/index.hpp @@ -0,0 +1,60 @@ +#ifndef __COMFORTABLE_SWIPE__index_hpp__ +#define __COMFORTABLE_SWIPE__index_hpp__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// global defines +#ifndef __COMFORTABLE_SWIPE__PROGRAM__ +#define __COMFORTABLE_SWIPE__PROGRAM__ "/usr/local/bin/comfortable-swipe" +#endif /* __COMFORTABLE_SWIPE__PROGRAM__ */ + +#ifndef __COMFORTABLE_SWIPE__CONFIG__ +#define __COMFORTABLE_SWIPE__CONFIG__ "/usr/local/share/comfortable-swipe/comfortable-swipe.conf" +#endif /* __COMFORTABLE_SWIPE__CONFIG__ */ + +#include // std::map +#include // std::string + +/** + * Make sure to include your header files here so that they can be imported by other modules. + */ +#include "gesture/swipe_gesture.h" +extern "C" +{ + namespace comfortable_swipe::util + { + extern const char* GESTURE_SWIPE_BEGIN_REGEX_PATTERN; + extern const char* GESTURE_SWIPE_UPDATE_REGEX_PATTERN; + extern const char* GESTURE_SWIPE_END_REGEX_PATTERN; + const char* autostart_filename(); + constexpr const char* conf_filename(); + std::map read_config_file(const char*); + } + namespace comfortable_swipe::service + { + void autostart(); + void buffer(); + void help(); + void restart(); + void start(); + void stop(); + } +} + +#endif /* __COMFORTABLE_SWIPE__index_hpp__ */ diff --git a/src/lib/service/autostart.cpp b/src/lib/service/autostart.cpp new file mode 100644 index 0000000..1a4575f --- /dev/null +++ b/src/lib/service/autostart.cpp @@ -0,0 +1,75 @@ +#ifndef __COMFORTABLE_SWIPE__service_autostart__ +#define __COMFORTABLE_SWIPE__service_autostart__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include // std::cerr, std::cout, std::endl +#include // std::ifstream, std::ofstream +#include // std::string +#include // std::remove +#include // std::system +#include "../index.hpp" + +namespace comfortable_swipe::service +{ + /** + * Toggles automatic startup of comfortable swipe. + */ + void autostart() + { + using comfortable_swipe::util::autostart_filename; + + const std::string& path = autostart_filename(); + if (std::ifstream(path.data()).good()) + { + // file found, delete it + if (std::remove(path.data()) != 0) + std::cerr << "Error: failed to switch off autostart. " + << "Maybe the autostart file is in use?" + << std::endl; + else + std::cout << "Autostart switched off" << std::endl; + } + else { + // file not found, create it + int result = std::system(("mkdir -p $(dirname " + path + ")").data()); + std::ofstream fout(path.data()); + if (result != 0 || !fout.good()) + std::cerr << "Error: failed to switch on autostart. " + << "Are you sure you have the permissions?" + << std::endl; + else { + fout << + "[Desktop Entry]\n" + "Type=Application\n" + "Exec=bash -c \"" + __COMFORTABLE_SWIPE__PROGRAM__ + " start\"\n" + "Hidden=false\n" + "NoDisplay=false\n" + "X-GNOME-Autostart-enabled=true\n" + "Name=Comfortable Swipe\n" + "Comment=3 or 4 touchpad gestures\n"; + std::cout << "Autostart switched on" << std::endl; + } + } + } +} + +#endif /* __COMFORTABLE_SWIPE__service_autostart__ */ diff --git a/src/lib/service/buffer.cpp b/src/lib/service/buffer.cpp new file mode 100644 index 0000000..c2d7303 --- /dev/null +++ b/src/lib/service/buffer.cpp @@ -0,0 +1,106 @@ +#ifndef __COMFORTABLE_SWIPE__service_buffer__ +#define __COMFORTABLE_SWIPE__service_buffer__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include // std::stoi, std::stof +#include // std::fgets_unlocked, stdin +#include // std::regex, std::regex_match, std::cmatch +#include "../index.hpp" + +/** + * Starts the comfortable-swipe service by buffering libinput debug-events. + */ +void comfortable_swipe::service::buffer() +{ + + // import utility methods + using comfortable_swipe::util::read_config_file; + using comfortable_swipe::util::conf_filename; + using comfortable_swipe::gesture::swipe_gesture; + + // import regex patterns + using comfortable_swipe::util::GESTURE_SWIPE_BEGIN_REGEX_PATTERN; + using comfortable_swipe::util::GESTURE_SWIPE_UPDATE_REGEX_PATTERN; + using comfortable_swipe::util::GESTURE_SWIPE_END_REGEX_PATTERN; + + // pre-compile regex patterns + static const std::regex gesture_begin(GESTURE_SWIPE_BEGIN_REGEX_PATTERN); + static const std::regex gesture_update(GESTURE_SWIPE_UPDATE_REGEX_PATTERN); + static const std::regex gesture_end(GESTURE_SWIPE_END_REGEX_PATTERN); + + // read config file + auto config = read_config_file(conf_filename()); + + // initialize swipegesture handler + swipe_gesture swipe + ( + config.count("threshold") ? std::atof(config["threshold"].data()) : 0.0, + config["left3"].c_str(), + config["left4"].c_str(), + config["right3"].c_str(), + config["right4"].c_str(), + config["up3"].c_str(), + config["up4"].c_str(), + config["down3"].c_str(), + config["down4"].c_str() + ); + + // prepare data containers + static const int MAX_LINE_LENGTH = 256; + static char data[MAX_LINE_LENGTH]; + static std::cmatch matches; + + // optimization flag for checking if GESTURE_SWIPE_BEGIN was dispatched + bool flag_begin = false; + + // start reading lines from input one by one + while (fgets_unlocked(data, MAX_LINE_LENGTH, stdin) != NULL) + { + if (!flag_begin) + { + if (std::regex_match(data, matches, gesture_begin) != 0) + { + swipe.fingers = std::stoi(matches[1]); + swipe.begin(); + flag_begin = true; + } + } + else /* flag_begin == true */ + { + if (std::regex_match(data, matches, gesture_update) != 0) + { + swipe.fingers = std::stoi(matches[1]); + swipe.dx = std::stof(matches[2]); + swipe.dy = std::stof(matches[3]); + swipe.udx = std::stof(matches[4]); + swipe.udy = std::stof(matches[5]); + swipe.update(); + } + else if (std::regex_match(data, matches, gesture_end) != 0) + { + swipe.fingers = std::stoi(matches[1]); + swipe.end(); + flag_begin = false; + } + } + } +} + +#endif /* __COMFORTABLE_SWIPE__service_buffer__ */ diff --git a/src/lib/service/help.cpp b/src/lib/service/help.cpp new file mode 100644 index 0000000..c73baf7 --- /dev/null +++ b/src/lib/service/help.cpp @@ -0,0 +1,46 @@ +#ifndef __COMFORTABLE_SWIPE__service_help__ +#define __COMFORTABLE_SWIPE__service_help__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include // std::puts, std::printf +#include "../index.hpp" + +namespace comfortable_swipe::service +{ + /** + * Shows the help window. + */ + void help() + { + using comfortable_swipe::util::conf_filename; + std::puts("comfortable-swipe [start|stop|restart|autostart|buffer|help]"); + std::puts(""); + std::puts("start - starts 3/4-finger gesture service"); + std::puts("stop - stops 3/4-finger gesture service"); + std::puts("restart - stops then starts 3/4-finger gesture service"); + std::puts("autostart - automatically run on startup (toggleable)"); + std::puts("buffer - parses output of libinput debug-events"); + std::puts("help - shows the help dialog"); + std::puts(""); + std::printf("Configuration file can be found in %s\n", conf_filename()); + } +} + +#endif /* __COMFORTABLE_SWIPE__service_help__ */ diff --git a/src/lib/service/restart.cpp b/src/lib/service/restart.cpp new file mode 100644 index 0000000..67bbaf0 --- /dev/null +++ b/src/lib/service/restart.cpp @@ -0,0 +1,36 @@ +#ifndef __COMFORTABLE_SWIPE__service_restart__ +#define __COMFORTABLE_SWIPE__service_restart__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "../index.hpp" + +namespace comfortable_swipe::service +{ + /** + * Restarts the comfortable-swipe service. + */ + void restart() + { + comfortable_swipe::service::start(); + comfortable_swipe::service::stop(); + } +} + +#endif /* __COMFORTABLE_SWIPE__service_restart__ */ diff --git a/src/lib/service/start.cpp b/src/lib/service/start.cpp new file mode 100644 index 0000000..61996fc --- /dev/null +++ b/src/lib/service/start.cpp @@ -0,0 +1,36 @@ +#ifndef __COMFORTABLE_SWIPE__service_start__ +#define __COMFORTABLE_SWIPE__service_start__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "../index.hpp" +#include // std::system + +namespace comfortable_swipe::service +{ + /** + * Starts the comfortable-swipe service by buffering libinput debug-events. + */ + void start() + { + (void) std::system("stdbuf -oL -e0 libinput debug-events | " __COMFORTABLE_SWIPE__PROGRAM__ " buffer"); + } +} + +#endif /* __COMFORTABLE_SWIPE__service_start__ */ diff --git a/src/lib/service/stop.cpp b/src/lib/service/stop.cpp new file mode 100644 index 0000000..655a6d2 --- /dev/null +++ b/src/lib/service/stop.cpp @@ -0,0 +1,73 @@ +#ifndef __COMFORTABLE_SWIPE__service_stop__ +#define __COMFORTABLE_SWIPE__service_stop__ + +#include // std::FILE, std::feof, std::fgets +#include // std::atoi, std::system +#include // std::string, std::to_string +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include // std::runtime_error +#include // popen, pclose, getpid + +namespace comfortable_swipe::service +{ + /** + * Stops all comfortable-swipe instances. + */ + void stop() + { + + // read all service names from process (pgrep) + char* buffer = new char[20]; + FILE* pipe = popen("pgrep -f comfortable-swipe", "r"); + + // make sure pipe exists + if (pipe == NULL) + { + throw std::runtime_error("stop command failed"); + } + + // buffer what to kill + std::string kill = "kill"; + + // read until end of line + while (!std::feof(pipe)) + { + if (std::fgets(buffer, 20, pipe) != NULL) + { + int pid = std::atoi(buffer); + if (pid != getpid()) + { + kill += " "; + kill += std::to_string(pid); + } + } + } + + // run "kill {pid1} {pid2}..." + (void) std::system(kill.data()); + delete[] buffer; + + // close the pipe + pclose(pipe); + + } +} + +#endif /* __COMFORTABLE_SWIPE__service_stop__ */ diff --git a/src/lib/util/autostart_filename.cpp b/src/lib/util/autostart_filename.cpp new file mode 100644 index 0000000..d3b0046 --- /dev/null +++ b/src/lib/util/autostart_filename.cpp @@ -0,0 +1,46 @@ +#ifndef __COMFORTABLE_SWIPE__util_autostart_filename__ +#define __COMFORTABLE_SWIPE__util_autostart_filename__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include // std::string +#include // getenv + +namespace comfortable_swipe::util +{ + /** + * The path where the autostart configuration is located. + */ + const char* autostart_filename() + { + static std::string filename; + if (filename.empty()) { + const char* xdg_config = getenv("XDG_CONFIG_HOME"); + std::string config( + xdg_config == NULL + ? std::string(getenv("HOME")) + "/.config" + : xdg_config + ); + filename = config + "/autostart/comfortable-swipe.desktop"; + } + return filename.data(); + } +} + +#endif /* __COMFORTABLE_SWIPE__util_autostart_filename__ */ diff --git a/src/lib/util/conf_filename.cpp b/src/lib/util/conf_filename.cpp new file mode 100644 index 0000000..5de5ae9 --- /dev/null +++ b/src/lib/util/conf_filename.cpp @@ -0,0 +1,35 @@ +#ifndef __COMFORTABLE_SWIPE__util_conf_filename__ +#define __COMFORTABLE_SWIPE__util_conf_filename__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "../index.hpp" + +namespace comfortable_swipe::util +{ + /** + * The path where the configuration file is located. + */ + constexpr const char* conf_filename() + { + return __COMFORTABLE_SWIPE__CONFIG__; + } +} + +#endif /* __COMFORTABLE_SWIPE__util_conf_filename__ */ diff --git a/src/lib/util/read_config_file.cpp b/src/lib/util/read_config_file.cpp new file mode 100644 index 0000000..d3eafb7 --- /dev/null +++ b/src/lib/util/read_config_file.cpp @@ -0,0 +1,100 @@ +#ifndef __COMFORTABLE_SWIPE__util_read_config_file__ +#define __COMFORTABLE_SWIPE__util_read_config_file__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include // std::map +#include // std::string +#include // std::ifstream +#include // std::cerr, std::endl, std::getline +#include // exit +#include // std::isspace + +namespace comfortable_swipe::util +{ + /** + * A utility method for reading the config file. + * + * @param filename (const char*) the path of the config file. + */ + std::map read_config_file(const char* filename) + { + + std::map conf; + std::ifstream fin(filename); + + if (!fin.is_open()) + { + std::cerr << "file \"" << filename << "\" does not exist!" << std::endl; + exit(1); + } + + static std::string line, token[2]; + int line_number = 0; + + while (std::getline(fin, line)) + { + ++line_number; + token[0].clear(); + token[1].clear(); + int length = line.length(); + int equal_flag = 0; + + // tokenize comfig config + for (int i = 0; i < length; ++i) + { + if (line[i] == '#') // skip comments + break; + if (line[i] == '=') // flag equal sign + { + if (++equal_flag > 1) + { + std::cerr << "error in conf file " << filename << std::endl; + std::cerr << "multiple equal signs in line " << line_number << std::endl; + exit(1); + } + } + else if (!std::isspace(line[i])) + { + // add to buffer + token[equal_flag].push_back(line[i]); + } + } + + // ignore empty lines + if (equal_flag == 0 && token[0].length() == 0) + continue; + + // no equal sign found in non-empty line + if (equal_flag == 0) + { + std::cerr << "error in conf file: " << filename << std::endl; + std::cerr << "equal sign expected in line " << line_number << std::endl; + exit(1); + } + + // equal sign found, add to tokens + if (token[1].length() > 0) + conf[token[0]] = token[1]; + } + + return conf; + } +} +#endif /* __COMFORTABLE_SWIPE__util_read_config_file__ */ diff --git a/src/lib/util/regex.cpp b/src/lib/util/regex.cpp new file mode 100644 index 0000000..b0556ce --- /dev/null +++ b/src/lib/util/regex.cpp @@ -0,0 +1,89 @@ +#ifndef __COMFORTABLE_SWIPE__util_regex__ +#define __COMFORTABLE_SWIPE__util_regex__ + +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +namespace comfortable_swipe::util +{ + /** + * Regex pattern for the libinput entry for start of swipe. + * Extracts one match for the number of fingers used during the swipe. + * + * eg. event15 GESTURE_SWIPE_BEGIN +34.33s 3 + * ^ + * fingers + */ + const char* GESTURE_SWIPE_BEGIN_REGEX_PATTERN = + "^" // start of string + "[ -]event\\d+" // event + "\\s+GESTURE_SWIPE_BEGIN" // gesture + "\\s+\\S+" // timestamp + "\\s+(\\d+)" // fingers + "\\s*$" // end of string + ; + + /** + * Regex pattern for the libinput entry for the end of swipe. + * Extracts one match for the number of fingers used during the swipe. + * + * eg. event15 GESTURE_SWIPE_END +35.03s 3 + * ^ + * fingers + */ + const char* GESTURE_SWIPE_END_REGEX_PATTERN = + "^" // start of string + "[ -]event\\d+" // event + "\\s+GESTURE_SWIPE_END" // gesture + "\\s+\\S+" // timestamp + "\\s+(\\d+)" // fingers + "\\s*$" // end of string + ; + + // matches signed decimal numbers (eg. "6.02" "-1.1") + #define CF_NUMBER_REGEX "-?\\d+(?:\\.\\d+)" + + // matches and extracts a space-prefixed signed fraction (eg. "-3.00/ 5.12") + #define CF_NUMBER_DIVISION "\\s*(" CF_NUMBER_REGEX ")/\\s*(" CF_NUMBER_REGEX ")" + + /** + * Regex pattern for the libinput entry for during a swipe. + * Extracts number of fingers used and the speed (normal and accelerated) of the swipe. + * + * eg. event15 GESTURE_SWIPE_UPDATE +34.70s 3 -0.12/ 4.99 (-0.33/13.50 unaccelerated) + * ^ ^ ^ ^ ^ + * fingers dx dy udx udy + */ + const char* GESTURE_SWIPE_UPDATE_REGEX_PATTERN = + "^" // start of string + "[ -]event\\d+" // event + "\\s+GESTURE_SWIPE_UPDATE" // gesture + "\\s+\\S+" // timestamp + "\\s+(\\d+)" // fingers + "\\s+" CF_NUMBER_DIVISION // speed (dx/dy) + "\\s+\\(" CF_NUMBER_DIVISION "\\s+unaccelerated\\)" // unaccelerated speed (udx/udy) + "\\s*$" // end of string + ; + + // delete macros + #undef CF_NUMBER_DIVISION + #undef CF_NUMBER_EXTRACT + #undef CF_NUMBER_REGEX +} + +#endif /* __COMFORTABLE_SWIPE__util_regex__ */ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..6f2b7b7 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,46 @@ +/* +Comfortable Swipe +by Rico Tiongson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// Compile: g++ main.cpp -std=c++11 -lxdo + +#include // std::string +#include "lib/comfortable_swipe" + +/* MAIN DRIVER FUNCTION */ + +int main(int argc, char** args) +{ + using namespace comfortable_swipe::service; + + if (argc > 1) + { + std::string arg = args[1]; + // select based on argument + if (arg == "start") start(); + else if (arg == "stop") stop(); + else if (arg == "restart") restart(); + else if (arg == "buffer") buffer(); + else if (arg == "autostart") autostart(); + else help(); + } + + else + help(); + + return 0; +} \ No newline at end of file