diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5502323..b5b260c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,6 +71,7 @@ set(libinputactions_SRCS libinputactions/interfaces/CursorShapeProvider.h libinputactions/interfaces/NotificationManager.cpp libinputactions/interfaces/OnScreenMessageManager.h + libinputactions/interfaces/OverlayManager.cpp libinputactions/interfaces/PointerPositionGetter.h libinputactions/interfaces/PointerPositionSetter.h libinputactions/interfaces/SessionLock.h diff --git a/src/libinputactions/InputActionsMain.cpp b/src/libinputactions/InputActionsMain.cpp index ebc4701..58c1775 100644 --- a/src/libinputactions/InputActionsMain.cpp +++ b/src/libinputactions/InputActionsMain.cpp @@ -9,6 +9,7 @@ #include "interfaces/CursorShapeProvider.h" #include "interfaces/NotificationManager.h" #include "interfaces/OnScreenMessageManager.h" +#include "interfaces/OverlayManager.h" #include "interfaces/PointerPositionGetter.h" #include "interfaces/PointerPositionSetter.h" #include "interfaces/SessionLock.h" @@ -34,6 +35,7 @@ InputActionsMain::~InputActionsMain() g_cursorShapeProvider.reset(); g_notificationManager.reset(); g_onScreenMessageManager.reset(); + g_overlayManager.reset(); g_pointerPositionGetter.reset(); g_pointerPositionSetter.reset(); g_textInput.reset(); @@ -64,6 +66,7 @@ void InputActionsMain::setMissingImplementations() setMissingImplementation(g_cursorShapeProvider); setMissingImplementation(g_notificationManager); setMissingImplementation(g_onScreenMessageManager); + setMissingImplementation(g_overlayManager); setMissingImplementation(g_pointerPositionGetter); setMissingImplementation(g_pointerPositionSetter); setMissingImplementation(g_textInput); diff --git a/src/libinputactions/config/ConfigIssue.cpp b/src/libinputactions/config/ConfigIssue.cpp index 42e4a1b..32cd0db 100644 --- a/src/libinputactions/config/ConfigIssue.cpp +++ b/src/libinputactions/config/ConfigIssue.cpp @@ -214,6 +214,16 @@ QString MissingRequiredPropertyConfigException::message() const return QString("Required property '%1' was not specified.").arg(m_property); } +OverlayProgramDoesNotExistConfigException::OverlayProgramDoesNotExistConfigException(const Node *node) + : ConfigException(node) +{ +} + +QString OverlayProgramDoesNotExistConfigException::message() const +{ + return QString("Failed to find the inputactions-overlay program. It must be installed in one of the directories specified in the PATH environment variable."); +} + UncaughtScriptErrorConfigException::UncaughtScriptErrorConfigException(const Node *node, QJSValue error) : ConfigException(node) , m_message(ScriptingEngine::errorToString(error)) diff --git a/src/libinputactions/config/ConfigIssue.h b/src/libinputactions/config/ConfigIssue.h index 3903f52..aecae8b 100644 --- a/src/libinputactions/config/ConfigIssue.h +++ b/src/libinputactions/config/ConfigIssue.h @@ -231,6 +231,16 @@ class MissingRequiredPropertyConfigException QString m_property; }; +class OverlayProgramDoesNotExistConfigException + : public ConfigException + , public virtual Copyable +{ +public: + OverlayProgramDoesNotExistConfigException(const Node *node); + + QString message() const override; +}; + class UncaughtScriptErrorConfigException : public ConfigException , public virtual Copyable diff --git a/src/libinputactions/config/ConfigLoader.cpp b/src/libinputactions/config/ConfigLoader.cpp index 04645ba..ae066d8 100644 --- a/src/libinputactions/config/ConfigLoader.cpp +++ b/src/libinputactions/config/ConfigLoader.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,8 @@ struct ConfigData std::vector deviceRules; std::set emergencyCombination = {KEY_BACKSPACE, KEY_SPACE, KEY_ENTER}; + + bool enableMouseStrokeOverlay{}; }; ConfigLoader::ConfigLoader() @@ -256,6 +259,15 @@ QFuture> ConfigLoader::createConfig(const QString &r config->touchscreenTriggerHandlerFactory(nullptr); } + if (const auto *overlayNode = root->mapAt("overlay")) { + if (const auto *mouseStrokeNode = overlayNode->mapAt("mouse_stroke")) { + loadMember(config->enableMouseStrokeOverlay, mouseStrokeNode->at("enable")); + if (config->enableMouseStrokeOverlay && !g_overlayManager->overlayProgramExists()) { + throw OverlayProgramDoesNotExistConfigException(overlayNode); + } + } + } + root->at("anchors"); // Allow users to define anchors somewhere without unused property issues root->addUnusedMapPropertyIssues(); return config; @@ -273,6 +285,9 @@ QFuture ConfigLoader::activateConfig(std::shared_ptr config, b auto *scriptingConfig = g_scriptingEngine->coreModule().config(); return scriptingConfig->aboutToBeActivatedSignal().emitAsync(false).then([initialize, scriptingConfig, config]() { + g_overlayManager->setEnabled(config->enableMouseStrokeOverlay); + g_overlayManager->setMouseStrokeOverlayEnabled(config->enableMouseStrokeOverlay); + g_globalConfig->setAllowExternalVariableAccess(config->allowExternalVariableAccess); g_globalConfig->setAutoReload(config->autoReload); g_globalConfig->setSendNotificationOnError(config->sendNotificationOnError); diff --git a/src/libinputactions/dbus/MainDBusInterface.cpp b/src/libinputactions/dbus/MainDBusInterface.cpp index cc13387..936ee65 100644 --- a/src/libinputactions/dbus/MainDBusInterface.cpp +++ b/src/libinputactions/dbus/MainDBusInterface.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -67,6 +68,7 @@ void MainDBusInterface::recordStroke(const QDBusMessage &message) return; } + g_overlayManager->showMouseStrokeOverlay(); g_onScreenMessageManager->showMessage("InputActions is recording input. Perform a stroke gesture by moving the mouse or any amount of fingers all in the " "same direction on a touchpad or a touchscreen. Recording will end after 250 ms of inactivity."); @@ -74,9 +76,11 @@ void MainDBusInterface::recordStroke(const QDBusMessage &message) m_reply = message.createReply(); g_strokeRecorder->recordStroke([this](const auto &stroke) { + g_overlayManager->hideMouseStrokeOverlay(); + g_onScreenMessageManager->hideMessage(); + m_reply << strokeToBase64(stroke); m_bus.send(m_reply); - g_onScreenMessageManager->hideMessage(); }); } diff --git a/src/libinputactions/handlers/MotionTriggerHandler.cpp b/src/libinputactions/handlers/MotionTriggerHandler.cpp index 7ec9665..c173261 100644 --- a/src/libinputactions/handlers/MotionTriggerHandler.cpp +++ b/src/libinputactions/handlers/MotionTriggerHandler.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -260,6 +261,8 @@ TriggerManagementOperationResult MotionTriggerHandler::endTriggersCustom(Trigger return result; } + g_overlayManager->hideMouseStrokeOverlay(); + const Stroke stroke(m_deltas); Trigger *best = nullptr; double bestScore = 0; diff --git a/src/libinputactions/handlers/MouseTriggerHandler.cpp b/src/libinputactions/handlers/MouseTriggerHandler.cpp index 5f22927..b9f8172 100644 --- a/src/libinputactions/handlers/MouseTriggerHandler.cpp +++ b/src/libinputactions/handlers/MouseTriggerHandler.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -220,6 +221,16 @@ void MouseTriggerHandler::triggerActivated(const Trigger *trigger) MotionTriggerHandler::triggerActivated(trigger); } +void MouseTriggerHandler::activeTriggersChanged() +{ + const auto hasActiveStrokeTriggers = hasActiveTriggers(TriggerType::Stroke); + if (!hasActiveStrokeTriggers && g_overlayManager->mouseStrokeOverlayVisible()) { + g_overlayManager->hideMouseStrokeOverlay(); + } else if (hasActiveStrokeTriggers && !g_overlayManager->mouseStrokeOverlayVisible()) { + g_overlayManager->showMouseStrokeOverlay(); + } +} + std::unique_ptr MouseTriggerHandler::createActivationEvent() const { auto event = TriggerHandler::createActivationEvent(); diff --git a/src/libinputactions/handlers/MouseTriggerHandler.h b/src/libinputactions/handlers/MouseTriggerHandler.h index 7fbc0c2..8fb9eb4 100644 --- a/src/libinputactions/handlers/MouseTriggerHandler.h +++ b/src/libinputactions/handlers/MouseTriggerHandler.h @@ -49,6 +49,7 @@ class MouseTriggerHandler : public MotionTriggerHandler std::unique_ptr createActivationEvent() const override; void triggerActivated(const Trigger *trigger) override; + void activeTriggersChanged() override; private: /** diff --git a/src/libinputactions/handlers/TriggerHandler.cpp b/src/libinputactions/handlers/TriggerHandler.cpp index dbeb2b6..3679f53 100644 --- a/src/libinputactions/handlers/TriggerHandler.cpp +++ b/src/libinputactions/handlers/TriggerHandler.cpp @@ -61,6 +61,7 @@ TriggerManagementOperationResult TriggerHandler::activateTriggers(TriggerTypes t for (auto &trigger : triggers(types, event)) { triggerActivated(trigger); m_activeTriggers.push_back(trigger); + activeTriggersChanged(); result.success = true; result.block = result.block || trigger->blockEvents(); @@ -117,6 +118,7 @@ TriggerManagementOperationResult TriggerHandler::updateTriggers(const std::mapcancel(); } it = m_activeTriggers.erase(it); + activeTriggersChanged(); continue; } @@ -169,6 +171,7 @@ TriggerManagementOperationResult TriggerHandler::doEndTriggers(TriggerTypes type result.block = result.block || trigger->blockEvents(); it = m_activeTriggers.erase(it); + activeTriggersChanged(); if (!trigger->canEnd()) { trigger->cancel(); continue; @@ -215,6 +218,7 @@ TriggerManagementOperationResult TriggerHandler::doCancelTriggers(TriggerTypes t trigger->cancel(); it = m_activeTriggers.erase(it); + activeTriggersChanged(); } return result; } @@ -226,6 +230,7 @@ void TriggerHandler::cancelTriggers(Trigger *except) if (gesture != except) { gesture->cancel(); it = m_activeTriggers.erase(it); + activeTriggersChanged(); continue; } it++; diff --git a/src/libinputactions/handlers/TriggerHandler.h b/src/libinputactions/handlers/TriggerHandler.h index 9c70a29..bdca984 100644 --- a/src/libinputactions/handlers/TriggerHandler.h +++ b/src/libinputactions/handlers/TriggerHandler.h @@ -119,6 +119,7 @@ class TriggerHandler : public QObject virtual void triggerActivated(const Trigger *trigger) {} virtual void activatingTriggers(TriggerTypes types) {} + virtual void activeTriggersChanged() {} virtual TriggerManagementOperationResult endTriggersCustom(TriggerTypes types) { return {}; } /** diff --git a/src/libinputactions/interfaces/OverlayManager.cpp b/src/libinputactions/interfaces/OverlayManager.cpp new file mode 100644 index 0000000..3b8a8e8 --- /dev/null +++ b/src/libinputactions/interfaces/OverlayManager.cpp @@ -0,0 +1,98 @@ +/* + Input Actions - Input handler that executes user-defined actions + Copyright (C) 2026 Marcin Woźniak + + 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 "OverlayManager.h" +#include +#include + +namespace InputActions +{ + +OverlayManager::OverlayManager() + : m_process(new QProcess) +{ + m_process->setProgram("/usr/bin/env"); + m_process->setArguments({"inputactions-overlay"}); + m_process->setUnixProcessParameters(QProcess::UnixProcessFlag::ResetIds); +} + +OverlayManager::~OverlayManager() +{ + if (m_process->state() == QProcess::NotRunning) { + m_process->deleteLater(); + } else { + QObject::connect(m_process, &QProcess::finished, [process = m_process]() { + process->deleteLater(); + }); + m_process->terminate(); + } +} + +void OverlayManager::setEnabled(bool value) +{ + if (!value && m_process->state() != QProcess::NotRunning) { + m_process->terminate(); + } else if (value && m_process->state() == QProcess::NotRunning) { + m_process->start(); + } +} + +bool OverlayManager::overlayProgramExists() const +{ + return !QStandardPaths::findExecutable("inputactions-overlay").isEmpty(); +} + +void OverlayManager::showMouseStrokeOverlay() +{ + if (auto *interface = mouseStrokeOverlayInterface()) { + interface->call("show"); + m_mouseStrokeOverlayVisible = true; + } +} + +void OverlayManager::hideMouseStrokeOverlay() +{ + if (auto *interface = mouseStrokeOverlayInterface()) { + interface->call("hide"); + m_mouseStrokeOverlayVisible = false; + } +} + +QDBusInterface *OverlayManager::mouseStrokeOverlayInterface() +{ + if (!m_mouseStrokeOverlayEnabled) { + return {}; + } + + if (m_mouseStrokeOverlayInterface) { + return &m_mouseStrokeOverlayInterface.value(); + } + + m_mouseStrokeOverlayInterface.emplace("org.inputactions.overlay", + "/org/inputactions/overlay/MouseStrokeOverlay", + "org.inputactions.overlay.MouseStrokeOverlay", + QDBusConnectionHelpers::sessionBus()); + if (m_mouseStrokeOverlayInterface->isValid()) { + return &m_mouseStrokeOverlayInterface.value(); + } + + m_mouseStrokeOverlayInterface.reset(); + return {}; +} + +} \ No newline at end of file diff --git a/src/libinputactions/interfaces/OverlayManager.h b/src/libinputactions/interfaces/OverlayManager.h new file mode 100644 index 0000000..267e20a --- /dev/null +++ b/src/libinputactions/interfaces/OverlayManager.h @@ -0,0 +1,60 @@ +/* + Input Actions - Input handler that executes user-defined actions + Copyright (C) 2026 Marcin Woźniak + + 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 . +*/ + +#pragma once + +#include +#include +#include + +namespace InputActions +{ + +class OverlayManager : public QObject +{ + Q_OBJECT + +public: + OverlayManager(); + ~OverlayManager() override; + + void setEnabled(bool value); + void setMouseStrokeOverlayEnabled(bool value) { m_mouseStrokeOverlayEnabled = value; } + + bool overlayProgramExists() const; + + void showMouseStrokeOverlay(); + void hideMouseStrokeOverlay(); + bool mouseStrokeOverlayVisible() const { return m_mouseStrokeOverlayVisible; } + +private: + /** + * Nullptr if not available. + */ + QDBusInterface *mouseStrokeOverlayInterface(); + + QProcess *m_process; + + std::optional m_mouseStrokeOverlayInterface; + bool m_mouseStrokeOverlayVisible{}; + bool m_mouseStrokeOverlayEnabled{}; +}; + +inline std::unique_ptr g_overlayManager; + +} \ No newline at end of file