diff --git a/cmd/launcher/CAdvancedOptions.cpp b/cmd/launcher/CAdvancedOptions.cpp new file mode 100644 index 00000000..a6742ea1 --- /dev/null +++ b/cmd/launcher/CAdvancedOptions.cpp @@ -0,0 +1,183 @@ +/* + * 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 "ProtocolTypes.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CAdvancedOptions.h" +#include "LaunchUtil.h" +#include "resource.h" + +// +// CAdvancedOptions +// + +CAdvancedOptions* CAdvancedOptions::s_singleton = NULL; + +CAdvancedOptions::CAdvancedOptions(HWND parent, CConfig* config) : + m_parent(parent), + m_config(config), + m_isClient(false), + m_screenName(ARCH->getHostName()), + m_port(kDefaultPort) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CAdvancedOptions::~CAdvancedOptions() +{ + s_singleton = NULL; +} + +void +CAdvancedOptions::doModal(bool isClient) +{ + // save state + m_isClient = isClient; + + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_ADVANCED_OPTIONS), + m_parent, dlgProc, (LPARAM)this); +} + +CString +CAdvancedOptions::getScreenName() const +{ + return m_screenName; +} + +int +CAdvancedOptions::getPort() const +{ + return m_port; +} + +CString +CAdvancedOptions::getCommandLine(bool isClient, const CString& serverName) const +{ + CString cmdLine; + + // screen name + if (!m_screenName.empty()) { + cmdLine += " --name "; + cmdLine += m_screenName; + } + + // port + char portString[20]; + sprintf(portString, "%d", m_port); + if (isClient) { + cmdLine += " "; + cmdLine += serverName; + cmdLine += ":"; + cmdLine += portString; + } + else { + cmdLine += " --address :"; + cmdLine += portString; + } + + return cmdLine; +} + +void +CAdvancedOptions::init(HWND hwnd) +{ + HWND child; + char buffer[20]; + sprintf(buffer, "%d", m_port); + child = getItem(hwnd, IDC_ADVANCED_PORT_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)buffer); + + child = getItem(hwnd, IDC_ADVANCED_NAME_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)m_screenName.c_str()); +} + +bool +CAdvancedOptions::save(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_ADVANCED_NAME_EDIT); + CString name = getWindowText(child); + if (!m_config->isValidScreenName(name)) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_SCREEN_NAME).c_str(), + name.c_str())); + SetFocus(child); + return false; + } + if (!m_isClient && !m_config->isScreen(name)) { + showError(hwnd, CStringUtil::format( + getString(IDS_UNKNOWN_SCREEN_NAME).c_str(), + name.c_str())); + SetFocus(child); + return false; + } + + // get and verify port + child = getItem(hwnd, IDC_ADVANCED_PORT_EDIT); + CString portString = getWindowText(child); + int port = atoi(portString.c_str()); + if (port < 1 || port > 65535) { + CString defaultPortString = CStringUtil::print("%d", kDefaultPort); + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_PORT).c_str(), + portString.c_str(), + defaultPortString.c_str())); + SetFocus(child); + return false; + } + + // save state + m_screenName = name; + m_port = port; + + return true; +} + +BOOL +CAdvancedOptions::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + init(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (save(hwnd)) { + EndDialog(hwnd, 0); + } + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CAdvancedOptions::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} diff --git a/cmd/launcher/CAdvancedOptions.h b/cmd/launcher/CAdvancedOptions.h new file mode 100644 index 00000000..f073f821 --- /dev/null +++ b/cmd/launcher/CAdvancedOptions.h @@ -0,0 +1,74 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef CADVANCEDOPTIONS_H +#define CADVANCEDOPTIONS_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +class CConfig; + +//! Advanced options dialog for Microsoft Windows launcher +class CAdvancedOptions { +public: + CAdvancedOptions(HWND parent, CConfig*); + ~CAdvancedOptions(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(bool isClient); + + //@} + //! @name accessors + //@{ + + //! Get the screen name + CString getScreenName() const; + + //! Get the port + int getPort() const; + + //! Convert options to command line string + CString getCommandLine(bool isClient, + const CString& serverName) const; + + //@} + +private: + void init(HWND hwnd); + bool save(HWND hwnd); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CAdvancedOptions* s_singleton; + + HWND m_parent; + CConfig* m_config; + bool m_isClient; + CString m_screenName; + int m_port; +}; + +#endif diff --git a/cmd/launcher/CGlobalOptions.cpp b/cmd/launcher/CGlobalOptions.cpp new file mode 100644 index 00000000..445b07a4 --- /dev/null +++ b/cmd/launcher/CGlobalOptions.cpp @@ -0,0 +1,211 @@ +/* + * 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 "ProtocolTypes.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CGlobalOptions.h" +#include "LaunchUtil.h" +#include "resource.h" + +static const int s_defaultDelay = 250; + +// +// CGlobalOptions +// + +CGlobalOptions* CGlobalOptions::s_singleton = NULL; + +CGlobalOptions::CGlobalOptions(HWND parent, CConfig* config) : + m_parent(parent), + m_config(config), + m_delayTime(s_defaultDelay), + m_twoTapTime(s_defaultDelay) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CGlobalOptions::~CGlobalOptions() +{ + s_singleton = NULL; +} + +void +CGlobalOptions::doModal() +{ + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_GLOBAL_OPTIONS), + m_parent, dlgProc, (LPARAM)this); +} + +void +CGlobalOptions::init(HWND hwnd) +{ + HWND child; + char buffer[30]; + + // reset options + sprintf(buffer, "%d", m_delayTime); + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + setItemChecked(child, false); + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + setWindowText(child, buffer); + sprintf(buffer, "%d", m_twoTapTime); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + setItemChecked(child, false); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + setWindowText(child, buffer); + + // get the global options + const CConfig::CScreenOptions* options = m_config->getOptions(""); + if (options != NULL) { + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + const OptionID id = index->first; + const OptionValue value = index->second; + if (id == kOptionScreenSwitchDelay) { + if (value > 0) { + sprintf(buffer, "%d", value); + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + setItemChecked(child, true); + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + setWindowText(child, buffer); + } + } + else if (id == kOptionScreenSwitchTwoTap) { + if (value > 0) { + sprintf(buffer, "%d", value); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + setItemChecked(child, true); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + setWindowText(child, buffer); + } + } + } + } +} + +bool +CGlobalOptions::save(HWND hwnd) +{ + HWND child; + int newDelayTime = 0; + int newTwoTapTime = 0; + + // get requested options + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + if (isItemChecked(child)) { + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + newDelayTime = getTime(hwnd, child, true); + if (newDelayTime == 0) { + return false; + } + } + else { + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + newDelayTime = getTime(hwnd, child, false); + if (newDelayTime == 0) { + newDelayTime = s_defaultDelay; + } + } + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + if (isItemChecked(child)) { + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + newTwoTapTime = getTime(hwnd, child, true); + if (newTwoTapTime == 0) { + return false; + } + } + else { + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + newTwoTapTime = getTime(hwnd, child, false); + if (newTwoTapTime == 0) { + newTwoTapTime = s_defaultDelay; + } + } + + // remove existing config options + m_config->removeOption("", kOptionScreenSwitchDelay); + m_config->removeOption("", kOptionScreenSwitchTwoTap); + + // add requested options + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + if (isItemChecked(child)) { + m_config->addOption("", kOptionScreenSwitchDelay, newDelayTime); + } + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + if (isItemChecked(child)) { + m_config->addOption("", kOptionScreenSwitchTwoTap, newTwoTapTime); + } + + // save last values + m_delayTime = newDelayTime; + m_twoTapTime = newTwoTapTime; + + return true; +} + +int +CGlobalOptions::getTime(HWND hwnd, HWND child, bool reportError) +{ + CString valueString = getWindowText(child); + int value = atoi(valueString.c_str()); + if (value < 1) { + if (reportError) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_TIME).c_str(), + valueString.c_str())); + SetFocus(child); + } + return 0; + } + return value; +} + +BOOL +CGlobalOptions::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + init(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (save(hwnd)) { + EndDialog(hwnd, 0); + } + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CGlobalOptions::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} diff --git a/cmd/launcher/CGlobalOptions.h b/cmd/launcher/CGlobalOptions.h new file mode 100644 index 00000000..520df187 --- /dev/null +++ b/cmd/launcher/CGlobalOptions.h @@ -0,0 +1,66 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef CGLOBALOPTIONS_H +#define CGLOBALOPTIONS_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +class CConfig; + +//! Global options dialog for Microsoft Windows launcher +class CGlobalOptions { +public: + CGlobalOptions(HWND parent, CConfig*); + ~CGlobalOptions(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(); + + //@} + //! @name accessors + //@{ + + + //@} + +private: + void init(HWND hwnd); + bool save(HWND hwnd); + + int getTime(HWND hwnd, HWND child, bool reportError); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CGlobalOptions* s_singleton; + + HWND m_parent; + CConfig* m_config; + int m_delayTime; + int m_twoTapTime; +}; + +#endif diff --git a/cmd/launcher/LaunchUtil.cpp b/cmd/launcher/LaunchUtil.cpp index ccd35c07..6a41f0c5 100644 --- a/cmd/launcher/LaunchUtil.cpp +++ b/cmd/launcher/LaunchUtil.cpp @@ -103,6 +103,18 @@ enableItem(HWND hwnd, int id, bool enabled) EnableWindow(GetDlgItem(hwnd, id), enabled); } +void +setItemChecked(HWND hwnd, bool checked) +{ + SendMessage(hwnd, BM_SETCHECK, checked ? BST_CHECKED : BST_UNCHECKED, 0); +} + +bool +isItemChecked(HWND hwnd) +{ + return (SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED); +} + CString getAppPath(const CString& appName) { diff --git a/cmd/launcher/LaunchUtil.h b/cmd/launcher/LaunchUtil.h index 414246e6..a7638a10 100644 --- a/cmd/launcher/LaunchUtil.h +++ b/cmd/launcher/LaunchUtil.h @@ -42,6 +42,9 @@ CString getWindowText(HWND hwnd); HWND getItem(HWND hwnd, int id); void enableItem(HWND hwnd, int id, bool enabled); +void setItemChecked(HWND, bool); +bool isItemChecked(HWND); + CString getAppPath(const CString& appName); bool loadConfig(CConfig& config); diff --git a/cmd/launcher/Makefile.am b/cmd/launcher/Makefile.am index acf31abd..782d771a 100644 --- a/cmd/launcher/Makefile.am +++ b/cmd/launcher/Makefile.am @@ -16,8 +16,12 @@ DEPTH = ../.. VDEPTH = ./$(VPATH)/$(DEPTH) EXTRA_DIST = \ + CAdvancedOptions.cpp \ + CAdvancedOptions.h \ CAutoStart.cpp \ CAutoStart.h \ + CGlobalOptions.cpp \ + CGlobalOptions.h \ LaunchUtil.cpp \ LaunchUtil.h \ launcher.cpp \ diff --git a/cmd/launcher/launcher.cpp b/cmd/launcher/launcher.cpp index 4b28794a..fe13e452 100644 --- a/cmd/launcher/launcher.cpp +++ b/cmd/launcher/launcher.cpp @@ -13,6 +13,8 @@ */ #include "CConfig.h" +#include "KeyTypes.h" +#include "OptionTypes.h" #include "ProtocolTypes.h" #include "CLog.h" #include "CStringUtil.h" @@ -24,6 +26,8 @@ // these must come after the above because it includes windows.h #include "LaunchUtil.h" #include "CAutoStart.h" +#include "CGlobalOptions.h" +#include "CAdvancedOptions.h" #define CONFIG_NAME "synergy.sgc" #define CLIENT_APP "synergyc.exe" @@ -47,10 +51,28 @@ public: HANDLE m_stop; }; -HINSTANCE s_instance = NULL; +struct CModifierInfo { +public: + int m_ctrlID; + const char* m_name; + KeyModifierID m_modifierID; + OptionID m_optionID; +}; -static const TCHAR* s_mainClass = TEXT("GoSynergy"); -static const TCHAR* s_layoutClass = TEXT("SynergyLayout"); +static const CModifierInfo s_modifiers[] = { + { IDC_ADD_MOD_SHIFT, "Shift", + kKeyModifierIDShift, kOptionModifierMapForShift }, + { IDC_ADD_MOD_CTRL, "Ctrl", + kKeyModifierIDControl, kOptionModifierMapForControl }, + { IDC_ADD_MOD_ALT, "Alt", + kKeyModifierIDAlt, kOptionModifierMapForAlt }, + { IDC_ADD_MOD_META, "Meta", + kKeyModifierIDMeta, kOptionModifierMapForMeta }, + { IDC_ADD_MOD_SUPER, "Super", + kKeyModifierIDSuper, kOptionModifierMapForSuper } +}; + +static const KeyModifierID baseModifier = kKeyModifierIDShift; static const char* s_debugName[][2] = { { TEXT("Error"), "ERROR" }, @@ -63,6 +85,14 @@ static const char* s_debugName[][2] = { }; static const int s_defaultDebug = 3; // INFO +HINSTANCE s_instance = NULL; + +static CGlobalOptions* s_globalOptions = NULL; +static CAdvancedOptions* s_advancedOptions = NULL; + +static const TCHAR* s_mainClass = TEXT("GoSynergy"); +static const TCHAR* s_layoutClass = TEXT("SynergyLayout"); + // // program arguments // @@ -127,7 +157,7 @@ bool isClientChecked(HWND hwnd) { HWND child = getItem(hwnd, IDC_MAIN_CLIENT_RADIO); - return (SendMessage(child, BM_GETCHECK, 0, 0) == BST_CHECKED); + return isItemChecked(child); } static @@ -476,58 +506,20 @@ static CString getCommandLine(HWND hwnd, bool testing) { - // decide if client or server - const bool isClient = isClientChecked(hwnd); - - // get and verify screen name - HWND child = getItem(hwnd, IDC_MAIN_ADVANCED_NAME_EDIT); - CString name = getWindowText(child); - if (!ARG->m_config.isValidScreenName(name)) { - showError(hwnd, CStringUtil::format( - getString(IDS_INVALID_SCREEN_NAME).c_str(), - name.c_str())); - SetFocus(child); - return CString(); - } - if (!isClient && !ARG->m_config.isScreen(name)) { - showError(hwnd, CStringUtil::format( - getString(IDS_UNKNOWN_SCREEN_NAME).c_str(), - name.c_str())); - SetFocus(child); - return CString(); - } - - // get and verify port - child = getItem(hwnd, IDC_MAIN_ADVANCED_PORT_EDIT); - CString portString = getWindowText(child); - UInt32 port = (UInt32)atoi(portString.c_str()); - if (port < 1 || port > 65535) { - CString defaultPortString = CStringUtil::print("%d", kDefaultPort); - showError(hwnd, CStringUtil::format( - getString(IDS_INVALID_PORT).c_str(), - portString.c_str(), - defaultPortString.c_str())); - SetFocus(child); - return CString(); - } - - // prepare command line CString cmdLine; - if (testing) { - // constant testing args - cmdLine += " -z --no-restart --no-daemon"; - // debug level testing arg - child = getItem(hwnd, IDC_MAIN_DEBUG); - cmdLine += " --debug "; - cmdLine += s_debugName[SendMessage(child, CB_GETCURSEL, 0, 0)][1]; + // add constant testing args + if (testing) { + cmdLine += " -z --no-restart --no-daemon"; } - cmdLine += " --name "; - cmdLine += name; + + // get the server name + CString server; + bool isClient = isClientChecked(hwnd); if (isClient) { // check server name - child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT); - CString server = getWindowText(child); + HWND child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT); + server = getWindowText(child); if (!ARG->m_config.isValidScreenName(server)) { showError(hwnd, CStringUtil::format( getString(IDS_INVALID_SERVER_NAME).c_str(), @@ -551,16 +543,19 @@ getCommandLine(HWND hwnd, bool testing) if (testing) { cmdLine += " --no-camp"; } - cmdLine += " "; - cmdLine += server; - cmdLine += ":"; - cmdLine += portString; } - else { - cmdLine += " --address :"; - cmdLine += portString; + + // debug level + if (testing) { + HWND child = getItem(hwnd, IDC_MAIN_DEBUG); + DWORD debug = SendMessage(child, CB_GETCURSEL, 0, 0); + cmdLine += " --debug "; + cmdLine += s_debugName[debug][1]; } + // add advanced options + cmdLine += s_advancedOptions->getCommandLine(isClient, server); + return cmdLine; } @@ -711,11 +706,9 @@ initMainWindow(HWND hwnd) // choose client/server radio buttons HWND child; child = getItem(hwnd, IDC_MAIN_CLIENT_RADIO); - SendMessage(child, BM_SETCHECK, !configLoaded ? - BST_CHECKED : BST_UNCHECKED, 0); + setItemChecked(child, !configLoaded); child = getItem(hwnd, IDC_MAIN_SERVER_RADIO); - SendMessage(child, BM_SETCHECK, configLoaded ? - BST_CHECKED : BST_UNCHECKED, 0); + setItemChecked(child, configLoaded); // if config is loaded then initialize server controls if (configLoaded) { @@ -729,16 +722,7 @@ initMainWindow(HWND hwnd) } } - // initialize other controls - char buffer[256]; - sprintf(buffer, "%d", kDefaultPort); - child = getItem(hwnd, IDC_MAIN_ADVANCED_PORT_EDIT); - SendMessage(child, WM_SETTEXT, 0, (LPARAM)buffer); - - CString hostname = ARCH->getHostName(); - child = getItem(hwnd, IDC_MAIN_ADVANCED_NAME_EDIT); - SendMessage(child, WM_SETTEXT, 0, (LPARAM)hostname.c_str()); - + // debug level child = getItem(hwnd, IDC_MAIN_DEBUG); for (unsigned int i = 0; i < sizeof(s_debugName) / sizeof(s_debugName[0]); ++i) { @@ -794,19 +778,32 @@ addDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) CConfig::CScreenOptions::const_iterator index; child = getItem(hwnd, IDC_ADD_HD_CAPS_CHECK); index = info->m_options.find(kOptionHalfDuplexCapsLock); - if (index != info->m_options.end() && index->second != 0) { - SendMessage(child, BM_SETCHECK, BST_CHECKED, 0); - } - else { - SendMessage(child, BM_SETCHECK, BST_UNCHECKED, 0); - } + setItemChecked(child, (index != info->m_options.end() && + index->second != 0)); child = getItem(hwnd, IDC_ADD_HD_NUM_CHECK); index = info->m_options.find(kOptionHalfDuplexNumLock); - if (index != info->m_options.end() && index->second != 0) { - SendMessage(child, BM_SETCHECK, BST_CHECKED, 0); - } - else { - SendMessage(child, BM_SETCHECK, BST_UNCHECKED, 0); + setItemChecked(child, (index != info->m_options.end() && + index->second != 0)); + + // modifier options + for (UInt32 i = 0; i < sizeof(s_modifiers) / + sizeof(s_modifiers[0]); ++i) { + child = getItem(hwnd, s_modifiers[i].m_ctrlID); + + // fill in options + for (UInt32 j = 0; j < sizeof(s_modifiers) / + sizeof(s_modifiers[0]); ++j) { + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)s_modifiers[j].m_name); + } + + // choose current value + index = info->m_options.find(s_modifiers[i].m_optionID); + KeyModifierID id = s_modifiers[i].m_modifierID; + if (index != info->m_options.end()) { + id = index->second; + } + SendMessage(child, CB_SETCURSEL, id - baseModifier, 0); } return TRUE; @@ -883,20 +880,36 @@ addDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) // save options child = getItem(hwnd, IDC_ADD_HD_CAPS_CHECK); - if (SendMessage(child, BM_GETCHECK, 0, 0) == BST_CHECKED) { + if (isItemChecked(child)) { info->m_options[kOptionHalfDuplexCapsLock] = 1; } else { info->m_options.erase(kOptionHalfDuplexCapsLock); } child = getItem(hwnd, IDC_ADD_HD_NUM_CHECK); - if (SendMessage(child, BM_GETCHECK, 0, 0) == BST_CHECKED) { + if (isItemChecked(child)) { info->m_options[kOptionHalfDuplexNumLock] = 1; } else { info->m_options.erase(kOptionHalfDuplexNumLock); } + // save modifier options + child = getItem(hwnd, IDC_ADD_HD_CAPS_CHECK); + for (UInt32 i = 0; i < sizeof(s_modifiers) / + sizeof(s_modifiers[0]); ++i) { + child = getItem(hwnd, s_modifiers[i].m_ctrlID); + KeyModifierID id = static_cast( + SendMessage(child, CB_GETCURSEL, 0, 0) + + baseModifier); + if (id != s_modifiers[i].m_modifierID) { + info->m_options[s_modifiers[i].m_optionID] = id; + } + else { + info->m_options.erase(s_modifiers[i].m_optionID); + } + } + // success EndDialog(hwnd, 1); info = NULL; @@ -1065,6 +1078,16 @@ mainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) return 0; } break; + + case IDC_MAIN_OPTIONS: + s_globalOptions->doModal(); + enableSaveControls(hwnd); + break; + + case IDC_MAIN_ADVANCED: + s_advancedOptions->doModal(isClientChecked(hwnd)); + enableSaveControls(hwnd); + break; } default: @@ -1076,7 +1099,7 @@ mainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int nCmdShow) { - CArch arch; + CArch arch(instance); CLOG; CArgs args; @@ -1108,8 +1131,10 @@ WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int nCmdShow) HWND m_mainWindow = CreateDialog(s_instance, MAKEINTRESOURCE(IDD_MAIN), 0, NULL); - // prep window + // prep windows initMainWindow(m_mainWindow); + s_globalOptions = new CGlobalOptions(m_mainWindow, &ARG->m_config); + s_advancedOptions = new CAdvancedOptions(m_mainWindow, &ARG->m_config); // show window ShowWindow(m_mainWindow, nCmdShow); diff --git a/cmd/launcher/launcher.dsp b/cmd/launcher/launcher.dsp index 51096677..730a701c 100644 --- a/cmd/launcher/launcher.dsp +++ b/cmd/launcher/launcher.dsp @@ -95,10 +95,18 @@ LINK32=link.exe # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" # Begin Source File +SOURCE=.\CAdvancedOptions.cpp +# End Source File +# Begin Source File + SOURCE=.\CAutoStart.cpp # End Source File # Begin Source File +SOURCE=.\CGlobalOptions.cpp +# End Source File +# Begin Source File + SOURCE=.\launcher.cpp # End Source File # Begin Source File @@ -115,10 +123,18 @@ SOURCE=.\LaunchUtil.cpp # PROP Default_Filter "h;hpp;hxx;hm;inl" # Begin Source File +SOURCE=.\CAdvancedOptions.h +# End Source File +# Begin Source File + SOURCE=.\CAutoStart.h # End Source File # Begin Source File +SOURCE=.\CGlobalOptions.h +# End Source File +# Begin Source File + SOURCE=.\LaunchUtil.h # End Source File # Begin Source File diff --git a/cmd/launcher/resource.h b/cmd/launcher/resource.h index 9b7acf49..6e1c1aba 100644 --- a/cmd/launcher/resource.h +++ b/cmd/launcher/resource.h @@ -40,11 +40,14 @@ #define IDS_SERVER_IS_CLIENT 36 #define IDS_ADD_SCREEN 37 #define IDS_EDIT_SCREEN 38 +#define IDS_INVALID_TIME 39 #define IDD_MAIN 101 #define IDD_ADD 102 #define IDD_WAIT 103 #define IDI_SYNERGY 104 #define IDD_AUTOSTART 105 +#define IDD_ADVANCED_OPTIONS 106 +#define IDD_GLOBAL_OPTIONS 107 #define IDC_MAIN_CLIENT_RADIO 1000 #define IDC_MAIN_SERVER_RADIO 1001 #define IDC_MAIN_CLIENT_SERVER_NAME_EDIT 1002 @@ -76,18 +79,31 @@ #define IDC_AUTOSTART_INSTALL_USER 1033 #define IDC_AUTOSTART_INSTALL_SYSTEM 1034 #define IDC_MAIN_AUTOSTART 1035 -#define IDC_MAIN_DEBUG 1036 +#define IDC_MAIN_OPTIONS 1036 #define IDC_ADD_HD_CAPS_CHECK 1037 +#define IDC_MAIN_ADVANCED 1037 #define IDC_ADD_HD_NUM_CHECK 1038 +#define IDC_ADVANCED_NAME_EDIT 1038 +#define IDC_ADVANCED_PORT_EDIT 1039 +#define IDC_MAIN_DEBUG 1040 +#define IDC_GLOBAL_DELAY_CHECK 1041 +#define IDC_GLOBAL_DELAY_TIME 1042 +#define IDC_GLOBAL_TWO_TAP_CHECK 1043 +#define IDC_ADD_MOD_SHIFT 1043 +#define IDC_GLOBAL_TWO_TAP_TIME 1044 +#define IDC_ADD_MOD_CTRL 1044 +#define IDC_ADD_MOD_ALT 1045 +#define IDC_ADD_MOD_META 1046 +#define IDC_ADD_MOD_SUPER 1047 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 -#define _APS_NEXT_RESOURCE_VALUE 106 +#define _APS_NEXT_RESOURCE_VALUE 108 #define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1038 +#define _APS_NEXT_CONTROL_VALUE 1044 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/cmd/synergyc/CClientTaskBarReceiver.cpp b/cmd/synergyc/CClientTaskBarReceiver.cpp new file mode 100644 index 00000000..9c60c5bf --- /dev/null +++ b/cmd/synergyc/CClientTaskBarReceiver.cpp @@ -0,0 +1,163 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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 "CClientTaskBarReceiver.h" +#include "CClient.h" +#include "CLock.h" +#include "TMethodJob.h" +#include "CArch.h" + +// +// CClientTaskBarReceiver +// + +CClientTaskBarReceiver::CClientTaskBarReceiver() : + m_quit(NULL), + m_state(kNotRunning), + m_client(NULL) +{ + // create a job for getting notification when the client's + // status changes. + m_job = new TMethodJob(this, + &CClientTaskBarReceiver::statusChanged, NULL); +} + +CClientTaskBarReceiver::~CClientTaskBarReceiver() +{ + if (m_client != NULL) { + m_client->removeStatusJob(m_job); + } + delete m_job; + delete m_quit; +} + +void +CClientTaskBarReceiver::setClient(CClient* client) +{ + { + CLock lock(&m_mutex); + if (m_client != client) { + if (m_client != NULL) { + m_client->removeStatusJob(m_job); + } + m_client = client; + if (m_client != NULL) { + m_client->addStatusJob(m_job); + } + } + } + ARCH->updateReceiver(this); +} + +void +CClientTaskBarReceiver::setState(EState state) +{ + { + CLock lock(&m_mutex); + m_state = state; + } + ARCH->updateReceiver(this); +} + +void +CClientTaskBarReceiver::setQuitJob(IJob* job) +{ + CLock lock(&m_mutex); + delete m_quit; + m_quit = job; +} + +CClientTaskBarReceiver::EState +CClientTaskBarReceiver::getState() const +{ + return m_state; +} + +CClient* +CClientTaskBarReceiver::getClient() const +{ + return m_client; +} + +void +CClientTaskBarReceiver::lock() const +{ + m_mutex.lock(); +} + +void +CClientTaskBarReceiver::unlock() const +{ + m_mutex.unlock(); +} + +std::string +CClientTaskBarReceiver::getToolTip() const +{ + switch (m_state) { + case kNotRunning: + return "Synergy: Not running"; + + case kNotWorking: + return CString("Synergy: ") + m_errorMessage; + + case kNotConnected: + return "Synergy: Waiting for clients"; + + case kConnected: + return "Synergy: Connected"; + + default: + return ""; + } +} + +void +CClientTaskBarReceiver::quit() +{ + if (m_quit != NULL) { + m_quit->run(); + } +} + +void +CClientTaskBarReceiver::onStatusChanged() +{ + // do nothing +} + +void +CClientTaskBarReceiver::statusChanged(void*) +{ + // update our status + switch (m_client->getStatus(&m_errorMessage)) { + case CClient::kNotRunning: + setState(kNotRunning); + break; + + case CClient::kRunning: + setState(kConnected); + break; + + case CClient::kError: + setState(kNotWorking); + break; + + default: + break; + } + + // let subclasses have a go + onStatusChanged(); +} diff --git a/cmd/synergyc/CClientTaskBarReceiver.h b/cmd/synergyc/CClientTaskBarReceiver.h new file mode 100644 index 00000000..aa52b9d6 --- /dev/null +++ b/cmd/synergyc/CClientTaskBarReceiver.h @@ -0,0 +1,110 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef CCLIENTTASKBARRECEIVER_H +#define CCLIENTTASKBARRECEIVER_H + +#include "CMutex.h" +#include "CString.h" +#include "IArchTaskBarReceiver.h" + +class CClient; +class IJob; + +//! Implementation of IArchTaskBarReceiver for the synergy server +class CClientTaskBarReceiver : public IArchTaskBarReceiver { +public: + enum EState { + kNotRunning, + kNotWorking, + kNotConnected, + kConnected, + kMaxState + }; + + CClientTaskBarReceiver(); + virtual ~CClientTaskBarReceiver(); + + //! @name manipulators + //@{ + + //! Set server + /*! + Sets the server. The receiver will query state from this server. + */ + void setClient(CClient*); + + //! Set state + /*! + Sets the current server state. + */ + void setState(EState); + + //! Set the quit job that causes the server to quit + /*! + Set the job that causes the server to quit. + */ + void setQuitJob(IJob* adopted); + + //@} + //! @name accessors + //@{ + + //! Get state + /*! + Returns the current server state. The receiver is not locked + by this call; the caller must do the locking. + */ + EState getState() const; + + //! Get server + /*! + Returns the server set by \c setClient(). + */ + CClient* getClient() const; + + //@} + + // IArchTaskBarReceiver overrides + virtual void showStatus() = 0; + virtual void runMenu(int x, int y) = 0; + virtual void primaryAction() = 0; + virtual void lock() const; + virtual void unlock() const; + virtual const Icon getIcon() const = 0; + virtual std::string getToolTip() const; + +protected: + void quit(); + + //! Status change notification + /*! + Called when status changes. The default implementation does + nothing. + */ + virtual void onStatusChanged(); + +private: + void statusChanged(void*); + +private: + CMutex m_mutex; + IJob* m_quit; + EState m_state; + CClient* m_client; + IJob* m_job; + CString m_errorMessage; +}; + +#endif diff --git a/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp new file mode 100644 index 00000000..f348a2a8 --- /dev/null +++ b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp @@ -0,0 +1,280 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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 "CMSWindowsClientTaskBarReceiver.h" +#include "CClient.h" +#include "BasicTypes.h" +#include "CArch.h" +#include "CArchTaskBarWindows.h" +#include "resource.h" + +static const UINT g_stateToIconID[CMSWindowsClientTaskBarReceiver::kMaxState] = +{ + IDI_TASKBAR_NOT_RUNNING, + IDI_TASKBAR_NOT_WORKING, + IDI_TASKBAR_NOT_CONNECTED, + IDI_TASKBAR_CONNECTED +}; + +// +// CMSWindowsClientTaskBarReceiver +// + +CMSWindowsClientTaskBarReceiver::CMSWindowsClientTaskBarReceiver( + HINSTANCE appInstance) : + CClientTaskBarReceiver(), + m_appInstance(appInstance), + m_window(NULL) +{ + for (UInt32 i = 0; i < kMaxState; ++i) { + m_icon[i] = loadIcon(g_stateToIconID[i]); + } + m_menu = LoadMenu(m_appInstance, MAKEINTRESOURCE(IDR_TASKBAR)); + + // don't create the window yet. we'll create it on demand. this + // has the side benefit of being created in the thread used for + // the task bar. that's good because it means the existence of + // the window won't prevent changing the main thread's desktop. + + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CMSWindowsClientTaskBarReceiver::~CMSWindowsClientTaskBarReceiver() +{ + ARCH->removeReceiver(this); + for (UInt32 i = 0; i < kMaxState; ++i) { + deleteIcon(m_icon[i]); + } + DestroyMenu(m_menu); + destroyWindow(); +} + +void +CMSWindowsClientTaskBarReceiver::showStatus() +{ + // create the window + createWindow(); + + // lock self while getting status + lock(); + + // get the current status + std::string status = getToolTip(); + + // done getting status + unlock(); + + // update dialog + HWND child = GetDlgItem(m_window, IDC_TASKBAR_STATUS_STATUS); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)status.c_str()); + + if (!IsWindowVisible(m_window)) { + // position it by the mouse + POINT cursorPos; + GetCursorPos(&cursorPos); + RECT windowRect; + GetWindowRect(m_window, &windowRect); + int x = cursorPos.x; + int y = cursorPos.y; + int fw = GetSystemMetrics(SM_CXDLGFRAME); + int fh = GetSystemMetrics(SM_CYDLGFRAME); + int ww = windowRect.right - windowRect.left; + int wh = windowRect.bottom - windowRect.top; + int sw = GetSystemMetrics(SM_CXFULLSCREEN); + int sh = GetSystemMetrics(SM_CYFULLSCREEN); + if (fw < 1) { + fw = 1; + } + if (fh < 1) { + fh = 1; + } + if (x + ww - fw > sw) { + x -= ww - fw; + } + else { + x -= fw; + } + if (x < 0) { + x = 0; + } + if (y + wh - fh > sh) { + y -= wh - fh; + } + else { + y -= fh; + } + if (y < 0) { + y = 0; + } + SetWindowPos(m_window, HWND_TOPMOST, x, y, ww, wh, + SWP_SHOWWINDOW); + } +} + +void +CMSWindowsClientTaskBarReceiver::runMenu(int x, int y) +{ + // do popup menu. we need a window to pass to TrackPopupMenu(). + // the SetForegroundWindow() and SendMessage() calls around + // TrackPopupMenu() are to get the menu to be dismissed when + // another window gets activated and are just one of those + // win32 weirdnesses. + createWindow(); + SetForegroundWindow(m_window); + HMENU menu = GetSubMenu(m_menu, 0); + SetMenuDefaultItem(menu, IDC_TASKBAR_STATUS, FALSE); + int n = TrackPopupMenu(menu, + TPM_NONOTIFY | + TPM_RETURNCMD | + TPM_LEFTBUTTON | + TPM_RIGHTBUTTON, + x, y, 0, m_window, NULL); + SendMessage(m_window, WM_NULL, 0, 0); + + // perform the requested operation + switch (n) { + case IDC_TASKBAR_STATUS: + showStatus(); + break; + + case IDC_TASKBAR_QUIT: + quit(); + break; + } +} + +void +CMSWindowsClientTaskBarReceiver::primaryAction() +{ + showStatus(); +} + +const IArchTaskBarReceiver::Icon +CMSWindowsClientTaskBarReceiver::getIcon() const +{ + return reinterpret_cast(m_icon[getState()]); +} + +void +CMSWindowsClientTaskBarReceiver::onStatusChanged() +{ + if (IsWindowVisible(m_window)) { + showStatus(); + } +} + +HICON +CMSWindowsClientTaskBarReceiver::loadIcon(UINT id) +{ + HANDLE icon = LoadImage(m_appInstance, + MAKEINTRESOURCE(id), + IMAGE_ICON, + 0, 0, + LR_DEFAULTCOLOR); + return reinterpret_cast(icon); +} + +void +CMSWindowsClientTaskBarReceiver::deleteIcon(HICON icon) +{ + if (icon != NULL) { + DestroyIcon(icon); + } +} + +void +CMSWindowsClientTaskBarReceiver::createWindow() +{ + // ignore if already created + if (m_window != NULL) { + return; + } + + // get the status dialog + m_window = CreateDialogParam(m_appInstance, + MAKEINTRESOURCE(IDD_TASKBAR_STATUS), + NULL, + &CMSWindowsClientTaskBarReceiver::staticDlgProc, + reinterpret_cast( + reinterpret_cast(this))); + + // window should appear on top of everything, including (especially) + // the task bar. + DWORD style = GetWindowLong(m_window, GWL_EXSTYLE); + style |= WS_EX_TOOLWINDOW | WS_EX_TOPMOST; + SetWindowLong(m_window, GWL_EXSTYLE, style); + + // tell the task bar about this dialog + CArchTaskBarWindows::addDialog(m_window); +} + +void +CMSWindowsClientTaskBarReceiver::destroyWindow() +{ + if (m_window != NULL) { + CArchTaskBarWindows::removeDialog(m_window); + DestroyWindow(m_window); + m_window = NULL; + } +} + +BOOL +CMSWindowsClientTaskBarReceiver::dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM) +{ + switch (msg) { + case WM_INITDIALOG: + // use default focus + return TRUE; + + case WM_ACTIVATE: + // hide when another window is activated + if (LOWORD(wParam) == WA_INACTIVE) { + ShowWindow(hwnd, SW_HIDE); + } + break; + } + return FALSE; +} + +BOOL CALLBACK +CMSWindowsClientTaskBarReceiver::staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + // if msg is WM_INITDIALOG, extract the CMSWindowsClientTaskBarReceiver* + // and put it in the extra window data then forward the call. + CMSWindowsClientTaskBarReceiver* self = NULL; + if (msg == WM_INITDIALOG) { + self = reinterpret_cast( + reinterpret_cast(lParam)); + SetWindowLong(hwnd, GWL_USERDATA, lParam); + } + else { + // get the extra window data and forward the call + LONG data = GetWindowLong(hwnd, GWL_USERDATA); + if (data != 0) { + self = reinterpret_cast( + reinterpret_cast(data)); + } + } + + // forward the message + if (self != NULL) { + return self->dlgProc(hwnd, msg, wParam, lParam); + } + else { + return (msg == WM_INITDIALOG) ? TRUE : FALSE; + } +} diff --git a/cmd/synergyc/CMSWindowsClientTaskBarReceiver.h b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.h new file mode 100644 index 00000000..0fdb2cc5 --- /dev/null +++ b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.h @@ -0,0 +1,58 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef CMSWINDOWSCLIENTTASKBARRECEIVER_H +#define CMSWINDOWSCLIENTTASKBARRECEIVER_H + +#define WIN32_LEAN_AND_MEAN + +#include "CClientTaskBarReceiver.h" +#include + +//! Implementation of CClientTaskBarReceiver for Microsoft Windows +class CMSWindowsClientTaskBarReceiver : public CClientTaskBarReceiver { +public: + CMSWindowsClientTaskBarReceiver(HINSTANCE); + virtual ~CMSWindowsClientTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; + +protected: + // CClientTaskBarReceiver overrides + virtual void onStatusChanged(); + +private: + HICON loadIcon(UINT); + void deleteIcon(HICON); + void createWindow(); + void destroyWindow(); + + BOOL dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + static BOOL CALLBACK + staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + +private: + HINSTANCE m_appInstance; + HWND m_window; + HMENU m_menu; + HICON m_icon[kMaxState]; +}; + +#endif diff --git a/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp b/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp new file mode 100644 index 00000000..f60585ea --- /dev/null +++ b/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp @@ -0,0 +1,61 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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 "CXWindowsClientTaskBarReceiver.h" +#include "CArch.h" + +// +// CXWindowsClientTaskBarReceiver +// + +CXWindowsClientTaskBarReceiver::CXWindowsClientTaskBarReceiver() +{ + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CXWindowsClientTaskBarReceiver::~CXWindowsClientTaskBarReceiver() +{ + ARCH->removeReceiver(this); +} + +void +CXWindowsClientTaskBarReceiver::showStatus() +{ + // do nothing +} + +void +CXWindowsClientTaskBarReceiver::runMenu(int, int) +{ + // do nothing +} + +void +CXWindowsClientTaskBarReceiver::primaryAction() +{ + // do nothing +} + +const IArchTaskBarReceiver::Icon +CXWindowsClientTaskBarReceiver::getIcon() const +{ + return NULL; +} + +void +CXWindowsClientTaskBarReceiver::onStatusChanged() +{ + // do nothing +} diff --git a/cmd/synergyc/CXWindowsClientTaskBarReceiver.h b/cmd/synergyc/CXWindowsClientTaskBarReceiver.h new file mode 100644 index 00000000..e6028ce2 --- /dev/null +++ b/cmd/synergyc/CXWindowsClientTaskBarReceiver.h @@ -0,0 +1,37 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef CXWINDOWSCLIENTTASKBARRECEIVER_H +#define CXWINDOWSCLIENTTASKBARRECEIVER_H + +#include "CClientTaskBarReceiver.h" + +//! Implementation of CClientTaskBarReceiver for X Windows +class CXWindowsClientTaskBarReceiver : public CClientTaskBarReceiver { +public: + CXWindowsClientTaskBarReceiver(); + virtual ~CXWindowsClientTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; + +protected: + // CClientTaskBarReceiver overrides + virtual void onStatusChanged(); +}; + +#endif diff --git a/cmd/synergyc/Makefile.am b/cmd/synergyc/Makefile.am index 9af3e12c..6b2510ce 100644 --- a/cmd/synergyc/Makefile.am +++ b/cmd/synergyc/Makefile.am @@ -15,19 +15,29 @@ NULL = DEPTH = ../.. VDEPTH = ./$(VPATH)/$(DEPTH) -EXTRA_DIST = \ - resource.h \ - synergyc.dsp \ - synergyc.ico \ - synergyc.rc \ +EXTRA_DIST = \ + CMSWindowsClientTaskBarReceiver.cpp \ + CMSWindowsClientTaskBarReceiver.h \ + resource.h \ + synergyc.dsp \ + synergyc.ico \ + synergyc.rc \ + tb_error.ico \ + tb_idle.ico \ + tb_run.ico \ + tb_wait.ico \ $(NULL) -MAINTAINERCLEANFILES = \ - Makefile.in \ +MAINTAINERCLEANFILES = \ + Makefile.in \ $(NULL) bin_PROGRAMS = synergyc synergyc_SOURCES = \ + CClientTaskBarReceiver.cpp \ + CClientTaskBarReceiver.h \ + CXWindowsClientTaskBarReceiver.cpp \ + CXWindowsClientTaskBarReceiver.h \ synergyc.cpp \ $(NULL) synergyc_LDADD = \ diff --git a/cmd/synergyc/resource.h b/cmd/synergyc/resource.h index c5887fb2..d80c2389 100644 --- a/cmd/synergyc/resource.h +++ b/cmd/synergyc/resource.h @@ -4,6 +4,15 @@ // #define IDS_FAILED 1 #define IDI_SYNERGY 101 +#define IDI_TASKBAR_NOT_RUNNING 102 +#define IDI_TASKBAR_NOT_WORKING 103 +#define IDI_TASKBAR_NOT_CONNECTED 104 +#define IDI_TASKBAR_CONNECTED 105 +#define IDR_TASKBAR 107 +#define IDD_TASKBAR_STATUS 108 +#define IDC_TASKBAR_STATUS_STATUS 1000 +#define IDC_TASKBAR_QUIT 40003 +#define IDC_TASKBAR_STATUS 40004 // Next default values for new objects // diff --git a/cmd/synergyc/synergyc.cpp b/cmd/synergyc/synergyc.cpp index a16d3407..bc0125c4 100644 --- a/cmd/synergyc/synergyc.cpp +++ b/cmd/synergyc/synergyc.cpp @@ -25,6 +25,7 @@ #include "CMutex.h" #include "CThread.h" #include "XThread.h" +#include "CFunctionJob.h" #include "CLog.h" #include "LogOutputters.h" #include "CString.h" @@ -34,12 +35,15 @@ #define DAEMON_RUNNING(running_) #if WINDOWS_LIKE +#include "CMSWindowsScreen.h" #include "CMSWindowsSecondaryScreen.h" +#include "CMSWindowsClientTaskBarReceiver.h" #include "resource.h" #undef DAEMON_RUNNING #define DAEMON_RUNNING(running_) CArchMiscWindows::daemonRunning(running_) #elif UNIX_LIKE #include "CXWindowsSecondaryScreen.h" +#include "CXWindowsClientTaskBarReceiver.h" #endif // platform dependent name of a daemon @@ -112,11 +116,46 @@ CSecondaryScreenFactory::create(IScreenReceiver* receiver) } +//! CQuitJob +/*! +A job that cancels a given thread. +*/ +class CQuitJob : public IJob { +public: + CQuitJob(const CThread& thread); + ~CQuitJob(); + + // IJob overrides + virtual void run(); + +private: + CThread m_thread; +}; + +CQuitJob::CQuitJob(const CThread& thread) : + m_thread(thread) +{ + // do nothing +} + +CQuitJob::~CQuitJob() +{ + // do nothing +} + +void +CQuitJob::run() +{ + m_thread.cancel(); +} + + // // platform independent main // -static CClient* s_client = NULL; +static CClient* s_client = NULL; +static CClientTaskBarReceiver* s_taskBarReceiver = NULL; static int @@ -137,6 +176,7 @@ realMain(void) // open client try { + s_taskBarReceiver->setClient(s_client); s_client->open(); opened = true; @@ -160,11 +200,10 @@ realMain(void) DAEMON_RUNNING(false); \ locked = true; \ } \ - if (s_client != NULL) { \ - if (opened) { \ - s_client->close(); \ - } \ + if (opened) { \ + s_client->close(); \ } \ + s_taskBarReceiver->setClient(NULL); \ delete s_client; \ s_client = NULL; \ } while (false) @@ -205,6 +244,43 @@ realMain(void) return result; } +static +void +realMainEntry(void*) +{ + CThread::exit(reinterpret_cast(realMain())); +} + +static +int +runMainInThread(void) +{ + CThread appThread(new CFunctionJob(&realMainEntry)); + try { +#if WINDOWS_LIKE + MSG msg; + while (appThread.waitForEvent(-1.0) == CThread::kEvent) { + // check for a quit event + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + CThread::getCurrentThread().cancel(); + } + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +#else + appThread.wait(-1.0); +#endif + return reinterpret_cast(appThread.getResult()); + } + catch (XThread&) { + appThread.cancel(); + appThread.wait(-1.0); + throw; + } +} + // // command line parsing @@ -273,7 +349,7 @@ help() static bool -isArg(int argi, int argc, const char** argv, +isArg(int argi, int argc, const char* const* argv, const char* name1, const char* name2, int minRequiredParameters = 0) { @@ -294,7 +370,7 @@ isArg(int argi, int argc, const char** argv, static void -parse(int argc, const char** argv) +parse(int argc, const char* const* argv) { assert(ARG->m_pname != NULL); assert(argv != NULL); @@ -424,16 +500,6 @@ parse(int argc, const char** argv) } } -static -void -useSystemLog() -{ - // redirect log messages - ILogOutputter* logger = new CSystemLogOutputter; - logger->open(DAEMON_NAME); - CLOG->insert(new CStopLogOutputter); - CLOG->insert(logger); -} // // platform dependent entry points @@ -441,8 +507,6 @@ useSystemLog() #if WINDOWS_LIKE -#include "CMSWindowsScreen.h" - static bool s_hasImportantLogMessages = false; // @@ -494,7 +558,10 @@ static int daemonStartup(int argc, const char** argv) { - useSystemLog(); + CSystemLogger sysLogger(DAEMON_NAME); + + // have to cancel this thread to quit + s_taskBarReceiver->setQuitJob(new CQuitJob(CThread::getCurrentThread())); // catch errors that would normally exit bye = &byeThrow; @@ -513,14 +580,62 @@ static int daemonStartup95(int, const char**) { - useSystemLog(); - return realMain(); + CSystemLogger sysLogger(DAEMON_NAME); + return runMainInThread(); +} + +static +int +run(int argc, char** argv) +{ + // windows NT family starts services using no command line options. + // since i'm not sure how to tell the difference between that and + // a user providing no options we'll assume that if there are no + // arguments and we're on NT then we're being invoked as a service. + // users on NT can use `--daemon' or `--no-daemon' to force us out + // of the service code path. + if (argc <= 1 && !CArchMiscWindows::isWindows95Family()) { + try { + return ARCH->daemonize(DAEMON_NAME, &daemonStartup); + } + catch (XArchDaemon& e) { + LOG((CLOG_CRIT "failed to start as a service: %s" BYE, e.what().c_str(), ARG->m_pname)); + } + return kExitFailed; + } + + // parse command line + parse(argc, argv); + + // daemonize if requested + if (ARG->m_daemon) { + // start as a daemon + if (CArchMiscWindows::isWindows95Family()) { + try { + return ARCH->daemonize(DAEMON_NAME, &daemonStartup95); + } + catch (XArchDaemon& e) { + LOG((CLOG_CRIT "failed to start as a service: %s" BYE, e.what().c_str(), ARG->m_pname)); + } + return kExitFailed; + } + else { + // cannot start a service from the command line so just + // run normally (except with log messages redirected). + CSystemLogger sysLogger(DAEMON_NAME); + return runMainInThread(); + } + } + else { + // run + return runMainInThread(); + } } int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) { - CArch arch; + CArch arch(instance); CLOG; CArgs args; @@ -533,52 +648,27 @@ WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) // send PRINT and FATAL output to a message box CLOG->insert(new CMessageBoxOutputter); - // windows NT family starts services using no command line options. - // since i'm not sure how to tell the difference between that and - // a user providing no options we'll assume that if there are no - // arguments and we're on NT then we're being invoked as a service. - // users on NT can use `--daemon' or `--no-daemon' to force us out - // of the service code path. - if (__argc <= 1 && !CArchMiscWindows::isWindows95Family()) { - int result = kExitFailed; - try { - result = ARCH->daemonize(DAEMON_NAME, &daemonStartup); - } - catch (XArchDaemon& e) { - LOG((CLOG_CRIT "failed to start as a service: %s" BYE, e.what().c_str(), ARG->m_pname)); - } - delete CLOG; - return result; - } + // make the task bar receiver. the user can control this app + // through the task bar. + s_taskBarReceiver = new CMSWindowsClientTaskBarReceiver(instance); + s_taskBarReceiver->setQuitJob(new CQuitJob(CThread::getCurrentThread())); - // parse command line - parse(__argc, const_cast(__argv)); - - // daemonize if requested int result; - if (ARG->m_daemon) { - // start as a daemon - if (CArchMiscWindows::isWindows95Family()) { - try { - result = ARCH->daemonize(DAEMON_NAME, &daemonStartup95); - } - catch (XArchDaemon& e) { - LOG((CLOG_CRIT "failed to start as a service: %s" BYE, e.what().c_str(), ARG->m_pname)); - result = kExitFailed; - } - } - else { - // cannot start a service from the command line so just - // run normally (except with log messages redirected). - useSystemLog(); - result = realMain(); - } + try { + // run in foreground or as a daemon + result = run(__argc, __argv); } - else { - // run - result = realMain(); + catch (...) { + // note that we don't rethrow thread cancellation. we'll + // be exiting soon so it doesn't matter. what we'd like + // is for everything after this try/catch to be in a + // finally block. + result = kExitFailed; } + // done with task bar receiver + delete s_taskBarReceiver; + // let user examine any messages if we're running as a backend // by putting up a dialog box before exiting. if (ARG->m_backend && s_hasImportantLogMessages) { @@ -598,7 +688,7 @@ static int daemonStartup(int, const char**) { - useSystemLog(); + CSystemLogger sysLogger(DAEMON_NAME); return realMain(); } @@ -612,8 +702,13 @@ main(int argc, char** argv) // get program name ARG->m_pname = ARCH->getBasename(argv[0]); + // make the task bar receiver. the user can control this app + // through the task bar. + s_taskBarReceiver = new CXWindowsClientTaskBarReceiver; + s_taskBarReceiver->setQuitJob(new CQuitJob(CThread::getCurrentThread())); + // parse command line - parse(argc, const_cast(argv)); + parse(argc, argv); // daemonize if requested int result; @@ -630,6 +725,9 @@ main(int argc, char** argv) result = realMain(); } + // done with task bar receiver + delete s_taskBarReceiver; + return result; } diff --git a/cmd/synergyc/synergyc.dsp b/cmd/synergyc/synergyc.dsp index e99b0910..21842b62 100644 --- a/cmd/synergyc/synergyc.dsp +++ b/cmd/synergyc/synergyc.dsp @@ -94,6 +94,14 @@ LINK32=link.exe # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" # Begin Source File +SOURCE=.\CClientTaskBarReceiver.cpp +# End Source File +# Begin Source File + +SOURCE=.\CMSWindowsClientTaskBarReceiver.cpp +# End Source File +# Begin Source File + SOURCE=.\synergyc.cpp # End Source File # Begin Source File @@ -106,6 +114,14 @@ SOURCE=.\synergyc.rc # PROP Default_Filter "h;hpp;hxx;hm;inl" # Begin Source File +SOURCE=.\CClientTaskBarReceiver.h +# End Source File +# Begin Source File + +SOURCE=.\CMSWindowsClientTaskBarReceiver.h +# End Source File +# Begin Source File + SOURCE=.\resource.h # End Source File # End Group @@ -116,6 +132,22 @@ SOURCE=.\resource.h SOURCE=.\synergyc.ico # End Source File +# Begin Source File + +SOURCE=.\tb_error.ico +# End Source File +# Begin Source File + +SOURCE=.\tb_idle.ico +# End Source File +# Begin Source File + +SOURCE=.\tb_run.ico +# End Source File +# Begin Source File + +SOURCE=.\tb_wait.ico +# End Source File # End Group # End Target # End Project diff --git a/cmd/synergyc/tb_error.ico b/cmd/synergyc/tb_error.ico new file mode 100644 index 00000000..a304dc18 Binary files /dev/null and b/cmd/synergyc/tb_error.ico differ diff --git a/cmd/synergyc/tb_idle.ico b/cmd/synergyc/tb_idle.ico new file mode 100644 index 00000000..3625257e Binary files /dev/null and b/cmd/synergyc/tb_idle.ico differ diff --git a/cmd/synergyc/tb_run.ico b/cmd/synergyc/tb_run.ico new file mode 100644 index 00000000..228555a9 Binary files /dev/null and b/cmd/synergyc/tb_run.ico differ diff --git a/cmd/synergyc/tb_wait.ico b/cmd/synergyc/tb_wait.ico new file mode 100644 index 00000000..ed56a5f0 Binary files /dev/null and b/cmd/synergyc/tb_wait.ico differ diff --git a/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp b/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp new file mode 100644 index 00000000..adcc132d --- /dev/null +++ b/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp @@ -0,0 +1,300 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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 "CMSWindowsServerTaskBarReceiver.h" +#include "CServer.h" +#include "BasicTypes.h" +#include "CArch.h" +#include "CArchTaskBarWindows.h" +#include "resource.h" + +static const UINT g_stateToIconID[CMSWindowsServerTaskBarReceiver::kMaxState] = +{ + IDI_TASKBAR_NOT_RUNNING, + IDI_TASKBAR_NOT_WORKING, + IDI_TASKBAR_NOT_CONNECTED, + IDI_TASKBAR_CONNECTED +}; + +// +// CMSWindowsServerTaskBarReceiver +// + +CMSWindowsServerTaskBarReceiver::CMSWindowsServerTaskBarReceiver( + HINSTANCE appInstance) : + CServerTaskBarReceiver(), + m_appInstance(appInstance), + m_window(NULL) +{ + for (UInt32 i = 0; i < kMaxState; ++i) { + m_icon[i] = loadIcon(g_stateToIconID[i]); + } + m_menu = LoadMenu(m_appInstance, MAKEINTRESOURCE(IDR_TASKBAR)); + + // don't create the window yet. we'll create it on demand. this + // has the side benefit of being created in the thread used for + // the task bar. that's good because it means the existence of + // the window won't prevent changing the main thread's desktop. + + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CMSWindowsServerTaskBarReceiver::~CMSWindowsServerTaskBarReceiver() +{ + ARCH->removeReceiver(this); + for (UInt32 i = 0; i < kMaxState; ++i) { + deleteIcon(m_icon[i]); + } + DestroyMenu(m_menu); + destroyWindow(); +} + +void +CMSWindowsServerTaskBarReceiver::showStatus() +{ + // create the window + createWindow(); + + // lock self while getting status + lock(); + + // get the current status + std::string status = getToolTip(); + + // get the connect clients, if any + typedef std::vector CClientList; + CClientList clients; + CServer* server = getServer(); + if (server != NULL) { + server->getClients(clients); + } + + // done getting status + unlock(); + + // update dialog + HWND child = GetDlgItem(m_window, IDC_TASKBAR_STATUS_STATUS); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)status.c_str()); + child = GetDlgItem(m_window, IDC_TASKBAR_STATUS_CLIENTS); + SendMessage(child, LB_RESETCONTENT, 0, 0); + for (CClientList::const_iterator index = clients.begin(); + index != clients.end(); ) { + const char* client = index->c_str(); + if (++index == clients.end()) { + SendMessage(child, LB_ADDSTRING, 0, (LPARAM)client); + } + else { + SendMessage(child, LB_INSERTSTRING, (WPARAM)-1, (LPARAM)client); + } + } + + if (!IsWindowVisible(m_window)) { + // position it by the mouse + POINT cursorPos; + GetCursorPos(&cursorPos); + RECT windowRect; + GetWindowRect(m_window, &windowRect); + int x = cursorPos.x; + int y = cursorPos.y; + int fw = GetSystemMetrics(SM_CXDLGFRAME); + int fh = GetSystemMetrics(SM_CYDLGFRAME); + int ww = windowRect.right - windowRect.left; + int wh = windowRect.bottom - windowRect.top; + int sw = GetSystemMetrics(SM_CXFULLSCREEN); + int sh = GetSystemMetrics(SM_CYFULLSCREEN); + if (fw < 1) { + fw = 1; + } + if (fh < 1) { + fh = 1; + } + if (x + ww - fw > sw) { + x -= ww - fw; + } + else { + x -= fw; + } + if (x < 0) { + x = 0; + } + if (y + wh - fh > sh) { + y -= wh - fh; + } + else { + y -= fh; + } + if (y < 0) { + y = 0; + } + SetWindowPos(m_window, HWND_TOPMOST, x, y, ww, wh, + SWP_SHOWWINDOW); + } +} + +void +CMSWindowsServerTaskBarReceiver::runMenu(int x, int y) +{ + // do popup menu. we need a window to pass to TrackPopupMenu(). + // the SetForegroundWindow() and SendMessage() calls around + // TrackPopupMenu() are to get the menu to be dismissed when + // another window gets activated and are just one of those + // win32 weirdnesses. + createWindow(); + SetForegroundWindow(m_window); + HMENU menu = GetSubMenu(m_menu, 0); + SetMenuDefaultItem(menu, IDC_TASKBAR_STATUS, FALSE); + int n = TrackPopupMenu(menu, + TPM_NONOTIFY | + TPM_RETURNCMD | + TPM_LEFTBUTTON | + TPM_RIGHTBUTTON, + x, y, 0, m_window, NULL); + SendMessage(m_window, WM_NULL, 0, 0); + + // perform the requested operation + switch (n) { + case IDC_TASKBAR_STATUS: + showStatus(); + break; + + case IDC_TASKBAR_QUIT: + quit(); + break; + } +} + +void +CMSWindowsServerTaskBarReceiver::primaryAction() +{ + showStatus(); +} + +const IArchTaskBarReceiver::Icon +CMSWindowsServerTaskBarReceiver::getIcon() const +{ + return reinterpret_cast(m_icon[getState()]); +} + +void +CMSWindowsServerTaskBarReceiver::onStatusChanged() +{ + if (IsWindowVisible(m_window)) { + showStatus(); + } +} + +HICON +CMSWindowsServerTaskBarReceiver::loadIcon(UINT id) +{ + HANDLE icon = LoadImage(m_appInstance, + MAKEINTRESOURCE(id), + IMAGE_ICON, + 0, 0, + LR_DEFAULTCOLOR); + return reinterpret_cast(icon); +} + +void +CMSWindowsServerTaskBarReceiver::deleteIcon(HICON icon) +{ + if (icon != NULL) { + DestroyIcon(icon); + } +} + +void +CMSWindowsServerTaskBarReceiver::createWindow() +{ + // ignore if already created + if (m_window != NULL) { + return; + } + + // get the status dialog + m_window = CreateDialogParam(m_appInstance, + MAKEINTRESOURCE(IDD_TASKBAR_STATUS), + NULL, + &CMSWindowsServerTaskBarReceiver::staticDlgProc, + reinterpret_cast( + reinterpret_cast(this))); + + // window should appear on top of everything, including (especially) + // the task bar. + DWORD style = GetWindowLong(m_window, GWL_EXSTYLE); + style |= WS_EX_TOOLWINDOW | WS_EX_TOPMOST; + SetWindowLong(m_window, GWL_EXSTYLE, style); + + // tell the task bar about this dialog + CArchTaskBarWindows::addDialog(m_window); +} + +void +CMSWindowsServerTaskBarReceiver::destroyWindow() +{ + if (m_window != NULL) { + CArchTaskBarWindows::removeDialog(m_window); + DestroyWindow(m_window); + m_window = NULL; + } +} + +BOOL +CMSWindowsServerTaskBarReceiver::dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM) +{ + switch (msg) { + case WM_INITDIALOG: + // use default focus + return TRUE; + + case WM_ACTIVATE: + // hide when another window is activated + if (LOWORD(wParam) == WA_INACTIVE) { + ShowWindow(hwnd, SW_HIDE); + } + break; + } + return FALSE; +} + +BOOL CALLBACK +CMSWindowsServerTaskBarReceiver::staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + // if msg is WM_INITDIALOG, extract the CMSWindowsServerTaskBarReceiver* + // and put it in the extra window data then forward the call. + CMSWindowsServerTaskBarReceiver* self = NULL; + if (msg == WM_INITDIALOG) { + self = reinterpret_cast( + reinterpret_cast(lParam)); + SetWindowLong(hwnd, GWL_USERDATA, lParam); + } + else { + // get the extra window data and forward the call + LONG data = GetWindowLong(hwnd, GWL_USERDATA); + if (data != 0) { + self = reinterpret_cast( + reinterpret_cast(data)); + } + } + + // forward the message + if (self != NULL) { + return self->dlgProc(hwnd, msg, wParam, lParam); + } + else { + return (msg == WM_INITDIALOG) ? TRUE : FALSE; + } +} diff --git a/cmd/synergys/CMSWindowsServerTaskBarReceiver.h b/cmd/synergys/CMSWindowsServerTaskBarReceiver.h new file mode 100644 index 00000000..372aad0b --- /dev/null +++ b/cmd/synergys/CMSWindowsServerTaskBarReceiver.h @@ -0,0 +1,58 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef CMSWINDOWSSERVERTASKBARRECEIVER_H +#define CMSWINDOWSSERVERTASKBARRECEIVER_H + +#define WIN32_LEAN_AND_MEAN + +#include "CServerTaskBarReceiver.h" +#include + +//! Implementation of CServerTaskBarReceiver for Microsoft Windows +class CMSWindowsServerTaskBarReceiver : public CServerTaskBarReceiver { +public: + CMSWindowsServerTaskBarReceiver(HINSTANCE); + virtual ~CMSWindowsServerTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; + +protected: + // CServerTaskBarReceiver overrides + virtual void onStatusChanged(); + +private: + HICON loadIcon(UINT); + void deleteIcon(HICON); + void createWindow(); + void destroyWindow(); + + BOOL dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + static BOOL CALLBACK + staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + +private: + HINSTANCE m_appInstance; + HWND m_window; + HMENU m_menu; + HICON m_icon[kMaxState]; +}; + +#endif diff --git a/cmd/synergys/CServerTaskBarReceiver.cpp b/cmd/synergys/CServerTaskBarReceiver.cpp new file mode 100644 index 00000000..8349a13c --- /dev/null +++ b/cmd/synergys/CServerTaskBarReceiver.cpp @@ -0,0 +1,171 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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 "CServerTaskBarReceiver.h" +#include "CServer.h" +#include "CLock.h" +#include "TMethodJob.h" +#include "CArch.h" + +// +// CServerTaskBarReceiver +// + +CServerTaskBarReceiver::CServerTaskBarReceiver() : + m_quit(NULL), + m_state(kNotRunning), + m_server(NULL) +{ + // create a job for getting notification when the server's + // status changes. + m_job = new TMethodJob(this, + &CServerTaskBarReceiver::statusChanged, NULL); +} + +CServerTaskBarReceiver::~CServerTaskBarReceiver() +{ + if (m_server != NULL) { + m_server->removeStatusJob(m_job); + } + delete m_job; + delete m_quit; +} + +void +CServerTaskBarReceiver::setServer(CServer* server) +{ + { + CLock lock(&m_mutex); + if (m_server != server) { + if (m_server != NULL) { + m_server->removeStatusJob(m_job); + } + m_server = server; + if (m_server != NULL) { + m_server->addStatusJob(m_job); + } + } + } + ARCH->updateReceiver(this); +} + +void +CServerTaskBarReceiver::setState(EState state) +{ + { + CLock lock(&m_mutex); + m_state = state; + } + ARCH->updateReceiver(this); +} + +void +CServerTaskBarReceiver::setQuitJob(IJob* job) +{ + CLock lock(&m_mutex); + delete m_quit; + m_quit = job; +} + +CServerTaskBarReceiver::EState +CServerTaskBarReceiver::getState() const +{ + return m_state; +} + +CServer* +CServerTaskBarReceiver::getServer() const +{ + return m_server; +} + +void +CServerTaskBarReceiver::lock() const +{ + m_mutex.lock(); +} + +void +CServerTaskBarReceiver::unlock() const +{ + m_mutex.unlock(); +} + +std::string +CServerTaskBarReceiver::getToolTip() const +{ + switch (m_state) { + case kNotRunning: + return "Synergy: Not running"; + + case kNotWorking: + return CString("Synergy: ") + m_errorMessage; + + case kNotConnected: + return "Synergy: Waiting for clients"; + + case kConnected: + return "Synergy: Connected"; + + default: + return ""; + } +} + +void +CServerTaskBarReceiver::quit() +{ + if (m_quit != NULL) { + m_quit->run(); + } +} + +void +CServerTaskBarReceiver::onStatusChanged() +{ + // do nothing +} + +void +CServerTaskBarReceiver::statusChanged(void*) +{ + // update our status + switch (m_server->getStatus(&m_errorMessage)) { + case CServer::kNotRunning: + setState(kNotRunning); + break; + + case CServer::kRunning: + if (m_server->getNumClients() > 1) + setState(kConnected); + else + setState(kNotConnected); + break; + + case CServer::kServerNameUnknown: + m_errorMessage = "Server name is not in configuration"; + setState(kNotWorking); + break; + + case CServer::kError: + setState(kNotWorking); + break; + + default: + break; + } + + // let subclasses have a go + onStatusChanged(); +} diff --git a/cmd/synergys/CServerTaskBarReceiver.h b/cmd/synergys/CServerTaskBarReceiver.h new file mode 100644 index 00000000..0591a097 --- /dev/null +++ b/cmd/synergys/CServerTaskBarReceiver.h @@ -0,0 +1,110 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef CSERVERTASKBARRECEIVER_H +#define CSERVERTASKBARRECEIVER_H + +#include "CMutex.h" +#include "CString.h" +#include "IArchTaskBarReceiver.h" + +class CServer; +class IJob; + +//! Implementation of IArchTaskBarReceiver for the synergy server +class CServerTaskBarReceiver : public IArchTaskBarReceiver { +public: + enum EState { + kNotRunning, + kNotWorking, + kNotConnected, + kConnected, + kMaxState + }; + + CServerTaskBarReceiver(); + virtual ~CServerTaskBarReceiver(); + + //! @name manipulators + //@{ + + //! Set server + /*! + Sets the server. The receiver will query state from this server. + */ + void setServer(CServer*); + + //! Set state + /*! + Sets the current server state. + */ + void setState(EState); + + //! Set the quit job that causes the server to quit + /*! + Set the job that causes the server to quit. + */ + void setQuitJob(IJob* adopted); + + //@} + //! @name accessors + //@{ + + //! Get state + /*! + Returns the current server state. The receiver is not locked + by this call; the caller must do the locking. + */ + EState getState() const; + + //! Get server + /*! + Returns the server set by \c setServer(). + */ + CServer* getServer() const; + + //@} + + // IArchTaskBarReceiver overrides + virtual void showStatus() = 0; + virtual void runMenu(int x, int y) = 0; + virtual void primaryAction() = 0; + virtual void lock() const; + virtual void unlock() const; + virtual const Icon getIcon() const = 0; + virtual std::string getToolTip() const; + +protected: + void quit(); + + //! Status change notification + /*! + Called when status changes. The default implementation does + nothing. + */ + virtual void onStatusChanged(); + +private: + void statusChanged(void*); + +private: + CMutex m_mutex; + IJob* m_quit; + EState m_state; + CServer* m_server; + IJob* m_job; + CString m_errorMessage; +}; + +#endif diff --git a/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp b/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp new file mode 100644 index 00000000..20118732 --- /dev/null +++ b/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp @@ -0,0 +1,61 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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 "CXWindowsServerTaskBarReceiver.h" +#include "CArch.h" + +// +// CXWindowsServerTaskBarReceiver +// + +CXWindowsServerTaskBarReceiver::CXWindowsServerTaskBarReceiver() +{ + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CXWindowsServerTaskBarReceiver::~CXWindowsServerTaskBarReceiver() +{ + ARCH->removeReceiver(this); +} + +void +CXWindowsServerTaskBarReceiver::showStatus() +{ + // do nothing +} + +void +CXWindowsServerTaskBarReceiver::runMenu(int, int) +{ + // do nothing +} + +void +CXWindowsServerTaskBarReceiver::primaryAction() +{ + // do nothing +} + +const IArchTaskBarReceiver::Icon +CXWindowsServerTaskBarReceiver::getIcon() const +{ + return NULL; +} + +void +CXWindowsServerTaskBarReceiver::onStatusChanged() +{ + // do nothing +} diff --git a/cmd/synergys/CXWindowsServerTaskBarReceiver.h b/cmd/synergys/CXWindowsServerTaskBarReceiver.h new file mode 100644 index 00000000..a05a4307 --- /dev/null +++ b/cmd/synergys/CXWindowsServerTaskBarReceiver.h @@ -0,0 +1,37 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef CXWINDOWSSERVERTASKBARRECEIVER_H +#define CXWINDOWSSERVERTASKBARRECEIVER_H + +#include "CServerTaskBarReceiver.h" + +//! Implementation of CServerTaskBarReceiver for X Windows +class CXWindowsServerTaskBarReceiver : public CServerTaskBarReceiver { +public: + CXWindowsServerTaskBarReceiver(); + virtual ~CXWindowsServerTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; + +protected: + // CServerTaskBarReceiver overrides + virtual void onStatusChanged(); +}; + +#endif diff --git a/cmd/synergys/Makefile.am b/cmd/synergys/Makefile.am index 8da55b4b..b3384ce6 100644 --- a/cmd/synergys/Makefile.am +++ b/cmd/synergys/Makefile.am @@ -15,19 +15,29 @@ NULL = DEPTH = ../.. VDEPTH = ./$(VPATH)/$(DEPTH) -EXTRA_DIST = \ - resource.h \ - synergys.ico \ - synergys.dsp \ - synergys.rc \ +EXTRA_DIST = \ + CMSWindowsServerTaskBarReceiver.cpp \ + CMSWindowsServerTaskBarReceiver.h \ + resource.h \ + synergys.ico \ + synergys.dsp \ + synergys.rc \ + tb_error.ico \ + tb_idle.ico \ + tb_run.ico \ + tb_wait.ico \ $(NULL) -MAINTAINERCLEANFILES = \ - Makefile.in \ +MAINTAINERCLEANFILES = \ + Makefile.in \ $(NULL) bin_PROGRAMS = synergys synergys_SOURCES = \ + CServerTaskBarReceiver.cpp \ + CServerTaskBarReceiver.h \ + CXWindowsServerTaskBarReceiver.cpp \ + CXWindowsServerTaskBarReceiver.h \ synergys.cpp \ $(NULL) synergys_LDADD = \ diff --git a/cmd/synergys/resource.h b/cmd/synergys/resource.h index 6bfc7486..2e716a64 100644 --- a/cmd/synergys/resource.h +++ b/cmd/synergys/resource.h @@ -4,14 +4,24 @@ // #define IDS_FAILED 1 #define IDI_SYNERGY 101 +#define IDI_TASKBAR_NOT_RUNNING 102 +#define IDI_TASKBAR_NOT_WORKING 103 +#define IDI_TASKBAR_NOT_CONNECTED 104 +#define IDI_TASKBAR_CONNECTED 105 +#define IDR_TASKBAR 107 +#define IDD_TASKBAR_STATUS 108 +#define IDC_TASKBAR_STATUS_STATUS 1000 +#define IDC_TASKBAR_STATUS_CLIENTS 1001 +#define IDC_TASKBAR_QUIT 40003 +#define IDC_TASKBAR_STATUS 40004 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_RESOURCE_VALUE 109 +#define _APS_NEXT_COMMAND_VALUE 40005 +#define _APS_NEXT_CONTROL_VALUE 1003 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/cmd/synergys/synergys.cpp b/cmd/synergys/synergys.cpp index 0e676d0a..f2c4924e 100644 --- a/cmd/synergys/synergys.cpp +++ b/cmd/synergys/synergys.cpp @@ -24,6 +24,7 @@ #include "CMutex.h" #include "CThread.h" #include "XThread.h" +#include "CFunctionJob.h" #include "CLog.h" #include "LogOutputters.h" #include "CArch.h" @@ -33,12 +34,15 @@ #define DAEMON_RUNNING(running_) #if WINDOWS_LIKE +#include "CMSWindowsScreen.h" #include "CMSWindowsPrimaryScreen.h" +#include "CMSWindowsServerTaskBarReceiver.h" #include "resource.h" #undef DAEMON_RUNNING #define DAEMON_RUNNING(running_) CArchMiscWindows::daemonRunning(running_) #elif UNIX_LIKE #include "CXWindowsPrimaryScreen.h" +#include "CXWindowsServerTaskBarReceiver.h" #endif // platform dependent name of a daemon @@ -123,11 +127,46 @@ CPrimaryScreenFactory::create(IScreenReceiver* receiver, } +//! CQuitJob +/*! +A job that cancels a given thread. +*/ +class CQuitJob : public IJob { +public: + CQuitJob(const CThread& thread); + ~CQuitJob(); + + // IJob overrides + virtual void run(); + +private: + CThread m_thread; +}; + +CQuitJob::CQuitJob(const CThread& thread) : + m_thread(thread) +{ + // do nothing +} + +CQuitJob::~CQuitJob() +{ + // do nothing +} + +void +CQuitJob::run() +{ + m_thread.cancel(); +} + + // // platform independent main // -static CServer* s_server = NULL; +static CServer* s_server = NULL; +static CServerTaskBarReceiver* s_taskBarReceiver = NULL; static int @@ -168,6 +207,7 @@ realMain(void) // open server try { + s_taskBarReceiver->setServer(s_server); s_server->open(); opened = true; @@ -177,18 +217,17 @@ realMain(void) s_server->mainLoop(); // clean up -#define FINALLY do { \ - if (!locked) { \ - DAEMON_RUNNING(false); \ - locked = true; \ - } \ - if (s_server != NULL) { \ - if (opened) { \ - s_server->close(); \ - } \ - } \ - delete s_server; \ - s_server = NULL; \ +#define FINALLY do { \ + if (!locked) { \ + DAEMON_RUNNING(false); \ + locked = true; \ + } \ + if (opened) { \ + s_server->close(); \ + } \ + s_taskBarReceiver->setServer(NULL); \ + delete s_server; \ + s_server = NULL; \ } while (false) FINALLY; } @@ -227,6 +266,41 @@ realMain(void) return result; } +static +void +realMainEntry(void*) +{ + CThread::exit(reinterpret_cast(realMain())); +} + +static +int +runMainInThread(void) +{ + CThread appThread(new CFunctionJob(&realMainEntry)); + try { +#if WINDOWS_LIKE + MSG msg; + while (appThread.waitForEvent(-1.0) == CThread::kEvent) { + // check for a quit event + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + CThread::getCurrentThread().cancel(); + } + } + } +#else + appThread.wait(-1.0); +#endif + return reinterpret_cast(appThread.getResult()); + } + catch (XThread&) { + appThread.cancel(); + appThread.wait(-1.0); + throw; + } +} + // // command line parsing @@ -329,7 +403,7 @@ PLATFORM_EXTRA static bool -isArg(int argi, int argc, const char** argv, +isArg(int argi, int argc, const char* const* argv, const char* name1, const char* name2, int minRequiredParameters = 0) { @@ -350,7 +424,7 @@ isArg(int argi, int argc, const char** argv, static void -parse(int argc, const char** argv) +parse(int argc, const char* const* argv) { assert(ARG->m_pname != NULL); assert(argv != NULL); @@ -553,16 +627,6 @@ loadConfig() } } -static -void -useSystemLog() -{ - // redirect log messages - ILogOutputter* logger = new CSystemLogOutputter; - logger->open(DAEMON_NAME); - CLOG->insert(new CStopLogOutputter); - CLOG->insert(logger); -} // // platform dependent entry points @@ -570,8 +634,6 @@ useSystemLog() #if WINDOWS_LIKE -#include "CMSWindowsScreen.h" - static bool s_hasImportantLogMessages = false; // @@ -623,7 +685,10 @@ static int daemonStartup(int argc, const char** argv) { - useSystemLog(); + CSystemLogger sysLogger(DAEMON_NAME); + + // have to cancel this thread to quit + s_taskBarReceiver->setQuitJob(new CQuitJob(CThread::getCurrentThread())); // catch errors that would normally exit bye = &byeThrow; @@ -645,14 +710,65 @@ static int daemonStartup95(int, const char**) { - useSystemLog(); - return realMain(); + CSystemLogger sysLogger(DAEMON_NAME); + return runMainInThread(); +} + +static +int +run(int argc, char** argv) +{ + // windows NT family starts services using no command line options. + // since i'm not sure how to tell the difference between that and + // a user providing no options we'll assume that if there are no + // arguments and we're on NT then we're being invoked as a service. + // users on NT can use `--daemon' or `--no-daemon' to force us out + // of the service code path. + if (argc <= 1 && !CArchMiscWindows::isWindows95Family()) { + try { + return ARCH->daemonize(DAEMON_NAME, &daemonStartup); + } + catch (XArchDaemon& e) { + LOG((CLOG_CRIT "failed to start as a service: %s" BYE, e.what().c_str(), ARG->m_pname)); + } + return kExitFailed; + } + + // parse command line + parse(argc, argv); + + // load configuration + loadConfig(); + + // daemonize if requested + if (ARG->m_daemon) { + // start as a daemon + if (CArchMiscWindows::isWindows95Family()) { + try { + return ARCH->daemonize(DAEMON_NAME, &daemonStartup95); + } + catch (XArchDaemon& e) { + LOG((CLOG_CRIT "failed to start as a service: %s" BYE, e.what().c_str(), ARG->m_pname)); + } + return kExitFailed; + } + else { + // cannot start a service from the command line so just + // run normally (except with log messages redirected). + CSystemLogger sysLogger(DAEMON_NAME); + return runMainInThread(); + } + } + else { + // run + return runMainInThread(); + } } int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) { - CArch arch; + CArch arch(instance); CLOG; CArgs args; @@ -665,55 +781,27 @@ WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) // send PRINT and FATAL output to a message box CLOG->insert(new CMessageBoxOutputter); - // windows NT family starts services using no command line options. - // since i'm not sure how to tell the difference between that and - // a user providing no options we'll assume that if there are no - // arguments and we're on NT then we're being invoked as a service. - // users on NT can use `--daemon' or `--no-daemon' to force us out - // of the service code path. - if (__argc <= 1 && !CArchMiscWindows::isWindows95Family()) { - int result = kExitFailed; - try { - result = ARCH->daemonize(DAEMON_NAME, &daemonStartup); - } - catch (XArchDaemon& e) { - LOG((CLOG_CRIT "failed to start as a service: %s" BYE, e.what().c_str(), ARG->m_pname)); - } - delete CLOG; - return result; - } + // make the task bar receiver. the user can control this app + // through the task bar. + s_taskBarReceiver = new CMSWindowsServerTaskBarReceiver(instance); + s_taskBarReceiver->setQuitJob(new CQuitJob(CThread::getCurrentThread())); - // parse command line - parse(__argc, const_cast(__argv)); - - // load configuration - loadConfig(); - - // daemonize if requested int result; - if (ARG->m_daemon) { - // start as a daemon - if (CArchMiscWindows::isWindows95Family()) { - try { - result = ARCH->daemonize(DAEMON_NAME, &daemonStartup95); - } - catch (XArchDaemon& e) { - LOG((CLOG_CRIT "failed to start as a service: %s" BYE, e.what().c_str(), ARG->m_pname)); - result = kExitFailed; - } - } - else { - // cannot start a service from the command line so just - // run normally (except with log messages redirected). - useSystemLog(); - result = realMain(); - } + try { + // run in foreground or as a daemon + result = run(__argc, __argv); } - else { - // run - result = realMain(); + catch (...) { + // note that we don't rethrow thread cancellation. we'll + // be exiting soon so it doesn't matter. what we'd like + // is for everything after this try/catch to be in a + // finally block. + result = kExitFailed; } + // done with task bar receiver + delete s_taskBarReceiver; + // let user examine any messages if we're running as a backend // by putting up a dialog box before exiting. if (ARG->m_backend && s_hasImportantLogMessages) { @@ -733,7 +821,7 @@ static int daemonStartup(int, const char**) { - useSystemLog(); + CSystemLogger sysLogger(DAEMON_NAME); return realMain(); } @@ -747,8 +835,13 @@ main(int argc, char** argv) // get program name ARG->m_pname = ARCH->getBasename(argv[0]); + // make the task bar receiver. the user can control this app + // through the task bar. + s_taskBarReceiver = new CXWindowsServerTaskBarReceiver; + s_taskBarReceiver->setQuitJob(new CQuitJob(CThread::getCurrentThread())); + // parse command line - parse(argc, const_cast(argv)); + parse(argc, argv); // load configuration loadConfig(); @@ -768,6 +861,9 @@ main(int argc, char** argv) result = realMain(); } + // done with task bar receiver + delete s_taskBarReceiver; + return result; } diff --git a/cmd/synergys/synergys.dsp b/cmd/synergys/synergys.dsp index 39accfa4..cbc3e626 100644 --- a/cmd/synergys/synergys.dsp +++ b/cmd/synergys/synergys.dsp @@ -94,6 +94,14 @@ LINK32=link.exe # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" # Begin Source File +SOURCE=.\CMSWindowsServerTaskBarReceiver.cpp +# End Source File +# Begin Source File + +SOURCE=.\CServerTaskBarReceiver.cpp +# End Source File +# Begin Source File + SOURCE=.\synergys.cpp # End Source File # Begin Source File @@ -106,6 +114,14 @@ SOURCE=.\synergys.rc # PROP Default_Filter "h;hpp;hxx;hm;inl" # Begin Source File +SOURCE=.\CMSWindowsServerTaskBarReceiver.h +# End Source File +# Begin Source File + +SOURCE=.\CServerTaskBarReceiver.h +# End Source File +# Begin Source File + SOURCE=.\resource.h # End Source File # End Group @@ -116,6 +132,22 @@ SOURCE=.\resource.h SOURCE=.\synergys.ico # End Source File +# Begin Source File + +SOURCE=.\tb_error.ico +# End Source File +# Begin Source File + +SOURCE=.\tb_idle.ico +# End Source File +# Begin Source File + +SOURCE=.\tb_run.ico +# End Source File +# Begin Source File + +SOURCE=.\tb_wait.ico +# End Source File # End Group # End Target # End Project diff --git a/cmd/synergys/tb_error.ico b/cmd/synergys/tb_error.ico new file mode 100644 index 00000000..a304dc18 Binary files /dev/null and b/cmd/synergys/tb_error.ico differ diff --git a/cmd/synergys/tb_idle.ico b/cmd/synergys/tb_idle.ico new file mode 100644 index 00000000..3625257e Binary files /dev/null and b/cmd/synergys/tb_idle.ico differ diff --git a/cmd/synergys/tb_run.ico b/cmd/synergys/tb_run.ico new file mode 100644 index 00000000..228555a9 Binary files /dev/null and b/cmd/synergys/tb_run.ico differ diff --git a/cmd/synergys/tb_wait.ico b/cmd/synergys/tb_wait.ico new file mode 100644 index 00000000..ed56a5f0 Binary files /dev/null and b/cmd/synergys/tb_wait.ico differ diff --git a/lib/arch/CArch.cpp b/lib/arch/CArch.cpp index 6eb99e0d..b8322c33 100644 --- a/lib/arch/CArch.cpp +++ b/lib/arch/CArch.cpp @@ -23,6 +23,7 @@ #undef ARCH_NETWORK #undef ARCH_SLEEP #undef ARCH_STRING +#undef ARCH_TASKBAR #undef ARCH_TIME // include appropriate architecture implementation @@ -35,6 +36,7 @@ # include "CArchNetworkWinsock.h" # include "CArchSleepWindows.h" # include "CArchStringWindows.h" +# include "CArchTaskBarWindows.h" # include "CArchTimeWindows.h" #elif UNIX_LIKE # include "CArchConsoleUnix.h" @@ -47,6 +49,7 @@ # include "CArchNetworkBSD.h" # include "CArchSleepUnix.h" # include "CArchStringUnix.h" +# include "CArchTaskBarXWindows.h" # include "CArchTimeUnix.h" #endif @@ -82,6 +85,10 @@ # error unsupported platform for string #endif +#if !defined(ARCH_TASKBAR) +# error unsupported platform for taskbar +#endif + #if !defined(ARCH_TIME) # error unsupported platform for time #endif @@ -92,7 +99,7 @@ CArch* CArch::s_instance = NULL; -CArch::CArch(ARCH_ARGS) +CArch::CArch(ARCH_ARGS* args) { // only once instance of CArch assert(s_instance == NULL); @@ -108,11 +115,13 @@ CArch::CArch(ARCH_ARGS) m_time = new ARCH_TIME; m_console = new ARCH_CONSOLE; m_daemon = new ARCH_DAEMON; + m_taskbar = new ARCH_TASKBAR(args); } CArch::~CArch() { // clean up + delete m_taskbar; delete m_daemon; delete m_console; delete m_time; @@ -337,10 +346,10 @@ CArch::wait(CArchThread thread, double timeout) return m_mt->wait(thread, timeout); } -bool -CArch::waitForEvent(double timeout) +IArchMultithread::EWaitResult +CArch::waitForEvent(CArchThread thread, double timeout) { - return m_mt->waitForEvent(timeout); + return m_mt->waitForEvent(thread, timeout); } bool @@ -577,6 +586,24 @@ CArch::getWideCharEncoding() return m_string->getWideCharEncoding(); } +void +CArch::addReceiver(IArchTaskBarReceiver* receiver) +{ + m_taskbar->addReceiver(receiver); +} + +void +CArch::removeReceiver(IArchTaskBarReceiver* receiver) +{ + m_taskbar->removeReceiver(receiver); +} + +void +CArch::updateReceiver(IArchTaskBarReceiver* receiver) +{ + m_taskbar->updateReceiver(receiver); +} + double CArch::time() { diff --git a/lib/arch/CArch.h b/lib/arch/CArch.h index e459658f..61315b63 100644 --- a/lib/arch/CArch.h +++ b/lib/arch/CArch.h @@ -23,6 +23,7 @@ #include "IArchNetwork.h" #include "IArchSleep.h" #include "IArchString.h" +#include "IArchTaskBar.h" #include "IArchTime.h" /*! @@ -31,7 +32,7 @@ This macro evaluates to the singleton CArch object. */ #define ARCH (CArch::getInstance()) -#define ARCH_ARGS +#define ARCH_ARGS void //! Delegating mplementation of architecture dependent interfaces /*! @@ -51,9 +52,10 @@ class CArch : public IArchConsole, public IArchNetwork, public IArchSleep, public IArchString, + public IArchTaskBar, public IArchTime { public: - CArch(ARCH_ARGS); + CArch(ARCH_ARGS* args = NULL); ~CArch(); // @@ -114,7 +116,7 @@ public: virtual void setPriorityOfThread(CArchThread, int n); virtual void testCancelThread(); virtual bool wait(CArchThread, double timeout); - virtual bool waitForEvent(double timeout); + virtual EWaitResult waitForEvent(CArchThread, double timeout); virtual bool isSameThread(CArchThread, CArchThread); virtual bool isExitedThread(CArchThread); virtual void* getResultOfThread(CArchThread); @@ -164,6 +166,11 @@ public: virtual EWideCharEncoding getWideCharEncoding(); + // IArchTaskBar + virtual void addReceiver(IArchTaskBarReceiver*); + virtual void removeReceiver(IArchTaskBarReceiver*); + virtual void updateReceiver(IArchTaskBarReceiver*); + // IArchTime overrides virtual double time(); @@ -178,6 +185,7 @@ private: IArchNetwork* m_net; IArchSleep* m_sleep; IArchString* m_string; + IArchTaskBar* m_taskbar; IArchTime* m_time; }; diff --git a/lib/arch/CArchConsoleWindows.cpp b/lib/arch/CArchConsoleWindows.cpp index c7afb95c..a89a42f4 100644 --- a/lib/arch/CArchConsoleWindows.cpp +++ b/lib/arch/CArchConsoleWindows.cpp @@ -13,6 +13,7 @@ */ #include "CArchConsoleWindows.h" +#include "IArchMultithread.h" #include "CArch.h" #include @@ -20,12 +21,12 @@ // CArchConsoleWindows // -DWORD CArchConsoleWindows::s_thread = 0; +CArchThread CArchConsoleWindows::s_thread = 0; CArchConsoleWindows::CArchConsoleWindows() : m_output(NULL) { - s_thread = GetCurrentThreadId(); + s_thread = ARCH->newCurrentThread(); m_mutex = ARCH->newMutex(); } @@ -33,6 +34,7 @@ CArchConsoleWindows::CArchConsoleWindows() : CArchConsoleWindows::~CArchConsoleWindows() { ARCH->closeMutex(m_mutex); + ARCH->closeThread(s_thread); } void @@ -101,7 +103,7 @@ CArchConsoleWindows::getNewlineForConsole() BOOL WINAPI CArchConsoleWindows::signalHandler(DWORD) { - // terminate cleanly and skip remaining handlers - PostThreadMessage(s_thread, WM_QUIT, 0, 0); + // terminate thread and skip remaining handlers + ARCH->cancelThread(s_thread); return TRUE; } diff --git a/lib/arch/CArchConsoleWindows.h b/lib/arch/CArchConsoleWindows.h index e51f6616..bf9f0a32 100644 --- a/lib/arch/CArchConsoleWindows.h +++ b/lib/arch/CArchConsoleWindows.h @@ -39,7 +39,7 @@ private: static BOOL WINAPI signalHandler(DWORD); private: - static DWORD s_thread; + static CArchThread s_thread; CArchMutex m_mutex; HANDLE m_output; diff --git a/lib/arch/CArchImpl.cpp b/lib/arch/CArchImpl.cpp index c4017122..9e57d0a7 100644 --- a/lib/arch/CArchImpl.cpp +++ b/lib/arch/CArchImpl.cpp @@ -25,6 +25,7 @@ # include "CArchNetworkWinsock.cpp" # include "CArchSleepWindows.cpp" # include "CArchStringWindows.cpp" +# include "CArchTaskBarWindows.cpp" # include "CArchTimeWindows.cpp" # include "XArchWindows.cpp" #elif UNIX_LIKE @@ -38,6 +39,7 @@ # include "CArchNetworkBSD.cpp" # include "CArchSleepUnix.cpp" # include "CArchStringUnix.cpp" +# include "CArchTaskBarXWindows.cpp" # include "CArchTimeUnix.cpp" # include "XArchUnix.cpp" #endif diff --git a/lib/arch/CArchMultithreadPosix.cpp b/lib/arch/CArchMultithreadPosix.cpp index 8fdd9dde..bef806bf 100644 --- a/lib/arch/CArchMultithreadPosix.cpp +++ b/lib/arch/CArchMultithreadPosix.cpp @@ -517,11 +517,11 @@ CArchMultithreadPosix::wait(CArchThread target, double timeout) } } -bool -CArchMultithreadPosix::waitForEvent(double /*timeout*/) +IArchMultithread::EWaitResult +CArchMultithreadPosix::waitForEvent(CArchThread, double /*timeout*/) { // not implemented - return false; + return kTimeout; } bool diff --git a/lib/arch/CArchMultithreadPosix.h b/lib/arch/CArchMultithreadPosix.h index 07e2f1d9..d62e2d4d 100644 --- a/lib/arch/CArchMultithreadPosix.h +++ b/lib/arch/CArchMultithreadPosix.h @@ -55,7 +55,7 @@ public: virtual void setPriorityOfThread(CArchThread, int n); virtual void testCancelThread(); virtual bool wait(CArchThread, double timeout); - virtual bool waitForEvent(double timeout); + virtual EWaitResult waitForEvent(CArchThread, double timeout); virtual bool isSameThread(CArchThread, CArchThread); virtual bool isExitedThread(CArchThread); virtual void* getResultOfThread(CArchThread); diff --git a/lib/arch/CArchMultithreadWindows.cpp b/lib/arch/CArchMultithreadWindows.cpp index 315a3979..c847e0ed 100644 --- a/lib/arch/CArchMultithreadWindows.cpp +++ b/lib/arch/CArchMultithreadWindows.cpp @@ -453,6 +453,89 @@ CArchMultithreadWindows::wait(CArchThread target, double timeout) } } +IArchMultithread::EWaitResult +CArchMultithreadWindows::waitForEvent(CArchThread target, double timeout) +{ + // find current thread. ref the target so it can't go away while + // we're watching it. + lockMutex(m_threadMutex); + CArchThreadImpl* self = findNoRef(GetCurrentThreadId()); + assert(self != NULL); + if (target != NULL) { + refThread(target); + } + unlockMutex(m_threadMutex); + + // see if we've been cancelled before checking if any events + // are pending. + DWORD result = WaitForSingleObject(self->m_cancel, 0); + if (result == WAIT_OBJECT_0) { + if (target != NULL) { + closeThread(target); + } + testCancelThreadImpl(self); + } + + // check if messages are available first. if we don't do this then + // MsgWaitForMultipleObjects() will block even if the queue isn't + // empty if the messages in the queue were there before the last + // call to GetMessage()/PeekMessage(). + if (HIWORD(GetQueueStatus(QS_ALLINPUT)) != 0) { + return kEvent; + } + + // convert timeout + DWORD t; + if (timeout < 0.0) { + t = INFINITE; + } + else { + t = (DWORD)(1000.0 * timeout); + } + + // wait for this thread to be cancelled or for the target thread to + // terminate. + DWORD n = (target == NULL || target == self) ? 1 : 2; + HANDLE handles[2]; + handles[0] = self->m_cancel; + handles[1] = (n == 2) ? target->m_exit : NULL; + result = MsgWaitForMultipleObjects(n, handles, FALSE, t, QS_ALLINPUT); + + // cancel takes priority + if (result != WAIT_OBJECT_0 + 0 && + WaitForSingleObject(handles[0], 0) == WAIT_OBJECT_0) { + result = WAIT_OBJECT_0 + 0; + } + + // release target + if (target != NULL) { + closeThread(target); + } + + // handle result + switch (result) { + case WAIT_OBJECT_0 + 0: + // this thread was cancelled. does not return. + testCancelThreadImpl(self); + + case WAIT_OBJECT_0 + 1: + // target thread terminated + if (n == 2) { + return kExit; + } + // fall through + + case WAIT_OBJECT_0 + 2: + // message is available + return kEvent; + + default: + // timeout or error + return kTimeout; + } +} + +/* bool CArchMultithreadWindows::waitForEvent(double timeout) { @@ -499,6 +582,7 @@ CArchMultithreadWindows::waitForEvent(double timeout) return false; } } +*/ bool CArchMultithreadWindows::isSameThread(CArchThread thread1, CArchThread thread2) diff --git a/lib/arch/CArchMultithreadWindows.h b/lib/arch/CArchMultithreadWindows.h index e5003c1a..2a8d5462 100644 --- a/lib/arch/CArchMultithreadWindows.h +++ b/lib/arch/CArchMultithreadWindows.h @@ -69,7 +69,7 @@ public: virtual void setPriorityOfThread(CArchThread, int n); virtual void testCancelThread(); virtual bool wait(CArchThread, double timeout); - virtual bool waitForEvent(double timeout); + virtual EWaitResult waitForEvent(CArchThread, double timeout); virtual bool isSameThread(CArchThread, CArchThread); virtual bool isExitedThread(CArchThread); virtual void* getResultOfThread(CArchThread); diff --git a/lib/arch/CArchTaskBarWindows.cpp b/lib/arch/CArchTaskBarWindows.cpp new file mode 100644 index 00000000..987ac6c0 --- /dev/null +++ b/lib/arch/CArchTaskBarWindows.cpp @@ -0,0 +1,518 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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 "CArchTaskBarWindows.h" +#include "IArchTaskBarReceiver.h" +#include "CArch.h" +#include "XArch.h" +#include +#include + +static const UINT kAddReceiver = WM_USER + 10; +static const UINT kRemoveReceiver = WM_USER + 11; +static const UINT kUpdateReceiver = WM_USER + 12; +static const UINT kNotifyReceiver = WM_USER + 13; +static const UINT kFirstReceiverID = WM_USER + 14; + +// +// CArchTaskBarWindows +// + +CArchTaskBarWindows* CArchTaskBarWindows::s_instance = NULL; +HINSTANCE CArchTaskBarWindows::s_appInstance = NULL; + +CArchTaskBarWindows::CArchTaskBarWindows(void* appInstance) : + m_nextID(kFirstReceiverID) +{ + // save the singleton instance + s_instance = this; + + // save app instance + s_appInstance = reinterpret_cast(appInstance); + + // we need a mutex + m_mutex = ARCH->newMutex(); + + // and a condition variable which uses the above mutex + m_ready = false; + m_condVar = ARCH->newCondVar(); + + // we're going to want to get a result from the thread we're + // about to create to know if it initialized successfully. + // so we lock the condition variable. + ARCH->lockMutex(m_mutex); + + // open a window and run an event loop in a separate thread. + // this has to happen in a separate thread because if we + // create a window on the current desktop with the current + // thread then the current thread won't be able to switch + // desktops if it needs to. + m_thread = ARCH->newThread(&CArchTaskBarWindows::threadEntry, this); + + // wait for child thread + while (!m_ready) { + ARCH->waitCondVar(m_condVar, m_mutex, -1.0); + } + + // ready + ARCH->unlockMutex(m_mutex); +} + +CArchTaskBarWindows::~CArchTaskBarWindows() +{ + if (m_thread != NULL) { + ARCH->cancelThread(m_thread); + ARCH->wait(m_thread, -1.0); + ARCH->closeThread(m_thread); + } + ARCH->closeCondVar(m_condVar); + ARCH->closeMutex(m_mutex); + s_instance = NULL; +} + +void +CArchTaskBarWindows::addDialog(HWND hwnd) +{ + // add dialog to added dialogs list + ARCH->lockMutex(s_instance->m_mutex); + s_instance->m_addedDialogs.insert(std::make_pair(hwnd, true)); + ARCH->unlockMutex(s_instance->m_mutex); +} + +void +CArchTaskBarWindows::removeDialog(HWND hwnd) +{ + // mark dialog as removed + ARCH->lockMutex(s_instance->m_mutex); + CDialogs::iterator index = s_instance->m_dialogs.find(hwnd); + if (index != s_instance->m_dialogs.end()) { + index->second = false; + } + s_instance->m_addedDialogs.erase(hwnd); + ARCH->unlockMutex(s_instance->m_mutex); +} + +void +CArchTaskBarWindows::addReceiver(IArchTaskBarReceiver* receiver) +{ + // ignore bogus receiver + if (receiver == NULL) { + return; + } + + // add receiver if necessary + CReceiverToInfoMap::iterator index = m_receivers.find(receiver); + if (index == m_receivers.end()) { + // add it, creating a new message ID for it + CReceiverInfo info; + info.m_id = getNextID(); + index = m_receivers.insert(std::make_pair(receiver, info)).first; + + // add ID to receiver mapping + m_idTable.insert(std::make_pair(info.m_id, index)); + } + + // add receiver + PostMessage(m_hwnd, kAddReceiver, index->second.m_id, 0); +} + +void +CArchTaskBarWindows::removeReceiver(IArchTaskBarReceiver* receiver) +{ + // find receiver + CReceiverToInfoMap::iterator index = m_receivers.find(receiver); + if (index == m_receivers.end()) { + return; + } + + // remove icon. wait for this to finish before returning. + SendMessage(m_hwnd, kRemoveReceiver, index->second.m_id, 0); + + // recycle the ID + recycleID(index->second.m_id); + + // discard + m_idTable.erase(index->second.m_id); + m_receivers.erase(index); +} + +void +CArchTaskBarWindows::updateReceiver(IArchTaskBarReceiver* receiver) +{ + // find receiver + CReceiverToInfoMap::const_iterator index = m_receivers.find(receiver); + if (index == m_receivers.end()) { + return; + } + + // update icon and tool tip + PostMessage(m_hwnd, kUpdateReceiver, index->second.m_id, 0); +} + +UINT +CArchTaskBarWindows::getNextID() +{ + if (m_oldIDs.empty()) { + return m_nextID++; + } + UINT id = m_oldIDs.back(); + m_oldIDs.pop_back(); + return id; +} + +void +CArchTaskBarWindows::recycleID(UINT id) +{ + m_oldIDs.push_back(id); +} + +void +CArchTaskBarWindows::addIcon(UINT id) +{ + ARCH->lockMutex(m_mutex); + CIDToReceiverMap::const_iterator index = m_idTable.find(id); + if (index != m_idTable.end()) { + modifyIconNoLock(index->second, NIM_ADD); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::removeIcon(UINT id) +{ + ARCH->lockMutex(m_mutex); + removeIconNoLock(id); + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::updateIcon(UINT id) +{ + ARCH->lockMutex(m_mutex); + CIDToReceiverMap::const_iterator index = m_idTable.find(id); + if (index != m_idTable.end()) { + modifyIconNoLock(index->second, NIM_MODIFY); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::addAllIcons() +{ + ARCH->lockMutex(m_mutex); + for (CReceiverToInfoMap::const_iterator index = m_receivers.begin(); + index != m_receivers.end(); ++index) { + modifyIconNoLock(index, NIM_ADD); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::removeAllIcons() +{ + ARCH->lockMutex(m_mutex); + for (CReceiverToInfoMap::const_iterator index = m_receivers.begin(); + index != m_receivers.end(); ++index) { + removeIconNoLock(index->second.m_id); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::modifyIconNoLock( + CReceiverToInfoMap::const_iterator index, DWORD taskBarMessage) +{ + // get receiver + UINT id = index->second.m_id; + IArchTaskBarReceiver* receiver = index->first; + + // lock receiver so icon and tool tip are guaranteed to be consistent + receiver->lock(); + + // get icon data + HICON icon = reinterpret_cast( + const_cast(receiver->getIcon())); + + // get tool tip + std::string toolTip = receiver->getToolTip(); + + // done querying + receiver->unlock(); + + // prepare to add icon + NOTIFYICONDATA data; + data.cbSize = sizeof(NOTIFYICONDATA); + data.hWnd = m_hwnd; + data.uID = id; + data.uFlags = NIF_MESSAGE; + data.uCallbackMessage = kNotifyReceiver; + data.hIcon = icon; + if (icon != NULL) { + data.uFlags |= NIF_ICON; + } + if (!toolTip.empty()) { + strncpy(data.szTip, toolTip.c_str(), sizeof(data.szTip)); + data.szTip[sizeof(data.szTip) - 1] = '\0'; + data.uFlags |= NIF_TIP; + } + else { + data.szTip[0] = '\0'; + } + + // add icon + if (Shell_NotifyIcon(taskBarMessage, &data) == 0) { + // failed + } +} + +void +CArchTaskBarWindows::removeIconNoLock(UINT id) +{ + NOTIFYICONDATA data; + data.cbSize = sizeof(NOTIFYICONDATA); + data.hWnd = m_hwnd; + data.uID = id; + if (Shell_NotifyIcon(NIM_DELETE, &data) == 0) { + // failed + } +} + +void +CArchTaskBarWindows::handleIconMessage( + IArchTaskBarReceiver* receiver, LPARAM lParam) +{ + // process message + switch (lParam) { + case WM_LBUTTONDOWN: + receiver->showStatus(); + break; + + case WM_LBUTTONDBLCLK: + receiver->primaryAction(); + break; + + case WM_RBUTTONUP: { + POINT p; + GetCursorPos(&p); + receiver->runMenu(p.x, p.y); + break; + } + + case WM_MOUSEMOVE: + // currently unused + break; + + default: + // unused + break; + } +} + +bool +CArchTaskBarWindows::processDialogs(MSG* msg) +{ + // only one thread can be in this method on any particular object + // at any given time. that's not a problem since only our event + // loop calls this method and there's just one of those. + + ARCH->lockMutex(m_mutex); + + // remove removed dialogs + m_dialogs.erase(false); + + // merge added dialogs into the dialog list + for (CDialogs::const_iterator index = m_addedDialogs.begin(); + index != m_addedDialogs.end(); ++index) { + m_dialogs.insert(std::make_pair(index->first, index->second)); + } + m_addedDialogs.clear(); + + ARCH->unlockMutex(m_mutex); + + // check message against all dialogs until one handles it. + // note that we don't hold a lock while checking because + // the message is processed and may make calls to this + // object. that's okay because addDialog() and + // removeDialog() don't change the map itself (just the + // values of some elements). + ARCH->lockMutex(m_mutex); + for (CDialogs::const_iterator index = m_dialogs.begin(); + index != m_dialogs.end(); ++index) { + if (index->second) { + ARCH->unlockMutex(m_mutex); + if (IsDialogMessage(index->first, msg)) { + return true; + } + ARCH->lockMutex(m_mutex); + } + } + ARCH->unlockMutex(m_mutex); + + return false; +} + +LRESULT +CArchTaskBarWindows::wndProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case kNotifyReceiver: { + // lookup receiver + CIDToReceiverMap::const_iterator index = m_idTable.find(wParam); + if (index != m_idTable.end()) { + IArchTaskBarReceiver* receiver = index->second->first; + handleIconMessage(receiver, lParam); + return 0; + } + break; + } + + case kAddReceiver: + addIcon(wParam); + break; + + case kRemoveReceiver: + removeIcon(wParam); + break; + + case kUpdateReceiver: + updateIcon(wParam); + break; + + default: + if (msg == m_taskBarRestart) { + // task bar was recreated so re-add our icons + addAllIcons(); + } + break; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT CALLBACK +CArchTaskBarWindows::staticWndProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + // if msg is WM_NCCREATE, extract the CArchTaskBarWindows* and put + // it in the extra window data then forward the call. + CArchTaskBarWindows* self = NULL; + if (msg == WM_NCCREATE) { + CREATESTRUCT* createInfo; + createInfo = reinterpret_cast(lParam); + self = reinterpret_cast( + createInfo->lpCreateParams); + SetWindowLong(hwnd, 0, reinterpret_cast(self)); + } + else { + // get the extra window data and forward the call + LONG data = GetWindowLong(hwnd, 0); + if (data != 0) { + self = reinterpret_cast( + reinterpret_cast(data)); + } + } + + // forward the message + if (self != NULL) { + return self->wndProc(hwnd, msg, wParam, lParam); + } + else { + return DefWindowProc(hwnd, msg, wParam, lParam); + } +} + +void +CArchTaskBarWindows::threadMainLoop() +{ + // register the task bar restart message + m_taskBarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); + + // register a window class + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_NOCLOSE; + classInfo.lpfnWndProc = &CArchTaskBarWindows::staticWndProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = sizeof(CArchTaskBarWindows*); + classInfo.hInstance = s_appInstance; + classInfo.hIcon = NULL; + classInfo.hCursor = NULL; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = TEXT("SynergyTaskBar"); + classInfo.hIconSm = NULL; + ATOM windowClass = RegisterClassEx(&classInfo); + + // create window + m_hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, + reinterpret_cast(windowClass), + TEXT("Synergy Task Bar"), + WS_POPUP, + 0, 0, 1, 1, + NULL, + NULL, + s_appInstance, + reinterpret_cast(this)); + + // signal ready + ARCH->lockMutex(m_mutex); + m_ready = true; + ARCH->broadcastCondVar(m_condVar); + ARCH->unlockMutex(m_mutex); + + // handle failure + if (m_hwnd == NULL) { + UnregisterClass((LPCTSTR)windowClass, s_appInstance); + return; + } + + try { + // main loop + MSG msg; + for (;;) { + // wait for message + if (ARCH->waitForEvent(NULL, -1.0) != IArchMultithread::kEvent) { + continue; + } + + // peek for message and remove it. we don't GetMessage() + // because we should never block here, only in waitForEvent(). + if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + continue; + } + + // check message against dialogs + if (!processDialogs(&msg)) { + // process message + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + catch (XThread&) { + // clean up + removeAllIcons(); + DestroyWindow(m_hwnd); + UnregisterClass((LPCTSTR)windowClass, s_appInstance); + throw; + } +} + +void* +CArchTaskBarWindows::threadEntry(void* self) +{ + reinterpret_cast(self)->threadMainLoop(); + return NULL; +} diff --git a/lib/arch/CArchTaskBarWindows.h b/lib/arch/CArchTaskBarWindows.h new file mode 100644 index 00000000..67e9af17 --- /dev/null +++ b/lib/arch/CArchTaskBarWindows.h @@ -0,0 +1,110 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef CARCHTASKBARWINDOWS_H +#define CARCHTASKBARWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "IArchTaskBar.h" +#include "IArchMultithread.h" +#include "stdmap.h" +#include "stdvector.h" +#include + +#define ARCH_TASKBAR CArchTaskBarWindows + +//! Win32 implementation of IArchTaskBar +class CArchTaskBarWindows : public IArchTaskBar { +public: + CArchTaskBarWindows(void*); + virtual ~CArchTaskBarWindows(); + + //! Add a dialog window + /*! + Tell the task bar event loop about a dialog. Win32 annoyingly + requires messages destined for modeless dialog boxes to be + dispatched differently than other messages. + */ + static void addDialog(HWND); + + //! Remove a dialog window + /*! + Remove a dialog window added via \c addDialog(). + */ + static void removeDialog(HWND); + + // IArchTaskBar overrides + virtual void addReceiver(IArchTaskBarReceiver*); + virtual void removeReceiver(IArchTaskBarReceiver*); + virtual void updateReceiver(IArchTaskBarReceiver*); + +private: + class CReceiverInfo { + public: + UINT m_id; + }; + + typedef std::map CReceiverToInfoMap; + typedef std::map CIDToReceiverMap; + typedef std::vector CIDStack; + typedef std::map CDialogs; + + UINT getNextID(); + void recycleID(UINT); + + void addIcon(UINT); + void removeIcon(UINT); + void updateIcon(UINT); + void addAllIcons(); + void removeAllIcons(); + void modifyIconNoLock(CReceiverToInfoMap::const_iterator, + DWORD taskBarMessage); + void removeIconNoLock(UINT id); + void handleIconMessage(IArchTaskBarReceiver*, LPARAM); + + bool processDialogs(MSG*); + LRESULT wndProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK + staticWndProc(HWND, UINT, WPARAM, LPARAM); + void threadMainLoop(); + static void* threadEntry(void*); + +private: + static CArchTaskBarWindows* s_instance; + static HINSTANCE s_appInstance; + + // multithread data + CArchMutex m_mutex; + CArchCond m_condVar; + bool m_ready; + int m_result; + CArchThread m_thread; + + // child thread data + HWND m_hwnd; + UINT m_taskBarRestart; + + // shared data + CReceiverToInfoMap m_receivers; + CIDToReceiverMap m_idTable; + CIDStack m_oldIDs; + UINT m_nextID; + + // dialogs + CDialogs m_dialogs; + CDialogs m_addedDialogs; +}; + +#endif diff --git a/lib/arch/CArchTaskBarXWindows.cpp b/lib/arch/CArchTaskBarXWindows.cpp new file mode 100644 index 00000000..6934f271 --- /dev/null +++ b/lib/arch/CArchTaskBarXWindows.cpp @@ -0,0 +1,47 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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 "CArchTaskBarXWindows.h" + +// +// CArchTaskBarXWindows +// + +CArchTaskBarXWindows::CArchTaskBarXWindows(void*) +{ + // do nothing +} + +CArchTaskBarXWindows::~CArchTaskBarXWindows() +{ + // do nothing +} + +void +CArchTaskBarXWindows::addReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} + +void +CArchTaskBarXWindows::removeReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} + +void +CArchTaskBarXWindows::updateReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} diff --git a/lib/arch/CArchTaskBarXWindows.h b/lib/arch/CArchTaskBarXWindows.h new file mode 100644 index 00000000..abf28012 --- /dev/null +++ b/lib/arch/CArchTaskBarXWindows.h @@ -0,0 +1,34 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef CARCHTASKBARXWINDOWS_H +#define CARCHTASKBARXWINDOWS_H + +#include "IArchTaskBar.h" + +#define ARCH_TASKBAR CArchTaskBarXWindows + +//! X11 implementation of IArchTaskBar +class CArchTaskBarXWindows : public IArchTaskBar { +public: + CArchTaskBarXWindows(void*); + virtual ~CArchTaskBarXWindows(); + + // IArchTaskBar overrides + virtual void addReceiver(IArchTaskBarReceiver*); + virtual void removeReceiver(IArchTaskBarReceiver*); + virtual void updateReceiver(IArchTaskBarReceiver*); +}; + +#endif diff --git a/lib/arch/IArchMultithread.h b/lib/arch/IArchMultithread.h index 05abd6be..ee376771 100644 --- a/lib/arch/IArchMultithread.h +++ b/lib/arch/IArchMultithread.h @@ -67,6 +67,13 @@ synergy. Each architecture must implement this interface. */ class IArchMultithread : public IInterface { public: + //! Result of waitForEvent() + enum EWaitResult { + kEvent, //!< An event is pending + kExit, //!< Thread exited + kTimeout //!< Wait timed out + }; + //! Type of thread entry point typedef void* (*ThreadFunc)(void*); //! Type of thread identifier @@ -102,10 +109,12 @@ public: //! Wait on a condition variable /*! - Waiting on a conditation variable for up to \c timeout seconds. + Wait on a conditation variable for up to \c timeout seconds. If \c timeout is < 0 then there is no timeout. The mutex must be locked when this method is called. The mutex is unlocked - during the wait and locked again before returning. + during the wait and locked again before returning. Returns + true if the condition variable was signalled and false on + timeout. (Cancellation point) */ @@ -206,14 +215,18 @@ public: //! Wait for a user event /*! - Waits for up to \c timeout seconds for a pending user event. - Returns true if an event occurred, false otherwise. + Waits for up to \c timeout seconds for a pending user event or + \c thread to exit (normally or by cancellation). Waits forever + if \c timeout < 0. Returns kEvent if an event occurred, kExit + if \c thread exited, or kTimeout if the timeout expired. If + \c thread is NULL then it doesn't wait for any thread to exit + and it will not return kExit. This method is not required by all platforms. (Cancellation point) */ - virtual bool waitForEvent(double timeout) = 0; + virtual EWaitResult waitForEvent(CArchThread thread, double timeout) = 0; //! Compare threads /*! diff --git a/lib/arch/IArchTaskBar.h b/lib/arch/IArchTaskBar.h new file mode 100644 index 00000000..2cd20ded --- /dev/null +++ b/lib/arch/IArchTaskBar.h @@ -0,0 +1,63 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef IARCHTASKBAR_H +#define IARCHTASKBAR_H + +#include "IInterface.h" + +class IArchTaskBarReceiver; + +//! Interface for architecture dependent task bar control +/*! +This interface defines the task bar icon operations required +by synergy. Each architecture must implement this interface +though each operation can be a no-op. +*/ +class IArchTaskBar : public IInterface { +public: + // Event data is architecture dependent + typedef void* Event; + + //! @name manipulators + //@{ + + //! Add a receiver + /*! + Add a receiver object to be notified of user and application + events. This should be called before other methods. When + the receiver is added to the task bar, its icon appears on + the task bar. + */ + virtual void addReceiver(IArchTaskBarReceiver*) = 0; + + //! Remove a receiver + /*! + Remove a receiver object from the task bar. This removes the + icon from the task bar. + */ + virtual void removeReceiver(IArchTaskBarReceiver*) = 0; + + //! Update a receiver + /*! + Updates the display of the receiver on the task bar. This + should be called when the receiver appearance may have changed + (e.g. it's icon or tool tip has changed). + */ + virtual void updateReceiver(IArchTaskBarReceiver*) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchTaskBarReceiver.h b/lib/arch/IArchTaskBarReceiver.h new file mode 100644 index 00000000..917f2fbf --- /dev/null +++ b/lib/arch/IArchTaskBarReceiver.h @@ -0,0 +1,90 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 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. + */ + +#ifndef IARCHTASKBARRECEIVER_H +#define IARCHTASKBARRECEIVER_H + +#include "IInterface.h" +#include "stdstring.h" + +//! Interface for architecture dependent task bar event handling +/*! +This interface defines the task bar icon event handlers required +by synergy. Each architecture must implement this interface +though each operation can be a no-op. +*/ +class IArchTaskBarReceiver : public IInterface { +public: + // Icon data is architecture dependent + typedef void* Icon; + + //! @name manipulators + //@{ + + //! Show status window + /*! + Open a window displaying current status. This should return + immediately without waiting for the window to be closed. + */ + virtual void showStatus() = 0; + + //! Popup menu + /*! + Popup a menu of operations at or around \c x,y and perform the + chosen operation. + */ + virtual void runMenu(int x, int y) = 0; + + //! Perform primary action + /*! + Perform the primary (default) action. + */ + virtual void primaryAction() = 0; + + //@} + //! @name accessors + //@{ + + //! Lock receiver + /*! + Locks the receiver from changing state. The receiver should be + locked when querying it's state to ensure consistent results. + Each call to \c lock() must have a matching \c unlock() and + locks cannot be nested. + */ + virtual void lock() const = 0; + + //! Unlock receiver + virtual void unlock() const = 0; + + //! Get icon + /*! + Returns the icon to display in the task bar. The interface + to set the icon is left to subclasses. Getting and setting + the icon must be thread safe. + */ + virtual const Icon getIcon() const = 0; + + //! Get tooltip + /*! + Returns the tool tip to display in the task bar. The interface + to set the tooltip is left to sublclasses. Getting and setting + the icon must be thread safe. + */ + virtual std::string getToolTip() const = 0; + + //@} +}; + +#endif diff --git a/lib/arch/Makefile.am b/lib/arch/Makefile.am index 008e2169..2b32473c 100644 --- a/lib/arch/Makefile.am +++ b/lib/arch/Makefile.am @@ -26,6 +26,7 @@ EXTRA_DIST = \ CArchNetworkWinsock.cpp \ CArchSleepWindows.cpp \ CArchStringWindows.cpp \ + CArchTaskBarWindows.cpp \ CArchTimeWindows.cpp \ XArchWindows.cpp \ CArchConsoleWindows.h \ @@ -37,6 +38,7 @@ EXTRA_DIST = \ CArchNetworkWinsock.h \ CArchSleepWindows.h \ CArchStringWindows.h \ + CArchTaskBarWindows.h \ CArchTimeWindows.h \ XArchWindows.h \ $(NULL) @@ -59,6 +61,8 @@ libarch_a_SOURCES = \ IArchNetwork.h \ IArchSleep.h \ IArchString.h \ + IArchTaskBar.h \ + IArchTaskBarReceiver.h \ IArchTime.h \ XArch.h \ $(NULL) @@ -72,6 +76,7 @@ EXTRA_libarch_a_SOURCES = \ CArchNetworkBSD.cpp \ CArchSleepUnix.cpp \ CArchStringUnix.cpp \ + CArchTaskBarXWindows.cpp \ CArchTimeUnix.cpp \ CMultibyte.cpp \ CMultibyteOS.cpp \ @@ -87,6 +92,7 @@ EXTRA_libarch_a_SOURCES = \ CArchNetworkBSD.h \ CArchSleepUnix.h \ CArchStringUnix.h \ + CArchTaskBarXWindows.h \ CArchTimeUnix.h \ XArchUnix.h \ $(NULL) diff --git a/lib/arch/arch.dsp b/lib/arch/arch.dsp index cb82663a..126a11e3 100644 --- a/lib/arch/arch.dsp +++ b/lib/arch/arch.dsp @@ -115,6 +115,10 @@ SOURCE=.\CArchDaemonWindows.h # End Source File # Begin Source File +SOURCE=.\CArchFileWindows.h +# End Source File +# Begin Source File + SOURCE=.\CArchImpl.h # End Source File # Begin Source File @@ -143,6 +147,10 @@ SOURCE=.\CArchStringWindows.h # End Source File # Begin Source File +SOURCE=.\CArchTaskBarWindows.h +# End Source File +# Begin Source File + SOURCE=.\CArchTimeWindows.h # End Source File # Begin Source File @@ -151,6 +159,14 @@ SOURCE=.\IArchConsole.h # End Source File # Begin Source File +SOURCE=.\IArchDaemon.h +# End Source File +# Begin Source File + +SOURCE=.\IArchFile.h +# End Source File +# Begin Source File + SOURCE=.\IArchLog.h # End Source File # Begin Source File @@ -171,6 +187,14 @@ SOURCE=.\IArchString.h # End Source File # Begin Source File +SOURCE=.\IArchTaskBar.h +# End Source File +# Begin Source File + +SOURCE=.\IArchTaskBarReceiver.h +# End Source File +# Begin Source File + SOURCE=.\IArchTime.h # End Source File # Begin Source File @@ -232,6 +256,11 @@ SOURCE=.\CArchStringWindows.cpp # End Source File # Begin Source File +SOURCE=.\CArchTaskBarWindows.cpp +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + SOURCE=.\CArchTimeWindows.cpp # PROP Exclude_From_Build 1 # End Source File diff --git a/lib/base/CJobList.cpp b/lib/base/CJobList.cpp new file mode 100644 index 00000000..37358890 --- /dev/null +++ b/lib/base/CJobList.cpp @@ -0,0 +1,113 @@ +/* + * 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 "CJobList.h" +#include "IJob.h" +#include "CArch.h" + +// +// CJobList +// + +CJobList::CJobList() +{ + m_mutex = ARCH->newMutex(); +} + +CJobList::~CJobList() +{ + ARCH->closeMutex(m_mutex); +} + +void +CJobList::addJob(IJob* job) +{ + // ignore bogus job + if (job == NULL) { + return; + } + + // make a temporary list with the job. later we'll splice this + // list into our jobs list. since splice() never throws we + // don't have to try/catch to unlock the mutex. + CJobs tmpList; + tmpList.push_front(job); + + // add job to list + ARCH->lockMutex(m_mutex); + m_jobs.splice(m_jobs.begin(), tmpList); + ARCH->unlockMutex(m_mutex); +} + +void +CJobList::removeJob(IJob* job) +{ + if (job != NULL) { + ARCH->lockMutex(m_mutex); + m_jobs.remove(job); + ARCH->unlockMutex(m_mutex); + } +} + +void +CJobList::runJobs() const +{ + // this is a little tricky. to allow any number of threads to + // traverse the list while jobs are added and removed while + // not holding the mutex when running a job (so other threads + // or the running job can add and remove jobs), we insert a + // new element into the list. this element has a NULL job and + // is a "safe place" while we traverse the list. the safe place + // is inserted at the start of the list (with the mutex locked) + // then, for each job, we lock the mutex and move the safe place + // past the next job, unlock the mutex, run the job and repeat + // until there are no more jobs. the safe place will not be + // removed by any other thread and is after every job that has + // been run and before every job that still needs to run. when + // all the jobs have been run we remove the safe place. + + // add the safe place + CJobs tmpList; + tmpList.push_front(NULL); + ARCH->lockMutex(m_mutex); + m_jobs.splice(m_jobs.begin(), tmpList); + CJobs::iterator safePlace = m_jobs.begin(); + + // find the next non-NULL job (NULL jobs are safe places) + CJobs::iterator next = safePlace; + while (next != m_jobs.end() && *next == NULL) + ++next; + while (next != m_jobs.end()) { + // found a job. run it without holding a lock. note the + // race condition here: we release the lock, allowing + // removeJob() to remove this job before we run the job. + // therefore the caller cannot safely destroy the job + // until all runJobs() complete. + IJob* job = *next; + ++next; + m_jobs.splice(next, m_jobs, safePlace); + ARCH->unlockMutex(m_mutex); + job->run(); + + // next real job + ARCH->lockMutex(m_mutex); + next = safePlace; + while (next != m_jobs.end() && *next == NULL) + ++next; + } + + // remove the safe place + m_jobs.erase(safePlace); + ARCH->unlockMutex(m_mutex); +} diff --git a/lib/base/CJobList.h b/lib/base/CJobList.h new file mode 100644 index 00000000..780d19da --- /dev/null +++ b/lib/base/CJobList.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#ifndef CJOBLIST_H +#define CJOBLIST_H + +#include "IArchMultithread.h" +#include "stdlist.h" + +class IJob; + +class CJobList { +public: + CJobList(); + ~CJobList(); + + //! @name manipulators + //@{ + + //! Add a job + /*! + Add a job to the list. The client keeps ownership of the job. + Jobs can be safely added while \c runJobs() is executing. + */ + void addJob(IJob*); + + //! Remove a job + /*! + Remove a job from the list. The client keeps ownership of the job. + Jobs can be safely removed while \c runJobs() is executing. + */ + void removeJob(IJob*); + + //@} + //! @name accessors + //@{ + + //! Run all jobs + /*! + Run all jobs in the list. Any number of threads can call + \c runJobs() at once. Jobs can be added and removed while + \c runJobs() is executing in the same or another thread. + Any job added after \c runJobs() starts will not be run + by that call to runJobs(). Destroying a removed job + while \c runJobs() is executing is not safe unless the + removed completed before \c runJobs() started. + */ + void runJobs() const; + + //@} + +private: + typedef std::list CJobs; + typedef CJobs::iterator iterator; + + CArchMutex m_mutex; + mutable CJobs m_jobs; +}; + +#endif + diff --git a/lib/base/LogOutputters.cpp b/lib/base/LogOutputters.cpp index 0f0050ce..f8e0b65e 100644 --- a/lib/base/LogOutputters.cpp +++ b/lib/base/LogOutputters.cpp @@ -156,3 +156,26 @@ CSystemLogOutputter::getNewline() const { return ""; } + + +// +// CSystemLogger +// + +CSystemLogger::CSystemLogger(const char* title) +{ + // redirect log messages + m_syslog = new CSystemLogOutputter; + m_stop = new CStopLogOutputter; + m_syslog->open(title); + CLOG->insert(m_stop); + CLOG->insert(m_syslog); +} + +CSystemLogger::~CSystemLogger() +{ + CLOG->remove(m_syslog); + CLOG->remove(m_stop); + delete m_stop; + delete m_syslog; +} diff --git a/lib/base/LogOutputters.h b/lib/base/LogOutputters.h index 74d18814..21602438 100644 --- a/lib/base/LogOutputters.h +++ b/lib/base/LogOutputters.h @@ -68,4 +68,22 @@ public: virtual const char* getNewline() const; }; +//! Write log to system log only +/*! +Creating an object of this type inserts a CStopLogOutputter followed +by a CSystemLogOutputter into CLog. The destructor removes those +outputters. Add one of these to any scope that needs to write to +the system log (only) and restore the old outputters when exiting +the scope. +*/ +class CSystemLogger { +public: + CSystemLogger(const char* title); + ~CSystemLogger(); + +private: + ILogOutputter* m_syslog; + ILogOutputter* m_stop; +}; + #endif diff --git a/lib/base/Makefile.am b/lib/base/Makefile.am index a89cf120..f750ba7f 100644 --- a/lib/base/Makefile.am +++ b/lib/base/Makefile.am @@ -26,6 +26,7 @@ MAINTAINERCLEANFILES = \ noinst_LIBRARIES = libbase.a libbase_a_SOURCES = \ CFunctionJob.cpp \ + CJobList.cpp \ CLog.cpp \ CStopwatch.cpp \ CStringUtil.cpp \ @@ -33,6 +34,7 @@ libbase_a_SOURCES = \ LogOutputters.cpp \ XBase.cpp \ CFunctionJob.h \ + CJobList.h \ CLog.h \ CStopwatch.h \ CString.h \ diff --git a/lib/base/base.dsp b/lib/base/base.dsp index a90dabc0..e0c96638 100644 --- a/lib/base/base.dsp +++ b/lib/base/base.dsp @@ -91,6 +91,10 @@ SOURCE=.\CFunctionJob.cpp # End Source File # Begin Source File +SOURCE=.\CJobList.cpp +# End Source File +# Begin Source File + SOURCE=.\CLog.cpp # End Source File # Begin Source File @@ -123,6 +127,10 @@ SOURCE=.\CFunctionJob.h # End Source File # Begin Source File +SOURCE=.\CJobList.h +# End Source File +# Begin Source File + SOURCE=.\CLog.h # End Source File # Begin Source File diff --git a/lib/client/CClient.cpp b/lib/client/CClient.cpp index 6f690ab1..7ae42424 100644 --- a/lib/client/CClient.cpp +++ b/lib/client/CClient.cpp @@ -52,7 +52,8 @@ CClient::CClient(const CString& clientName) : m_streamFilterFactory(NULL), m_session(NULL), m_active(false), - m_rejected(true) + m_rejected(true), + m_status(kNotRunning) { // do nothing } @@ -108,15 +109,61 @@ CClient::exitMainLoop() m_screen->exitMainLoop(); } +void +CClient::addStatusJob(IJob* job) +{ + m_statusJobs.addJob(job); +} + +void +CClient::removeStatusJob(IJob* job) +{ + m_statusJobs.removeJob(job); +} + bool CClient::wasRejected() const { return m_rejected; } +CClient::EStatus +CClient::getStatus(CString* msg) const +{ + CLock lock(&m_mutex); + if (msg != NULL) { + *msg = m_statusMessage; + } + return m_status; +} + +void +CClient::runStatusJobs() const +{ + m_statusJobs.runJobs(); +} + +void +CClient::setStatus(EStatus status, const char* msg) +{ + { + CLock lock(&m_mutex); + m_status = status; + if (m_status == kError) { + m_statusMessage = (msg == NULL) ? "Error" : msg; + } + else { + m_statusMessage = (msg == NULL) ? "" : msg; + } + } + runStatusJobs(); +} + void CClient::onError() { + setStatus(kError); + // close down session but don't wait too long deleteSession(3.0); } @@ -172,9 +219,11 @@ CClient::open() try { LOG((CLOG_INFO "opening screen")); openSecondaryScreen(); + setStatus(kNotRunning); } - catch (XScreenOpenFailure&) { + catch (XScreenOpenFailure& e) { // can't open screen + setStatus(kError, e.what()); LOG((CLOG_INFO "failed to open screen")); throw; } @@ -195,6 +244,7 @@ CClient::mainLoop() } try { + setStatus(kNotRunning); LOG((CLOG_NOTE "starting client \"%s\"", m_name.c_str())); // start server interactions @@ -213,6 +263,7 @@ CClient::mainLoop() } catch (XMT& e) { LOG((CLOG_ERR "client error: %s", e.what())); + setStatus(kError, e.what()); // clean up deleteSession(); @@ -221,6 +272,7 @@ CClient::mainLoop() } catch (XBase& e) { LOG((CLOG_ERR "client error: %s", e.what())); + setStatus(kError, e.what()); // clean up deleteSession(); @@ -229,6 +281,8 @@ CClient::mainLoop() m_rejected = false; } catch (XThread&) { + setStatus(kNotRunning); + // clean up deleteSession(); LOG((CLOG_NOTE "stopping client \"%s\"", m_name.c_str())); @@ -236,6 +290,7 @@ CClient::mainLoop() } catch (...) { LOG((CLOG_DEBUG "unknown client error")); + setStatus(kError); // clean up deleteSession(); @@ -529,11 +584,12 @@ CClient::runServer() { IDataSocket* socket = NULL; CServerProxy* proxy = NULL; + bool timedOut; try { for (;;) { try { // allow connect this much time to succeed - CTimerThread timer(15.0); + CTimerThread timer(15.0, &timedOut); // create socket and attempt to connect to server LOG((CLOG_DEBUG1 "connecting to server")); @@ -546,6 +602,7 @@ CClient::runServer() break; } catch (XSocketConnect& e) { + setStatus(kError, e.what()); LOG((CLOG_DEBUG "failed to connect to server: %s", e.what())); // failed to connect. if not camping then rethrow. @@ -565,31 +622,47 @@ CClient::runServer() m_server = proxy; } catch (XThread&) { - LOG((CLOG_ERR "connection timed out")); + if (timedOut) { + LOG((CLOG_ERR "connection timed out")); + setStatus(kError, "connection timed out"); + } + else { + // cancelled by some thread other than the timer + } delete socket; throw; } catch (XBase& e) { LOG((CLOG_ERR "connection failed: %s", e.what())); + setStatus(kError, e.what()); LOG((CLOG_DEBUG "disconnecting from server")); delete socket; return; } catch (...) { LOG((CLOG_ERR "connection failed: ")); + setStatus(kError); LOG((CLOG_DEBUG "disconnecting from server")); delete socket; return; } try { + // prepare for remote control + m_screen->remoteControl(); + // process messages bool rejected = true; if (proxy != NULL) { LOG((CLOG_DEBUG1 "communicating with server")); + setStatus(kRunning); rejected = !proxy->mainLoop(); + setStatus(kNotRunning); } + // prepare for local control + m_screen->localControl(); + // clean up CLock lock(&m_mutex); m_rejected = rejected; @@ -600,6 +673,8 @@ CClient::runServer() delete socket; } catch (...) { + setStatus(kNotRunning); + m_screen->localControl(); CLock lock(&m_mutex); m_rejected = false; m_server = NULL; @@ -664,9 +739,11 @@ CClient::handshakeServer(IDataSocket* socket) } catch (XIncompatibleClient& e) { LOG((CLOG_ERR "server has incompatible version %d.%d", e.getMajor(), e.getMinor())); + setStatus(kError, e.what()); } catch (XBase& e) { LOG((CLOG_WARN "error communicating with server: %s", e.what())); + setStatus(kError, e.what()); } catch (...) { // probably timed out diff --git a/lib/client/CClient.h b/lib/client/CClient.h index de0818a5..1cdcf3fa 100644 --- a/lib/client/CClient.h +++ b/lib/client/CClient.h @@ -20,6 +20,7 @@ #include "IClipboard.h" #include "CNetworkAddress.h" #include "CMutex.h" +#include "CJobList.h" class CSecondaryScreen; class CServerProxy; @@ -36,6 +37,13 @@ This class implements the top-level client algorithms for synergy. */ class CClient : public IScreenReceiver, public IClient { public: + enum EStatus { + kNotRunning, + kRunning, + kError, + kMaxStatus + }; + /*! This client will attempt to connect the server using \c clientName as its name. @@ -93,6 +101,22 @@ public: */ void exitMainLoop(); + //! Add a job to notify of status changes + /*! + The added job is run whenever the server's status changes in + certain externally visible ways. The client keeps ownership + of the job. + */ + void addStatusJob(IJob*); + + //! Remove a job to notify of status changes + /*! + Removes a previously added status notification job. A job can + remove itself when called but must not remove any other jobs. + The client keeps ownership of the job. + */ + void removeStatusJob(IJob*); + //@} //! @name accessors //@{ @@ -103,6 +127,12 @@ public: */ bool wasRejected() const; + //! Get the status + /*! + Returns the current status and status message. + */ + EStatus getStatus(CString* = NULL) const; + //@} // IScreenReceiver overrides @@ -140,6 +170,12 @@ public: virtual void getCursorCenter(SInt32& x, SInt32& y) const; private: + // notify status jobs of a change + void runStatusJobs() const; + + // set new status + void setStatus(EStatus, const char* msg = NULL); + // open/close the secondary screen void openSecondaryScreen(); void closeSecondaryScreen(); @@ -169,6 +205,11 @@ private: bool m_ownClipboard[kClipboardEnd]; IClipboard::Time m_timeClipboard[kClipboardEnd]; CString m_dataClipboard[kClipboardEnd]; + + // the status change jobs and status + CJobList m_statusJobs; + EStatus m_status; + CString m_statusMessage; }; #endif diff --git a/lib/client/CServerProxy.cpp b/lib/client/CServerProxy.cpp index ce0db115..6f6690cc 100644 --- a/lib/client/CServerProxy.cpp +++ b/lib/client/CServerProxy.cpp @@ -420,6 +420,7 @@ CServerProxy::translateModifierMask(KeyModifierMask mask) const if ((mask & KeyModifierSuper) != 0) { newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDSuper]]; } + return newMask; } void diff --git a/lib/mt/CThread.cpp b/lib/mt/CThread.cpp index 27b0cedd..fcf6832c 100644 --- a/lib/mt/CThread.cpp +++ b/lib/mt/CThread.cpp @@ -97,10 +97,16 @@ CThread::wait(double timeout) const return ARCH->wait(m_thread, timeout); } -bool -CThread::waitForEvent(double timeout) +CThread::EWaitResult +CThread::waitForEvent(double timeout) const { - return ARCH->waitForEvent(timeout); + // IArchMultithread EWaitResults map directly to our EWaitResults + static const EWaitResult s_map[] = { + kEvent, + kExit, + kTimeout + }; + return s_map[ARCH->waitForEvent(m_thread, timeout)]; } void* diff --git a/lib/mt/CThread.h b/lib/mt/CThread.h index 6592426b..73982612 100644 --- a/lib/mt/CThread.h +++ b/lib/mt/CThread.h @@ -39,6 +39,13 @@ documentation. // note -- do not derive from this class class CThread { public: + //! Result of waitForEvent() + enum EWaitResult { + kEvent, //!< An event is pending + kExit, //!< Thread exited + kTimeout //!< Wait timed out + }; + //! Run \c adoptedJob in a new thread /*! Create and start a new thread executing the \c adoptedJob. The @@ -159,18 +166,19 @@ public: //! Wait for an event (win32) /*! - Wait for the message queue to contain a message for up to \c timeout - seconds. This returns immediately if any message is available - (including messages that were already in the queue during the last - call to \c GetMessage() or \c PeekMessage() or waitForEvent(). - Returns true iff a message is available. This will wait forever - if \c timeout < 0.0. + Wait for the message queue to contain a message or for the thread + to exit for up to \c timeout seconds. This returns immediately if + any message is available (including messages that were already in + the queue during the last call to \c GetMessage() or + \c PeekMessage() or waitForEvent(). Returns kEvent if a message + is available, kExit if the thread exited, and kTimeout otherwise. + This will wait forever if \c timeout < 0.0. This method is available under win32 only. (cancellation point) */ - static bool waitForEvent(double timeout = -1.0); + EWaitResult waitForEvent(double timeout = -1.0) const; //! Get the exit result /*! diff --git a/lib/mt/CTimerThread.cpp b/lib/mt/CTimerThread.cpp index 963b5799..2ac63522 100644 --- a/lib/mt/CTimerThread.cpp +++ b/lib/mt/CTimerThread.cpp @@ -22,8 +22,13 @@ // CTimerThread // -CTimerThread::CTimerThread(double timeout) : m_timeout(timeout) +CTimerThread::CTimerThread(double timeout, bool* timedOut) : + m_timeout(timeout), + m_timedOut(timedOut) { + if (m_timedOut != NULL) { + *m_timedOut = false; + } if (m_timeout >= 0.0) { m_callingThread = new CThread(CThread::getCurrentThread()); m_timingThread = new CThread(new TMethodJob( @@ -53,5 +58,8 @@ CTimerThread::timer(void*) LOG((CLOG_DEBUG1 "timeout in %f seconds", m_timeout)); ARCH->sleep(m_timeout); LOG((CLOG_DEBUG1 "timeout")); + if (m_timedOut != NULL) { + *m_timedOut = true; + } m_callingThread->cancel(); } diff --git a/lib/mt/CTimerThread.h b/lib/mt/CTimerThread.h index c0a43c51..1a0a2b57 100644 --- a/lib/mt/CTimerThread.h +++ b/lib/mt/CTimerThread.h @@ -30,9 +30,11 @@ public: /*! Cancels the calling thread after \c timeout seconds unless destroyed before then. If \c timeout is less than zero then it never times - out and this is a no-op. + out and this is a no-op. If \c timedOutFlag is not NULL then it's + set to false in the c'tor and to true if the timeout exipires before + it's cancelled. */ - CTimerThread(double timeout); + CTimerThread(double timeout, bool* timedOutFlag = NULL); //! Cancel the timer thread ~CTimerThread(); @@ -45,6 +47,7 @@ private: private: double m_timeout; + bool* m_timedOut; CThread* m_callingThread; CThread* m_timingThread; }; diff --git a/lib/platform/CMSWindowsPrimaryScreen.cpp b/lib/platform/CMSWindowsPrimaryScreen.cpp index f8622970..cc3f5d9f 100644 --- a/lib/platform/CMSWindowsPrimaryScreen.cpp +++ b/lib/platform/CMSWindowsPrimaryScreen.cpp @@ -112,6 +112,12 @@ CMSWindowsPrimaryScreen::setOptions(const COptionsList& /*options*/) // no options } +UInt32 +CMSWindowsPrimaryScreen::addOneShotTimer(double timeout) +{ + return m_screen->addOneShotTimer(timeout); +} + KeyModifierMask CMSWindowsPrimaryScreen::getToggleMask() const { @@ -376,6 +382,12 @@ CMSWindowsPrimaryScreen::onEvent(CEvent* event) return false; } +void +CMSWindowsPrimaryScreen::onOneShotTimerExpired(UInt32 id) +{ + m_receiver->onOneShotTimerExpired(id); +} + SInt32 CMSWindowsPrimaryScreen::getJumpZoneSize() const { diff --git a/lib/platform/CMSWindowsPrimaryScreen.h b/lib/platform/CMSWindowsPrimaryScreen.h index 91dc2b11..c274de40 100644 --- a/lib/platform/CMSWindowsPrimaryScreen.h +++ b/lib/platform/CMSWindowsPrimaryScreen.h @@ -39,6 +39,7 @@ public: virtual void warpCursor(SInt32 x, SInt32 y); virtual void resetOptions(); virtual void setOptions(const COptionsList& options); + virtual UInt32 addOneShotTimer(double timeout); virtual KeyModifierMask getToggleMask() const; virtual bool isLockedToScreen() const; virtual IScreen* getScreen() const; @@ -47,6 +48,7 @@ public: virtual void onScreensaver(bool activated); virtual bool onPreDispatch(const CEvent* event); virtual bool onEvent(CEvent* event); + virtual void onOneShotTimerExpired(UInt32 id); virtual SInt32 getJumpZoneSize() const; virtual void postCreateWindow(HWND); virtual void preDestroyWindow(HWND); diff --git a/lib/platform/CMSWindowsScreen.cpp b/lib/platform/CMSWindowsScreen.cpp index d1471218..188c070c 100644 --- a/lib/platform/CMSWindowsScreen.cpp +++ b/lib/platform/CMSWindowsScreen.cpp @@ -122,6 +122,11 @@ CMSWindowsScreen::closeDesktop() // remove timer if (m_timer != 0) { KillTimer(NULL, m_timer); + m_timer = 0; + } + if (m_oneShotTimer != 0) { + KillTimer(NULL, m_oneShotTimer); + m_oneShotTimer = 0; } // disconnect from desktop @@ -134,6 +139,18 @@ CMSWindowsScreen::closeDesktop() assert(m_desk == NULL); } +UInt32 +CMSWindowsScreen::addOneShotTimer(double timeout) +{ + // FIXME -- support multiple one-shot timers + if (m_oneShotTimer != 0) { + KillTimer(NULL, m_oneShotTimer); + } + m_oneShotTimer = SetTimer(NULL, 0, + static_cast(1000.0 * timeout), NULL); + return 0; +} + bool CMSWindowsScreen::isMultimon() const { @@ -205,8 +222,12 @@ CMSWindowsScreen::mainLoop() event.m_result = 0; for (;;) { // wait for an event in a cancellable way - CThread::waitForEvent(); - GetMessage(&event.m_msg, NULL, 0, 0); + if (CThread::getCurrentThread().waitForEvent(-1.0) != CThread::kEvent) { + continue; + } + if (!PeekMessage(&event.m_msg, NULL, 0, 0, PM_REMOVE)) { + continue; + } // handle quit message if (event.m_msg.message == WM_QUIT) { @@ -467,27 +488,35 @@ CMSWindowsScreen::onPreDispatch(const CEvent* event) } case WM_TIMER: - // if current desktop is not the input desktop then switch to it. - // windows 95 doesn't support multiple desktops so don't bother - // to check under it. - if (!m_is95Family) { - HDESK desk = openInputDesktop(); - if (desk != NULL) { - if (isCurrentDesktop(desk)) { - CloseDesktop(desk); - } - else if (!m_screensaver->isActive()) { - // don't switch desktops when the screensaver is - // active. we'd most likely switch to the - // screensaver desktop which would have the side - // effect of forcing the screensaver to stop. - switchDesktop(desk); - } - else { - CloseDesktop(desk); + if (msg->wParam == m_timer) { + // if current desktop is not the input desktop then switch to it. + // windows 95 doesn't support multiple desktops so don't bother + // to check under it. + if (!m_is95Family) { + HDESK desk = openInputDesktop(); + if (desk != NULL) { + if (isCurrentDesktop(desk)) { + CloseDesktop(desk); + } + else if (!m_screensaver->isActive()) { + // don't switch desktops when the screensaver is + // active. we'd most likely switch to the + // screensaver desktop which would have the side + // effect of forcing the screensaver to stop. + switchDesktop(desk); + } + else { + CloseDesktop(desk); + } } } } + else if (msg->wParam == m_oneShotTimer) { + // one shot timer expired + KillTimer(NULL, m_oneShotTimer); + m_oneShotTimer = 0; + m_eventHandler->onOneShotTimerExpired(0); + } return true; } diff --git a/lib/platform/CMSWindowsScreen.h b/lib/platform/CMSWindowsScreen.h index 6183d50f..d11f5ef1 100644 --- a/lib/platform/CMSWindowsScreen.h +++ b/lib/platform/CMSWindowsScreen.h @@ -64,6 +64,14 @@ public: */ void closeDesktop(); + //! Install a one-shot timer + /*! + Installs a one-shot timer for \c timeout seconds and returns the + id of the timer (which will be passed to the receiver's + \c onTimerExpired()). + */ + UInt32 addOneShotTimer(double timeout); + //@} //! @name accessors //@{ @@ -169,6 +177,9 @@ private: // the timer used to check for desktop switching UINT m_timer; + // the one shot timer + UINT m_oneShotTimer; + // the current desk and it's name HDESK m_desk; CString m_deskName; diff --git a/lib/platform/CMSWindowsSecondaryScreen.cpp b/lib/platform/CMSWindowsSecondaryScreen.cpp index 7783e885..a1e14d04 100644 --- a/lib/platform/CMSWindowsSecondaryScreen.cpp +++ b/lib/platform/CMSWindowsSecondaryScreen.cpp @@ -262,6 +262,12 @@ CMSWindowsSecondaryScreen::onEvent(CEvent* event) return false; } +void +CMSWindowsSecondaryScreen::onOneShotTimerExpired(UInt32) +{ + // ignore +} + SInt32 CMSWindowsSecondaryScreen::getJumpZoneSize() const { @@ -272,6 +278,11 @@ void CMSWindowsSecondaryScreen::postCreateWindow(HWND window) { m_window = window; + + // update key state + updateKeys(); + + // hide cursor if this screen isn't active if (!isActive()) { showWindow(); } diff --git a/lib/platform/CMSWindowsSecondaryScreen.h b/lib/platform/CMSWindowsSecondaryScreen.h index 0681e236..acaa10b0 100644 --- a/lib/platform/CMSWindowsSecondaryScreen.h +++ b/lib/platform/CMSWindowsSecondaryScreen.h @@ -53,6 +53,7 @@ public: virtual void onScreensaver(bool activated); virtual bool onPreDispatch(const CEvent* event); virtual bool onEvent(CEvent* event); + virtual void onOneShotTimerExpired(UInt32 id); virtual SInt32 getJumpZoneSize() const; virtual void postCreateWindow(HWND); virtual void preDestroyWindow(HWND); @@ -69,6 +70,7 @@ protected: virtual void hideWindow(); virtual void warpCursor(SInt32 x, SInt32 y); virtual void updateKeys(); + virtual void releaseKeys(); virtual void setToggleState(KeyModifierMask); virtual KeyModifierMask getToggleState() const; @@ -98,7 +100,6 @@ private: KeyModifierMask, EKeyAction) const; void doKeystrokes(const Keystrokes&, SInt32 count); - void releaseKeys(); void toggleKey(UINT virtualKey, KeyModifierMask mask); UINT virtualKeyToScanCode(UINT& virtualKey) const; bool isExtendedKey(UINT virtualKey) const; diff --git a/lib/platform/CSynergyHook.cpp b/lib/platform/CSynergyHook.cpp index a68aade9..8cd3271f 100644 --- a/lib/platform/CSynergyHook.cpp +++ b/lib/platform/CSynergyHook.cpp @@ -260,6 +260,12 @@ mouseHook(int code, WPARAM wParam, LPARAM lParam) PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); return 1; } + else { + x += g_xScreen; + y += g_yScreen; + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); + return 0; + } } } diff --git a/lib/platform/CXWindowsSecondaryScreen.cpp b/lib/platform/CXWindowsSecondaryScreen.cpp index 31f1affc..c398d6ac 100644 --- a/lib/platform/CXWindowsSecondaryScreen.cpp +++ b/lib/platform/CXWindowsSecondaryScreen.cpp @@ -356,7 +356,7 @@ CXWindowsSecondaryScreen::destroyWindow() CDisplayLock display(m_screen); if (display != NULL) { // release keys that are still pressed - releaseKeys(display); + doReleaseKeys(display); // no longer impervious to server grabs XTestGrabControl(display, False); @@ -901,7 +901,7 @@ CXWindowsSecondaryScreen::maskToX(KeyModifierMask inMask) const } void -CXWindowsSecondaryScreen::releaseKeys(Display* display) +CXWindowsSecondaryScreen::doReleaseKeys(Display* display) { assert(display != NULL); @@ -969,6 +969,15 @@ CXWindowsSecondaryScreen::updateKeys() updateModifiers(display); } +void +CXWindowsSecondaryScreen::releaseKeys() +{ + CDisplayLock display(m_screen); + if (display != NULL) { + doReleaseKeys(display); + } +} + void CXWindowsSecondaryScreen::updateModifiers(Display* display) { diff --git a/lib/platform/CXWindowsSecondaryScreen.h b/lib/platform/CXWindowsSecondaryScreen.h index 6711ee0f..96482f9f 100644 --- a/lib/platform/CXWindowsSecondaryScreen.h +++ b/lib/platform/CXWindowsSecondaryScreen.h @@ -67,6 +67,7 @@ protected: virtual void hideWindow(); virtual void warpCursor(SInt32 x, SInt32 y); virtual void updateKeys(); + virtual void releaseKeys(); virtual void setToggleState(KeyModifierMask); virtual KeyModifierMask getToggleState() const; @@ -97,14 +98,10 @@ private: unsigned int mapKey(Keystrokes&, KeyCode&, KeyID, KeyModifierMask, EKeyAction) const; -/* - bool findKeyCode(KeyCode&, unsigned int&, - KeyID id, unsigned int) const; -*/ void doKeystrokes(const Keystrokes&, SInt32 count); unsigned int maskToX(KeyModifierMask) const; - void releaseKeys(Display*); + void doReleaseKeys(Display*); void updateKeycodeMap(Display* display); void updateModifiers(Display* display); void updateModifierMap(Display* display); diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp index 37c43133..65067b1a 100644 --- a/lib/server/CServer.cpp +++ b/lib/server/CServer.cpp @@ -66,7 +66,8 @@ CServer::CServer(const CString& serverName) : m_switchWaitEngaged(false), m_switchTwoTapDelay(0.0), m_switchTwoTapEngaged(false), - m_switchTwoTapArmed(false) + m_switchTwoTapArmed(false), + m_status(kNotRunning) { // do nothing } @@ -85,14 +86,17 @@ CServer::open() try { LOG((CLOG_INFO "opening screen")); openPrimaryScreen(); + setStatus(kNotRunning); } - catch (XScreen&) { + catch (XScreen& e) { // can't open screen + setStatus(kError, e.what()); LOG((CLOG_INFO "failed to open screen")); throw; } catch (XUnknownClient& e) { // can't open screen + setStatus(kServerNameUnknown); LOG((CLOG_CRIT "unknown screen name `%s'", e.getName().c_str())); throw; } @@ -108,6 +112,7 @@ CServer::mainLoop() } try { + setStatus(kNotRunning); LOG((CLOG_NOTE "starting server")); // start listening for new clients @@ -138,11 +143,13 @@ CServer::mainLoop() stopThreads(); \ delete m_httpServer; \ m_httpServer = NULL; \ + runStatusJobs(); \ } while (false) FINALLY; } catch (XMT& e) { LOG((CLOG_ERR "server error: %s", e.what())); + setStatus(kError, e.what()); // clean up LOG((CLOG_NOTE "stopping server")); @@ -151,12 +158,15 @@ CServer::mainLoop() } catch (XBase& e) { LOG((CLOG_ERR "server error: %s", e.what())); + setStatus(kError, e.what()); // clean up LOG((CLOG_NOTE "stopping server")); FINALLY; } catch (XThread&) { + setStatus(kNotRunning); + // clean up LOG((CLOG_NOTE "stopping server")); FINALLY; @@ -164,6 +174,7 @@ CServer::mainLoop() } catch (...) { LOG((CLOG_DEBUG "unknown server error")); + setStatus(kError); // clean up LOG((CLOG_NOTE "stopping server")); @@ -260,6 +271,9 @@ CServer::setConfig(const CConfig& config) sendOptions(client); } + // notify of status + runStatusJobs(); + return true; } @@ -287,12 +301,52 @@ CServer::setStreamFilterFactory(IStreamFilterFactory* adopted) m_streamFilterFactory = adopted; } +void +CServer::addStatusJob(IJob* job) +{ + m_statusJobs.addJob(job); +} + +void +CServer::removeStatusJob(IJob* job) +{ + m_statusJobs.removeJob(job); +} + CString CServer::getPrimaryScreenName() const { return m_name; } +UInt32 +CServer::getNumClients() const +{ + CLock lock(&m_mutex); + return m_clients.size(); +} + +void +CServer::getClients(std::vector& list) const +{ + CLock lock(&m_mutex); + list.clear(); + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + list.push_back(index->first); + } +} + +CServer::EStatus +CServer::getStatus(CString* msg) const +{ + CLock lock(&m_mutex); + if (msg != NULL) { + *msg = m_statusMessage; + } + return m_status; +} + void CServer::getConfig(CConfig* config) const { @@ -302,6 +356,28 @@ CServer::getConfig(CConfig* config) const *config = m_config; } +void +CServer::runStatusJobs() const +{ + m_statusJobs.runJobs(); +} + +void +CServer::setStatus(EStatus status, const char* msg) +{ + { + CLock lock(&m_mutex); + m_status = status; + if (m_status == kError) { + m_statusMessage = (msg == NULL) ? "Error" : msg; + } + else { + m_statusMessage = (msg == NULL) ? "" : msg; + } + } + runStatusJobs(); +} + UInt32 CServer::getActivePrimarySides() const { @@ -325,6 +401,8 @@ CServer::getActivePrimarySides() const void CServer::onError() { + setStatus(kError); + // stop all running threads but don't wait too long since some // threads may be unable to proceed until this thread returns. stopThreads(3.0); @@ -743,6 +821,10 @@ CServer::onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy) case kBottom: clearWait = (m_y <= ay + ah - 1 + zoneSize); break; + + default: + clearWait = false; + break; } if (clearWait) { onNoSwitch(); @@ -1174,7 +1256,7 @@ CServer::isSwitchOkay(IClient* newScreen, EDirection dir, SInt32 x, SInt32 y) // if waiting before a switch then prepare to switch later if (!allowSwitch && m_switchWaitDelay > 0.0) { - if (isNewDirection) { + if (isNewDirection || !m_switchWaitEngaged) { m_switchWaitEngaged = true; m_switchWaitX = x; m_switchWaitY = y; @@ -1413,6 +1495,7 @@ CServer::acceptClients(void*) break; } catch (XSocketAddressInUse& e) { + setStatus(kError, e.what()); LOG((CLOG_WARN "bind failed: %s", e.what())); // give up if we've waited too long @@ -1427,6 +1510,7 @@ CServer::acceptClients(void*) } // accept connections and begin processing them + setStatus(kRunning); LOG((CLOG_DEBUG1 "waiting for client connections")); for (;;) { // accept connection @@ -1439,16 +1523,15 @@ CServer::acceptClients(void*) startThread(new TMethodJob( this, &CServer::runClient, socket)); } - - // clean up - delete listen; } catch (XBase& e) { + setStatus(kError, e.what()); LOG((CLOG_ERR "cannot listen for clients: %s", e.what())); delete listen; exitMainLoopWithError(); } catch (...) { + setStatus(kNotRunning); delete listen; throw; } @@ -1923,71 +2006,84 @@ CServer::addConnection(IClient* client) LOG((CLOG_DEBUG "adding connection \"%s\"", client->getName().c_str())); - CLock lock(&m_mutex); + { + CLock lock(&m_mutex); - // name must be in our configuration - if (!m_config.isScreen(client->getName())) { - throw XUnknownClient(client->getName()); + // name must be in our configuration + if (!m_config.isScreen(client->getName())) { + throw XUnknownClient(client->getName()); + } + + // can only have one screen with a given name at any given time + if (m_clients.count(client->getName()) != 0) { + throw XDuplicateClient(client->getName()); + } + + // save screen info + m_clients.insert(std::make_pair(client->getName(), client)); + LOG((CLOG_DEBUG "added connection \"%s\"", client->getName().c_str())); } - - // can only have one screen with a given name at any given time - if (m_clients.count(client->getName()) != 0) { - throw XDuplicateClient(client->getName()); - } - - // save screen info - m_clients.insert(std::make_pair(client->getName(), client)); - LOG((CLOG_DEBUG "added connection \"%s\"", client->getName().c_str())); + runStatusJobs(); } void CServer::removeConnection(const CString& name) { LOG((CLOG_DEBUG "removing connection \"%s\"", name.c_str())); - CLock lock(&m_mutex); + bool updateStatus; + { + CLock lock(&m_mutex); - // find client - CClientList::iterator index = m_clients.find(name); - assert(index != m_clients.end()); + // find client + CClientList::iterator index = m_clients.find(name); + assert(index != m_clients.end()); - // if this is active screen then we have to jump off of it - IClient* active = (m_activeSaver != NULL) ? m_activeSaver : m_active; - if (active == index->second && active != m_primaryClient) { - // record new position (center of primary screen) - m_primaryClient->getCursorCenter(m_x, m_y); + // if this is active screen then we have to jump off of it + IClient* active = (m_activeSaver != NULL) ? m_activeSaver : m_active; + if (active == index->second && active != m_primaryClient) { + // record new position (center of primary screen) + m_primaryClient->getCursorCenter(m_x, m_y); - // stop waiting to switch if we were - if (active == m_switchScreen) { - clearSwitchState(); + // stop waiting to switch if we were + if (active == m_switchScreen) { + clearSwitchState(); + } + + // don't notify active screen since it probably already + // disconnected. + LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", active->getName().c_str(), m_primaryClient->getName().c_str(), m_x, m_y)); + + // cut over + m_active = m_primaryClient; + + // enter new screen (unless we already have because of the + // screen saver) + if (m_activeSaver == NULL) { + m_primaryClient->enter(m_x, m_y, m_seqNum, + m_primaryClient->getToggleMask(), false); + } } - // don't notify active screen since it probably already disconnected - LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", active->getName().c_str(), m_primaryClient->getName().c_str(), m_x, m_y)); - - // cut over - m_active = m_primaryClient; - - // enter new screen (unless we already have because of the - // screen saver) - if (m_activeSaver == NULL) { - m_primaryClient->enter(m_x, m_y, m_seqNum, - m_primaryClient->getToggleMask(), false); + // if this screen had the cursor when the screen saver activated + // then we can't switch back to it when the screen saver + // deactivates. + if (m_activeSaver == index->second) { + m_activeSaver = NULL; } + + // done with client + delete index->second; + m_clients.erase(index); + + // remove any thread for this client + m_clientThreads.erase(name); + + updateStatus = (m_clients.size() <= 1); } - // if this screen had the cursor when the screen saver activated - // then we can't switch back to it when the screen saver - // deactivates. - if (m_activeSaver == index->second) { - m_activeSaver = NULL; + if (updateStatus) { + runStatusJobs(); } - - // done with client - delete index->second; - m_clients.erase(index); - - // remove any thread for this client - m_clientThreads.erase(name); } diff --git a/lib/server/CServer.h b/lib/server/CServer.h index 11896830..8e644853 100644 --- a/lib/server/CServer.h +++ b/lib/server/CServer.h @@ -22,9 +22,11 @@ #include "CCondVar.h" #include "CMutex.h" #include "CThread.h" +#include "CJobList.h" #include "CStopwatch.h" #include "stdlist.h" #include "stdmap.h" +#include "stdvector.h" class CClientProxy; class CHTTPServer; @@ -42,6 +44,14 @@ This class implements the top-level server algorithms for synergy. */ class CServer : public IServer, public IPrimaryScreenReceiver { public: + enum EStatus { + kNotRunning, + kRunning, + kServerNameUnknown, + kError, + kMaxStatus + }; + /*! The server will look itself up in the configuration using \c serverName as its name. @@ -116,6 +126,22 @@ public: */ void setStreamFilterFactory(IStreamFilterFactory*); + //! Add a job to notify of status changes + /*! + The added job is run whenever the server's status changes in + certain externally visible ways. The client keeps ownership + of the job. + */ + void addStatusJob(IJob*); + + //! Remove a job to notify of status changes + /*! + Removes a previously added status notification job. A job can + remove itself when called but must not remove any other jobs. + The client keeps ownership of the job. + */ + void removeStatusJob(IJob*); + //@} //! @name accessors //@{ @@ -132,6 +158,24 @@ public: */ CString getPrimaryScreenName() const; + //! Get number of connected clients + /*! + Returns the number of connected clients, including the server itself. + */ + UInt32 getNumClients() const; + + //! Get the list of connected clients + /*! + Set the \c list to the names of the currently connected clients. + */ + void getClients(std::vector& list) const; + + //! Get the status + /*! + Returns the current status and status message. + */ + EStatus getStatus(CString* = NULL) const; + //@} // IServer overrides @@ -170,6 +214,12 @@ protected: private: typedef std::list CThreadList; + // notify status jobs of a change + void runStatusJobs() const; + + // set new status + void setStatus(EStatus, const char* msg = NULL); + // get the sides of the primary screen that have neighbors UInt32 getActivePrimarySides() const; @@ -352,6 +402,11 @@ private: CStopwatch m_switchTwoTapTimer; bool m_switchTwoTapEngaged; bool m_switchTwoTapArmed; + + // the status change jobs and status + CJobList m_statusJobs; + EStatus m_status; + CString m_statusMessage; }; #endif diff --git a/lib/synergy/CSecondaryScreen.cpp b/lib/synergy/CSecondaryScreen.cpp index 554e91db..5df455a8 100644 --- a/lib/synergy/CSecondaryScreen.cpp +++ b/lib/synergy/CSecondaryScreen.cpp @@ -74,17 +74,6 @@ CSecondaryScreen::open() // create and prepare our window createWindow(); - // assume primary has all clipboards - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - grabClipboard(id); - } - - // update keyboard state - updateKeys(); - - // disable the screen saver - getScreen()->openScreensaver(false); - // subclass hook onPostOpen(); @@ -95,6 +84,30 @@ CSecondaryScreen::open() close(); throw; } +} + +void +CSecondaryScreen::close() +{ + onPreClose(); + destroyWindow(); + getScreen()->close(); + onPostClose(); +} + +void +CSecondaryScreen::remoteControl() +{ + // assume primary has all clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + grabClipboard(id); + } + + // update keyboard state + updateKeys(); + + // disable the screen saver + getScreen()->openScreensaver(false); // hide the cursor { @@ -105,13 +118,9 @@ CSecondaryScreen::open() } void -CSecondaryScreen::close() +CSecondaryScreen::localControl() { - onPreClose(); getScreen()->closeScreensaver(); - destroyWindow(); - getScreen()->close(); - onPostClose(); } void diff --git a/lib/synergy/CSecondaryScreen.h b/lib/synergy/CSecondaryScreen.h index bca229b8..3ffeba2b 100644 --- a/lib/synergy/CSecondaryScreen.h +++ b/lib/synergy/CSecondaryScreen.h @@ -41,11 +41,10 @@ public: //! Open screen /*! - Opens the screen. This includes initializing the screen, - hiding the cursor, and disabling the screen saver. It also causes - events to the reported to an IScreenReceiver (which is set through - some other interface). Calls close() before returning (rethrowing) - if it fails for any reason. + Opens the screen. It also causes events to the reported to an + IScreenReceiver (which is set through some other interface). + Calls close() before returning (rethrowing) if it fails for any + reason. */ void open(); @@ -64,12 +63,26 @@ public: */ void exitMainLoop(); + //! Prepare for remote control + /*! + Prepares the screen for remote control by the server. In + particular, it disables the screen saver. + */ + void remoteControl(); + + //! Release from remote control + /*! + Cleans up the screen from remote control by the server. In + particular, it enables the screen saver. It also synthesizes + key up events for any keys that are logically down; without + this the client will leave its keyboard in the wrong logical + state. + */ + void localControl(); + //! Close screen /*! - Closes the screen. This restores the screen saver, shows the cursor - and closes the screen. It also synthesizes key up events for any - keys that are logically down; without this the client will leave - its keyboard in the wrong logical state. + Closes the screen. */ void close(); @@ -332,6 +345,13 @@ protected: */ virtual void updateKeys() = 0; + //! Release keys + /*! + Synthesizes key release event for any key that our key state + says is down. + */ + virtual void releaseKeys() = 0; + //! Synchronize toggle key state /*! Toggle modifiers that don't match the given state so that they do.