diff --git a/src/apps/deskflow-daemon/deskflow-daemon.cpp b/src/apps/deskflow-daemon/deskflow-daemon.cpp index 33297b581..862fe01dc 100644 --- a/src/apps/deskflow-daemon/deskflow-daemon.cpp +++ b/src/apps/deskflow-daemon/deskflow-daemon.cpp @@ -55,6 +55,8 @@ int main(int argc, char **argv) QCoreApplication app(argc, argv); + // Thread must be heap-allocated for deferred deletion on thread exit. + // Avoid setting Qt ownership to prevent premature deletion (thread may run longer than Qt loop). auto *pDaemonThread = new QThread(); // NOSONAR pDaemon->moveToThread(pDaemonThread); @@ -78,9 +80,14 @@ int main(int argc, char **argv) Qt::DirectConnection ); QObject::connect( - ipcServer, &ipc::DaemonIpcServer::restartRequested, pDaemon, &DaemonApp::restartCoreProcess, // + ipcServer, &ipc::DaemonIpcServer::startProcessRequested, pDaemon, &DaemonApp::applyWatchdogCommand, // Qt::DirectConnection ); + QObject::connect( + ipcServer, &ipc::DaemonIpcServer::stopProcessRequested, pDaemon, &DaemonApp::clearWatchdogCommand, // + Qt::DirectConnection + ); + pDaemonThread->start(); return QCoreApplication::exec(); } diff --git a/src/lib/deskflow/DaemonApp.cpp b/src/lib/deskflow/DaemonApp.cpp index 9fbf37168..b1ac95de0 100644 --- a/src/lib/deskflow/DaemonApp.cpp +++ b/src/lib/deskflow/DaemonApp.cpp @@ -159,14 +159,25 @@ void DaemonApp::setCommand(const QString &command) } } -void DaemonApp::restartCoreProcess() +void DaemonApp::applyWatchdogCommand() const { - LOG_DEBUG("service restart requested"); + LOG_DEBUG("applying watchdog command"); #if SYSAPI_WIN32 m_watchdog->setCommand(m_command, m_elevate); #else - LOG_ERR("restart not implemented on this platform"); + LOG_ERR("applying watchdog command not implemented on this platform"); +#endif +} + +void DaemonApp::clearWatchdogCommand() +{ + LOG_DEBUG("clearing watchdog command"); + +#if SYSAPI_WIN32 + m_watchdog->setCommand("", false); +#else + LOG_ERR("clearing watchdog command not implemented on this platform"); #endif } diff --git a/src/lib/deskflow/DaemonApp.h b/src/lib/deskflow/DaemonApp.h index 5296c623e..fd25bb7e5 100644 --- a/src/lib/deskflow/DaemonApp.h +++ b/src/lib/deskflow/DaemonApp.h @@ -54,6 +54,8 @@ public: void saveLogLevel(const QString &logLevel) const; void setElevate(bool elevate); void setCommand(const QString &command); + void applyWatchdogCommand() const; + void clearWatchdogCommand(); static DaemonApp &instance() { diff --git a/src/lib/deskflow/ipc/DaemonIpcServer.cpp b/src/lib/deskflow/ipc/DaemonIpcServer.cpp index e1f19c93d..26814e484 100644 --- a/src/lib/deskflow/ipc/DaemonIpcServer.cpp +++ b/src/lib/deskflow/ipc/DaemonIpcServer.cpp @@ -14,13 +14,18 @@ namespace deskflow::core::ipc { -DaemonIpcServer::DaemonIpcServer(QObject *parent) : QObject(parent), m_server{new QLocalServer(this)} +const auto kAckMessage = "ok\n"; +const auto kErrorMessage = "error\n"; + +DaemonIpcServer::DaemonIpcServer(QObject *parent) + : QObject(parent), // + m_server{new QLocalServer(this)} // NOSONAR { // Daemon runs as system, but GUI runs as regular user, so we need to allow world access. m_server->setSocketOptions(QLocalServer::WorldAccessOption); connect(m_server, &QLocalServer::newConnection, this, &DaemonIpcServer::handleNewConnection); - m_server->removeServer(kDaemonIpcName); + QLocalServer::removeServer(kDaemonIpcName); if (m_server->listen(kDaemonIpcName)) { LOG_DEBUG("ipc server listening on: %s", kDaemonIpcName); } else { @@ -68,7 +73,7 @@ void DaemonIpcServer::handleReadyRead() // each message is delimited by a newline to keep the protocol super simple. while (data.contains('\n')) { - int index = data.indexOf('\n'); + const auto index = data.indexOf('\n'); QByteArray messageData = data.left(index); data.remove(0, index + 1); QString message = QString::fromUtf8(messageData); @@ -94,53 +99,101 @@ void DaemonIpcServer::handleErrorOccurred() void DaemonIpcServer::processMessage(QLocalSocket *clientSocket, const QString &message) { - const auto kAckMessage = "ok\n"; - const auto kErrorMessage = "error\n"; - LOG_DEBUG("ipc server got message: %s", message.toUtf8().constData()); - if (message == "hello") { + const auto parts = message.split('='); + if (parts.size() < 1) { + LOG_ERR("ipc server got invalid message: %s", message.toUtf8().constData()); + clientSocket->write(kErrorMessage); + return; + } + + const auto &command = parts[0]; + if (command == "hello") { // NOSONAR clientSocket->write("hello\n"); - } else if (message == "noop") { + } else if (command == "noop") { clientSocket->write(kAckMessage); - } else if (message.startsWith("logLevel=")) { - const auto logLevel = message.split('=')[1]; - if (logLevel.isEmpty()) { - LOG_ERR("ipc server got empty log level"); - clientSocket->write(kErrorMessage); - } else { - LOG_DEBUG("ipc server got new log level: %s", logLevel.toUtf8().constData()); - Q_EMIT logLevelChanged(logLevel); - clientSocket->write(kAckMessage); - } - } else if (message.startsWith("elevate=")) { - const auto elevateMode = message.split('=')[1]; - if (elevateMode != "yes" && elevateMode != "no") { - LOG_ERR("ipc server got invalid elevate value: %s", elevateMode.toUtf8().constData()); - clientSocket->write(kErrorMessage); - return; - } else { - LOG_DEBUG("ipc server got new elevate value: %s", elevateMode.toUtf8().constData()); - Q_EMIT elevateModeChanged(elevateMode == "yes"); - clientSocket->write(kAckMessage); - } - } else if (message.startsWith("command=")) { - const auto command = message.split('=')[1]; - if (command.isEmpty()) { - LOG_ERR("ipc server got empty command"); - clientSocket->write(kErrorMessage); - } else { - LOG_DEBUG("ipc server got new command: %s", command.toUtf8().constData()); - Q_EMIT commandChanged(command); - clientSocket->write(kAckMessage); - } - } else if (message == "restart") { - LOG_DEBUG("ipc server got restart message"); - Q_EMIT restartRequested(); + } else if (command == "logLevel") { + processLogLevel(clientSocket, parts); + } else if (command == "elevate") { + processElevate(clientSocket, parts); + } else if (command == "command") { + processCommand(clientSocket, parts); + } else if (command == "start") { + LOG_DEBUG("ipc server got start message"); + Q_EMIT startProcessRequested(); clientSocket->write(kAckMessage); + } else if (command == "stop") { + LOG_DEBUG("ipc server got stop message"); + Q_EMIT stopProcessRequested(); + clientSocket->write(kAckMessage); + } else if (command == "logPath") { + LOG_DEBUG("ipc server got log path request"); + // TODO: send log path } else { LOG_WARN("ipc server got unknown message: %s", message.toUtf8().constData()); } + clientSocket->flush(); } +void DaemonIpcServer::processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts) +{ + if (messageParts.size() < 2) { + LOG_ERR("ipc server got invalid log level message"); + clientSocket->write(kErrorMessage); + return; + } + + const auto &logLevel = messageParts[1]; + if (logLevel.isEmpty()) { + LOG_ERR("ipc server got empty log level"); + clientSocket->write(kErrorMessage); + return; + } + + LOG_DEBUG("ipc server got new log level: %s", logLevel.toUtf8().constData()); + Q_EMIT logLevelChanged(logLevel); + clientSocket->write(kAckMessage); +} + +void DaemonIpcServer::processElevate(QLocalSocket *&clientSocket, const QStringList &messageParts) +{ + if (messageParts.size() < 2) { + LOG_ERR("ipc server got invalid elevate message"); + clientSocket->write(kErrorMessage); + return; + } + + const auto &elevate = messageParts[1]; + if (elevate != "yes" && elevate != "no") { + LOG_ERR("ipc server got invalid elevate value: %s", elevate.toUtf8().constData()); + clientSocket->write(kErrorMessage); + return; + } + + LOG_DEBUG("ipc server got new elevate value: %s", elevate.toUtf8().constData()); + Q_EMIT elevateModeChanged(elevate == "yes"); + clientSocket->write(kAckMessage); +} + +void DaemonIpcServer::processCommand(QLocalSocket *&clientSocket, const QStringList &messageParts) +{ + if (messageParts.size() < 2) { + LOG_ERR("ipc server got invalid command message"); + clientSocket->write(kErrorMessage); + return; + } + + const auto &command = messageParts[1]; + if (command.isEmpty()) { + LOG_ERR("ipc server got empty command"); + clientSocket->write(kErrorMessage); + return; + } + + LOG_DEBUG("ipc server got new command: %s", command.toUtf8().constData()); + Q_EMIT commandChanged(command); + clientSocket->write(kAckMessage); +} + } // namespace deskflow::core::ipc diff --git a/src/lib/deskflow/ipc/DaemonIpcServer.h b/src/lib/deskflow/ipc/DaemonIpcServer.h index 68b54aae2..7a35452f6 100644 --- a/src/lib/deskflow/ipc/DaemonIpcServer.h +++ b/src/lib/deskflow/ipc/DaemonIpcServer.h @@ -19,17 +19,21 @@ class DaemonIpcServer : public QObject Q_OBJECT public: - DaemonIpcServer(QObject *parent); - ~DaemonIpcServer(); + explicit DaemonIpcServer(QObject *parent); + ~DaemonIpcServer() override; signals: void logLevelChanged(const QString &logLevel); void elevateModeChanged(bool elevate); void commandChanged(const QString &command); - void restartRequested(); + void startProcessRequested(); + void stopProcessRequested(); private: void processMessage(QLocalSocket *clientSocket, const QString &message); + void processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts); + void processElevate(QLocalSocket *&clientSocket, const QStringList &messageParts); + void processCommand(QLocalSocket *&clientSocket, const QStringList &messageParts); private slots: void handleNewConnection(); diff --git a/src/lib/gui/core/CoreProcess.cpp b/src/lib/gui/core/CoreProcess.cpp index 531d24477..3d49f20a6 100644 --- a/src/lib/gui/core/CoreProcess.cpp +++ b/src/lib/gui/core/CoreProcess.cpp @@ -291,8 +291,8 @@ void CoreProcess::startProcessFromDaemon(const QString &app, const QStringList & qInfo("running command: %s", qPrintable(commandQuoted)); - if (!m_daemonIpcClient->sendCommand(commandQuoted, m_appConfig.elevateMode())) { - qCritical("aborting process start, ipc connect failed"); + if (!m_daemonIpcClient->sendStartProcess(commandQuoted, m_appConfig.elevateMode())) { + qCritical("cannot start process, ipc command failed"); return; } @@ -325,12 +325,11 @@ void CoreProcess::stopProcessFromDaemon() qFatal("core process must be in stopping state"); } - if (!m_pDeps->ipcClient().isConnected()) { - qDebug("cannot stop process, ipc not connected"); + if (!m_daemonIpcClient->sendStopProcess()) { + qCritical("cannot stop process, ipc command failed"); return; } - m_pDeps->ipcClient().sendCommand("", m_appConfig.elevateMode()); setProcessState(ProcessState::Stopped); } diff --git a/src/lib/gui/ipc/DaemonIpcClient.cpp b/src/lib/gui/ipc/DaemonIpcClient.cpp index 2698f9bca..28f280181 100644 --- a/src/lib/gui/ipc/DaemonIpcClient.cpp +++ b/src/lib/gui/ipc/DaemonIpcClient.cpp @@ -17,7 +17,9 @@ namespace deskflow::gui::ipc { const auto kTimeout = 1000; -DaemonIpcClient::DaemonIpcClient(QObject *parent) : QObject(parent), m_socket{new QLocalSocket(this)} +DaemonIpcClient::DaemonIpcClient(QObject *parent) + : QObject(parent), // + m_socket{new QLocalSocket(this)} // NOSONAR { } @@ -81,15 +83,25 @@ bool DaemonIpcClient::sendLogLevel(const QString &logLevel) return true; } -bool DaemonIpcClient::sendCommand(const QString &command, ElevateMode elevateMode) +bool DaemonIpcClient::sendStartProcess(const QString &command, ElevateMode elevateMode) { if (!keepAlive()) return false; - sendMessage("elevate=" + (elevateMode == ElevateMode::kAlways ? QStringLiteral("yes") : QStringLiteral("no"))); - sendMessage("command=" + command); - sendMessage("restart"); - return true; + if (!sendMessage("elevate=" + (elevateMode == ElevateMode::kAlways ? QStringLiteral("yes") : QStringLiteral("no")))) { + return false; + } + + if (!sendMessage("command=" + command)) { + return false; + } + + return sendMessage("start"); +} + +bool DaemonIpcClient::sendStopProcess() +{ + return sendMessage("stop"); } bool DaemonIpcClient::sendMessage(const QString &message, const QString &expectAck, const bool expectConnected) diff --git a/src/lib/gui/ipc/DaemonIpcClient.h b/src/lib/gui/ipc/DaemonIpcClient.h index 91abe3404..f872effd3 100644 --- a/src/lib/gui/ipc/DaemonIpcClient.h +++ b/src/lib/gui/ipc/DaemonIpcClient.h @@ -22,7 +22,8 @@ public: explicit DaemonIpcClient(QObject *parent = nullptr); bool connectToServer(); bool sendLogLevel(const QString &logLevel); - bool sendCommand(const QString &command, ElevateMode elevateMode); + bool sendStartProcess(const QString &command, ElevateMode elevateMode); + bool sendStopProcess(); bool isConnected() const {