diff --git a/src/config/config.cpp b/src/config/config.cpp index 666aaaa7..5d9e4681 100755 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -73,6 +73,7 @@ Config::getGestureConfig(const std::string &application, void Config::loadDefaultGlobalSettings() { this->globalSettings["animation_delay"] = "150"; this->globalSettings["action_execute_threshold"] = "20"; + this->globalSettings["reposition_cursor"] = "NEVER"; } std::string Config::getConfigKey(const std::string &application, diff --git a/src/config/xml-config-loader.cpp b/src/config/xml-config-loader.cpp index b708d267..53250c7a 100644 --- a/src/config/xml-config-loader.cpp +++ b/src/config/xml-config-loader.cpp @@ -59,35 +59,39 @@ std::filesystem::path XmlConfigLoader::getConfigFilePath() { if (!std::filesystem::exists(usrConfigFile)) { throw std::runtime_error{ - "File /usr/share/touchegg/touchegg.conf not found.\n" - "Reinstall Touchégg to solve this issue"}; + "File \"/usr/share/touchegg/touchegg.conf\" not found.\n" + "\tReinstall Touchégg to solve this issue."}; } return std::filesystem::exists(homeConfigFile) ? homeConfigFile : usrConfigFile; } -void XmlConfigLoader::parseConfig() { +void XmlConfigLoader::parseConfig(bool isReload) { std::filesystem::path configPath = XmlConfigLoader::getConfigFilePath(); - tlg::info << "Using configuration file " << configPath << std::endl; + tlg::info << "\tUsing configuration file: " << configPath << std::endl; pugi::xml_document doc; pugi::xml_parse_result parsedSuccessfully = doc.load_file(configPath.c_str()); if (!parsedSuccessfully) { - throw std::runtime_error{"Error parsing configuration file"}; + throw std::runtime_error{"Error parsing configuration file."}; } pugi::xml_node rootNode = doc.document_element(); - this->parseGlobalSettings(rootNode); + this->parseGlobalSettings(rootNode, isReload); this->parseApplicationXmlNodes(rootNode); } -void XmlConfigLoader::parseGlobalSettings(const pugi::xml_node &rootNode) { +void XmlConfigLoader::parseGlobalSettings(const pugi::xml_node &rootNode, bool isReload) { pugi::xml_node settingsNode = rootNode.child("settings"); for (pugi::xml_node propertyNode : settingsNode.children("property")) { const std::string property = propertyNode.attribute("name").value(); const std::string value = propertyNode.child_value(); + if (isReload && property == "reposition_cursor") { + tlg::info << "\tNote: Changing \"reposition_cursor\" currently requires" + " restarting the client service.\n" << std::endl; + } this->config->saveGlobalSetting(property, value); } } @@ -168,10 +172,10 @@ void XmlConfigLoader::watchConfig() { } if (reloadSettings) { - tlg::info << "Your configuration file changed, reloading your settings" + tlg::info << "Your configuration file changed. Reloading your settings..." << std::endl; this->config->clear(); - this->parseConfig(); + this->parseConfig(true); } } }}; diff --git a/src/config/xml-config-loader.h b/src/config/xml-config-loader.h index 4155d7f4..34ccb8a9 100755 --- a/src/config/xml-config-loader.h +++ b/src/config/xml-config-loader.h @@ -64,13 +64,13 @@ class XmlConfigLoader { /** * Parse the XML configuration file placed in path. */ - void parseConfig(); + void parseConfig(bool isReload = false); /** * Parse the global settings. * @param rootNode XML root node, usually named "touchégg". */ - void parseGlobalSettings(const pugi::xml_node &rootNode); + void parseGlobalSettings(const pugi::xml_node &rootNode, bool isReload); /** * Parse the "application" nodes in the XML. diff --git a/src/daemon/daemon-client.cpp b/src/daemon/daemon-client.cpp index 2f8d25a9..561b532c 100644 --- a/src/daemon/daemon-client.cpp +++ b/src/daemon/daemon-client.cpp @@ -126,11 +126,19 @@ std::unique_ptr DaemonClient::makeGestureFromSignalParams( int fingers = -1; DeviceType deviceType = DeviceType::UNKNOWN; uint64_t elapsedTime = -1; - - g_variant_get(signalParameters, // NOLINT - "(uudiut)", &type, &direction, &percentage, &fingers, - &deviceType, &elapsedTime); + XYPosition cursorPosition = {-1, -1}; + + if (g_variant_check_format_string(signalParameters, "(uudiut)", false)) { + g_variant_get(signalParameters, // NOLINT + "(uudiut)", &type, &direction, &percentage, &fingers, + &deviceType, &elapsedTime); + } else { + g_variant_get( + signalParameters, // NOLINT + "(uudiut(dd))", &type, &direction, &percentage, &fingers, + &deviceType, &elapsedTime, &(cursorPosition.x), &(cursorPosition.y)); + } return std::make_unique(type, direction, percentage, fingers, - deviceType, elapsedTime); + deviceType, elapsedTime, cursorPosition); } diff --git a/src/daemon/daemon-server.cpp b/src/daemon/daemon-server.cpp index 0fbb25cb..f8d132ff 100644 --- a/src/daemon/daemon-server.cpp +++ b/src/daemon/daemon-server.cpp @@ -115,14 +115,31 @@ void DaemonServer::send(const std::string &signalName, std::vector closedConnections{}; // Copy every gesture field into the signal parameters for serialization - GVariant *signalParams = - g_variant_new("(uudiut)", // NOLINT - static_cast(gesture->type()), // u - static_cast(gesture->direction()), // u - gesture->percentage(), // d - gesture->fingers(), // i - static_cast(gesture->performedOnDeviceType()), // u - gesture->elapsedTime()); // t + GVariant *signalParams; + if ( + signalName == DBUS_ON_GESTURE_UPDATE + || signalName == DBUS_ON_GESTURE_END + ) { + signalParams = { + g_variant_new( + "(uudiut(dd))", // NOLINT + static_cast(gesture->type()), // u + static_cast(gesture->direction()), // u + gesture->percentage(), // d + gesture->fingers(), // i + static_cast(gesture->performedOnDeviceType()), // u + gesture->elapsedTime(), // t + gesture->cursorPosition())}; // dd + } else { + signalParams = {g_variant_new( + "(uudiut)", // NOLINT + static_cast(gesture->type()), // u + static_cast(gesture->direction()), // u + gesture->percentage(), // d + gesture->fingers(), // i + static_cast(gesture->performedOnDeviceType()), // u + gesture->elapsedTime())}; // t + } g_variant_ref_sink(signalParams); // Send the message to every client diff --git a/src/daemon/dbus.h b/src/daemon/dbus.h index 9e9f1924..ae90b65d 100644 --- a/src/daemon/dbus.h +++ b/src/daemon/dbus.h @@ -50,6 +50,7 @@ constexpr auto DBUS_INTROSPECTION_XML = " " " " " " + " " " " " " " " @@ -58,6 +59,7 @@ constexpr auto DBUS_INTROSPECTION_XML = " " " " " " + " " " " " " ""; diff --git a/src/gesture-controller/gesture-controller.cpp b/src/gesture-controller/gesture-controller.cpp old mode 100755 new mode 100644 index 5801c963..bde050e0 --- a/src/gesture-controller/gesture-controller.cpp +++ b/src/gesture-controller/gesture-controller.cpp @@ -33,7 +33,8 @@ GestureController::GestureController(const Config &config, const WindowSystem &windowSystem) - : config(config), windowSystem(windowSystem) {} + : config(config), windowSystem(windowSystem), + repositionCursor(repositionCursorOptFromStr(config.getGlobalSetting("reposition_cursor"))) {} void GestureController::onGestureBegin(std::unique_ptr gesture) { tlg::debug << "Gesture begin detected" << std::endl; @@ -76,6 +77,18 @@ void GestureController::onGestureBegin(std::unique_ptr gesture) { } void GestureController::onGestureUpdate(std::unique_ptr gesture) { + // Move cursor to gesture update position, if setting is enabled. + XYPosition curPos = gesture->cursorPosition(); + if ( + (repositionCursor & RepositionCursorOpt::GESTURE_UPDATE) + && gestureTypeSupportsCursorReposition(gesture->type()) + ) { + tlg::debug << "cursorPosition (GestureUpdate): {" + << curPos.x << ", " << curPos.y + << "}" << std::endl; + this->windowSystem.positionCursor(curPos.x, curPos.y); + } + if (this->executeAction) { tlg::debug << "Gesture update detected (" << gesture->percentage() << "%)" << std::endl; @@ -85,6 +98,20 @@ void GestureController::onGestureUpdate(std::unique_ptr gesture) { } void GestureController::onGestureEnd(std::unique_ptr gesture) { + // Move cursor to position where gesture ended, if setting is enabled. + XYPosition curPos = gesture->cursorPosition(); + if ( + (repositionCursor & RepositionCursorOpt::GESTURE_END) + && gestureTypeSupportsCursorReposition(gesture->type()) + ) { + tlg::debug << "cursorPosition (GestureEnd): {" + << curPos.x << ", " << curPos.y + << "}" << std::endl; + this->windowSystem.positionCursor(curPos.x, curPos.y, + // Extra verbose debugging if set to *only* GESTURE_END. + (repositionCursor == RepositionCursorOpt::GESTURE_END)); + } + if (this->executeAction) { tlg::debug << "Gesture end detected" << std::endl; gesture->setDirection(this->rotatedDirection); diff --git a/src/gesture-controller/gesture-controller.h b/src/gesture-controller/gesture-controller.h index e9dc6bdc..8909de5c 100755 --- a/src/gesture-controller/gesture-controller.h +++ b/src/gesture-controller/gesture-controller.h @@ -22,6 +22,7 @@ #include "actions/action.h" #include "gesture-controller/gesture-controller-delegate.h" +#include "utils/reposition-cursor.h" class Config; class Gesture; class WindowSystem; @@ -44,6 +45,9 @@ class GestureController : public GestureControllerDelegate { const Config &config; const WindowSystem &windowSystem; + // Setting values cached for efficient access: + RepositionCursorOpt repositionCursor; // Bitmask. + /** * The action to perform. */ diff --git a/src/gesture-gatherer/libinput-gesture-gatherer.h b/src/gesture-gatherer/libinput-gesture-gatherer.h old mode 100755 new mode 100644 diff --git a/src/gesture-gatherer/libinput-touch-handler.cpp b/src/gesture-gatherer/libinput-touch-handler.cpp index 2f8b3837..72bd453b 100644 --- a/src/gesture-gatherer/libinput-touch-handler.cpp +++ b/src/gesture-gatherer/libinput-touch-handler.cpp @@ -92,7 +92,8 @@ void LibinputTouchHandler::handleTouchUp(struct libinput_event *event) { auto gesture = std::make_unique( this->state.type, this->state.direction, percentage, - this->state.startFingers, DeviceType::TOUCHSCREEN, elapsedTime); + this->state.startFingers, DeviceType::TOUCHSCREEN, elapsedTime, + XYPosition{this->state.currentX[slot], this->state.currentY[slot]}); this->gestureController->onGestureEnd(std::move(gesture)); this->state.reset(); @@ -152,7 +153,8 @@ void LibinputTouchHandler::handleTouchMotion(struct libinput_event *event) { auto gesture = std::make_unique( this->state.type, this->state.direction, percentage, - this->state.startFingers, DeviceType::TOUCHSCREEN, elapsedTime); + this->state.startFingers, DeviceType::TOUCHSCREEN, elapsedTime, + XYPosition{this->state.currentX[slot], this->state.currentY[slot]}); this->gestureController->onGestureUpdate(std::move(gesture)); } } diff --git a/src/gesture/gesture.h b/src/gesture/gesture.h index 8a7e320d..30280c44 100644 --- a/src/gesture/gesture.h +++ b/src/gesture/gesture.h @@ -24,6 +24,15 @@ #include "gesture/gesture-direction.h" #include "gesture/gesture-type.h" +/** + * Intended to store absolute position on display in millimeters. + * Useful to export position from Libinput, which seems unaware of pixels. + */ +struct XYPosition { + double x; + double y; +}; + /** * Gestures implementations change depending on the driver/backend. This is the * basic interface of a gesture. @@ -38,6 +47,16 @@ class Gesture { gestureFingers(fingers), deviceType(performedOnDeviceType), gestureElapsedTime(elapsedTime) {} + Gesture(GestureType type, GestureDirection direction, double percentage, + int fingers, DeviceType performedOnDeviceType, uint64_t elapsedTime, + XYPosition cursorEndPosition) + : gestureType(type), + gestureDirection(direction), + gesturePercentage(percentage), + gestureFingers(fingers), + deviceType(performedOnDeviceType), + gestureElapsedTime(elapsedTime), + gestureEndPosition(cursorEndPosition) {} /** * @returns The gesture type. @@ -73,6 +92,12 @@ class Gesture { */ uint64_t elapsedTime() const { return this->gestureElapsedTime; } + /** + * Position to set cursor when gesture ends.. + * @returns A struct containing X and Y position integers. + */ + XYPosition cursorPosition() const { return this->gestureEndPosition; } + /** * Set the gesture direction. * @see GestureDirection @@ -88,6 +113,7 @@ class Gesture { int gestureFingers = -1; DeviceType deviceType = DeviceType::UNKNOWN; uint64_t gestureElapsedTime = -1; + XYPosition gestureEndPosition{-1, -1}; }; #endif // GESTURE_GESTURE_H_ diff --git a/src/utils/reposition-cursor.h b/src/utils/reposition-cursor.h new file mode 100644 index 00000000..a56ed70b --- /dev/null +++ b/src/utils/reposition-cursor.h @@ -0,0 +1,55 @@ +#ifndef UTILS_REPOSITION_CURSOR_H_ +#define UTILS_REPOSITION_CURSOR_H_ + +#include +#include "gesture/gesture-type.h" + +// Bitmask representing options for when cursor should be repositioned. +enum RepositionCursorOpt { + NEVER = 0b00, // 0 + GESTURE_END = 0b01, // 1 + GESTURE_UPDATE = 0b10, // 2 + GESTURE_UPDATE_AND_END = 0b11 // 3 +}; + +inline bool gestureTypeSupportsCursorReposition(GestureType gestureType) { + switch (gestureType) { + case GestureType::SWIPE: + case GestureType::TAP: + return true; + default: + return false; + } +}; + +inline std::string repositionCursorOptToStr(RepositionCursorOpt repositionCursorOpt) { + switch (repositionCursorOpt) { + case RepositionCursorOpt::NEVER: + return "NEVER"; + case RepositionCursorOpt::GESTURE_END: + return "GESTURE_END"; + case RepositionCursorOpt::GESTURE_UPDATE: + return "GESTURE_UPDATE"; + case RepositionCursorOpt::GESTURE_UPDATE_AND_END: + return "GESTURE_UPDATE_AND_END"; + default: + return "NEVER"; + } +} + +inline RepositionCursorOpt repositionCursorOptFromStr(const std::string &str) { + constexpr auto prefixLength = sizeof("GESTURE_")-1; // -1 for terminating null. + if (!str.compare(prefixLength, std::string::npos, "END")) { + return RepositionCursorOpt::GESTURE_END; + } + if (!str.compare(prefixLength, std::string::npos, "UPDATE")) { + return RepositionCursorOpt::GESTURE_UPDATE; + } + if (!str.compare(prefixLength, std::string::npos, "UPDATE_AND_END")) { + return RepositionCursorOpt::GESTURE_UPDATE_AND_END; + } + // Consider warning user if (str != "NEVER") ... + return RepositionCursorOpt::NEVER; +} + +#endif // UTILS_REPOSITION_CURSOR_H_ diff --git a/src/window-system/window-system.h b/src/window-system/window-system.h index 0c9fd75c..3884e888 100644 --- a/src/window-system/window-system.h +++ b/src/window-system/window-system.h @@ -150,6 +150,13 @@ class WindowSystem { */ virtual void sendMouseUp(int button) const = 0; + /** + * Directly positions cursor. + * Only one movement event should be triggered. + * No button click events should be sent. + */ + virtual void positionCursor(double x, double y, bool verboseDebug = false) const = 0; + /** * @returns The size of the desktop workarea, ie, the area of the desktop not * used by system elements like docks, panels, etc. diff --git a/src/window-system/x11.cpp b/src/window-system/x11.cpp index 3001ec32..ff88d756 100644 --- a/src/window-system/x11.cpp +++ b/src/window-system/x11.cpp @@ -32,6 +32,7 @@ #include #include "window-system/x11-cairo-surface.h" +#include "utils/logger.h" X11::X11() { this->display = XOpenDisplay(nullptr); @@ -476,6 +477,53 @@ void X11::sendMouseUp(int button) const { XFlush(this->display); } +void X11::positionCursor(double xMm, double yMm, bool verboseDebug) const { + int screen = DefaultScreen(this->display); + + // Note: These values are subject to system configuration (e.g. D.P.I.). + double dwMM = DisplayWidthMM(this->display, screen); + double dhMM = DisplayHeightMM(this->display, screen); + double dwPixels = DisplayWidth(this->display, screen); + double dhPixels = DisplayHeight(this->display, screen); + + int targetX = (xMm / dwMM) * dwPixels; + int targetY = (yMm / dhMM) * dhPixels; + + // Get current cursor position. + Window rootWindow = None; + Window childWindow = None; + int pointerX = 0; + int pointerY = 0; + int childX = 0; + int childY = 0; + unsigned int mask = 0; + int success = XQueryPointer(this->display, XDefaultRootWindow(this->display), + &rootWindow, &childWindow, &pointerX, &pointerY, + &childX, &childY, &mask); + if (!success) { + std::cout << "Failed to query pointer. Cursor will not be repositioned." + << std::endl; + return; + } + + targetX = targetX - pointerX; + targetY = targetY - pointerY; + + if (verboseDebug) tlg::debug + << " Gesture offset X (mm): " << xMm << '\n' + << " Gesture offset Y (mm): " << yMm << '\n' + << " Display width (mm): " << dwMM << '\n' + << " Display height (mm): " << dhMM << '\n' + << " Display width (pixels): " << dwPixels << '\n' + << "Display height (pixels): " << dhPixels << '\n' + << " Cursor target: " << targetX << ", " << targetY + << "Current cursor position: " << pointerX << ", " << pointerY + << " Relative distance: " << targetX << ", " << targetY + << std::endl; + XWarpPointer(this->display, None, None, 0, 0, 0, 0, targetX, targetY); + XFlush(this->display); +} + Rectangle X11::getDesktopWorkarea() const { // When multiple physical screens are connected, the root window's size is the // sum of all of them. Use Xrandr to get the screen size of the physical diff --git a/src/window-system/x11.h b/src/window-system/x11.h index a2713ee0..44a2176f 100644 --- a/src/window-system/x11.h +++ b/src/window-system/x11.h @@ -68,6 +68,7 @@ class X11 : public WindowSystem { bool isPress) const override; void sendMouseDown(int button) const override; void sendMouseUp(int button) const override; + void positionCursor(double xMm, double yMm, bool verboseDebug = false) const override; Rectangle getDesktopWorkarea() const override; void changeDesktop(ActionDirection direction, bool cyclic) const override;