2012-06-10 16:50:54 +00:00
|
|
|
/*
|
|
|
|
* synergy -- mouse and keyboard sharing utility
|
|
|
|
* Copyright (C) 2012 Nick Bolton
|
|
|
|
*
|
|
|
|
* This package is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* found in the file COPYING that should have accompanied this file.
|
|
|
|
*
|
|
|
|
* This package 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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// TODO: split this class into windows and unix to get rid
|
|
|
|
// of all the #ifdefs!
|
|
|
|
|
|
|
|
#include "CDaemonApp.h"
|
|
|
|
#include "CEventQueue.h"
|
|
|
|
#include "LogOutputters.h"
|
|
|
|
#include "CLog.h"
|
|
|
|
#include "XArch.h"
|
|
|
|
#include "CApp.h"
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
#include <iostream>
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
#if SYSAPI_WIN32
|
|
|
|
|
|
|
|
#include "CArchMiscWindows.h"
|
|
|
|
#include "XArchWindows.h"
|
|
|
|
#include "CScreen.h"
|
|
|
|
#include "CMSWindowsScreen.h"
|
|
|
|
#include "CMSWindowsRelauncher.h"
|
|
|
|
#include "CMSWindowsDebugOutputter.h"
|
|
|
|
#include "TMethodJob.h"
|
2012-07-03 14:15:05 +00:00
|
|
|
#include "TMethodEventJob.h"
|
|
|
|
#include "CIpcClientProxy.h"
|
|
|
|
#include "CIpcMessage.h"
|
|
|
|
#include "CSocketMultiplexer.h"
|
|
|
|
#include "CIpcLogOutputter.h"
|
2012-06-10 16:50:54 +00:00
|
|
|
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
#include <Windows.h>
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
CDaemonApp* CDaemonApp::s_instance = NULL;
|
|
|
|
|
|
|
|
int
|
|
|
|
mainLoopStatic()
|
|
|
|
{
|
|
|
|
CDaemonApp::s_instance->mainLoop(true);
|
|
|
|
return kExitSuccess;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
unixMainLoopStatic(int, const char**)
|
|
|
|
{
|
|
|
|
return mainLoopStatic();
|
|
|
|
}
|
|
|
|
|
|
|
|
#if SYSAPI_WIN32
|
|
|
|
int
|
|
|
|
winMainLoopStatic(int, const char**)
|
|
|
|
{
|
|
|
|
return CArchMiscWindows::runDaemon(mainLoopStatic);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2012-07-03 14:15:05 +00:00
|
|
|
CDaemonApp::CDaemonApp() :
|
|
|
|
m_ipcServer(nullptr),
|
|
|
|
m_ipcLogOutputter(nullptr)
|
2012-06-10 16:50:54 +00:00
|
|
|
#if SYSAPI_WIN32
|
2012-07-05 18:05:35 +00:00
|
|
|
,m_relauncher(nullptr)
|
2012-06-10 16:50:54 +00:00
|
|
|
#endif
|
|
|
|
{
|
|
|
|
s_instance = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
CDaemonApp::~CDaemonApp()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
CDaemonApp::run(int argc, char** argv)
|
|
|
|
{
|
2012-07-05 21:26:41 +00:00
|
|
|
bool uninstall = false;
|
2012-06-10 16:50:54 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
#if SYSAPI_WIN32
|
|
|
|
// win32 instance needed for threading, etc.
|
|
|
|
CArchMiscWindows::setInstanceWin32(GetModuleHandle(NULL));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if SYSAPI_WIN32
|
|
|
|
// sends debug messages to visual studio console window.
|
|
|
|
CLOG->insert(new CMSWindowsDebugOutputter());
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// default log level to system setting.
|
|
|
|
string logLevel = ARCH->setting("LogLevel");
|
|
|
|
if (logLevel != "")
|
|
|
|
CLOG->setFilter(logLevel.c_str());
|
|
|
|
|
|
|
|
bool foreground = false;
|
|
|
|
|
|
|
|
for (int i = 1; i < argc; ++i) {
|
|
|
|
string arg(argv[i]);
|
|
|
|
|
|
|
|
if (arg == "/f" || arg == "-f") {
|
|
|
|
foreground = true;
|
|
|
|
}
|
|
|
|
#if SYSAPI_WIN32
|
|
|
|
else if (arg == "/install") {
|
2012-07-05 21:26:41 +00:00
|
|
|
uninstall = true;
|
2012-06-10 16:50:54 +00:00
|
|
|
ARCH->installDaemon();
|
|
|
|
return kExitSuccess;
|
|
|
|
}
|
|
|
|
else if (arg == "/uninstall") {
|
|
|
|
ARCH->uninstallDaemon();
|
|
|
|
return kExitSuccess;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
else {
|
|
|
|
stringstream ss;
|
|
|
|
ss << "Unrecognized argument: " << arg;
|
|
|
|
foregroundError(ss.str().c_str());
|
|
|
|
return kExitArgs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (foreground) {
|
|
|
|
// run process in foreground instead of daemonizing.
|
|
|
|
// useful for debugging.
|
|
|
|
mainLoop(false);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
#if SYSAPI_WIN32
|
|
|
|
ARCH->daemonize("Synergy", winMainLoopStatic);
|
|
|
|
#elif SYSAPI_UNIX
|
|
|
|
ARCH->daemonize("Synergy", unixMainLoopStatic);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
return kExitSuccess;
|
|
|
|
}
|
|
|
|
catch (XArch& e) {
|
2012-07-05 21:26:41 +00:00
|
|
|
CString message = e.what();
|
|
|
|
if (uninstall && (message.find("The service has not been started") != CString::npos)) {
|
|
|
|
// TODO: if we're keeping this use error code instead (what is it?!).
|
|
|
|
// HACK: this message happens intermittently, not sure where from but
|
|
|
|
// it's quite misleading for the user. they thing something has gone
|
|
|
|
// horribly wrong, but it's just the service manager reporting a false
|
|
|
|
// positive (the service has actually shut down in most cases).
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
foregroundError(message.c_str());
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
return kExitFailed;
|
|
|
|
}
|
|
|
|
catch (std::exception& e) {
|
|
|
|
foregroundError(e.what());
|
|
|
|
return kExitFailed;
|
|
|
|
}
|
|
|
|
catch (...) {
|
|
|
|
foregroundError("Unrecognized error.");
|
|
|
|
return kExitFailed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
CDaemonApp::mainLoop(bool logToFile)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
DAEMON_RUNNING(true);
|
|
|
|
|
|
|
|
if (logToFile)
|
|
|
|
CLOG->insert(new CFileLogOutputter(logPath().c_str()));
|
|
|
|
|
|
|
|
CEventQueue eventQueue;
|
|
|
|
|
2012-07-03 14:15:05 +00:00
|
|
|
// create socket multiplexer. this must happen after daemonization
|
|
|
|
// on unix because threads evaporate across a fork().
|
|
|
|
CSocketMultiplexer multiplexer;
|
|
|
|
|
|
|
|
// uses event queue, must be created here.
|
|
|
|
m_ipcServer = new CIpcServer();
|
|
|
|
|
2012-07-05 18:05:35 +00:00
|
|
|
// send logging to gui via ipc, log system adopts outputter.
|
|
|
|
m_ipcLogOutputter = new CIpcLogOutputter(*m_ipcServer);
|
|
|
|
CLOG->insert(m_ipcLogOutputter);
|
|
|
|
|
|
|
|
#if SYSAPI_WIN32
|
|
|
|
m_relauncher = new CMSWindowsRelauncher(false, *m_ipcServer, *m_ipcLogOutputter);
|
|
|
|
#endif
|
|
|
|
|
2012-07-03 14:15:05 +00:00
|
|
|
eventQueue.adoptHandler(
|
|
|
|
CIpcServer::getClientConnectedEvent(), m_ipcServer,
|
|
|
|
new TMethodEventJob<CDaemonApp>(this, &CDaemonApp::handleIpcConnected));
|
|
|
|
|
|
|
|
m_ipcServer->listen();
|
|
|
|
|
2012-06-10 16:50:54 +00:00
|
|
|
#if SYSAPI_WIN32
|
|
|
|
// HACK: create a dummy screen, which can handle system events
|
|
|
|
// (such as a stop request from the service controller).
|
|
|
|
CMSWindowsScreen::init(CArchMiscWindows::instanceWin32());
|
|
|
|
CGameDeviceInfo gameDevice;
|
|
|
|
CScreen dummyScreen(new CMSWindowsScreen(false, true, gameDevice));
|
|
|
|
|
|
|
|
string command = ARCH->setting("Command");
|
|
|
|
if (command != "") {
|
|
|
|
LOG((CLOG_INFO "using last known command: %s", command.c_str()));
|
2012-07-05 18:05:35 +00:00
|
|
|
m_relauncher->command(command);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
2012-07-05 18:05:35 +00:00
|
|
|
m_relauncher->startAsync();
|
2012-06-10 16:50:54 +00:00
|
|
|
#endif
|
|
|
|
|
2012-07-03 14:15:05 +00:00
|
|
|
eventQueue.loop();
|
2012-06-10 16:50:54 +00:00
|
|
|
|
|
|
|
#if SYSAPI_WIN32
|
2012-07-05 18:05:35 +00:00
|
|
|
m_relauncher->stop();
|
2012-06-10 16:50:54 +00:00
|
|
|
#endif
|
|
|
|
|
2012-07-03 14:15:05 +00:00
|
|
|
eventQueue.removeHandler(
|
|
|
|
CIpcServer::getClientConnectedEvent(), m_ipcServer);
|
|
|
|
|
2012-07-05 19:10:04 +00:00
|
|
|
CLOG->remove(m_ipcLogOutputter);
|
|
|
|
delete m_ipcLogOutputter;
|
2012-07-03 14:15:05 +00:00
|
|
|
delete m_ipcServer;
|
|
|
|
|
2012-06-10 16:50:54 +00:00
|
|
|
DAEMON_RUNNING(false);
|
|
|
|
}
|
|
|
|
catch (XArch& e) {
|
2012-07-03 14:15:05 +00:00
|
|
|
LOG((CLOG_ERR "xarch exception: %s", e.what().c_str()));
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
catch (std::exception& e) {
|
2012-07-03 14:15:05 +00:00
|
|
|
LOG((CLOG_ERR "std exception: %s", e.what()));
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
catch (...) {
|
2012-07-03 14:15:05 +00:00
|
|
|
LOG((CLOG_ERR "unrecognized error."));
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
CDaemonApp::foregroundError(const char* message)
|
|
|
|
{
|
|
|
|
#if SYSAPI_WIN32
|
|
|
|
MessageBox(NULL, message, "Synergy Service", MB_OK | MB_ICONERROR);
|
|
|
|
#elif SYSAPI_UNIX
|
|
|
|
cerr << message << endl;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string
|
|
|
|
CDaemonApp::logPath()
|
|
|
|
{
|
|
|
|
#ifdef SYSAPI_WIN32
|
|
|
|
// TODO: move to CArchMiscWindows
|
|
|
|
// on windows, log to the same dir as the binary.
|
|
|
|
char fileNameBuffer[MAX_PATH];
|
|
|
|
GetModuleFileName(NULL, fileNameBuffer, MAX_PATH);
|
|
|
|
string fileName(fileNameBuffer);
|
|
|
|
size_t lastSlash = fileName.find_last_of("\\");
|
|
|
|
string path(fileName.substr(0, lastSlash));
|
|
|
|
path.append("\\").append(LOG_FILENAME);
|
|
|
|
return path;
|
|
|
|
#elif SYSAPI_UNIX
|
|
|
|
return "/var/log/" LOG_FILENAME;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2012-07-03 14:15:05 +00:00
|
|
|
CDaemonApp::handleIpcConnected(const CEvent& e, void*)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2012-07-05 21:26:41 +00:00
|
|
|
LOG((CLOG_DEBUG "ipc client connected"));
|
2012-07-03 14:15:05 +00:00
|
|
|
EVENTQUEUE->adoptHandler(
|
|
|
|
CIpcClientProxy::getMessageReceivedEvent(), e.getData(),
|
|
|
|
new TMethodEventJob<CDaemonApp>(
|
|
|
|
this, &CDaemonApp::handleIpcMessage));
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2012-07-03 14:15:05 +00:00
|
|
|
void
|
|
|
|
CDaemonApp::handleIpcMessage(const CEvent& e, void*)
|
|
|
|
{
|
2012-07-05 18:05:35 +00:00
|
|
|
CIpcMessage& m = *static_cast<CIpcMessage*>(e.getData());
|
2012-07-03 14:15:05 +00:00
|
|
|
switch (m.m_type) {
|
|
|
|
case kIpcCommand: {
|
2012-07-05 18:05:35 +00:00
|
|
|
CString& command = *static_cast<CString*>(m.m_data);
|
2012-07-03 17:33:19 +00:00
|
|
|
LOG((CLOG_DEBUG "got new command: %s", command.c_str()));
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2012-07-03 14:15:05 +00:00
|
|
|
try {
|
|
|
|
// store command in system settings. this is used when the daemon
|
|
|
|
// next starts.
|
|
|
|
ARCH->setting("Command", command);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
2012-07-03 14:15:05 +00:00
|
|
|
catch (XArch& e) {
|
2012-07-03 17:33:19 +00:00
|
|
|
LOG((CLOG_ERR "failed to save setting, %s", e.what().c_str()));
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
2012-07-03 17:33:19 +00:00
|
|
|
|
2012-06-10 16:50:54 +00:00
|
|
|
// tell the relauncher about the new command. this causes the
|
|
|
|
// relauncher to stop the existing command and start the new
|
|
|
|
// command.
|
2012-07-05 18:05:35 +00:00
|
|
|
m_relauncher->command(command);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|