/* * 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 . */ // 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 #include #include #if SYSAPI_WIN32 #include "CArchMiscWindows.h" #include "XArchWindows.h" #include "CScreen.h" #include "CMSWindowsScreen.h" #include "CMSWindowsRelauncher.h" #include "CMSWindowsDebugOutputter.h" #include "TMethodJob.h" #define WIN32_LEAN_AND_MEAN #include #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 CDaemonApp::CDaemonApp() #if SYSAPI_WIN32 : m_relauncher(false) #endif { s_instance = this; } CDaemonApp::~CDaemonApp() { } int CDaemonApp::run(int argc, char** argv) { try { #if SYSAPI_WIN32 // win32 instance needed for threading, etc. CArchMiscWindows::setInstanceWin32(GetModuleHandle(NULL)); #endif // send logging to gui via ipc CLOG->insert(new CIpcLogOutputter()); #if SYSAPI_WIN32 // sends debug messages to visual studio console window. CLOG->insert(new CMSWindowsDebugOutputter()); CThread pipeThread(new TMethodJob( this, &CDaemonApp::pipeThread, nullptr)); #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") { 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) { foregroundError(e.what().c_str()); 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; #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())); m_relauncher.command(command); } m_relauncher.startAsync(); #endif CEvent event; EVENTQUEUE->getEvent(event); while (event.getType() != CEvent::kQuit) { EVENTQUEUE->dispatchEvent(event); CEvent::deleteData(event); EVENTQUEUE->getEvent(event); } #if SYSAPI_WIN32 m_relauncher.stop(); #endif DAEMON_RUNNING(false); } catch (XArch& e) { LOG((CLOG_ERR, e.what().c_str())); } catch (std::exception& e) { LOG((CLOG_ERR, e.what())); } catch (...) { LOG((CLOG_ERR, "Unrecognized error.")); } } 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 } #ifdef SYSAPI_WIN32 void CDaemonApp::pipeThread(void*) { // TODO: move this to an IPC server class. while (true) { // grant access to everyone. SECURITY_DESCRIPTOR sd; InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); SetSecurityDescriptorDacl(&sd, TRUE, static_cast(0), FALSE); SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = &sd; HANDLE pipe = CreateNamedPipe( _T("\\\\.\\pipe\\Synergy"), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, &sa); if (pipe == INVALID_HANDLE_VALUE) XArch("could not create named pipe."); LOG((CLOG_DEBUG "opened daemon pipe: %d", pipe)); BOOL connectResult = ConnectNamedPipe(pipe, NULL); char buffer[1024]; DWORD bytesRead; while (true) { if (!ReadFile(pipe, buffer, sizeof(buffer), &bytesRead, NULL)) { break; } buffer[bytesRead] = '\0'; LOG((CLOG_DEBUG "ipc daemon server read: %s", buffer)); try { handlePipeMessage(buffer); } catch (XArch& ex) { LOG((CLOG_ERR "handle message failed: %s", ex.what().c_str())); } } DisconnectNamedPipe(pipe); CloseHandle(pipe); } } void CDaemonApp::handlePipeMessage(char* buffer) { switch (buffer[0]) { case kIpcCommand: { string command(++buffer); // store command in system settings. this is used when the daemon // next starts. ARCH->setting("Command", command); // tell the relauncher about the new command. this causes the // relauncher to stop the existing command and start the new // command. m_relauncher.command(command); } break; default: LOG((CLOG_WARN "unrecognized ipc message: %d", buffer[0])); break; } } #endif