Files
deskflow/src/lib/deskflow/ServerApp.cpp
2026-03-06 08:57:31 -05:00

612 lines
16 KiB
C++

/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 - 2026 Deskflow Developers
* SPDX-FileCopyrightText: (C) 2012 Symless Ltd.
* SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "deskflow/ServerApp.h"
#include "arch/Arch.h"
#include "base/IEventQueue.h"
#include "base/Log.h"
#include "common/ExitCodes.h"
#include "common/PlatformInfo.h"
#include "common/Settings.h"
#include "deskflow/App.h"
#include "deskflow/ProtocolTypes.h"
#include "deskflow/Screen.h"
#include "deskflow/ScreenException.h"
#include "net/SocketException.h"
#include "net/SocketMultiplexer.h"
#include "net/TCPSocketFactory.h"
#include "server/ClientListener.h"
#include "server/ClientProxy.h"
#include "server/Config.h"
#include "server/PrimaryClient.h"
#include "server/Server.h"
// must be before screen header includes
#include <QFileInfo>
#if defined(Q_OS_WIN)
#include "platform/MSWindowsScreen.h"
#endif
#if WINAPI_XWINDOWS
#include "platform/XWindowsScreen.h"
#endif
#if WINAPI_LIBEI
#include "platform/EiScreen.h"
#endif
#if defined(Q_OS_MAC)
#include "base/TMethodJob.h"
#include "mt/Thread.h"
#include "platform/OSXCocoaApp.h"
#include "platform/OSXScreen.h"
#endif
#include <fstream>
using namespace deskflow::server;
//
// ServerApp
//
ServerApp::ServerApp(IEventQueue *events, const QString &processName) : App(events, processName)
{
m_name = Settings::value(Settings::Core::ComputerName).toString().toStdString();
// do nothing
}
void ServerApp::parseArgs()
{
if (const auto address = Settings::value(Settings::Core::Interface).toString(); !address.isEmpty()) {
*m_deskflowAddress = NetworkAddress(address.toStdString(), Settings::value(Settings::Core::Port).toInt());
} else {
*m_deskflowAddress = NetworkAddress(Settings::value(Settings::Core::Port).toInt());
}
try {
m_deskflowAddress->resolve();
} catch (SocketAddressException &e) {
LOG_CRIT("%s: %s" BYE, qPrintable(processName()), e.what(), qPrintable(processName()));
bye(s_exitArgs);
}
}
void ServerApp::reloadSignalHandler(Arch::ThreadSignal, void *)
{
IEventQueue *events = App::instance().getEvents();
events->addEvent(Event(EventTypes::ServerAppReloadConfig, events->getSystemTarget()));
}
void ServerApp::reloadConfig()
{
LOG_DEBUG("reload configuration");
if (loadConfig(Settings::serverConfigFile())) {
if (m_server != nullptr) {
m_server->setConfig(*m_config);
}
LOG_NOTE("reloaded configuration");
}
}
void ServerApp::loadConfig()
{
const auto path = Settings::serverConfigFile();
if (path.isEmpty()) {
LOG_CRIT("no configuration path provided");
bye(s_exitConfig);
}
if (!loadConfig(path)) {
LOG_CRIT("%s: failed to load config: %s", qPrintable(processName()), path.toStdString().c_str());
bye(s_exitConfig);
}
}
bool ServerApp::loadConfig(const QString &filename)
{
const auto path = filename.toStdString();
try {
// load configuration
LOG_DEBUG("opening configuration \"%s\"", path.c_str());
#if defined(Q_OS_WIN)
std::ifstream configStream(filename.toStdWString());
#else
std::ifstream configStream(path);
#endif
if (!configStream.is_open()) {
LOG_ERR("cannot open configuration \"%s\"", path.c_str());
return false;
}
configStream >> *m_config;
LOG_DEBUG("configuration read successfully");
return true;
} catch (ServerConfigReadException &e) {
// report error in configuration file
LOG_ERR("cannot read configuration \"%s\": %s", path.c_str(), e.what());
}
return false;
}
void ServerApp::forceReconnect()
{
if (m_server != nullptr) {
m_server->disconnect();
}
}
void ServerApp::handleClientConnected(const Event &, ClientListener *listener)
{
ClientProxy *client = listener->getNextClient();
if (client != nullptr) {
m_server->adoptClient(client);
}
}
void ServerApp::closeServer(Server *server)
{
if (server == nullptr) {
return;
}
// tell all clients to disconnect
server->disconnect();
// wait for clients to disconnect for up to timeout seconds
double timeout = 3.0;
EventQueueTimer *timer = getEvents()->newOneShotTimer(timeout, nullptr);
getEvents()->addHandler(EventTypes::Timer, timer, [this](const auto &) {
getEvents()->addEvent(Event(EventTypes::Quit));
});
getEvents()->addHandler(EventTypes::ServerDisconnected, server, [this](const auto &) {
getEvents()->addEvent(Event(EventTypes::Quit));
});
getEvents()->loop();
getEvents()->removeHandler(EventTypes::Timer, timer);
getEvents()->deleteTimer(timer);
getEvents()->removeHandler(EventTypes::ServerDisconnected, server);
// done with server
delete server;
}
void ServerApp::stopRetryTimer()
{
if (m_timer != nullptr) {
getEvents()->removeHandler(EventTypes::Timer, m_timer);
getEvents()->deleteTimer(m_timer);
m_timer = nullptr;
}
}
void ServerApp::closeClientListener(ClientListener *listen)
{
if (listen != nullptr) {
getEvents()->removeHandler(EventTypes::ClientListenerAccepted, listen);
delete listen;
}
}
void ServerApp::stopServer()
{
using enum ServerState;
if (m_serverState == Started) {
closeServer(m_server);
closeClientListener(m_listener);
m_server = nullptr;
m_listener = nullptr;
m_serverState = Initialized;
} else if (m_serverState == Starting) {
stopRetryTimer();
m_serverState = Initialized;
}
assert(m_server == nullptr);
assert(m_listener == nullptr);
}
void ServerApp::closePrimaryClient(PrimaryClient *primaryClient)
{
delete primaryClient;
}
void ServerApp::closeServerScreen(deskflow::Screen *screen)
{
if (screen != nullptr) {
using enum EventTypes;
getEvents()->removeHandler(ScreenError, screen->getEventTarget());
getEvents()->removeHandler(ScreenSuspend, screen->getEventTarget());
getEvents()->removeHandler(ScreenResume, screen->getEventTarget());
delete screen;
}
}
void ServerApp::cleanupServer()
{
using enum ServerState;
stopServer();
if (m_serverState == Initialized) {
closePrimaryClient(m_primaryClient);
closeServerScreen(m_serverScreen);
m_primaryClient = nullptr;
m_serverScreen = nullptr;
m_serverState = Uninitialized;
} else if (m_serverState == Initializing || m_serverState == InitializingToStart) {
stopRetryTimer();
m_serverState = Uninitialized;
}
assert(m_primaryClient == nullptr);
assert(m_serverScreen == nullptr);
assert(m_serverState == Uninitialized);
}
void ServerApp::retryHandler()
{
// discard old timer
assert(m_timer != nullptr);
stopRetryTimer();
// try initializing/starting the server again
switch (m_serverState) {
using enum ServerState;
case Uninitialized:
case Initialized:
case Started:
assert(0 && "bad internal server state");
break;
case Initializing:
LOG_DEBUG1("retry server initialization");
m_serverState = Uninitialized;
if (!initServer()) {
getEvents()->addEvent(Event(EventTypes::Quit));
}
break;
case InitializingToStart:
LOG_DEBUG1("retry server initialization");
m_serverState = Uninitialized;
if (!initServer()) {
getEvents()->addEvent(Event(EventTypes::Quit));
} else if (m_serverState == Initialized) {
LOG_DEBUG1("starting server");
if (!startServer()) {
getEvents()->addEvent(Event(EventTypes::Quit));
}
}
break;
case Starting:
LOG_DEBUG1("retry starting server");
m_serverState = Initialized;
if (!startServer()) {
getEvents()->addEvent(Event(EventTypes::Quit));
}
break;
}
}
bool ServerApp::initServer()
{
using enum ServerState;
// skip if already initialized or initializing
if (m_serverState != Uninitialized) {
return true;
}
double retryTime;
deskflow::Screen *serverScreen = nullptr;
PrimaryClient *primaryClient = nullptr;
try {
std::string name = m_config->getCanonicalName(m_name);
serverScreen = openServerScreen();
primaryClient = openPrimaryClient(name, serverScreen);
m_serverScreen = serverScreen;
m_primaryClient = primaryClient;
m_serverState = Initialized;
return true;
} catch (ScreenUnavailableException &e) {
LOG_WARN("primary screen unavailable: %s", e.what());
closePrimaryClient(primaryClient);
closeServerScreen(serverScreen);
retryTime = e.getRetryTime();
} catch (ScreenOpenFailureException &e) {
LOG_CRIT("failed to start server: %s", e.what());
closePrimaryClient(primaryClient);
closeServerScreen(serverScreen);
return false;
} catch (BaseException &e) {
LOG_CRIT("failed to start server: %s", e.what());
closePrimaryClient(primaryClient);
closeServerScreen(serverScreen);
return false;
}
return false;
}
deskflow::Screen *ServerApp::openServerScreen()
{
deskflow::Screen *screen = createScreen();
getEvents()->addHandler(EventTypes::ScreenError, screen->getEventTarget(), [this](const auto &) {
handleScreenError();
});
getEvents()->addHandler(EventTypes::ScreenSuspend, screen->getEventTarget(), [this](const auto &) {
handleSuspend();
});
getEvents()->addHandler(EventTypes::ScreenResume, screen->getEventTarget(), [this](const auto &) { handleResume(); });
return screen;
}
bool ServerApp::startServer()
{
using enum ServerState;
// skip if already started or starting
if (m_serverState == Starting || m_serverState == Started) {
return true;
}
// initialize if necessary
if (m_serverState != Initialized) {
if (!initServer()) {
// hard initialization failure
return false;
}
if (m_serverState == Initializing) {
// not ready to start
m_serverState = InitializingToStart;
return true;
}
assert(m_serverState == Initialized);
}
ClientListener *listener = nullptr;
try {
listener = openClientListener(m_config->getDeskflowAddress());
m_server = openServer(*m_config, m_primaryClient);
listener->setServer(m_server);
m_server->setListener(listener);
m_listener = listener;
LOG_IPC("started server, waiting for clients");
m_serverState = Started;
return true;
} catch (SocketAddressInUseException &e) {
LOG_CRIT("cannot listen for clients: %s", e.what());
closeClientListener(listener);
} catch (BaseException &e) {
LOG_CRIT("failed to start server: %s", e.what());
closeClientListener(listener);
return false;
}
return false;
}
deskflow::Screen *ServerApp::createScreen()
{
#if defined(Q_OS_WIN)
return new deskflow::Screen(
new MSWindowsScreen(true, Settings::value(Settings::Core::UseHooks).toBool(), getEvents()), getEvents()
);
#elif defined(Q_OS_MAC)
return new deskflow::Screen(new OSXScreen(getEvents(), true), getEvents());
#else
if (deskflow::platform::isWayland()) {
#if WINAPI_LIBEI
LOG_INFO("using ei screen for wayland");
return new deskflow::Screen(new deskflow::EiScreen(true, getEvents(), true), getEvents());
#else
throw XNoEiSupport();
#endif
}
#if WINAPI_XWINDOWS
LOG_INFO("using legacy x windows screen");
return new deskflow::Screen(
new XWindowsScreen(qPrintable(Settings::value(Settings::Core::Display).toString()), true, getEvents()),
getEvents()
);
#endif
#endif // end os check
}
PrimaryClient *ServerApp::openPrimaryClient(const std::string &name, deskflow::Screen *screen)
{
LOG_DEBUG1("creating primary screen");
return new PrimaryClient(name, screen);
}
void ServerApp::handleSuspend()
{
if (!m_suspended) {
LOG_INFO("suspend");
stopServer();
m_suspended = true;
}
}
void ServerApp::handleResume()
{
if (m_suspended) {
LOG_INFO("resume");
startServer();
m_suspended = false;
}
}
ClientListener *ServerApp::openClientListener(const NetworkAddress &address)
{
using enum SecurityLevel;
auto securityLevel = PlainText;
if (Settings::value(Settings::Security::TlsEnabled).toBool()) {
if (Settings::value(Settings::Security::CheckPeers).toBool()) {
securityLevel = PeerAuth;
} else {
securityLevel = Encrypted;
}
}
auto *listen = new ClientListener(getAddress(address), getSocketFactory(), getEvents(), securityLevel);
getEvents()->addHandler(EventTypes::ClientListenerAccepted, listen, [this, listen](const auto &e) {
handleClientConnected(e, listen);
});
return listen;
}
Server *ServerApp::openServer(ServerConfig &config, PrimaryClient *primaryClient)
{
auto *server = new Server(config, primaryClient, m_serverScreen, getEvents());
try {
getEvents()->addHandler(EventTypes::ServerScreenSwitched, server, [this](const auto &) { handleScreenSwitched(); });
} catch (std::bad_alloc &ba) {
delete server;
throw ba;
}
return server;
}
void ServerApp::handleScreenSwitched() const
{
// do nothing
}
std::unique_ptr<ISocketFactory> ServerApp::getSocketFactory() const
{
return std::make_unique<TCPSocketFactory>(getEvents(), getSocketMultiplexer());
}
NetworkAddress ServerApp::getAddress(const NetworkAddress &address) const
{
return address;
}
int ServerApp::mainLoop()
{
// create socket multiplexer. this must happen after daemonization
// on unix because threads evaporate across a fork().
setSocketMultiplexer(std::make_unique<SocketMultiplexer>());
// if configuration has no screens then add this system
// as the default
if (m_config->begin() == m_config->end()) {
m_config->addScreen(m_name);
}
// set the contact address, if provided, in the config.
// otherwise, if the config doesn't have an address, use
// the default.
if (m_deskflowAddress->isValid()) {
m_config->setDeskflowAddress(*m_deskflowAddress);
} else if (!m_config->getDeskflowAddress().isValid()) {
m_config->setDeskflowAddress(NetworkAddress(kDefaultPort));
}
// canonicalize the primary screen name
if (std::string primaryName = m_config->getCanonicalName(m_name); primaryName.empty()) {
LOG_CRIT("unknown screen name `%s'", m_name.c_str());
return s_exitFailed;
}
// start server, etc
appUtil().startNode();
// handle hangup signal by reloading the server's configuration
ARCH->setSignalHandler(Arch::ThreadSignal::Hangup, &reloadSignalHandler, nullptr);
getEvents()->addHandler(EventTypes::ServerAppReloadConfig, getEvents()->getSystemTarget(), [this](const auto &) {
reloadConfig();
});
// handle force reconnect event by disconnecting clients. they'll
// reconnect automatically.
getEvents()->addHandler(EventTypes::ServerAppForceReconnect, getEvents()->getSystemTarget(), [this](const auto &) {
forceReconnect();
});
// to work around the sticky meta keys problem, we'll give users
// the option to reset the state of the server.
getEvents()->addHandler(EventTypes::ServerAppResetServer, getEvents()->getSystemTarget(), [this](const auto &) {
resetServer();
});
// run event loop. if startServer() failed we're supposed to retry
// later. the timer installed by startServer() will take care of
// that.
#if defined(Q_OS_MAC)
Thread thread(new TMethodJob<ServerApp>(this, &ServerApp::runEventsLoop, nullptr));
// wait until carbon loop is ready
OSXScreen *screen = dynamic_cast<OSXScreen *>(m_serverScreen->getPlatformScreen());
screen->waitForCarbonLoop();
runCocoaApp();
#else
getEvents()->loop();
#endif
// close down
LOG_DEBUG("stopping server");
getEvents()->removeHandler(EventTypes::ServerAppForceReconnect, getEvents()->getSystemTarget());
getEvents()->removeHandler(EventTypes::ServerAppReloadConfig, getEvents()->getSystemTarget());
cleanupServer();
LOG_NOTE("stopped server");
return s_exitSuccess;
}
void ServerApp::resetServer()
{
LOG_DEBUG1("resetting server");
stopServer();
cleanupServer();
startServer();
}
int ServerApp::runInner(StartupFunc startup)
{
// general initialization
m_deskflowAddress = new NetworkAddress;
m_config = std::make_shared<Config>(getEvents());
// run
int result = startup();
delete m_deskflowAddress;
return result;
}
int ServerApp::start()
{
initApp();
return mainLoop();
}
const char *ServerApp::daemonName() const
{
if (deskflow::platform::isWindows())
return "Deskflow Server";
return "deskflow-server";
}
void ServerApp::startNode()
{
// start the server. if this return false then we've failed and
// we shouldn't retry.
LOG_DEBUG1("starting server");
if (!startServer()) {
bye(s_exitFailed);
}
}