/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2002 Chris Schoeneman * * 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. */ #include "CConfig.h" #include "KeyTypes.h" #include "OptionTypes.h" #include "ProtocolTypes.h" #include "CLog.h" #include "CStringUtil.h" #include "CArch.h" #include "CArchMiscWindows.h" #include "XArch.h" #include "Version.h" #include "stdvector.h" #include "resource.h" // these must come after the above because it includes windows.h #include "LaunchUtil.h" #include "CAddScreen.h" #include "CAdvancedOptions.h" #include "CAutoStart.h" #include "CGlobalOptions.h" #include "CHotkeyOptions.h" #include "CInfo.h" #include "CScreensLinks.h" typedef std::vector CStringList; class CChildWaitInfo { public: HWND m_dialog; HANDLE m_child; DWORD m_childID; HANDLE m_ready; HANDLE m_stop; }; static const char* s_debugName[][2] = { { TEXT("Error"), "ERROR" }, { TEXT("Warning"), "WARNING" }, { TEXT("Note"), "NOTE" }, { TEXT("Info"), "INFO" }, { TEXT("Debug"), "DEBUG" }, { TEXT("Debug1"), "DEBUG1" }, { TEXT("Debug2"), "DEBUG2" } }; static const int s_defaultDebug = 1; // WARNING static const int s_minTestDebug = 3; // INFO HINSTANCE s_instance = NULL; static CGlobalOptions* s_globalOptions = NULL; static CAdvancedOptions* s_advancedOptions = NULL; static CHotkeyOptions* s_hotkeyOptions = NULL; static CScreensLinks* s_screensLinks = NULL; static CInfo* s_info = NULL; static bool s_userConfig = true; static time_t s_configTime = 0; static CConfig s_lastConfig; static const TCHAR* s_mainClass = TEXT("GoSynergy"); static const TCHAR* s_layoutClass = TEXT("SynergyLayout"); enum SaveMode { SAVE_QUITING, SAVE_NORMAL, SAVE_QUIET }; // // program arguments // #define ARG CArgs::s_instance class CArgs { public: CArgs() { s_instance = this; } ~CArgs() { s_instance = NULL; } public: static CArgs* s_instance; CConfig m_config; CStringList m_screens; }; CArgs* CArgs::s_instance = NULL; static BOOL CALLBACK addDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static bool isClientChecked(HWND hwnd) { HWND child = getItem(hwnd, IDC_MAIN_CLIENT_RADIO); return isItemChecked(child); } static void enableMainWindowControls(HWND hwnd) { bool client = isClientChecked(hwnd); enableItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_LABEL, client); enableItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT, client); enableItem(hwnd, IDC_MAIN_SERVER_SCREENS_LABEL, !client); enableItem(hwnd, IDC_MAIN_SCREENS, !client); enableItem(hwnd, IDC_MAIN_OPTIONS, !client); enableItem(hwnd, IDC_MAIN_HOTKEYS, !client); } static bool execApp(const char* app, const CString& cmdLine, PROCESS_INFORMATION* procInfo) { // prepare startup info STARTUPINFO startup; startup.cb = sizeof(startup); startup.lpReserved = NULL; startup.lpDesktop = NULL; startup.lpTitle = NULL; startup.dwX = (DWORD)CW_USEDEFAULT; startup.dwY = (DWORD)CW_USEDEFAULT; startup.dwXSize = (DWORD)CW_USEDEFAULT; startup.dwYSize = (DWORD)CW_USEDEFAULT; startup.dwXCountChars = 0; startup.dwYCountChars = 0; startup.dwFillAttribute = 0; startup.dwFlags = STARTF_FORCEONFEEDBACK; startup.wShowWindow = SW_SHOWDEFAULT; startup.cbReserved2 = 0; startup.lpReserved2 = NULL; startup.hStdInput = NULL; startup.hStdOutput = NULL; startup.hStdError = NULL; // prepare path to app CString appPath = getAppPath(app); // put path to app in command line CString commandLine = "\""; commandLine += appPath; commandLine += "\" "; commandLine += cmdLine; // start child if (CreateProcess(NULL, (char*)commandLine.c_str(), NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_PROCESS_GROUP | NORMAL_PRIORITY_CLASS, NULL, NULL, &startup, procInfo) == 0) { return false; } else { return true; } } static CString getCommandLine(HWND hwnd, bool testing, bool silent) { CString cmdLine; // add constant testing args if (testing) { cmdLine += " -z --no-restart --no-daemon"; } // can't start as service on NT else if (!CArchMiscWindows::isWindows95Family()) { cmdLine += " --no-daemon"; } // get the server name CString server; bool isClient = isClientChecked(hwnd); if (isClient) { // check server name HWND child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT); server = getWindowText(child); if (!ARG->m_config.isValidScreenName(server)) { if (!silent) { showError(hwnd, CStringUtil::format( getString(IDS_INVALID_SERVER_NAME).c_str(), server.c_str())); } SetFocus(child); return CString(); } // compare server name to local host. a common error // is to provide the client's name for the server. we // don't bother to check the addresses though that'd be // more accurate. if (CStringUtil::CaselessCmp::equal(ARCH->getHostName(), server)) { if (!silent) { showError(hwnd, CStringUtil::format( getString(IDS_SERVER_IS_CLIENT).c_str(), server.c_str())); } SetFocus(child); return CString(); } } // debug level. always include this. if (true) { HWND child = getItem(hwnd, IDC_MAIN_DEBUG); int debug = (int)SendMessage(child, CB_GETCURSEL, 0, 0); // if testing then we force the debug level to be no less than // s_minTestDebug. what's the point of testing if you can't // see the debugging info? if (testing && debug < s_minTestDebug) { debug = s_minTestDebug; } cmdLine += " --debug "; cmdLine += s_debugName[debug][1]; } // add advanced options cmdLine += s_advancedOptions->getCommandLine(isClient, server); return cmdLine; } static bool launchApp(HWND hwnd, bool testing, HANDLE* thread, DWORD* threadID) { if (thread != NULL) { *thread = NULL; } if (threadID != NULL) { *threadID = 0; } // start daemon if it's installed and we're not testing if (!testing && CAutoStart::startDaemon()) { return true; } // decide if client or server const bool isClient = isClientChecked(hwnd); const char* app = isClient ? CLIENT_APP : SERVER_APP; // prepare command line CString cmdLine = getCommandLine(hwnd, testing, false); if (cmdLine.empty()) { return false; } // start child PROCESS_INFORMATION procInfo; if (!execApp(app, cmdLine, &procInfo)) { showError(hwnd, CStringUtil::format( getString(IDS_STARTUP_FAILED).c_str(), getErrorString(GetLastError()).c_str())); return false; } // don't need process handle CloseHandle(procInfo.hProcess); // save thread handle and thread ID if desired if (thread != NULL) { *thread = procInfo.hThread; } if (threadID != NULL) { *threadID = procInfo.dwThreadId; } return true; } static BOOL CALLBACK waitDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { // only one wait dialog at a time! static CChildWaitInfo* info = NULL; switch (message) { case WM_INITDIALOG: // save info pointer info = reinterpret_cast(lParam); // save hwnd info->m_dialog = hwnd; // signal ready SetEvent(info->m_ready); return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDCANCEL: case IDOK: // signal stop SetEvent(info->m_stop); // done EndDialog(hwnd, 0); return TRUE; } } return FALSE; } static DWORD WINAPI waitForChildThread(LPVOID vinfo) { CChildWaitInfo* info = reinterpret_cast(vinfo); // wait for ready WaitForSingleObject(info->m_ready, INFINITE); // wait for thread to complete or stop event HANDLE handles[2]; handles[0] = info->m_child; handles[1] = info->m_stop; DWORD n = WaitForMultipleObjects(2, handles, FALSE, INFINITE); // if stop was raised then terminate child and wait for it if (n == WAIT_OBJECT_0 + 1) { PostThreadMessage(info->m_childID, WM_QUIT, 0, 0); WaitForSingleObject(info->m_child, INFINITE); } // otherwise post IDOK to dialog box else { PostMessage(info->m_dialog, WM_COMMAND, MAKEWPARAM(IDOK, 0), 0); } return 0; } static void waitForChild(HWND hwnd, HANDLE thread, DWORD threadID) { // prepare info for child wait dialog and thread CChildWaitInfo info; info.m_dialog = NULL; info.m_child = thread; info.m_childID = threadID; info.m_ready = CreateEvent(NULL, TRUE, FALSE, NULL); info.m_stop = CreateEvent(NULL, TRUE, FALSE, NULL); // create a thread to wait on the child thread and event DWORD id; HANDLE waiter = CreateThread(NULL, 0, &waitForChildThread, &info,0, &id); // do dialog that let's the user terminate the test DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_WAIT), hwnd, (DLGPROC)waitDlgProc, (LPARAM)&info); // force the waiter thread to finish and wait for it SetEvent(info.m_ready); SetEvent(info.m_stop); WaitForSingleObject(waiter, INFINITE); // clean up CloseHandle(waiter); CloseHandle(info.m_ready); CloseHandle(info.m_stop); } static void initMainWindow(HWND hwnd) { // append version number to title CString titleFormat = getString(IDS_TITLE); setWindowText(hwnd, CStringUtil::format(titleFormat.c_str(), kApplication, kVersion)); // load configuration bool configLoaded = loadConfig(ARG->m_config, s_configTime, s_userConfig); if (configLoaded) { s_lastConfig = ARG->m_config; } // get settings from registry bool isServer = configLoaded; int debugLevel = s_defaultDebug; CString server; HKEY key = CArchMiscWindows::openKey(HKEY_CURRENT_USER, getSettingsPath()); if (key != NULL) { if (isServer && CArchMiscWindows::hasValue(key, "isServer")) { isServer = (CArchMiscWindows::readValueInt(key, "isServer") != 0); } if (CArchMiscWindows::hasValue(key, "debug")) { debugLevel = static_cast( CArchMiscWindows::readValueInt(key, "debug")); if (debugLevel < 0) { debugLevel = 0; } else if (debugLevel > CLog::kDEBUG2) { debugLevel = CLog::kDEBUG2; } } server = CArchMiscWindows::readValueString(key, "server"); CArchMiscWindows::closeKey(key); } // choose client/server radio buttons HWND child; child = getItem(hwnd, IDC_MAIN_CLIENT_RADIO); setItemChecked(child, !isServer); child = getItem(hwnd, IDC_MAIN_SERVER_RADIO); setItemChecked(child, isServer); // set server name child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT); setWindowText(child, server); // debug level child = getItem(hwnd, IDC_MAIN_DEBUG); for (unsigned int i = 0; i < sizeof(s_debugName) / sizeof(s_debugName[0]); ++i) { SendMessage(child, CB_ADDSTRING, 0, (LPARAM)s_debugName[i][0]); } SendMessage(child, CB_SETCURSEL, debugLevel, 0); // update controls enableMainWindowControls(hwnd); } static bool saveMainWindow(HWND hwnd, SaveMode mode, CString* cmdLineOut = NULL) { DWORD errorID = 0; CString arg; CString cmdLine; // save dialog state bool isClient = isClientChecked(hwnd); HKEY key = CArchMiscWindows::addKey(HKEY_CURRENT_USER, getSettingsPath()); if (key != NULL) { HWND child; child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT); CArchMiscWindows::setValue(key, "server", getWindowText(child)); child = getItem(hwnd, IDC_MAIN_DEBUG); CArchMiscWindows::setValue(key, "debug", SendMessage(child, CB_GETCURSEL, 0, 0)); CArchMiscWindows::setValue(key, "isServer", isClient ? 0 : 1); CArchMiscWindows::closeKey(key); } // save user's configuration if (!s_userConfig || ARG->m_config != s_lastConfig) { time_t t; if (!saveConfig(ARG->m_config, false, t)) { errorID = IDS_SAVE_FAILED; arg = getErrorString(GetLastError()); goto failed; } if (s_userConfig) { s_configTime = t; s_lastConfig = ARG->m_config; } } // save autostart configuration if (CAutoStart::isDaemonInstalled()) { if (s_userConfig || ARG->m_config != s_lastConfig) { time_t t; if (!saveConfig(ARG->m_config, true, t)) { errorID = IDS_AUTOSTART_SAVE_FAILED; arg = getErrorString(GetLastError()); goto failed; } if (!s_userConfig) { s_configTime = t; s_lastConfig = ARG->m_config; } } } // get autostart command cmdLine = getCommandLine(hwnd, false, mode == SAVE_QUITING); if (cmdLineOut != NULL) { *cmdLineOut = cmdLine; } if (cmdLine.empty()) { return (mode == SAVE_QUITING); } // save autostart command if (CAutoStart::isDaemonInstalled()) { try { CAutoStart::reinstallDaemon(isClient, cmdLine); CAutoStart::uninstallDaemons(!isClient); } catch (XArchDaemon& e) { errorID = IDS_INSTALL_GENERIC_ERROR; arg = e.what(); goto failed; } } return true; failed: CString errorMessage = CStringUtil::format(getString(errorID).c_str(), arg.c_str()); if (mode == SAVE_QUITING) { errorMessage += "\n"; errorMessage += getString(IDS_UNSAVED_DATA_REALLY_QUIT); if (askVerify(hwnd, errorMessage)) { return true; } } else if (mode == SAVE_NORMAL) { showError(hwnd, errorMessage); } return false; } static LRESULT CALLBACK mainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_ACTIVATE: if (LOWORD(wParam) != WA_INACTIVE) { // activated // see if the configuration changed if (isConfigNewer(s_configTime, s_userConfig)) { CString message = getString(IDS_CONFIG_CHANGED); if (askVerify(hwnd, message)) { time_t configTime; bool userConfig; CConfig newConfig; if (loadConfig(newConfig, configTime, userConfig) && userConfig == s_userConfig) { ARG->m_config = newConfig; s_lastConfig = ARG->m_config; } else { message = getString(IDS_LOAD_FAILED); showError(hwnd, message); s_lastConfig = CConfig(); } } } } else { // deactivated; write configuration if (!isShowingDialog()) { saveMainWindow(hwnd, SAVE_QUIET); } } break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDCANCEL: // save data if (saveMainWindow(hwnd, SAVE_QUITING)) { // quit PostQuitMessage(0); } return 0; case IDOK: case IDC_MAIN_TEST: { // note if testing const bool testing = (LOWORD(wParam) == IDC_MAIN_TEST); // save data if (saveMainWindow(hwnd, SAVE_NORMAL)) { // launch child app DWORD threadID; HANDLE thread; if (!launchApp(hwnd, testing, &thread, &threadID)) { return 0; } // handle child program if (testing) { // wait for process to stop, allowing the user to kill it waitForChild(hwnd, thread, threadID); // clean up CloseHandle(thread); } else { // don't need thread handle if (thread != NULL) { CloseHandle(thread); } // notify of success askOkay(hwnd, getString(IDS_STARTED_TITLE), getString(IDS_STARTED)); // quit PostQuitMessage(0); } } return 0; } case IDC_MAIN_AUTOSTART: { CString cmdLine; if (saveMainWindow(hwnd, SAVE_NORMAL, &cmdLine)) { // run dialog CAutoStart autoStart(hwnd, !isClientChecked(hwnd), cmdLine); autoStart.doModal(); } return 0; } case IDC_MAIN_CLIENT_RADIO: case IDC_MAIN_SERVER_RADIO: enableMainWindowControls(hwnd); return 0; case IDC_MAIN_SCREENS: s_screensLinks->doModal(); break; case IDC_MAIN_OPTIONS: s_globalOptions->doModal(); break; case IDC_MAIN_ADVANCED: s_advancedOptions->doModal(isClientChecked(hwnd)); break; case IDC_MAIN_HOTKEYS: s_hotkeyOptions->doModal(); break; case IDC_MAIN_INFO: s_info->doModal(); break; } default: break; } return DefDlgProc(hwnd, message, wParam, lParam); } int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR cmdLine, int nCmdShow) { CArch arch(instance); CLOG; CArgs args; s_instance = instance; // if "/uninstall" is on the command line then just stop and // uninstall the service and quit. this is the only option // but we ignore any others. if (CString(cmdLine).find("/uninstall") != CString::npos) { CAutoStart::uninstallDaemons(false); CAutoStart::uninstallDaemons(true); return 0; } // register main window (dialog) class WNDCLASSEX classInfo; classInfo.cbSize = sizeof(classInfo); classInfo.style = CS_HREDRAW | CS_VREDRAW; classInfo.lpfnWndProc = &mainWndProc; classInfo.cbClsExtra = 0; classInfo.cbWndExtra = DLGWINDOWEXTRA; classInfo.hInstance = instance; classInfo.hIcon = (HICON)LoadImage(instance, MAKEINTRESOURCE(IDI_SYNERGY), IMAGE_ICON, 32, 32, LR_SHARED); classInfo.hCursor = LoadCursor(NULL, IDC_ARROW); classInfo.hbrBackground = reinterpret_cast(COLOR_3DFACE + 1); classInfo.lpszMenuName = NULL; classInfo.lpszClassName = s_mainClass; classInfo.hIconSm = (HICON)LoadImage(instance, MAKEINTRESOURCE(IDI_SYNERGY), IMAGE_ICON, 16, 16, LR_SHARED); RegisterClassEx(&classInfo); // create main window HWND mainWindow = CreateDialog(s_instance, MAKEINTRESOURCE(IDD_MAIN), 0, NULL); // prep windows initMainWindow(mainWindow); s_globalOptions = new CGlobalOptions(mainWindow, &ARG->m_config); s_advancedOptions = new CAdvancedOptions(mainWindow, &ARG->m_config); s_hotkeyOptions = new CHotkeyOptions(mainWindow, &ARG->m_config); s_screensLinks = new CScreensLinks(mainWindow, &ARG->m_config); s_info = new CInfo(mainWindow); // show window ShowWindow(mainWindow, nCmdShow); // main loop MSG msg; bool done = false; do { switch (GetMessage(&msg, NULL, 0, 0)) { case -1: // error break; case 0: // quit done = true; break; default: if (!IsDialogMessage(mainWindow, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } break; } } while (!done); return msg.wParam; }