diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..057c136 --- /dev/null +++ b/.clang-format @@ -0,0 +1,46 @@ +Language: Cpp +Standard: c++20 + +ColumnLimit: 160 +IndentWidth: 4 + +AllowShortBlocksOnASingleLine: Never +AllowShortFunctionsOnASingleLine: Inline +AllowShortLambdasOnASingleLine: Empty + +AlignAfterOpenBracket: Align +AllowAllArgumentsOnNextLine: true +AlignEscapedNewlines: LeftWithLastLine +AlignOperands: AlignAfterOperator +AlignTrailingComments: false + +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterEnum: true + AfterExternBlock: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakTemplateDeclarations: Yes + +AccessModifierOffset: -4 +IndentAccessModifiers: false + +IncludeBlocks: Merge +FixNamespaceComments: false + +AllowAllConstructorInitializersOnNextLine: true +BinPackArguments: false +IndentCaseLabels: true +SpaceAfterTemplateKeyword: false + +PenaltyBreakAssignment: 10000 +PenaltyBreakBeforeFirstCallParameter: 0 +PenaltyBreakOpenParenthesis: 10000 +PenaltyBreakScopeResolution: 10000 \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ad66f9a..2c85e7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,6 @@ if(NOT INPUTACTIONS_OVERRIDE_CORE) add_subdirectory(lib/core) endif() add_subdirectory(lib/libinput-cpp) -add_subdirectory(src/ipc) +add_subdirectory(src/common) add_subdirectory(src/client) add_subdirectory(src/daemon) diff --git a/lib/core b/lib/core index 12fb3d2..c551f90 160000 --- a/lib/core +++ b/lib/core @@ -1 +1 @@ -Subproject commit 12fb3d251af13b215711b302c481fec899e36c65 +Subproject commit c551f90c7584d15bb0026c5b2a756f89a317918e diff --git a/lib/libinput-cpp b/lib/libinput-cpp index 2c4bbb2..3553b81 160000 --- a/lib/libinput-cpp +++ b/lib/libinput-cpp @@ -1 +1 @@ -Subproject commit 2c4bbb232f218473e5ab0f2bd612dcabd340c2ea +Subproject commit 3553b813aebd477eaec77880d79bd89fca4401ff diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 6fdc408..f8333eb 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -4,21 +4,26 @@ pkg_search_module(WAYLAND_CLIENT REQUIRED wayland-client) add_executable(inputactions-client gnome/GNOMEClient.cpp + input/EvdevVirtualKeyboard.cpp + input/EvdevVirtualMouse.cpp + input/StandaloneInputBackend.cpp + input/StandaloneInputDevice.cpp + interfaces/DBusEnvironmentStateProvider.cpp plasma/PlasmaClient.cpp wayland/WaylandClient.cpp wayland/WaylandProtocol.cpp wayland/WaylandProtocolManager.cpp wayland/WlrForeignToplevelManagementV1.cpp Client.cpp - ClientDBusInterface.cpp - ClientMessageHandler.cpp + ClientHandler.cpp main.cpp ) target_compile_options(inputactions-client PUBLIC -fexceptions) target_link_libraries(inputactions-client PRIVATE + libinput-cpp libinputactions - libinputactions-standalone-ipc + libinputactions-standalone-common ${WAYLAND_CLIENT_LIBRARIES} ) target_include_directories(inputactions-client PRIVATE diff --git a/src/client/Client.cpp b/src/client/Client.cpp index 0f1fc57..6d2de7f 100644 --- a/src/client/Client.cpp +++ b/src/client/Client.cpp @@ -17,25 +17,11 @@ */ #include "Client.h" -#include "ClientDBusInterface.h" -#include -#include -#include -#include -#include -#include +#include namespace InputActions { -Client::Client() - : m_dbusInterface(this) - , m_currentTty(SessionHelpers::currentTty()) -{ -} - -Client::~Client() = default; - void Client::start() { m_connectionRetryTimer = new QTimer(this); @@ -45,53 +31,23 @@ void Client::start() auto socket = new QLocalSocket(this); m_connection = new MessageSocketConnection(socket, this); - connect(&configProvider, &ConfigProvider::configChanged, this, &Client::onConfigChanged); connect(socket, &QLocalSocket::connected, this, &Client::onConnected); connect(socket, &QLocalSocket::errorOccurred, this, &Client::onErrorOccurred); + connect(socket, &QLocalSocket::disconnected, this, &Client::onDisconnected); connect(m_connection, &MessageSocketConnection::messageReceived, this, &Client::messageReceived); socket->connectToServer(INPUTACTIONS_IPC_SOCKET_PATH); } -MessageSocketConnection *Client::socketConnection() const -{ - return m_connection; -} - void Client::onConnected() { m_connectionRetryTimer->stop(); - Q_EMIT connected(); - QThreadHelpers::runOnThread(QThreadHelpers::mainThread(), [this]() { - HandshakeRequestMessage handshakeRequest; - if (const auto response = m_connection->sendMessageAndWaitForResponse(handshakeRequest); !response->success()) { - qCritical().noquote().nospace() << "Handshake failed: " << response->error(); - QCoreApplication::exit(-1); - return; - } - - BeginSessionRequestMessage beginSessionRequest; - beginSessionRequest.setTty(m_currentTty); - if (const auto response = m_connection->sendMessageAndWaitForResponse(beginSessionRequest)) { - if (!response->success()) { - qCritical().noquote().nospace() << "Daemon rejected request to begin session: " << response->error(); - QCoreApplication::exit(-1); - return; - } - } else { - qCritical() << "Daemon did not reply to session begin request"; - QCoreApplication::exit(-1); - return; - } - - LoadConfigRequestMessage configRequest; - configRequest.setConfig(configProvider.currentConfig()); - m_connection->sendMessageAndWaitForResponse(configRequest); - }); + Q_EMIT connected(m_connection); } void Client::onDisconnected() { m_connectionRetryTimer->start(); + Q_EMIT disconnected(); } void Client::onErrorOccurred(QLocalSocket::LocalSocketError error) @@ -100,13 +56,6 @@ void Client::onErrorOccurred(QLocalSocket::LocalSocketError error) m_connectionRetryTimer->start(); } -void Client::onConfigChanged(const QString &config) -{ - LoadConfigRequestMessage request; - request.setConfig(config); - m_connection->sendMessage(request); -} - void Client::connectToDaemon() { m_connection->socket()->connectToServer(INPUTACTIONS_IPC_SOCKET_PATH); diff --git a/src/client/Client.h b/src/client/Client.h index d825238..a14a39a 100644 --- a/src/client/Client.h +++ b/src/client/Client.h @@ -18,15 +18,12 @@ #pragma once -#include "ClientDBusInterface.h" #include #include -#include namespace InputActions { -class ClientDBusInterface; class Message; class MessageSocketConnection; @@ -35,33 +32,23 @@ class Client : public QObject Q_OBJECT public: - Client(); - ~Client() override; - Q_INVOKABLE void start(); - MessageSocketConnection *socketConnection() const; - - FileConfigProvider configProvider; signals: - void connected(); - void messageReceived(std::shared_ptr message); + void connected(MessageSocketConnection *connection); + void disconnected(); + void messageReceived(std::shared_ptr message); private slots: void onConnected(); void onDisconnected(); void onErrorOccurred(QLocalSocket::LocalSocketError error); - void onConfigChanged(const QString &config); private: void connectToDaemon(); MessageSocketConnection *m_connection; QTimer *m_connectionRetryTimer{}; - - ClientDBusInterface m_dbusInterface; - - QString m_currentTty; }; } \ No newline at end of file diff --git a/src/client/ClientDBusInterface.cpp b/src/client/ClientDBusInterface.cpp deleted file mode 100644 index bf3b93b..0000000 --- a/src/client/ClientDBusInterface.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 "ClientDBusInterface.h" -#include "Client.h" -#include -#include - -namespace InputActions -{ - -static const QString ERROR_NO_REPLY = "Daemon did not reply"; - -ClientDBusInterface::ClientDBusInterface(Client *client) - : m_client(client) - , m_bus(QDBusConnection::sessionBus()) -{ - connect(client, &Client::connected, this, &ClientDBusInterface::onClientConnected); - - m_bus.registerService(INPUTACTIONS_DBUS_SERVICE); - m_bus.registerObject(INPUTACTIONS_DBUS_PATH, this, QDBusConnection::ExportAllContents); -} - -ClientDBusInterface::~ClientDBusInterface() -{ - m_bus.unregisterService(INPUTACTIONS_DBUS_SERVICE); - m_bus.unregisterObject(INPUTACTIONS_DBUS_PATH); -} - -void ClientDBusInterface::environmentState(QString state) -{ - EnvironmentStateMessage message; - message.setStateJson(state); - m_client->socketConnection()->sendMessage(message); -} - -QString ClientDBusInterface::deviceList() -{ - DeviceListRequestMessage request; - if (const auto response = m_client->socketConnection()->sendMessageAndWaitForResponse(request)) { - return response->success() ? response->result() : response->error(); - } - return ERROR_NO_REPLY; -} - -QString ClientDBusInterface::issues() -{ - ConfigIssuesRequestMessage request; - if (const auto response = m_client->socketConnection()->sendMessageAndWaitForResponse(request)) { - return response->success() ? response->result() : response->error(); - } - return ERROR_NO_REPLY; -} - -QString ClientDBusInterface::recordStroke() -{ - RecordStrokeRequestMessage request; - if (const auto response = m_client->socketConnection()->sendMessageAndWaitForResponse(request)) { - return response->success() ? response->result() : response->error(); - } - return ERROR_NO_REPLY; -} - -QString ClientDBusInterface::reloadConfig() -{ - LoadConfigRequestMessage request; - request.setConfig(m_client->configProvider.currentConfig()); - request.setManual(true); - if (const auto response = m_client->socketConnection()->sendMessageAndWaitForResponse(request)) { - return response->success() ? response->result() : response->error(); - } - return ERROR_NO_REPLY; -} - -QString ClientDBusInterface::suspend() -{ - SuspendRequestMessage request; - if (const auto response = m_client->socketConnection()->sendMessageAndWaitForResponse(request)) { - return response->success() ? "success" : response->error(); - } - return ERROR_NO_REPLY; -} - -QString ClientDBusInterface::variables(QString filter) -{ - VariableListRequestMessage request; - request.setFilter(filter); - if (const auto response = m_client->socketConnection()->sendMessageAndWaitForResponse(request)) { - return response->success() ? response->result() : response->error(); - } - return ERROR_NO_REPLY; -} - -void ClientDBusInterface::onClientConnected() -{ - Q_EMIT environmentStateRequested(); -} - -} \ No newline at end of file diff --git a/src/client/ClientDBusInterface.h b/src/client/ClientDBusInterface.h deleted file mode 100644 index 14b9a51..0000000 --- a/src/client/ClientDBusInterface.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 Client; - -class ClientDBusInterface : public QObject -{ - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.inputactions") - -public: - /** - * Registers the interface. - */ - ClientDBusInterface(Client *client); - - /** - * Unregisters the interface. - */ - ~ClientDBusInterface() override; - -signals: - void environmentStateRequested(); - -public slots: - void environmentState(QString state); - - QString deviceList(); - QString issues(); - QString recordStroke(); - QString reloadConfig(); - QString suspend(); - QString variables(QString filter = ""); - -private slots: - void onClientConnected(); - -private: - Client *m_client; - - QDBusConnection m_bus; - QDBusMessage m_reply; -}; - -} \ No newline at end of file diff --git a/src/client/ClientHandler.cpp b/src/client/ClientHandler.cpp new file mode 100644 index 0000000..c4bc292 --- /dev/null +++ b/src/client/ClientHandler.cpp @@ -0,0 +1,99 @@ +/* + Input Actions - Input handler that executes user-defined actions + Copyright (C) 2024-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 "ClientHandler.h" +#include "Client.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace InputActions +{ + +ClientHandler::ClientHandler(Client &client) + : m_tty(SessionHelpers::currentTty()) +{ + connect(&client, &Client::connected, this, &ClientHandler::onConnected); + connect(&client, &Client::disconnected, this, &ClientHandler::onDisconnected); + connect(&client, &Client::messageReceived, this, &ClientHandler::onMessageReceived); +} + +void ClientHandler::onConnected(MessageSocketConnection *connection) +{ + CInitializeRequestMessage initializeRequest; + initializeRequest.setTty(m_tty); + initializeRequest.setMainThreadId(gettid()); + + const auto response = connection->sendMessageAndWaitForResponse(initializeRequest); + if (!response) { + qCritical("Daemon did not respond to initialization request in time."); + QCoreApplication::exit(-1); + } else if (!response->success()) { + qCritical().noquote().nospace() << response->error(); + qCritical("Initialization request rejected by daemon."); + QCoreApplication::exit(-1); + } +} + +void ClientHandler::onDisconnected() +{ + g_inputActions->suspend(); +} + +void ClientHandler::onMessageReceived(std::shared_ptr message) +{ + switch (message->type()) { + case MessageType::SActivateRequest: + activateRequestMessage(std::dynamic_pointer_cast(message)); + break; + case MessageType::SDeactivateRequest: + deactivateRequestMessage(std::dynamic_pointer_cast(message)); + break; + } +} + +void ClientHandler::activateRequestMessage(std::shared_ptr message) +{ + g_mainDbusInterface->setAllowConfigLoading(true); + g_configLoader->load(); + message->reply(); +} + +void ClientHandler::deactivateRequestMessage(std::shared_ptr message) +{ + g_mainDbusInterface->setAllowConfigLoading(false); + g_inputActions->suspend(); + g_globalConfig->setAutoReload(false); + message->reply(); +} + +} \ No newline at end of file diff --git a/src/daemon/interfaces/IPCProcessRunner.h b/src/client/ClientHandler.h similarity index 57% rename from src/daemon/interfaces/IPCProcessRunner.h rename to src/client/ClientHandler.h index def1a64..f68bfdd 100644 --- a/src/daemon/interfaces/IPCProcessRunner.h +++ b/src/client/ClientHandler.h @@ -18,16 +18,33 @@ #pragma once -#include - namespace InputActions { -class IPCProcessRunner : public ProcessRunner +class Client; +class Message; +class MessageSocketConnection; +class SActivateRequestMessage; +class SDeactivateRequestMessage; +class SHeartbeatRequestMessage; + +class ClientHandler : public QObject { + Q_OBJECT + public: - void startProcess(const QString &program, const QStringList &arguments, std::map extraEnvironment, bool wait = false) override; - QString startProcessReadOutput(const QString &program, const QStringList &arguments, std::map extraEnvironment) override; + ClientHandler(Client &client); + +private slots: + void onConnected(MessageSocketConnection *connection); + void onDisconnected(); + void onMessageReceived(std::shared_ptr message); + +private: + void activateRequestMessage(std::shared_ptr message); + void deactivateRequestMessage(std::shared_ptr message); + + QString m_tty; }; } \ No newline at end of file diff --git a/src/client/ClientMessageHandler.cpp b/src/client/ClientMessageHandler.cpp deleted file mode 100644 index 88859b6..0000000 --- a/src/client/ClientMessageHandler.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 "ClientMessageHandler.h" -#include "Client.h" -#include - -namespace InputActions -{ - -ClientMessageHandler::ClientMessageHandler(Client *client) -{ - connect(client, &Client::messageReceived, this, [this](const auto &message) { - handleMessage(message); - }); -} - -void ClientMessageHandler::invokePlasmaGlobalShortcutMessage(const std::shared_ptr &message) -{ - m_plasmaGlobalShortcutInvoker.invoke(message->component(), message->shortcut()); - message->reply(); -} - -void ClientMessageHandler::sendNotificationMessage(const std::shared_ptr &message) -{ - m_notificationManager.sendNotification(message->title(), message->content()); -} - -void ClientMessageHandler::startProcessRequestMessage(const std::shared_ptr &message) -{ - auto response = message->makeResponse(); - if (message->output()) { - response.setResult(m_processRunner.startProcessReadOutput(message->program(), message->arguments(), message->environment())); - } else { - m_processRunner.startProcess(message->program(), message->arguments(), message->environment(), message->wait()); - } - message->reply(response); -} - -} \ No newline at end of file diff --git a/src/client/ClientMessageHandler.h b/src/client/ClientMessageHandler.h deleted file mode 100644 index 2a21e45..0000000 --- a/src/client/ClientMessageHandler.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 -#include - -namespace InputActions -{ - -class Client; - -class ClientMessageHandler - : public QObject - , public MessageHandler -{ - Q_OBJECT - -public: - ClientMessageHandler(Client *client); - -protected: - void invokePlasmaGlobalShortcutMessage(const std::shared_ptr &message) override; - void sendNotificationMessage(const std::shared_ptr &message) override; - void startProcessRequestMessage(const std::shared_ptr &message) override; - -private: - DBusNotificationManager m_notificationManager; - DBusPlasmaGlobalShortcutInvoker m_plasmaGlobalShortcutInvoker; - ProcessRunnerImpl m_processRunner; -}; - -} \ No newline at end of file diff --git a/src/client/gnome/inputactions@inputactions.org/extension.js b/src/client/gnome/inputactions@inputactions.org/extension.js index a90c878..8a19877 100644 --- a/src/client/gnome/inputactions@inputactions.org/extension.js +++ b/src/client/gnome/inputactions@inputactions.org/extension.js @@ -27,13 +27,13 @@ export default class MyExtension extends Extension { enable() { this._dbusDataRequestedSignalSubscription = Gio.DBus.session.signal_subscribe( "org.inputactions", - "org.inputactions", - "environmentStateRequested", - "/", + "org.inputactions.standalone.DBusEnvironmentStateProvider", + "stateRequested", + "/org/inputactions/standalone/DBusEnvironmentStateProvider", null, Gio.DBusSignalFlags.NONE, (connection, sender, path, iface, signal, params) => { - this._sendData(params.deep_unpack()[0]); + this._sendData([]); } ); @@ -175,9 +175,9 @@ export default class MyExtension extends Extension { Gio.DBus.session.call( "org.inputactions", - "/", - "org.inputactions", - "environmentState", + "/org/inputactions/standalone/DBusEnvironmentStateProvider", + "org.inputactions.standalone.DBusEnvironmentStateProvider", + "updateState", new GLib.Variant("(s)", [JSON.stringify(data)]), null, Gio.DBusCallFlags.NONE, diff --git a/src/client/gnome/inputactions@inputactions.org/metadata.json b/src/client/gnome/inputactions@inputactions.org/metadata.json index 093b50b..37ed0af 100644 --- a/src/client/gnome/inputactions@inputactions.org/metadata.json +++ b/src/client/gnome/inputactions@inputactions.org/metadata.json @@ -3,5 +3,5 @@ "name": "InputActions", "description": "GNOME integration for InputActions", "version": 1, - "shell-version": ["48", "49"] + "shell-version": ["48", "49", "50"] } diff --git a/src/daemon/input/EvdevVirtualKeyboard.cpp b/src/client/input/EvdevVirtualKeyboard.cpp similarity index 100% rename from src/daemon/input/EvdevVirtualKeyboard.cpp rename to src/client/input/EvdevVirtualKeyboard.cpp diff --git a/src/daemon/input/EvdevVirtualKeyboard.h b/src/client/input/EvdevVirtualKeyboard.h similarity index 100% rename from src/daemon/input/EvdevVirtualKeyboard.h rename to src/client/input/EvdevVirtualKeyboard.h diff --git a/src/daemon/input/EvdevVirtualMouse.cpp b/src/client/input/EvdevVirtualMouse.cpp similarity index 100% rename from src/daemon/input/EvdevVirtualMouse.cpp rename to src/client/input/EvdevVirtualMouse.cpp diff --git a/src/daemon/input/EvdevVirtualMouse.h b/src/client/input/EvdevVirtualMouse.h similarity index 100% rename from src/daemon/input/EvdevVirtualMouse.h rename to src/client/input/EvdevVirtualMouse.h diff --git a/src/daemon/input/StandaloneInputBackend.cpp b/src/client/input/StandaloneInputBackend.cpp similarity index 100% rename from src/daemon/input/StandaloneInputBackend.cpp rename to src/client/input/StandaloneInputBackend.cpp diff --git a/src/daemon/input/StandaloneInputBackend.h b/src/client/input/StandaloneInputBackend.h similarity index 99% rename from src/daemon/input/StandaloneInputBackend.h rename to src/client/input/StandaloneInputBackend.h index 4746c05..0eb9d91 100644 --- a/src/daemon/input/StandaloneInputBackend.h +++ b/src/client/input/StandaloneInputBackend.h @@ -21,7 +21,6 @@ #include "EvdevVirtualKeyboard.h" #include "EvdevVirtualMouse.h" #include -#include #include #include #include diff --git a/src/daemon/input/StandaloneInputDevice.cpp b/src/client/input/StandaloneInputDevice.cpp similarity index 100% rename from src/daemon/input/StandaloneInputDevice.cpp rename to src/client/input/StandaloneInputDevice.cpp diff --git a/src/daemon/input/StandaloneInputDevice.h b/src/client/input/StandaloneInputDevice.h similarity index 100% rename from src/daemon/input/StandaloneInputDevice.h rename to src/client/input/StandaloneInputDevice.h diff --git a/src/daemon/interfaces/IPCEnvironmentInterfaces.cpp b/src/client/interfaces/DBusEnvironmentStateProvider.cpp similarity index 74% rename from src/daemon/interfaces/IPCEnvironmentInterfaces.cpp rename to src/client/interfaces/DBusEnvironmentStateProvider.cpp index 8c044cd..d227742 100644 --- a/src/daemon/interfaces/IPCEnvironmentInterfaces.cpp +++ b/src/client/interfaces/DBusEnvironmentStateProvider.cpp @@ -16,33 +16,44 @@ along with this program. If not, see . */ -#include "IPCEnvironmentInterfaces.h" -#include "Server.h" +#include "DBusEnvironmentStateProvider.h" #include #include #include +#include namespace InputActions { -IPCEnvironmentInterfaces::IPCEnvironmentInterfaces() - : m_activeWindow(std::make_unique()) - , m_windowUnderPointer(std::make_unique()) +static const QString DBUS_OBJECT_PATH = "/org/inputactions/standalone/DBusEnvironmentStateProvider"; + +DBusEnvironmentStateProvider::DBusEnvironmentStateProvider() + : m_activeWindow(std::make_unique()) + , m_windowUnderPointer(std::make_unique()) + , m_bus(QDBusConnectionHelpers::sessionBus()) { + m_bus.registerObject(DBUS_OBJECT_PATH, this, QDBusConnection::ExportAllContents); } -std::shared_ptr IPCEnvironmentInterfaces::activeWindow() +DBusEnvironmentStateProvider::~DBusEnvironmentStateProvider() +{ + m_bus.unregisterObject(DBUS_OBJECT_PATH); +} + +std::shared_ptr DBusEnvironmentStateProvider::activeWindow() { return m_activeWindow; } -std::shared_ptr IPCEnvironmentInterfaces::windowUnderPointer() +std::shared_ptr DBusEnvironmentStateProvider::windowUnderPointer() { return m_windowUnderPointer; } -void IPCEnvironmentInterfaces::updateEnvironmentState(const QString &json) +void DBusEnvironmentStateProvider::updateState(const QString &json) { + qWarning() << json; + const auto jsonDocument = QJsonDocument::fromJson(json.toUtf8()); const auto object = jsonDocument.object(); @@ -99,52 +110,52 @@ void IPCEnvironmentInterfaces::updateEnvironmentState(const QString &json) readPoint(m_screenPointerPosition, object["pointer_position_screen_percentage"]); } -std::optional IPCWindow::id() +std::optional DBusWindow::id() { return m_id; } -std::optional IPCWindow::pid() +std::optional DBusWindow::pid() { return m_pid; } -std::optional IPCWindow::geometry() +std::optional DBusWindow::geometry() { return m_geometry; } -std::optional IPCWindow::title() +std::optional DBusWindow::title() { return m_title; } -std::optional IPCWindow::resourceClass() +std::optional DBusWindow::resourceClass() { return m_resourceClass; } -std::optional IPCWindow::resourceName() +std::optional DBusWindow::resourceName() { return m_resourceName; } -std::optional IPCWindow::maximized() +std::optional DBusWindow::maximized() { return m_maximized; } -std::optional IPCWindow::fullscreen() +std::optional DBusWindow::fullscreen() { return m_fullscreen; } -std::optional IPCEnvironmentInterfaces::globalPointerPosition() +std::optional DBusEnvironmentStateProvider::globalPointerPosition() { return m_globalPointerPosition; } -std::optional IPCEnvironmentInterfaces::screenPointerPosition() +std::optional DBusEnvironmentStateProvider::screenPointerPosition() { return m_screenPointerPosition; } diff --git a/src/daemon/interfaces/IPCEnvironmentInterfaces.h b/src/client/interfaces/DBusEnvironmentStateProvider.h similarity index 81% rename from src/daemon/interfaces/IPCEnvironmentInterfaces.h rename to src/client/interfaces/DBusEnvironmentStateProvider.h index 27fe8c0..bfd38bd 100644 --- a/src/daemon/interfaces/IPCEnvironmentInterfaces.h +++ b/src/client/interfaces/DBusEnvironmentStateProvider.h @@ -18,18 +18,18 @@ #pragma once +#include #include #include #include #include -#include namespace InputActions { class Server; -class IPCWindow : public Window +class DBusWindow : public Window { public: std::optional id() override; @@ -54,13 +54,17 @@ class IPCWindow : public Window /** * A set of interfaces for interacting with and getting the state of the environment through IPC. */ -class IPCEnvironmentInterfaces +class DBusEnvironmentStateProvider : public QObject , public PointerPositionGetter , public WindowProvider { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.inputactions.standalone.DBusEnvironmentStateProvider") + public: - IPCEnvironmentInterfaces(); + DBusEnvironmentStateProvider(); + ~DBusEnvironmentStateProvider() override; std::shared_ptr activeWindow() override; std::shared_ptr windowUnderPointer() override; @@ -68,14 +72,20 @@ class IPCEnvironmentInterfaces std::optional globalPointerPosition() override; std::optional screenPointerPosition() override; - void updateEnvironmentState(const QString &json); +public slots: + void updateState(const QString &json); + +signals: + void stateRequested(); private: - std::shared_ptr m_activeWindow; - std::shared_ptr m_windowUnderPointer; + std::shared_ptr m_activeWindow; + std::shared_ptr m_windowUnderPointer; std::optional m_globalPointerPosition; std::optional m_screenPointerPosition; + + QDBusConnection m_bus; }; } \ No newline at end of file diff --git a/src/client/main.cpp b/src/client/main.cpp index b2d3173..5f1e556 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -17,14 +17,19 @@ */ #include "Client.h" -#include "ClientDBusInterface.h" -#include "ClientMessageHandler.h" +#include "ClientHandler.h" #include "gnome/GNOMEClient.h" +#include "input/StandaloneInputBackend.h" +#include "interfaces/DBusEnvironmentStateProvider.h" #include "plasma/PlasmaClient.h" #include "wayland/WaylandClient.h" #include +#include #include #include +#include +#include +#include using namespace InputActions; @@ -32,6 +37,7 @@ void handleSignal(int signal) { if (signal == SIGINT) { QCoreApplication::quit(); + std::signal(SIGINT, SIG_DFL); } } @@ -41,9 +47,21 @@ int main() QCoreApplication app(argc, nullptr); std::signal(SIGINT, handleSignal); + InputActionsMain main; + g_inputBackend = std::make_unique(); + + auto dbusEnvironmentStateProvider = std::make_shared(); + g_pointerPositionGetter = dbusEnvironmentStateProvider; + g_windowProvider = dbusEnvironmentStateProvider; + + main.setMissingImplementations(); + main.initialize(); + + Q_EMIT dbusEnvironmentStateProvider->stateRequested(); + auto *clientThread = new QThread; auto *client = new Client; - ClientDBusInterface dbusInterface(client); + ClientHandler clientHandler(*client); client->moveToThread(clientThread); QObject::connect(clientThread, &QThread::started, [&client]() { @@ -51,11 +69,9 @@ int main() }); clientThread->start(); - ClientMessageHandler messageHandler(client); - GNOMEClient gnomeClient; PlasmaClient plasmaClient; - WaylandClient waylandClient(client); + WaylandClient waylandClient(*dbusEnvironmentStateProvider); gnomeClient.initialize() || plasmaClient.initialize() || waylandClient.initialize(); return app.exec(); diff --git a/src/client/plasma/PlasmaClient.cpp b/src/client/plasma/PlasmaClient.cpp index 7486b94..4fc7117 100644 --- a/src/client/plasma/PlasmaClient.cpp +++ b/src/client/plasma/PlasmaClient.cpp @@ -18,6 +18,7 @@ #include "PlasmaClient.h" #include +#include namespace InputActions { @@ -37,15 +38,19 @@ bool PlasmaClient::initialize() return false; } - QDBusInterface scripting("org.kde.KWin", "/Scripting", "org.kde.kwin.Scripting"); + QDBusInterface scripting("org.kde.KWin", "/Scripting", "org.kde.kwin.Scripting", QDBusConnectionHelpers::sessionBus()); scripting.call("unloadScript", "inputactions"); const auto reply = scripting.call("loadScript", KWIN_SCRIPT_PATH, "inputactions"); if (reply.arguments().size() == 0) { + qWarning() << "noarg"; return false; } const auto scriptId = reply.arguments().at(0).toInt(); - m_kwinScriptInterface = std::make_unique("org.kde.KWin", "/Scripting/Script" + QString::number(scriptId), "org.kde.kwin.Script"); + m_kwinScriptInterface = std::make_unique("org.kde.KWin", + "/Scripting/Script" + QString::number(scriptId), + "org.kde.kwin.Script", + QDBusConnectionHelpers::sessionBus()); m_kwinScriptInterface->call("run"); return true; diff --git a/src/client/plasma/script.js b/src/client/plasma/script.js index 63d501b..586f978 100644 --- a/src/client/plasma/script.js +++ b/src/client/plasma/script.js @@ -76,7 +76,7 @@ function sendData(keys) { data[key] = dataAccessors[key](); } - callDBus("org.inputactions", "/", "org.inputactions", "environmentState", JSON.stringify(data)); + callDBus("org.inputactions", "/org/inputactions/standalone/DBusEnvironmentStateProvider", "org.inputactions.standalone.DBusEnvironmentStateProvider", "updateState", JSON.stringify(data)); } sendData([]); diff --git a/src/client/wayland/WaylandClient.cpp b/src/client/wayland/WaylandClient.cpp index 7385385..c721cd2 100644 --- a/src/client/wayland/WaylandClient.cpp +++ b/src/client/wayland/WaylandClient.cpp @@ -24,8 +24,8 @@ namespace InputActions { -WaylandClient::WaylandClient(Client *client) - : m_client(client) +WaylandClient::WaylandClient(DBusEnvironmentStateProvider &dbusEnvironmentStateProvider) + : m_dbusEnvironmentStateProvider(dbusEnvironmentStateProvider) { connect(&m_displayDispatchTimer, &QTimer::timeout, this, &WaylandClient::onDisplayDispatchTimerTick); m_displayDispatchTimer.setInterval(100); @@ -41,7 +41,7 @@ bool WaylandClient::initialize() } m_protocolManager = std::make_unique(wl_display_get_registry(m_display)); - m_protocolManager->addProtocol(std::make_unique(m_client)); + m_protocolManager->addProtocol(std::make_unique(m_dbusEnvironmentStateProvider)); m_displayDispatchTimer.start(); return true; } diff --git a/src/client/wayland/WaylandClient.h b/src/client/wayland/WaylandClient.h index 61c8c77..2d75f76 100644 --- a/src/client/wayland/WaylandClient.h +++ b/src/client/wayland/WaylandClient.h @@ -25,13 +25,13 @@ namespace InputActions { -class Client; +class DBusEnvironmentStateProvider; class WaylandProtocolManager; class WaylandClient : public QObject { public: - WaylandClient(Client *client); + WaylandClient(DBusEnvironmentStateProvider &dbusEnvironmentStateProvider); ~WaylandClient() override; /** @@ -47,7 +47,7 @@ private slots: QTimer m_displayDispatchTimer; std::unique_ptr m_protocolManager; - Client *m_client; + DBusEnvironmentStateProvider &m_dbusEnvironmentStateProvider; }; } \ No newline at end of file diff --git a/src/client/wayland/WlrForeignToplevelManagementV1.cpp b/src/client/wayland/WlrForeignToplevelManagementV1.cpp index d3313ca..3337990 100644 --- a/src/client/wayland/WlrForeignToplevelManagementV1.cpp +++ b/src/client/wayland/WlrForeignToplevelManagementV1.cpp @@ -17,18 +17,16 @@ */ #include "WlrForeignToplevelManagementV1.h" -#include "Client.h" +#include "interfaces/DBusEnvironmentStateProvider.h" #include #include -#include -#include namespace InputActions { -WlrForeignToplevelManagementV1::WlrForeignToplevelManagementV1(Client *client) +WlrForeignToplevelManagementV1::WlrForeignToplevelManagementV1(DBusEnvironmentStateProvider &dbusEnvironmentStateProvider) : WaylandProtocol(zwlr_foreign_toplevel_manager_v1_interface.name) - , m_client(client) + , m_dbusEnvironmentStateProvider(dbusEnvironmentStateProvider) { self = this; } @@ -137,15 +135,13 @@ void WlrForeignToplevelManagementV1::handleDone(void *data, zwlr_foreign_topleve return; } + // No point on doing this the right way, it's getting rewritten in the future anyways QJsonObject json; json["active_window_class"] = window->resourceClass; json["active_window_fullscreen"] = window->fullscreen; json["active_window_maximized"] = window->maximized; json["active_window_title"] = window->title; - - EnvironmentStateMessage message; - message.setStateJson(QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact)); - self->m_client->socketConnection()->sendMessage(message); + self->m_dbusEnvironmentStateProvider.updateState(QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact)); } } \ No newline at end of file diff --git a/src/client/wayland/WlrForeignToplevelManagementV1.h b/src/client/wayland/WlrForeignToplevelManagementV1.h index 1aea44c..22b1d64 100644 --- a/src/client/wayland/WlrForeignToplevelManagementV1.h +++ b/src/client/wayland/WlrForeignToplevelManagementV1.h @@ -24,7 +24,7 @@ namespace InputActions { -class Client; +class DBusEnvironmentStateProvider; struct WlrForeignToplevelManagementV1Window { @@ -37,7 +37,7 @@ struct WlrForeignToplevelManagementV1Window class WlrForeignToplevelManagementV1 : public WaylandProtocol { public: - WlrForeignToplevelManagementV1(Client *client); + WlrForeignToplevelManagementV1(DBusEnvironmentStateProvider &dbusEnvironmentStateProvider); ~WlrForeignToplevelManagementV1() override; protected: @@ -58,7 +58,7 @@ class WlrForeignToplevelManagementV1 : public WaylandProtocol std::vector> m_windows; WlrForeignToplevelManagementV1Window *m_activeWindow; - Client *m_client; + DBusEnvironmentStateProvider &m_dbusEnvironmentStateProvider; inline static WlrForeignToplevelManagementV1 *self; }; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt new file mode 100644 index 0000000..93d1a1a --- /dev/null +++ b/src/common/CMakeLists.txt @@ -0,0 +1,16 @@ +find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS + Network +) + +add_library(libinputactions-standalone-common STATIC + libinputactions-standalone-common/helpers/Session.cpp + libinputactions-standalone-common/ipc/JsonSerializer.cpp + libinputactions-standalone-common/ipc/messages.cpp + libinputactions-standalone-common/ipc/MessageSocketConnection.cpp +) +target_link_libraries(libinputactions-standalone-common PUBLIC + libinputactions + Qt6::Network +) +target_include_directories(libinputactions-standalone-common PUBLIC libinputactions-standalone-common) +set_target_properties(libinputactions-standalone-common PROPERTIES PREFIX "") diff --git a/src/daemon/interfaces/IPCNotificationManager.h b/src/common/libinputactions-standalone-common/helpers/Session.cpp similarity index 70% rename from src/daemon/interfaces/IPCNotificationManager.h rename to src/common/libinputactions-standalone-common/helpers/Session.cpp index 2a4d416..088a556 100644 --- a/src/daemon/interfaces/IPCNotificationManager.h +++ b/src/common/libinputactions-standalone-common/helpers/Session.cpp @@ -16,22 +16,19 @@ along with this program. If not, see . */ -#pragma once +#include "Session.h" +#include -#include -#include - -namespace InputActions +namespace InputActions::SessionHelpers { -class IPCNotificationManager - : public QObject - , public NotificationManager +QString currentTty() { -public: - IPCNotificationManager() = default; - - void sendNotification(const QString &title, const QString &content) override; -}; + QFile f("/sys/class/tty/tty0/active"); + if (f.open(QIODeviceBase::ReadOnly)) { + return QString::fromUtf8(f.readAll()).trimmed(); + } + return "unknown"; +} } \ No newline at end of file diff --git a/src/daemon/interfaces/IPCPlasmaGlobalShortcutInvoker.h b/src/common/libinputactions-standalone-common/helpers/Session.h similarity index 75% rename from src/daemon/interfaces/IPCPlasmaGlobalShortcutInvoker.h rename to src/common/libinputactions-standalone-common/helpers/Session.h index 4cc4bca..fad4a64 100644 --- a/src/daemon/interfaces/IPCPlasmaGlobalShortcutInvoker.h +++ b/src/common/libinputactions-standalone-common/helpers/Session.h @@ -18,15 +18,11 @@ #pragma once -#include +#include -namespace InputActions +namespace InputActions::SessionHelpers { -class IPCPlasmaGlobalShortcutInvoker : public PlasmaGlobalShortcutInvoker -{ -public: - void invoke(const QString &component, const QString &shortcut) override; -}; +QString currentTty(); } \ No newline at end of file diff --git a/src/ipc/libinputactions-standalone-ipc/JsonSerializer.cpp b/src/common/libinputactions-standalone-common/ipc/JsonSerializer.cpp similarity index 62% rename from src/ipc/libinputactions-standalone-ipc/JsonSerializer.cpp rename to src/common/libinputactions-standalone-common/ipc/JsonSerializer.cpp index 99a0ac4..a843ac4 100644 --- a/src/ipc/libinputactions-standalone-ipc/JsonSerializer.cpp +++ b/src/common/libinputactions-standalone-common/ipc/JsonSerializer.cpp @@ -61,47 +61,23 @@ std::shared_ptr JsonSerializer::deserializeMessage(const QString &json) std::shared_ptr message; switch (static_cast(type.toInt())) { - case MessageType::BeginSessionRequest: - message = std::make_shared(); + case MessageType::CInitializeRequest: + message = std::make_shared(); break; - case MessageType::ConfigIssuesRequest: - message = std::make_shared(); + case MessageType::SInitializeResponse: + message = std::make_shared(); break; - case MessageType::DeviceListRequest: - message = std::make_shared(); + case MessageType::SActivateRequest: + message = std::make_shared(); break; - case MessageType::EnvironmentState: - message = std::make_shared(); + case MessageType::CActivateResponse: + message = std::make_shared(); break; - case MessageType::GenericResponse: - message = std::make_shared(); + case MessageType::SDeactivateRequest: + message = std::make_shared(); break; - case MessageType::HandshakeRequest: - message = std::make_shared(); - break; - case MessageType::InvokePlasmaGlobalShortcutRequest: - message = std::make_shared(); - break; - case MessageType::LoadConfigRequest: - message = std::make_shared(); - break; - case MessageType::RecordStrokeRequest: - message = std::make_shared(); - break; - case MessageType::SendNotification: - message = std::make_shared(); - break; - case MessageType::SimpleStringResponse: - message = std::make_shared(); - break; - case MessageType::StartProcessRequest: - message = std::make_shared(); - break; - case MessageType::SuspendRequest: - message = std::make_shared(); - break; - case MessageType::VariableListRequest: - message = std::make_shared(); + case MessageType::CDeactivateResponse: + message = std::make_shared(); break; } Q_ASSERT(message); diff --git a/src/ipc/libinputactions-standalone-ipc/JsonSerializer.h b/src/common/libinputactions-standalone-common/ipc/JsonSerializer.h similarity index 100% rename from src/ipc/libinputactions-standalone-ipc/JsonSerializer.h rename to src/common/libinputactions-standalone-common/ipc/JsonSerializer.h diff --git a/src/ipc/libinputactions-standalone-ipc/MessageSocketConnection.cpp b/src/common/libinputactions-standalone-common/ipc/MessageSocketConnection.cpp similarity index 97% rename from src/ipc/libinputactions-standalone-ipc/MessageSocketConnection.cpp rename to src/common/libinputactions-standalone-common/ipc/MessageSocketConnection.cpp index 06f2b7f..5ac4657 100644 --- a/src/ipc/libinputactions-standalone-ipc/MessageSocketConnection.cpp +++ b/src/common/libinputactions-standalone-common/ipc/MessageSocketConnection.cpp @@ -26,7 +26,7 @@ Q_LOGGING_CATEGORY(INPUTACTIONS_IPC, "inputactions.ipc", QtWarningMsg) namespace InputActions { -static const std::chrono::milliseconds RESPONSE_TIMEOUT{10000L}; // Timeout must not be too low due to stroke recording +static const std::chrono::milliseconds RESPONSE_TIMEOUT{2000L}; MessageSocketConnection::MessageSocketConnection(QLocalSocket *socket, QObject *parent) : QObject(parent) diff --git a/src/ipc/libinputactions-standalone-ipc/MessageSocketConnection.h b/src/common/libinputactions-standalone-common/ipc/MessageSocketConnection.h similarity index 100% rename from src/ipc/libinputactions-standalone-ipc/MessageSocketConnection.h rename to src/common/libinputactions-standalone-common/ipc/MessageSocketConnection.h diff --git a/src/ipc/libinputactions-standalone-ipc/messages.cpp b/src/common/libinputactions-standalone-common/ipc/messages.cpp similarity index 90% rename from src/ipc/libinputactions-standalone-ipc/messages.cpp rename to src/common/libinputactions-standalone-common/ipc/messages.cpp index 8f1c5ea..186372d 100644 --- a/src/ipc/libinputactions-standalone-ipc/messages.cpp +++ b/src/common/libinputactions-standalone-common/ipc/messages.cpp @@ -22,12 +22,6 @@ namespace InputActions { -void ResponseMessage::setError(QString error) -{ - m_error = std::move(error); - m_success = false; -} - void RequestMessageBase::sendResponse(const ResponseMessage &response) const { m_sender->sendMessage(response); diff --git a/src/common/libinputactions-standalone-common/ipc/messages.h b/src/common/libinputactions-standalone-common/ipc/messages.h new file mode 100644 index 0000000..8ec90ee --- /dev/null +++ b/src/common/libinputactions-standalone-common/ipc/messages.h @@ -0,0 +1,220 @@ +/* + Input Actions - Input handler that executes user-defined actions + Copyright (C) 2024-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 + +namespace InputActions +{ + +static const int INPUTACTIONS_IPC_PROTOCOL_VERSION = 5; + +class MessageSocketConnection; + +enum class MessageType : int +{ + CInitializeRequest, + SInitializeResponse, + + SActivateRequest, + CActivateResponse, + + SDeactivateRequest, + CDeactivateResponse, +}; + +class Message : public QObject +{ + Q_OBJECT + Q_PROPERTY(int type MEMBER m_type) + +public: + Message(MessageType type) + : m_type(static_cast(type)) + { + } + + virtual ~Message() = default; + + MessageType type() const { return static_cast(m_type); } + + MessageSocketConnection *sender() const { return m_sender; } + void setSender(MessageSocketConnection *value) { m_sender = value; } + +protected: + MessageSocketConnection *m_sender; + +private: + int m_type; +}; + +class ResponseMessage : public Message +{ + Q_OBJECT + Q_PROPERTY(QString requestId MEMBER m_requestId) + +public: + ResponseMessage(MessageType type) + : Message(type) + { + } + + const QString &requestId() const { return m_requestId; } + void setRequestId(const QString &value) { m_requestId = value; } + +private: + QString m_requestId; + + bool m_success = true; + QString m_error; +}; + +class RequestMessageBase : public Message +{ + Q_OBJECT + Q_PROPERTY(QString requestId MEMBER m_requestId) + +public: + RequestMessageBase(MessageType type) + : Message(type) + { + } + + const QString &requestId() const { return m_requestId; } + +protected: + void sendResponse(const ResponseMessage &response) const; + +private: + QString m_requestId = QUuid::createUuid().toString(); +}; + +template +class RequestMessage : public RequestMessageBase +{ +public: + using RequestMessageBase::RequestMessageBase; + + TResponse makeResponse() const { return {}; } + + void reply() const + { + TResponse response; + reply(response); + } + + void reply(TResponse &response) const + { + response.setRequestId(requestId()); + sendResponse(response); + } +}; + +class SInitializeResponseMessage : public ResponseMessage +{ + Q_OBJECT + Q_PROPERTY(bool success MEMBER m_success) + Q_PROPERTY(QString error MEMBER m_error) + +public: + SInitializeResponseMessage() + : ResponseMessage(MessageType::SInitializeResponse) + { + } + + bool success() const { return m_success; } + void setSuccess(bool value) { m_success = value; } + + const QString &error() const { return m_error; } + void setError(QString value) { m_error = std::move(value); } + +private: + bool m_success{}; + QString m_error; +}; + +class CInitializeRequestMessage : public RequestMessage +{ + Q_OBJECT + Q_PROPERTY(QString tty MEMBER m_tty) + Q_PROPERTY(int64_t mainThreadId MEMBER m_mainThreadId) + +public: + CInitializeRequestMessage() + : RequestMessage(MessageType::CInitializeRequest) + { + } + + const QString &tty() const { return m_tty; } + void setTty(const QString &value) { m_tty = value; } + + int64_t mainThreadId() const { return m_mainThreadId; } + void setMainThreadId(int64_t value) { m_mainThreadId = value; } + +private: + QString m_tty; + int64_t m_mainThreadId; +}; + +class CActivateResponseMessage : public ResponseMessage +{ + Q_OBJECT + +public: + CActivateResponseMessage() + : ResponseMessage(MessageType::CActivateResponse) + { + } +}; + +class SActivateRequestMessage : public RequestMessage +{ + Q_OBJECT + +public: + SActivateRequestMessage() + : RequestMessage(MessageType::SActivateRequest) + { + } +}; + +class CDeactivateResponseMessage : public ResponseMessage +{ + Q_OBJECT + +public: + CDeactivateResponseMessage() + : ResponseMessage(MessageType::CDeactivateResponse) + { + } +}; + +class SDeactivateRequestMessage : public RequestMessage +{ + Q_OBJECT + +public: + SDeactivateRequestMessage() + : RequestMessage(MessageType::SDeactivateRequest) + { + } +}; + +} \ No newline at end of file diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index e58a78d..7024b89 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -1,30 +1,11 @@ -pkg_search_module(LIBINPUT REQUIRED libinput) -pkg_search_module(LIBUDEV REQUIRED libudev) - add_executable(inputactionsd - input/StandaloneInputBackend.cpp - input/StandaloneInputDevice.cpp - input/EvdevVirtualKeyboard.cpp - input/EvdevVirtualMouse.cpp - interfaces/IPCEnvironmentInterfaces.cpp - interfaces/IPCNotificationManager.cpp - interfaces/IPCPlasmaGlobalShortcutInvoker.cpp - interfaces/IPCProcessRunner.cpp main.cpp Server.cpp - SessionManager.cpp + ServerHandler.cpp ) target_compile_options(inputactionsd PUBLIC -fexceptions) target_link_libraries(inputactionsd PRIVATE - libinput-cpp - libinputactions - libinputactions-standalone-ipc - ${LIBINPUT_LIBRARIES} - ${LIBUDEV_LIBRARIES} -) -target_include_directories(inputactionsd PUBLIC - ${LIBEVDEV_INCLUDE_DIRS} - ${LIBINPUT_INCLUDE_DIRS} + libinputactions-standalone-common ) install( diff --git a/src/daemon/Server.cpp b/src/daemon/Server.cpp index 5a3f363..df2ced4 100644 --- a/src/daemon/Server.cpp +++ b/src/daemon/Server.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include namespace InputActions @@ -52,9 +52,6 @@ void Server::onNewConnection() auto *qtSocket = m_server->nextPendingConnection(); auto *socket = new MessageSocketConnection(qtSocket, this); - connect(qtSocket, &QLocalSocket::disconnected, this, [socket]() { - socket->deleteLater(); - }); connect(socket, &MessageSocketConnection::messageReceived, this, [this](const auto &message) { Q_EMIT messageReceived(message); }); diff --git a/src/daemon/Server.h b/src/daemon/Server.h index 8765f17..a5cdcaa 100644 --- a/src/daemon/Server.h +++ b/src/daemon/Server.h @@ -36,7 +36,7 @@ class Server : public QObject Q_INVOKABLE void start(); signals: - void messageReceived(std::shared_ptr message); + void messageReceived(std::shared_ptr message); private slots: void onNewConnection(); diff --git a/src/daemon/ServerHandler.cpp b/src/daemon/ServerHandler.cpp new file mode 100644 index 0000000..90d4ce3 --- /dev/null +++ b/src/daemon/ServerHandler.cpp @@ -0,0 +1,241 @@ +/* + Input Actions - Input handler that executes user-defined actions + Copyright (C) 2024-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 "ServerHandler.h" +#include "Server.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace InputActions +{ + +ServerHandler::ServerHandler(Server &server, gid_t inputActionsGroupId) + : m_inputActionsGroupId(inputActionsGroupId) + , m_currentTty(SessionHelpers::currentTty()) + , m_freedesktopLoginDbusInterface("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", QDBusConnection::systemBus()) +{ + connect(&server, &Server::messageReceived, this, &ServerHandler::onMessageReceived); + + connect(&m_ttyChangeDetectionTimer, &QTimer::timeout, this, &ServerHandler::onTtyChangeDetectionTimerTick); + m_ttyChangeDetectionTimer.setInterval(1000); + m_ttyChangeDetectionTimer.start(); +} + +void ServerHandler::onClientDisconnected(const ClientConnection &client) +{ + if (&client == m_currentClient) { + m_currentClient = {}; + } + m_clients.erase(client.tty); + client.connection->deleteLater(); +} + +void ServerHandler::onMessageReceived(std::shared_ptr message) +{ + switch (message->type()) { + case MessageType::CInitializeRequest: + initializeRequestMessage(std::dynamic_pointer_cast(message)); + break; + } +} + +void ServerHandler::onTtyChangeDetectionTimerTick() +{ + const auto tty = SessionHelpers::currentTty(); + if (m_currentTty == tty) { + return; + } + + qCDebug(INPUTACTIONS).noquote().nospace() << "TTY changed to " << tty; + m_currentTty = tty; + + if (m_clients.contains(tty)) { + activateClient(m_clients[tty]); + } else { + deactivateCurrentClient(); + } +} + +void ServerHandler::initializeRequestMessage(std::shared_ptr message) +{ + auto response = message->makeResponse(); + + ucred cred; + socklen_t len = sizeof(struct ucred); + + if (getsockopt(message->sender()->socket()->socketDescriptor(), SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) { + response.setError(QString("getsockopt failed: %1").arg(errno)); + message->reply(response); + return; + } + + if (cred.gid != m_inputActionsGroupId) { + response.setError("inputactions-client is not running as the 'inputactions' group."); + message->reply(response); + return; + } + + if (m_freedesktopLoginDbusInterface.isValid()) { + const auto reply = m_freedesktopLoginDbusInterface.call("ListSessionsEx"); + if (reply.type() == QDBusMessage::MessageType::ErrorMessage) { + response.setError(QString("ListSessionsEx call failed: %1").arg(reply.errorMessage())); + message->reply(response); + return; + } + + if (reply.arguments().count() == 0) { + response.setError("ListSessionEx returned no sessions."); + message->reply(response); + return; + } + + bool success{}; + const auto sessionData = reply.arguments().at(0).value(); + sessionData.beginArray(); + while (!sessionData.atEnd()) { + bool b; + QDBusObjectPath o; + QString s; + quint64 t; + uint32_t u; + + uint32_t uid; + QString tty; + + sessionData.beginStructure(); + sessionData >> s >> uid >> s >> s >> u >> s >> tty >> b >> t >> o; + sessionData.endStructure(); + + if (cred.uid == uid && message->tty() == tty) { + success = true; + break; + } + } + sessionData.endArray(); + + if (!success) { + response.setError(QString("User logged into tty '%1' is different from the user who started inputactions-client.").arg(message->tty())); + message->reply(response); + return; + } + } else { + QString ttyUser; + setutent(); + utmp *entry; + while ((entry = getutent()) != nullptr) { + if (entry->ut_type == USER_PROCESS && message->tty() == entry->ut_line) { + ttyUser = QString::fromLatin1(entry->ut_user, sizeof(entry->ut_user)); + break; + } + } + endutent(); + + if (ttyUser.isEmpty()) { + response.setError(QString("Failed to get username of user logged into tty '%1'.").arg(message->tty())); + message->reply(response); + return; + } + + passwd *pwd = getpwnam(ttyUser.toStdString().c_str()); + if (!pwd) { + response.setError("Failed to get uid from username."); + message->reply(response); + return; + } + + if (cred.uid != pwd->pw_uid) { + response.setError(QString("User logged into tty '%1' is different from the user who started inputactions-client.").arg(message->tty())); + message->reply(response); + return; + } + } + + if (m_clients.contains(message->tty())) { + response.setError(QString("Tty '%1' already has a running instance of inputactions-client.").arg(message->tty())); + message->reply(response); + return; + } + + response.setSuccess(true); + message->reply(response); + + const auto &tty = message->tty(); + auto &client = m_clients[tty] = { + .connection = message->sender(), + .tty = tty, + .mainThreadId = message->mainThreadId(), + .pid = cred.pid, + }; + connect(message->sender()->socket(), &QLocalSocket::disconnected, this, [this, &client]() { + onClientDisconnected(client); + }); + + if (tty == m_currentTty) { + activateClient(client); + } +} + +void ServerHandler::activateClient(const ClientConnection &client) +{ + deactivateCurrentClient(); + + const sched_param param{ + .sched_priority = sched_get_priority_min(SCHED_RR), + }; + if (sched_setscheduler(client.mainThreadId, SCHED_RR | SCHED_RESET_ON_FORK, ¶m) != 0) { + qWarning(INPUTACTIONS, "Failed to set real time thread priority: %s", strerror(errno)); + } + + if (!client.connection->sendMessageAndWaitForResponse(SActivateRequestMessage())) { + qWarning(INPUTACTIONS, "Client with PID %d did not respond to activation request in time, assuming success.", client.pid); + } + m_currentClient = &client; +} + +void ServerHandler::deactivateCurrentClient() +{ + if (!m_currentClient) { + return; + } + + if (m_currentClient->connection->sendMessageAndWaitForResponse(SDeactivateRequestMessage())) { + const sched_param param{ + .sched_priority = 0, + }; + sched_setscheduler(m_currentClient->mainThreadId, SCHED_OTHER, ¶m); + } else { + qWarning(INPUTACTIONS, "Client with PID %d did not respond to deactivation request in time, sending SIGKILL.", m_currentClient->pid); + kill(m_currentClient->pid, SIGKILL); + m_clients.erase(m_currentClient->tty); + } + + m_currentClient = {}; +} + +} \ No newline at end of file diff --git a/src/daemon/ServerHandler.h b/src/daemon/ServerHandler.h new file mode 100644 index 0000000..3e39f2b --- /dev/null +++ b/src/daemon/ServerHandler.h @@ -0,0 +1,70 @@ +/* + Input Actions - Input handler that executes user-defined actions + Copyright (C) 2024-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 +#include + +namespace InputActions +{ + +class CInitializeRequestMessage; +class Message; +class Server; + +struct ClientConnection +{ + MessageSocketConnection *connection; + QString tty; + int64_t mainThreadId; + pid_t pid; +}; + +class ServerHandler : public QObject +{ + Q_OBJECT + +public: + ServerHandler(Server &server, gid_t inputActionsGroupId); + +private slots: + void onClientDisconnected(const ClientConnection &client); + void onMessageReceived(std::shared_ptr message); + void onTtyChangeDetectionTimerTick(); + +private: + void initializeRequestMessage(std::shared_ptr message); + + void activateClient(const ClientConnection &client); + void deactivateCurrentClient(); + + std::map m_clients; + const ClientConnection *m_currentClient{}; + + gid_t m_inputActionsGroupId; + + QTimer m_ttyChangeDetectionTimer; + QString m_currentTty; + + QDBusInterface m_freedesktopLoginDbusInterface; +}; + +} \ No newline at end of file diff --git a/src/daemon/SessionManager.cpp b/src/daemon/SessionManager.cpp deleted file mode 100644 index 0cbaadf..0000000 --- a/src/daemon/SessionManager.cpp +++ /dev/null @@ -1,347 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 "SessionManager.h" -#include "Server.h" -#include "interfaces/IPCEnvironmentInterfaces.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace InputActions -{ - -static const QString ERROR_SESSION_INACTIVE = "This client's session is inactive"; - -SessionManager::SessionManager(Server *server) - : m_freedesktopLoginDbusInterface("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", QDBusConnection::systemBus()) - , m_currentTty(SessionHelpers::currentTty()) -{ - m_currentSession = &m_sessions[m_currentTty]; - - connect(server, &Server::messageReceived, this, [this](const auto &message) { - handleMessage(message); - }); - - connect(&m_sessionChangeDetectionTimer, &QTimer::timeout, this, &SessionManager::onSessionChangeDetectionTimerTick); - m_sessionChangeDetectionTimer.setInterval(1000); - m_sessionChangeDetectionTimer.start(); - - m_etcConfigProvider = std::make_unique(); - if (m_etcConfigProvider->currentPath() != INPUTACTIONS_ETC_CONFIG_PATH) { - m_etcConfigProvider.reset(); - } -} - -SessionManager::~SessionManager() = default; - -Session &SessionManager::currentSession() -{ - return *m_currentSession; -} - -Session *SessionManager::sessionForClient(MessageSocketConnection *client) -{ - for (auto &[_, session] : m_sessions) { - if (session.client() == client) { - return &session; - } - } - return {}; -} - -void SessionManager::beginSessionRequestMessage(const std::shared_ptr &message) -{ - auto response = message->makeResponse(); - - ucred cred; - socklen_t len = sizeof(struct ucred); - - if (getsockopt(message->sender()->socket()->socketDescriptor(), SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) { - response.setError("Authentication failed: could not get uid from connection"); - message->reply(response); - return; - } - - if (m_freedesktopLoginDbusInterface.isValid()) { - const auto reply = m_freedesktopLoginDbusInterface.call("ListSessionsEx"); - if (reply.type() == QDBusMessage::MessageType::ErrorMessage) { - response.setError(QString("Authentication failed: ListSessionsEx call failed: %1").arg(reply.errorMessage())); - message->reply(response); - return; - } - - if (reply.arguments().count() == 0) { - response.setError("Authentication failed: ListSessionEx returned no sessions"); - message->reply(response); - return; - } - - bool success{}; - const auto sessionData = reply.arguments().at(0).value(); - sessionData.beginArray(); - while (!sessionData.atEnd()) { - bool b; - QDBusObjectPath o; - QString s; - quint64 t; - uint32_t u; - - uint32_t uid; - QString tty; - - sessionData.beginStructure(); - sessionData >> s >> uid >> s >> s >> u >> s >> tty >> b >> t >> o; - sessionData.endStructure(); - - if (cred.uid == uid && message->tty() == tty) { - success = true; - break; - } - } - sessionData.endArray(); - - if (!success) { - response.setError("Permission denied: cannot begin session for another user"); - message->reply(response); - return; - } - } else { - QString ttyUser; - setutent(); - utmp *entry; - while ((entry = getutent()) != nullptr) { - if (entry->ut_type == USER_PROCESS && message->tty() == entry->ut_line) { - ttyUser = QString::fromLatin1(entry->ut_user, sizeof(entry->ut_user)); - break; - } - } - endutent(); - - if (ttyUser.isEmpty()) { - response.setError("Authentication failed: could not get username of tty owner"); - message->reply(response); - return; - } - - passwd *pwd = getpwnam(ttyUser.toStdString().c_str()); - if (!pwd) { - response.setError("Authentication failed: could not get pid from username"); - message->reply(response); - return; - } - - if (cred.uid != pwd->pw_uid) { - response.setError("Permission denied: cannot begin session for another user"); - message->reply(response); - return; - } - } - - auto &session = m_sessions[message->tty()]; - if (session.m_client) { - response.setError("This TTY already has an initialized session"); - } else { - session.m_client = message->sender(); - session.m_ipcEnvironmentInterfaces = std::make_shared(); - session.m_variableRegistry = std::make_shared(); - g_inputActions->registerGlobalVariables(session.m_variableRegistry.get(), session.m_ipcEnvironmentInterfaces, session.m_ipcEnvironmentInterfaces); - - if (SessionHelpers::currentTty() == message->tty()) { - activateSession(session, false); - } - } - message->reply(response); - - connect(message->sender()->socket(), &QLocalSocket::disconnected, this, [this, socket = message->sender()]() { - if (auto *session = sessionForClient(socket)) { - session->m_client = {}; - session->m_config = {}; - - if (session == ¤tSession()) { - qCDebug(INPUTACTIONS) << "Client disconnected, suspending current session"; - activateSession(*session); - } - } - }); -} - -void SessionManager::configIssuesRequestMessage(const std::shared_ptr &message) -{ - if (auto *session = sessionForClient(message->sender())) { - auto response = message->makeResponse(); - if (¤tSession() == session) { - response.setResult(g_configIssueManager->issuesToString()); - } else { - response.setError("Session must be active in order to check issues."); - } - message->reply(response); - } -} - -void SessionManager::deviceListRequestMessage(const std::shared_ptr &message) -{ - if (const auto *session = sessionForClient(message->sender())) { - auto response = message->makeResponse(); - if (¤tSession() == session) { - response.setResult(m_dbusInterfaceBase.deviceList()); - } else { - response.setError(ERROR_SESSION_INACTIVE); - } - - message->reply(response); - } -} - -void SessionManager::environmentStateMessage(const std::shared_ptr &message) -{ - if (auto *session = sessionForClient(message->sender())) { - session->m_ipcEnvironmentInterfaces->updateEnvironmentState(message->stateJson()); - } -} - -void SessionManager::handshakeRequestMessage(const std::shared_ptr &message) -{ - auto response = message->makeResponse(); - if (message->protocolVersion() != INPUTACTIONS_IPC_PROTOCOL_VERSION) { - response.setError(QString("Protocol version mismatch (daemon: %1, client: %2)") - .arg(QString::number(INPUTACTIONS_IPC_PROTOCOL_VERSION), QString::number(message->protocolVersion()))); - } - message->reply(response); -} - -void SessionManager::loadConfigRequestMessage(const std::shared_ptr &message) -{ - auto response = message->makeResponse(); - if (auto *session = sessionForClient(message->sender())) { - session->m_suspended = false; - auto config = message->config(); - if (m_etcConfigProvider) { - config = m_etcConfigProvider->currentConfig(); - qCDebug(INPUTACTIONS).noquote().nospace() << INPUTACTIONS_ETC_CONFIG_PATH << " exists, overriding local config"; - } - - session->m_config = config; - if (¤tSession() == session) { - if (!g_configLoader->load({ - .config = config, - .manual = message->manual(), - })) { - response.setError(g_configIssueManager->issuesToString()); - } - response.setResult(g_configIssueManager->issuesToString()); - } - } - - message->reply(response); -} - -void SessionManager::recordStrokeRequestMessage(const std::shared_ptr &message) -{ - if (const auto *session = sessionForClient(message->sender())) { - if (¤tSession() != session) { - auto response = message->makeResponse(); - response.setError(ERROR_SESSION_INACTIVE); - message->reply(response); - return; - } else if (!g_inputBackend->initialized()) { - auto response = message->makeResponse(); - response.setError("Stroke recording requires a valid configuration to be active."); - message->reply(response); - return; - } - - g_strokeRecorder->recordStroke([this, message](const auto &stroke) { - auto response = message->makeResponse(); - response.setResult(m_dbusInterfaceBase.strokeToBase64(stroke)); - message->reply(response); - }); - } -} - -void SessionManager::suspendRequestMessage(const std::shared_ptr &message) -{ - if (auto *session = sessionForClient(message->sender())) { - session->m_suspended = true; - - if (¤tSession() == session) { - activateSession(*session, false); - } - - message->reply(); - } -} - -void SessionManager::variableListRequestMessage(const std::shared_ptr &message) -{ - if (auto *session = sessionForClient(message->sender())) { - auto response = message->makeResponse(); - response.setResult(m_dbusInterfaceBase.variableList(session->m_variableRegistry.get(), message->filter())); - message->reply(response); - } -} - -void SessionManager::activateSession(Session &session, bool loadConfig) -{ - // Ensure the previous session's config doesn't remain active if this session's config fails to load - g_configLoader->loadEmpty(); - - m_currentSession = &session; - if (session.m_suspended) { - qCDebug(INPUTACTIONS) << "Session is suspended"; - return; - } - if (!session.m_client) { - qCDebug(INPUTACTIONS) << "No client/config for current session, suspending"; - return; - } - - if (loadConfig) { - g_configLoader->load({ - .config = session.m_config, - }); - } - - g_pointerPositionGetter = session.m_ipcEnvironmentInterfaces; - g_variableRegistry = session.m_variableRegistry; - g_windowProvider = session.m_ipcEnvironmentInterfaces; -} - -void SessionManager::onSessionChangeDetectionTimerTick() -{ - const auto tty = SessionHelpers::currentTty(); - if (m_currentTty != tty) { - qCDebug(INPUTACTIONS).noquote().nospace() << "TTY changed to " << tty; - m_currentTty = tty; - activateSession(m_sessions[tty]); - } -} - -} \ No newline at end of file diff --git a/src/daemon/SessionManager.h b/src/daemon/SessionManager.h deleted file mode 100644 index 96cb4fe..0000000 --- a/src/daemon/SessionManager.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 -#include -#include -#include - -namespace InputActions -{ - -class FileConfigProvider; -class IPCEnvironmentInterfaces; -class Message; -class MessageSocketConnection; -class Server; -class VariableRegistry; - -class Session -{ -public: - MessageSocketConnection *client() const { return m_client; } - -private: - bool m_hasClient{}; - QString m_config; - MessageSocketConnection *m_client{}; - bool m_suspended{}; - - std::shared_ptr m_ipcEnvironmentInterfaces; - std::shared_ptr m_variableRegistry; - - friend class SessionManager; -}; - -class SessionManager - : public QObject - , public MessageHandler -{ - Q_OBJECT - -public: - SessionManager(Server *server); - ~SessionManager() override; - - Session ¤tSession(); - Session *sessionForClient(MessageSocketConnection *client); - -protected: - void beginSessionRequestMessage(const std::shared_ptr &message) override; - void configIssuesRequestMessage(const std::shared_ptr &message) override; - void deviceListRequestMessage(const std::shared_ptr &message) override; - void environmentStateMessage(const std::shared_ptr &message) override; - void handshakeRequestMessage(const std::shared_ptr &message) override; - void loadConfigRequestMessage(const std::shared_ptr &message) override; - void recordStrokeRequestMessage(const std::shared_ptr &message) override; - void suspendRequestMessage(const std::shared_ptr &message) override; - void variableListRequestMessage(const std::shared_ptr &message) override; - -private slots: - void onSessionChangeDetectionTimerTick(); - -private: - void activateSession(Session &session, bool loadConfig = true); - - DBusInterfaceBase m_dbusInterfaceBase; - QDBusInterface m_freedesktopLoginDbusInterface; - - std::unique_ptr m_etcConfigProvider; - - QTimer m_sessionChangeDetectionTimer; - QString m_currentTty; - Session *m_currentSession; - std::map m_sessions; -}; - -inline std::shared_ptr g_sessionManager; - -} \ No newline at end of file diff --git a/src/daemon/inputactionsd.service.in b/src/daemon/inputactionsd.service.in index 9a6d5f1..b1f1e0c 100644 --- a/src/daemon/inputactionsd.service.in +++ b/src/daemon/inputactionsd.service.in @@ -6,6 +6,7 @@ Type=simple ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/inputactionsd Restart=on-failure RestartSec=5s +PAMName=login [Install] WantedBy=multi-user.target \ No newline at end of file diff --git a/src/daemon/interfaces/IPCNotificationManager.cpp b/src/daemon/interfaces/IPCNotificationManager.cpp deleted file mode 100644 index 1ccc37b..0000000 --- a/src/daemon/interfaces/IPCNotificationManager.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 "IPCNotificationManager.h" -#include "SessionManager.h" -#include -#include - -namespace InputActions -{ - -void IPCNotificationManager::sendNotification(const QString &title, const QString &content) -{ - SendNotificationMessage message; - message.setTitle(title); - message.setContent(content); - g_sessionManager->currentSession().client()->sendMessage(message); -} - -} \ No newline at end of file diff --git a/src/daemon/interfaces/IPCPlasmaGlobalShortcutInvoker.cpp b/src/daemon/interfaces/IPCPlasmaGlobalShortcutInvoker.cpp deleted file mode 100644 index 46ec1ed..0000000 --- a/src/daemon/interfaces/IPCPlasmaGlobalShortcutInvoker.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 "IPCPlasmaGlobalShortcutInvoker.h" -#include "SessionManager.h" -#include -#include - -namespace InputActions -{ - -void IPCPlasmaGlobalShortcutInvoker::invoke(const QString &component, const QString &shortcut) -{ - InvokePlasmaGlobalShortcutRequestMessage message; - message.setComponent(component); - message.setShortcut(shortcut); - g_sessionManager->currentSession().client()->sendMessageAndWaitForResponse(message); -} - -} \ No newline at end of file diff --git a/src/daemon/interfaces/IPCProcessRunner.cpp b/src/daemon/interfaces/IPCProcessRunner.cpp deleted file mode 100644 index 55a3704..0000000 --- a/src/daemon/interfaces/IPCProcessRunner.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 "IPCProcessRunner.h" -#include "SessionManager.h" -#include -#include - -namespace InputActions -{ - -void IPCProcessRunner::startProcess(const QString &program, const QStringList &arguments, std::map extraEnvironment, bool wait) -{ - StartProcessRequestMessage message; - message.setProgram(program); - message.setArguments(arguments); - message.setEnvironment(extraEnvironment); - message.setWait(wait); - - if (wait) { - g_sessionManager->currentSession().client()->sendMessageAndWaitForResponse(message); - } else { - g_sessionManager->currentSession().client()->sendMessage(message); - } -} - -QString IPCProcessRunner::startProcessReadOutput(const QString &program, const QStringList &arguments, std::map extraEnvironment) -{ - StartProcessRequestMessage message; - message.setProgram(program); - message.setArguments(arguments); - message.setEnvironment(extraEnvironment); - message.setOutput(true); - - if (const auto reply = g_sessionManager->currentSession().client()->sendMessageAndWaitForResponse(message)) { - return reply->result(); - } - return {}; -} - -} \ No newline at end of file diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 18d345b..83ecd4c 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -17,21 +17,16 @@ */ #include "Server.h" -#include "SessionManager.h" -#include "input/StandaloneInputBackend.h" -#include "interfaces/IPCNotificationManager.h" -#include "interfaces/IPCPlasmaGlobalShortcutInvoker.h" -#include "interfaces/IPCProcessRunner.h" +#include "ServerHandler.h" #include #include +#include #include #include -#include -#include -#include -#include -#include +#include +#include #include +#include #include using namespace InputActions; @@ -43,30 +38,40 @@ void handleSignal(int signal) { if (signal == SIGINT) { QCoreApplication::quit(); + std::signal(SIGINT, SIG_DFL); } } -int main() +void setfacl(const QString &target, QStringList arguments) { - ScriptingEngine::disabled = true; + arguments.push_back(target); + + QProcess process; + process.setProgram("setfacl"); + process.setArguments(arguments); + process.start(); + if (!process.waitForFinished()) { + qWarning("setfacl failed: %s", process.errorString().toStdString().c_str()); + } +} +int main() +{ if (geteuid()) { qCritical() << "The daemon must be run as root."; return -1; } + auto *inputActionsGroup = getgrnam("inputactions"); + if (!inputActionsGroup) { + qCritical() << "The 'inputactions' group does not exist."; + return -1; + } + static int argc = 0; QCoreApplication app(argc, nullptr); - std::signal(SIGINT, handleSignal); - const int minPriority = sched_get_priority_min(SCHED_RR); - sched_param sp; - sp.sched_priority = minPriority; - if (pthread_setschedparam(pthread_self(), SCHED_RR | SCHED_RESET_ON_FORK, &sp) != 0) { - qWarning(INPUTACTIONS, "Failed to gain real time thread priority: %s", strerror(errno)); - } - if (!VAR_RUN_INPUTACTIONS_DIR.exists()) { VAR_RUN_INPUTACTIONS_DIR.mkpath("."); chmod(VAR_RUN_INPUTACTIONS_DIR.path().toStdString().c_str(), 0755); @@ -80,21 +85,16 @@ int main() } } - InputActionsMain inputActions; - g_inputBackend = std::make_unique(); - g_notificationManager = std::make_shared(); - g_plasmaGlobalShortcutInvoker = std::make_shared(); - g_processRunner = std::make_shared(); + setfacl("/dev/input", {"-Rdm", "g:inputactions:rw"}); + setfacl("/dev/input", {"-Rm", "g:inputactions:rw"}); + setfacl("/dev/input", {"-m", "g:inputactions:rwx"}); + setfacl("/dev/uinput", {"-m", "g:inputactions:rw"}); auto *serverThread = new QThread; auto *server = new Server; server->moveToThread(serverThread); - g_sessionManager = std::make_shared(server); - g_configProvider = std::make_shared(); // Config is managed by SessionManager - - inputActions.setMissingImplementations(); - inputActions.initialize(); + ServerHandler serverHandler(*server, inputActionsGroup->gr_gid); QObject::connect(serverThread, &QThread::started, [server]() { QMetaObject::invokeMethod(server, "start"); diff --git a/src/ipc/CMakeLists.txt b/src/ipc/CMakeLists.txt deleted file mode 100644 index 3853646..0000000 --- a/src/ipc/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS - Network -) - -add_library(libinputactions-standalone-ipc STATIC - libinputactions-standalone-ipc/JsonSerializer.cpp - libinputactions-standalone-ipc/MessageHandler.cpp - libinputactions-standalone-ipc/messages.cpp - libinputactions-standalone-ipc/MessageSocketConnection.cpp -) -target_link_libraries(libinputactions-standalone-ipc PUBLIC - libinputactions - Qt6::Network -) -target_include_directories(libinputactions-standalone-ipc PUBLIC libinputactions-standalone-ipc) -set_target_properties(libinputactions-standalone-ipc PROPERTIES PREFIX "") diff --git a/src/ipc/libinputactions-standalone-ipc/MessageHandler.cpp b/src/ipc/libinputactions-standalone-ipc/MessageHandler.cpp deleted file mode 100644 index 049da15..0000000 --- a/src/ipc/libinputactions-standalone-ipc/MessageHandler.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 "MessageHandler.h" -#include "messages.h" - -namespace InputActions -{ - -void MessageHandler::handleMessage(std::shared_ptr message) -{ - switch (message->type()) { - case MessageType::BeginSessionRequest: - beginSessionRequestMessage(std::dynamic_pointer_cast(message)); - break; - case MessageType::ConfigIssuesRequest: - configIssuesRequestMessage(std::dynamic_pointer_cast(message)); - break; - case MessageType::DeviceListRequest: - deviceListRequestMessage(std::dynamic_pointer_cast(message)); - break; - case MessageType::EnvironmentState: - environmentStateMessage(std::dynamic_pointer_cast(message)); - break; - case MessageType::HandshakeRequest: - handshakeRequestMessage(std::dynamic_pointer_cast(message)); - break; - case MessageType::InvokePlasmaGlobalShortcutRequest: - invokePlasmaGlobalShortcutMessage(std::dynamic_pointer_cast(message)); - break; - case MessageType::LoadConfigRequest: - loadConfigRequestMessage(std::dynamic_pointer_cast(message)); - break; - case MessageType::RecordStrokeRequest: - recordStrokeRequestMessage(std::dynamic_pointer_cast(message)); - break; - case MessageType::SendNotification: - sendNotificationMessage(std::dynamic_pointer_cast(message)); - break; - case MessageType::StartProcessRequest: - startProcessRequestMessage(std::dynamic_pointer_cast(message)); - break; - case MessageType::SuspendRequest: - suspendRequestMessage(std::dynamic_pointer_cast(message)); - break; - case MessageType::VariableListRequest: - variableListRequestMessage(std::dynamic_pointer_cast(message)); - break; - } -} - -} \ No newline at end of file diff --git a/src/ipc/libinputactions-standalone-ipc/MessageHandler.h b/src/ipc/libinputactions-standalone-ipc/MessageHandler.h deleted file mode 100644 index 62047b6..0000000 --- a/src/ipc/libinputactions-standalone-ipc/MessageHandler.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 - -namespace InputActions -{ - -class Message; -class BeginSessionRequestMessage; -class ConfigIssuesRequestMessage; -class DeviceListRequestMessage; -class EnvironmentStateMessage; -class HandshakeRequestMessage; -class InvokePlasmaGlobalShortcutRequestMessage; -class LoadConfigRequestMessage; -class RecordStrokeRequestMessage; -class SendNotificationMessage; -class StartProcessRequestMessage; -class SuspendRequestMessage; -class VariableListRequestMessage; - -class MessageHandler -{ -public: - MessageHandler() = default; - virtual ~MessageHandler() = default; - - void handleMessage(std::shared_ptr message); - -protected: - virtual void beginSessionRequestMessage(const std::shared_ptr &message) {} - virtual void configIssuesRequestMessage(const std::shared_ptr &message) {} - virtual void deviceListRequestMessage(const std::shared_ptr &message) {} - virtual void environmentStateMessage(const std::shared_ptr &message) {} - virtual void handshakeRequestMessage(const std::shared_ptr &message) {} - virtual void invokePlasmaGlobalShortcutMessage(const std::shared_ptr &message) {} - virtual void loadConfigRequestMessage(const std::shared_ptr &message) {} - virtual void recordStrokeRequestMessage(const std::shared_ptr &message) {} - virtual void sendNotificationMessage(const std::shared_ptr &message) {} - virtual void startProcessRequestMessage(const std::shared_ptr &message) {} - virtual void suspendRequestMessage(const std::shared_ptr &message) {} - virtual void variableListRequestMessage(const std::shared_ptr &message) {} -}; - -} \ No newline at end of file diff --git a/src/ipc/libinputactions-standalone-ipc/messages.h b/src/ipc/libinputactions-standalone-ipc/messages.h deleted file mode 100644 index ec1e40e..0000000 --- a/src/ipc/libinputactions-standalone-ipc/messages.h +++ /dev/null @@ -1,433 +0,0 @@ -/* - Input Actions - Input handler that executes user-defined actions - Copyright (C) 2024-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 -{ - -static const int INPUTACTIONS_IPC_PROTOCOL_VERSION = 5; - -class MessageSocketConnection; - -enum class MessageType : int -{ - HandshakeRequest, - - GenericResponse, - SimpleStringResponse, - - BeginSessionRequest, - ConfigIssuesRequest, - DeviceListRequest, - EnvironmentState, - InvokePlasmaGlobalShortcutRequest, - LoadConfigRequest, - RecordStrokeRequest, - SendNotification, - StartProcessRequest, - SuspendRequest, - VariableListRequest, -}; - -template -std::map toStdMap(QMap map) -{ - std::map result; - for (auto it = map.cbegin(); it != map.cend(); ++it) { - result[it.key()] = it.value().value(); - } - return result; -} - -template -QMap toQtMap(std::map map) -{ - QMap result; - for (const auto &[key, value] : map) { - result[key] = value; - } - return result; -} - -class Message : public QObject -{ - Q_OBJECT - Q_PROPERTY(int type MEMBER m_type) - -public: - Message(MessageType type) - : m_type(static_cast(type)) - { - } - - virtual ~Message() = default; - - MessageType type() const { return static_cast(m_type); } - - MessageSocketConnection *sender() const { return m_sender; } - void setSender(MessageSocketConnection *value) { m_sender = value; } - -protected: - MessageSocketConnection *m_sender; - -private: - int m_type; -}; - -class ResponseMessage : public Message -{ - Q_OBJECT - Q_PROPERTY(QString requestId MEMBER m_requestId) - Q_PROPERTY(bool success MEMBER m_success) - Q_PROPERTY(QString error MEMBER m_error) - -public: - ResponseMessage(MessageType type = MessageType::GenericResponse) - : Message(type) - { - } - - const QString &requestId() const { return m_requestId; } - void setRequestId(const QString &value) { m_requestId = value; } - - bool success() const { return m_success; } - - const QString &error() const { return m_error; } - void setError(QString error); - -private: - QString m_requestId; - - bool m_success = true; - QString m_error; -}; - -class SimpleStringResponseMessage : public ResponseMessage -{ - Q_OBJECT - Q_PROPERTY(QString result MEMBER m_result) - -public: - SimpleStringResponseMessage() - : ResponseMessage(MessageType::SimpleStringResponse) - { - } - - const QString &result() const { return m_result; } - void setResult(QString result) { m_result = std::move(result); } - -private: - QString m_result; -}; - -class RequestMessageBase : public Message -{ - Q_OBJECT - Q_PROPERTY(QString requestId MEMBER m_requestId) - -public: - RequestMessageBase(MessageType type) - : Message(type) - { - } - - const QString &requestId() const { return m_requestId; } - -protected: - void sendResponse(const ResponseMessage &response) const; - -private: - QString m_requestId = QUuid::createUuid().toString(); -}; - -template -class RequestMessage : public RequestMessageBase -{ -public: - using RequestMessageBase::RequestMessageBase; - - TResponse makeResponse() const { return {}; } - - void reply() const - { - TResponse response; - reply(response); - } - - void reply(TResponse &response) const - { - response.setRequestId(requestId()); - sendResponse(response); - } -}; - -/** - * Response result string is the device list. - */ -class DeviceListRequestMessage : public RequestMessage -{ - Q_OBJECT - -public: - DeviceListRequestMessage() - : RequestMessage(MessageType::DeviceListRequest) - { - } -}; - -class BeginSessionRequestMessage : public RequestMessage -{ - Q_OBJECT - Q_PROPERTY(QString tty MEMBER m_tty) - -public: - BeginSessionRequestMessage() - : RequestMessage(MessageType::BeginSessionRequest) - { - } - - const QString &tty() const { return m_tty; } - void setTty(const QString &value) { m_tty = value; } - -private: - QString m_tty; -}; - -/** - * Response result string are the issues. - */ -class ConfigIssuesRequestMessage : public RequestMessage -{ - Q_OBJECT - -public: - ConfigIssuesRequestMessage() - : RequestMessage(MessageType::ConfigIssuesRequest) - { - } -}; - -class HandshakeRequestMessage : public RequestMessage -{ - Q_OBJECT - Q_PROPERTY(int protocolVersion MEMBER m_protocolVersion) - -public: - HandshakeRequestMessage() - : RequestMessage(MessageType::HandshakeRequest) - { - } - - int protocolVersion() const { return m_protocolVersion; } - -private: - int m_protocolVersion = INPUTACTIONS_IPC_PROTOCOL_VERSION; -}; - -class EnvironmentStateMessage : public Message -{ - Q_OBJECT - Q_PROPERTY(QString stateJson MEMBER m_stateJson) - -public: - EnvironmentStateMessage() - : Message(MessageType::EnvironmentState) - { - } - - const QString &stateJson() const { return m_stateJson; } - void setStateJson(const QString &value) { m_stateJson = value; } - -private: - QString m_stateJson; -}; - -class InvokePlasmaGlobalShortcutRequestMessage : public RequestMessage -{ - Q_OBJECT - Q_PROPERTY(QString component MEMBER m_component) - Q_PROPERTY(QString shortcut MEMBER m_shortcut) - -public: - InvokePlasmaGlobalShortcutRequestMessage() - : RequestMessage(MessageType::InvokePlasmaGlobalShortcutRequest) - { - } - - const QString &component() const { return m_component; } - void setComponent(const QString &value) { m_component = value; } - - const QString &shortcut() const { return m_shortcut; } - void setShortcut(const QString &value) { m_shortcut = value; } - -private: - QString m_component; - QString m_shortcut; -}; - -/** - * Response result string are the issues. - */ -class LoadConfigRequestMessage : public RequestMessage -{ - Q_OBJECT - Q_PROPERTY(QString config MEMBER m_config) - Q_PROPERTY(bool manual MEMBER m_manual) - -public: - LoadConfigRequestMessage() - : RequestMessage(MessageType::LoadConfigRequest) - { - } - - const QString &config() const { return m_config; } - void setConfig(const QString &value) { m_config = value; } - - /** - * @see ConfigLoadSettings::manual - */ - bool manual() const { return m_manual; } - void setManual(bool value) { m_manual = value; } - -private: - QString m_config; - bool m_manual{}; -}; - -/** - * Response result string is the stroke. - */ -class RecordStrokeRequestMessage : public RequestMessage -{ - Q_OBJECT - -public: - RecordStrokeRequestMessage() - : RequestMessage(MessageType::RecordStrokeRequest) - { - } -}; - -class SendNotificationMessage : public Message -{ - Q_OBJECT - Q_PROPERTY(QString title MEMBER m_title) - Q_PROPERTY(QString content MEMBER m_content) - -public: - SendNotificationMessage() - : Message(MessageType::SendNotification) - { - } - - const QString &title() const { return m_title; } - void setTitle(const QString &value) { m_title = value; } - - const QString &content() const { return m_content; } - void setContent(const QString &value) { m_content = value; } - -private: - QString m_title; - QString m_content; -}; - -/** - * Response result string is the process output. - */ -class StartProcessRequestMessage : public RequestMessage -{ - Q_OBJECT - Q_PROPERTY(QString program MEMBER m_program) - Q_PROPERTY(QStringList arguments MEMBER m_arguments) - Q_PROPERTY(QVariantMap environment MEMBER m_environment) - Q_PROPERTY(bool wait MEMBER m_wait) - Q_PROPERTY(bool output MEMBER m_output) - -public: - StartProcessRequestMessage() - : RequestMessage(MessageType::StartProcessRequest) - { - } - - const QString &program() const { return m_program; } - void setProgram(const QString &value) { m_program = value; } - - const QStringList &arguments() const { return m_arguments; } - void setArguments(const QStringList &value) { m_arguments = value; } - - std::map environment() const { return toStdMap(m_environment); } - void setEnvironment(std::map value) { m_environment = toQtMap(value); } - - /** - * @return Wait for the process to exit before sending a reply. - */ - bool wait() const { return m_wait; } - void setWait(bool value) { m_wait = value; } - - /** - * @return Wait for the process to exit and provide its output in the reply. - */ - bool output() const { return m_output; } - void setOutput(bool value) { m_output = value; } - -private: - QString m_program; - QStringList m_arguments; - QVariantMap m_environment; - bool m_wait{}; - bool m_output{}; -}; - -class SuspendRequestMessage : public RequestMessage -{ - Q_OBJECT - -public: - SuspendRequestMessage() - : RequestMessage(MessageType::SuspendRequest) - { - } -}; - -/** - * Response result string is the variable list. - */ -class VariableListRequestMessage : public RequestMessage -{ - Q_OBJECT - Q_PROPERTY(QString filter MEMBER m_filter) - -public: - VariableListRequestMessage() - : RequestMessage(MessageType::VariableListRequest) - { - } - - const QString &filter() const { return m_filter; } - void setFilter(const QString &value) { m_filter = value; } - -private: - QString m_filter; -}; - -} \ No newline at end of file