From f974d8d68077a96e014373df1ff78c32f9d44cde Mon Sep 17 00:00:00 2001 From: Nick Bolton Date: Mon, 31 May 2010 21:30:29 +0000 Subject: [PATCH] Some work toward Issue 27 and Issue 319 --- CMakeLists.txt | 31 +- cmake/CMakeLists_cpack.txt | 14 +- cmake/CMakeLists_lib.txt | 32 +- cmake/CMakeLists_synergyc.txt | 3 +- cmake/CMakeLists_synergys.txt | 4 +- cmd/launcher/CAutoStart.cpp | 4 +- cmd/launcher/launcher.cpp | 4 +- .../CMSWindowsClientTaskBarReceiver.cpp | 19 + cmd/synergyc/COSXClientTaskBarReceiver.cpp | 7 + .../CXWindowsClientTaskBarReceiver.cpp | 6 + cmd/synergyc/synergyc.cpp | 932 +----------- .../CMSWindowsServerTaskBarReceiver.cpp | 23 +- cmd/synergys/COSXServerTaskBarReceiver.cpp | 6 + .../CXWindowsServerTaskBarReceiver.cpp | 6 + cmd/synergys/synergys.cpp | 1340 +---------------- gui/qsynergy.pro | 2 + gui/res/win/QSynergy.ico | Bin 8478 -> 287934 bytes gui/res/win/QSynergy.rc | 2 +- gui/src/AppConfig.cpp | 2 +- gui/src/MainWindow.cpp | 2 +- gui/src/main.cpp | 8 +- lib/arch/CArch.cpp | 45 +- lib/arch/CArch.h | 18 +- lib/arch/CArchAppUtil.cpp | 55 + lib/arch/CArchAppUtil.h | 33 + lib/arch/CArchAppUtilUnix.cpp | 56 + lib/arch/CArchAppUtilUnix.h | 28 + lib/arch/CArchAppUtilWindows.cpp | 268 ++++ lib/arch/CArchAppUtilWindows.h | 63 + lib/arch/CArchConsoleStd.cpp | 24 + lib/arch/CArchConsoleStd.h | 30 + lib/arch/CArchConsoleUnix.cpp | 46 +- lib/arch/CArchConsoleUnix.h | 19 +- lib/arch/CArchConsoleWindows.cpp | 458 +----- lib/arch/CArchConsoleWindows.h | 60 +- lib/arch/CArchDaemonWindows.cpp | 7 +- lib/arch/CArchMiscWindows.cpp | 988 ++++++------ lib/arch/CArchMiscWindows.h | 19 +- lib/arch/CArchTaskBarWindows.cpp | 20 +- lib/arch/CArchTaskBarWindows.h | 5 +- lib/arch/CArchTaskBarXWindows.cpp | 2 +- lib/arch/CArchTaskBarXWindows.h | 2 +- lib/arch/IArchAppUtil.h | 29 + lib/arch/IArchConsole.h | 7 - lib/arch/IArchTaskBarReceiver.h | 5 + lib/base/CLog.cpp | 619 ++++---- lib/base/CLog.h | 33 +- lib/base/ILogOutputter.h | 12 - lib/base/LogOutputters.cpp | 40 +- lib/base/LogOutputters.h | 10 +- lib/client/CClient.cpp | 11 +- lib/client/CClient.h | 6 +- lib/common/common.h | 13 + lib/net/CTCPListenSocket.cpp | 13 +- lib/net/CTCPSocket.cpp | 6 +- lib/net/IDataSocket.h | 5 +- lib/platform/CMSWindowsDesks.cpp | 18 +- lib/platform/CMSWindowsDesks.h | 5 +- lib/platform/CMSWindowsScreen.cpp | 17 +- lib/platform/CMSWindowsScreen.h | 5 +- lib/server/CServer.cpp | 20 +- lib/server/CServer.h | 8 +- lib/synergy/CApp.cpp | 273 ++++ lib/synergy/CApp.h | 105 ++ lib/synergy/CClientApp.cpp | 616 ++++++++ lib/synergy/CClientApp.h | 84 ++ .../synergy}/CClientTaskBarReceiver.cpp | 0 .../synergy}/CClientTaskBarReceiver.h | 8 +- lib/synergy/CKeyMap.cpp | 2 +- lib/synergy/CServerApp.cpp | 969 ++++++++++++ lib/synergy/CServerApp.h | 130 ++ .../synergy}/CServerTaskBarReceiver.cpp | 18 + .../synergy}/CServerTaskBarReceiver.h | 14 +- lib/synergy/INode.h | 19 + 74 files changed, 4057 insertions(+), 3756 deletions(-) create mode 100644 lib/arch/CArchAppUtil.cpp create mode 100644 lib/arch/CArchAppUtil.h create mode 100644 lib/arch/CArchAppUtilUnix.cpp create mode 100644 lib/arch/CArchAppUtilUnix.h create mode 100644 lib/arch/CArchAppUtilWindows.cpp create mode 100644 lib/arch/CArchAppUtilWindows.h create mode 100644 lib/arch/CArchConsoleStd.cpp create mode 100644 lib/arch/CArchConsoleStd.h create mode 100644 lib/arch/IArchAppUtil.h create mode 100644 lib/synergy/CApp.cpp create mode 100644 lib/synergy/CApp.h create mode 100644 lib/synergy/CClientApp.cpp create mode 100644 lib/synergy/CClientApp.h rename {cmd/synergyc => lib/synergy}/CClientTaskBarReceiver.cpp (100%) rename {cmd/synergyc => lib/synergy}/CClientTaskBarReceiver.h (89%) create mode 100644 lib/synergy/CServerApp.cpp create mode 100644 lib/synergy/CServerApp.h rename {cmd/synergys => lib/synergy}/CServerTaskBarReceiver.cpp (87%) rename {cmd/synergys => lib/synergy}/CServerTaskBarReceiver.h (84%) create mode 100644 lib/synergy/INode.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9dd3a349..cd6a37c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # Version number for Synergy+ SET(VERSION_MAJOR 1) -SET(VERSION_MINOR 3) -SET(VERSION_REV 5) +SET(VERSION_MINOR 4) +SET(VERSION_REV 0) SET(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REV}") @@ -44,11 +44,28 @@ INCLUDE(${cmake_dir}/CMakeLists_cpack.txt) IF(WIN32) # add /analyze in order to unconver potential bugs in the source code # Details: http://msdn.microsoft.com/en-us/library/fwkeyyhe.aspx - SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /analyze") - SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /analyze") + # add /FR to generate browse information (ncb files) usefull for using IDE + + #define _BIND_TO_CURRENT_CRT_VERSION 1 + #define _BIND_TO_CURRENT_ATL_VERSION 1 + #define _BIND_TO_CURRENT_MFC_VERSION 1 + #define _BIND_TO_CURRENT_OPENMP_VERSION 1 + # next line replaced the previous 4 ones: + #define _BIND_TO_CURRENT_VCLIBS_VERSION 1; + + # compiler: /MP - use multi cores to compile + # added _SECURE_SCL=1 for finding bugs with iterators - http://msdn.microsoft.com/en-us/library/aa985965.aspx + + # common args between all vs builds + SET(VS_ARGS "/FR /MP /D _BIND_TO_CURRENT_VCLIBS_VERSION=1 /D _SECURE_SCL=1 ${VS_ARGS_EXTRA}") + + # we may use `cmake -D VS_ARGS_EXTRA="/analyze"` for example to specify + # analyze mode (since we don't always want to use it; e.g. on non-team + # or non-x86 compiler editions where there's no support) + SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${VS_ARGS}") + SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${VS_ARGS}") - # this line removes "/D NDEBUG" from release, we want them in order to find bugs even on release builds. - SET(CMAKE_CXX_FLAGS_RELEASE "/MD /O2 /Ob2") ENDIF(WIN32) - +# this line removes "/D NDEBUG" from release, we want them in order to find bugs even on release builds. +SET(CMAKE_CXX_FLAGS_RELEASE "/MD /O2 /Ob2") diff --git a/cmake/CMakeLists_cpack.txt b/cmake/CMakeLists_cpack.txt index 4762dcdf..418e30e6 100644 --- a/cmake/CMakeLists_cpack.txt +++ b/cmake/CMakeLists_cpack.txt @@ -17,6 +17,18 @@ INSTALL( TARGETS ${cpack_targets} RUNTIME DESTINATION bin) +IF(WIN32) + INSTALL( + FILES + bin/Release/QtNetwork4.dll + bin/Release/QtGui4.dll + bin/Release/QtCore4.dll + bin/Release/qsynergy.exe + bin/Release/mingwm10.dll + bin/Release/libgcc_s_dw2-1.dll + DESTINATION bin) +ENDIF(WIN32) + # The default CPack behaviour is not to append the system processor # type, which is undesirable in our case, since we want to support # both 32-bit and 64-bit processors. @@ -62,7 +74,7 @@ IF(WIN32) SET(CPACK_NSIS_MUI_UNIICON ${WIN32_ICON}) SET(CPACK_NSIS_INSTALLED_ICON_NAME launcher) SET(CPACK_PACKAGE_INSTALL_DIRECTORY "Synergy+") - SET(CPACK_PACKAGE_EXECUTABLES launcher;Synergy+) + SET(CPACK_PACKAGE_EXECUTABLES qsynergy;Synergy+) ENDIF(WIN32) # For source package, leave out temp and Mercurial stuff. diff --git a/cmake/CMakeLists_lib.txt b/cmake/CMakeLists_lib.txt index ff74ec14..7fb53de3 100644 --- a/cmake/CMakeLists_lib.txt +++ b/cmake/CMakeLists_lib.txt @@ -1,12 +1,15 @@ SET(root_lib ${root_dir}/lib) SET(src_lib_arch + ${root_lib}/arch/CArchAppUtil.cpp ${root_lib}/arch/CArch.cpp ${root_lib}/arch/CArchDaemonNone.cpp ${root_lib}/arch/XArch.cpp + ${root_lib}/arch/CArchConsoleStd.cpp ) SET(src_lib_arch_unix + ${root_lib}/arch/CArchAppUtilUnix.cpp ${root_lib}/arch/CArchConsoleUnix.cpp ${root_lib}/arch/CArchDaemonUnix.cpp ${root_lib}/arch/CArchFileUnix.cpp @@ -22,6 +25,7 @@ SET(src_lib_arch_unix ) SET(src_lib_arch_windows + ${root_lib}/arch/CArchAppUtilWindows.cpp ${root_lib}/arch/CArchConsoleWindows.cpp ${root_lib}/arch/CArchDaemonWindows.cpp ${root_lib}/arch/CArchFileWindows.cpp @@ -38,6 +42,8 @@ SET(src_lib_arch_windows ) SET(inc_lib_arch_windows + ${root_lib}/arch/CArchAppUtil.h + ${root_lib}/arch/CArchAppUtilWindows.h ${root_lib}/arch/CArchConsoleWindows.h ${root_lib}/arch/CArchDaemonWindows.h ${root_lib}/arch/CArchFileWindows.h @@ -50,6 +56,8 @@ SET(inc_lib_arch_windows ${root_lib}/arch/CArchSystemWindows.h ${root_lib}/arch/CArchTaskBarWindows.h ${root_lib}/arch/CArchTimeWindows.h + ${root_lib}/arch/CArchConsoleStd.h + ${root_lib}/arch/IArchAppUtil.h ${root_lib}/arch/XArchWindows.h ) @@ -264,6 +272,11 @@ SET(inc_lib_server ) SET(src_lib_synergy + ${root_lib}/synergy/CClientTaskBarReceiver.cpp + ${root_lib}/synergy/CServerTaskBarReceiver.cpp + ${root_lib}/synergy/CApp.cpp + ${root_lib}/synergy/CClientApp.cpp + ${root_lib}/synergy/CServerApp.cpp ${root_lib}/synergy/CClipboard.cpp ${root_lib}/synergy/CKeyMap.cpp ${root_lib}/synergy/CKeyState.cpp @@ -282,6 +295,11 @@ SET(src_lib_synergy ) SET(inc_lib_synergy + ${root_lib}/synergy/CClientTaskBarReceiver.h + ${root_lib}/synergy/CServerTaskBarReceiver.h + ${root_lib}/synergy/CApp.h + ${root_lib}/synergy/CClientApp.h + ${root_lib}/synergy/CServerApp.h ${root_lib}/synergy/CClipboard.h ${root_lib}/synergy/CKeyMap.h ${root_lib}/synergy/CKeyState.h @@ -324,9 +342,17 @@ IF(UNIX) LIST(APPEND src_lib ${src_lib_arch_unix}) IF(APPLE) - LIST(APPEND src_lib ${src_lib_platform_carbon}) + LIST(APPEND src_lib + ${src_lib_platform_carbon} + ${inc_lib_synergy_carbon} + ${src_lib_synergy_carbon} + ) ELSE(APPLE) - LIST(APPEND src_lib ${src_lib_platform_xwindows}) + LIST(APPEND src_lib + ${src_lib_platform_xwindows} + ${inc_lib_synergy_xwindows} + ${src_lib_synergy_xwindows} + ) ENDIF(APPLE) ENDIF(UNIX) @@ -345,6 +371,8 @@ IF(WIN32) ${src_lib_arch_windows} ${inc_lib_platform_mswindows} ${src_lib_platform_mswindows} + ${inc_lib_synergy_mswindows} + ${src_lib_synergy_mswindows} ) ENDIF(WIN32) diff --git a/cmake/CMakeLists_synergyc.txt b/cmake/CMakeLists_synergyc.txt index 4bf000c1..5bd3a0b5 100644 --- a/cmake/CMakeLists_synergyc.txt +++ b/cmake/CMakeLists_synergyc.txt @@ -1,7 +1,6 @@ SET(root_cmd_synergyc ${root_dir}/cmd/synergyc) SET(src_cmd_synergyc_common - ${root_cmd_synergyc}/CClientTaskBarReceiver.cpp ${root_cmd_synergyc}/synergyc.cpp ) @@ -66,5 +65,5 @@ SET(inc_dirs_cmd_synergyc ) INCLUDE_DIRECTORIES(${inc_dirs_cmd_synergyc}) -ADD_EXECUTABLE(synergyc WIN32 ${src_cmd_synergyc}) +ADD_EXECUTABLE(synergyc ${src_cmd_synergyc}) TARGET_LINK_LIBRARIES(synergyc synergy ${libs}) diff --git a/cmake/CMakeLists_synergys.txt b/cmake/CMakeLists_synergys.txt index d1768f6b..22ea2fe1 100644 --- a/cmake/CMakeLists_synergys.txt +++ b/cmake/CMakeLists_synergys.txt @@ -1,7 +1,6 @@ SET(root_cmd_synergys ${root_dir}/cmd/synergys) SET(src_cmd_synergys_common - ${root_cmd_synergys}/CServerTaskBarReceiver.cpp ${root_cmd_synergys}/synergys.cpp ) @@ -14,7 +13,6 @@ SET(src_cmd_synergys_mswindows ) SET(inc_cmd_synergys_mswindows - ${root_cmd_synergys}/CServerTaskBarReceiver.h ${root_cmd_synergys}/CXWindowsServerTaskBarReceiver.h ${root_cmd_synergys}/resource.h ${root_cmd_synergys}/COSXServerTaskBarReceiver.h @@ -69,5 +67,5 @@ SET(inc_dirs_cmd_synergys ) INCLUDE_DIRECTORIES(${inc_dirs_cmd_synergys}) -ADD_EXECUTABLE(synergys WIN32 ${src_cmd_synergys}) +ADD_EXECUTABLE(synergys ${src_cmd_synergys}) TARGET_LINK_LIBRARIES(synergys synergy ${libs}) diff --git a/cmd/launcher/CAutoStart.cpp b/cmd/launcher/CAutoStart.cpp index eb18a6b0..b57fb559 100644 --- a/cmd/launcher/CAutoStart.cpp +++ b/cmd/launcher/CAutoStart.cpp @@ -21,8 +21,8 @@ #include "LaunchUtil.h" #include "resource.h" -static const char* CLIENT_DAEMON_NAME = "Synergy Client"; -static const char* SERVER_DAEMON_NAME = "Synergy Server"; +static const char* CLIENT_DAEMON_NAME = "Synergy+ Client"; +static const char* SERVER_DAEMON_NAME = "Synergy+ Server"; static const char* CLIENT_DAEMON_INFO = "Uses a shared mouse and keyboard."; static const char* SERVER_DAEMON_INFO = "Shares this system's mouse and keyboard with others."; diff --git a/cmd/launcher/launcher.cpp b/cmd/launcher/launcher.cpp index 55109545..043293ae 100644 --- a/cmd/launcher/launcher.cpp +++ b/cmd/launcher/launcher.cpp @@ -675,7 +675,9 @@ mainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR cmdLine, int nCmdShow) { - CArch arch(instance); + CArchMiscWindows::setInstanceWin32(instance); + + CArch arch; CLOG; CArgs args; diff --git a/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp index 6f41bc17..689cc307 100644 --- a/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp +++ b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp @@ -19,7 +19,9 @@ #include "BasicTypes.h" #include "CArch.h" #include "CArchTaskBarWindows.h" +#include "CArchMiscWindows.h" #include "resource.h" +#include "CMSWindowsScreen.h" // // CMSWindowsClientTaskBarReceiver @@ -343,3 +345,20 @@ CMSWindowsClientTaskBarReceiver::staticDlgProc(HWND hwnd, return (msg == WM_INITDIALOG) ? TRUE : FALSE; } } + +IArchTaskBarReceiver* +createTaskBarReceiver(const CBufferedLogOutputter* logBuffer) +{ + CArchMiscWindows::setIcons( + (HICON)LoadImage(CArchMiscWindows::instanceWin32(), + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 32, 32, LR_SHARED), + (HICON)LoadImage(CArchMiscWindows::instanceWin32(), + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 16, 16, LR_SHARED)); + + return new CMSWindowsClientTaskBarReceiver( + CMSWindowsScreen::getInstance(), logBuffer); +} diff --git a/cmd/synergyc/COSXClientTaskBarReceiver.cpp b/cmd/synergyc/COSXClientTaskBarReceiver.cpp index c380ac4d..08d1fad7 100644 --- a/cmd/synergyc/COSXClientTaskBarReceiver.cpp +++ b/cmd/synergyc/COSXClientTaskBarReceiver.cpp @@ -54,3 +54,10 @@ COSXClientTaskBarReceiver::getIcon() const { return NULL; } + +IArchTaskBarReceiver* +createTaskBarReceiver(const CBufferedLogOutputter* logBuffer) +{ + return new COSXClientTaskBarReceiver(logBuffer); +} + diff --git a/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp b/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp index 681f9be5..965f698e 100644 --- a/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp +++ b/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp @@ -54,3 +54,9 @@ CXWindowsClientTaskBarReceiver::getIcon() const { return NULL; } + +IArchTaskBarReceiver* +createTaskBarReceiver(const CBufferedLogOutputter* logBuffer) +{ + return new CXWindowsClientTaskBarReceiver(logBuffer); +} diff --git a/cmd/synergyc/synergyc.cpp b/cmd/synergyc/synergyc.cpp index 69959ba6..f1707e90 100644 --- a/cmd/synergyc/synergyc.cpp +++ b/cmd/synergyc/synergyc.cpp @@ -12,943 +12,21 @@ * GNU General Public License for more details. */ -#include "CClient.h" -#include "CScreen.h" -#include "ProtocolTypes.h" -#include "Version.h" -#include "XScreen.h" -#include "CNetworkAddress.h" -#include "CSocketMultiplexer.h" -#include "CTCPSocketFactory.h" -#include "XSocket.h" -#include "CThread.h" -#include "CEventQueue.h" -#include "CFunctionEventJob.h" -#include "CFunctionJob.h" -#include "CLog.h" -#include "CString.h" -#include "CStringUtil.h" -#include "LogOutputters.h" -#include "CArch.h" -#include "XArch.h" -#include +#include "CClientApp.h" -#define DAEMON_RUNNING(running_) #if WINAPI_MSWINDOWS -#include "CArchMiscWindows.h" -#include "CMSWindowsScreen.h" -#include "CMSWindowsUtil.h" #include "CMSWindowsClientTaskBarReceiver.h" -#include "resource.h" -#undef DAEMON_RUNNING -#define DAEMON_RUNNING(running_) CArchMiscWindows::daemonRunning(running_) #elif WINAPI_XWINDOWS -#include "CXWindowsScreen.h" #include "CXWindowsClientTaskBarReceiver.h" #elif WINAPI_CARBON -#include "COSXScreen.h" #include "COSXClientTaskBarReceiver.h" -#endif - -// platform dependent name of a daemon -#if SYSAPI_WIN32 -#define DAEMON_NAME "Synergy+ Client" -#elif SYSAPI_UNIX -#define DAEMON_NAME "synergyc" -#endif - -typedef int (*StartupFunc)(int, char**); -static bool startClient(); -static void parse(int argc, const char* const* argv); - -// -// program arguments -// - -#define ARG CArgs::s_instance - -class CArgs { -public: - CArgs() : - m_pname(NULL), - m_backend(false), - m_restartable(true), - m_daemon(true), - m_yscroll(0), - m_logFilter(NULL), - m_display(NULL), - m_serverAddress(NULL), - m_logFile(NULL) - { s_instance = this; } - ~CArgs() { s_instance = NULL; } - -public: - static CArgs* s_instance; - const char* m_pname; - bool m_backend; - bool m_restartable; - bool m_daemon; - int m_yscroll; - const char* m_logFilter; - const char* m_display; - CString m_name; - CNetworkAddress* m_serverAddress; - const char* m_logFile; - -}; - -CArgs* CArgs::s_instance = NULL; - - -// -// platform dependent factories -// - -static -CScreen* -createScreen() -{ -#if WINAPI_MSWINDOWS - return new CScreen(new CMSWindowsScreen(false)); -#elif WINAPI_XWINDOWS - return new CScreen(new CXWindowsScreen(ARG->m_display, false, ARG->m_yscroll)); -#elif WINAPI_CARBON - return new CScreen(new COSXScreen(false)); -#endif -} - -static -CClientTaskBarReceiver* -createTaskBarReceiver(const CBufferedLogOutputter* logBuffer) -{ -#if WINAPI_MSWINDOWS - return new CMSWindowsClientTaskBarReceiver( - CMSWindowsScreen::getInstance(), logBuffer); -#elif WINAPI_XWINDOWS - return new CXWindowsClientTaskBarReceiver(logBuffer); -#elif WINAPI_CARBON - return new COSXClientTaskBarReceiver(logBuffer); -#endif -} - - -// -// platform independent main -// - -static CClient* s_client = NULL; -static CScreen* s_clientScreen = NULL; -static CClientTaskBarReceiver* s_taskBarReceiver = NULL; -//static double s_retryTime = 0.0; -static bool s_suspened = false; - -#define RETRY_TIME 1.0 - -static -void -updateStatus() -{ - s_taskBarReceiver->updateStatus(s_client, ""); -} - -static -void -updateStatus(const CString& msg) -{ - s_taskBarReceiver->updateStatus(s_client, msg); -} - -static -void -resetRestartTimeout() -{ - // retry time can nolonger be changed - //s_retryTime = 0.0; -} - -static -double -nextRestartTimeout() -{ - // retry at a constant rate (Issue 52) - return RETRY_TIME; - - /* - // choose next restart timeout. we start with rapid retries - // then slow down. - if (s_retryTime < 1.0) { - s_retryTime = 1.0; - } - else if (s_retryTime < 3.0) { - s_retryTime = 3.0; - } - else { - s_retryTime = 5.0; - } - return s_retryTime; - */ -} - -static -void -handleScreenError(const CEvent&, void*) -{ - LOG((CLOG_CRIT "error on screen")); - EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); -} - -static -CScreen* -openClientScreen() -{ - CScreen* screen = createScreen(); - EVENTQUEUE->adoptHandler(IScreen::getErrorEvent(), - screen->getEventTarget(), - new CFunctionEventJob( - &handleScreenError)); - return screen; -} - -static -void -closeClientScreen(CScreen* screen) -{ - if (screen != NULL) { - EVENTQUEUE->removeHandler(IScreen::getErrorEvent(), - screen->getEventTarget()); - delete screen; - } -} - -static -void -handleClientRestart(const CEvent&, void* vtimer) -{ - // discard old timer - CEventQueueTimer* timer = reinterpret_cast(vtimer); - EVENTQUEUE->deleteTimer(timer); - EVENTQUEUE->removeHandler(CEvent::kTimer, timer); - - // reconnect - startClient(); -} - -static -void -scheduleClientRestart(double retryTime) -{ - // install a timer and handler to retry later - LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); - CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL); - EVENTQUEUE->adoptHandler(CEvent::kTimer, timer, - new CFunctionEventJob(&handleClientRestart, timer)); -} - -static -void -handleClientConnected(const CEvent&, void*) -{ - LOG((CLOG_NOTE "connected to server")); - resetRestartTimeout(); - updateStatus(); -} - -static -void -handleClientFailed(const CEvent& e, void*) -{ - CClient::CFailInfo* info = - reinterpret_cast(e.getData()); - - updateStatus(CString("Failed to connect to server: ") + info->m_what); - if (!ARG->m_restartable || !info->m_retry) { - LOG((CLOG_ERR "failed to connect to server: %s", info->m_what)); - EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); - } - else { - LOG((CLOG_WARN "failed to connect to server: %s", info->m_what)); - if (!s_suspened) { - scheduleClientRestart(nextRestartTimeout()); - } - } -} - -static -void -handleClientDisconnected(const CEvent&, void*) -{ - LOG((CLOG_NOTE "disconnected from server")); - if (!ARG->m_restartable) { - EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); - } - else if (!s_suspened) { - s_client->connect(); - } - updateStatus(); -} - -static -CClient* -openClient(const CString& name, const CNetworkAddress& address, CScreen* screen) -{ - CClient* client = new CClient(name, address, - new CTCPSocketFactory, NULL, screen); - EVENTQUEUE->adoptHandler(CClient::getConnectedEvent(), - client->getEventTarget(), - new CFunctionEventJob(handleClientConnected)); - EVENTQUEUE->adoptHandler(CClient::getConnectionFailedEvent(), - client->getEventTarget(), - new CFunctionEventJob(handleClientFailed)); - EVENTQUEUE->adoptHandler(CClient::getDisconnectedEvent(), - client->getEventTarget(), - new CFunctionEventJob(handleClientDisconnected)); - return client; -} - -static -void -closeClient(CClient* client) -{ - if (client == NULL) { - return; - } - - EVENTQUEUE->removeHandler(CClient::getConnectedEvent(), client); - EVENTQUEUE->removeHandler(CClient::getConnectionFailedEvent(), client); - EVENTQUEUE->removeHandler(CClient::getDisconnectedEvent(), client); - delete client; -} - -static -bool -startClient() -{ - double retryTime; - CScreen* clientScreen = NULL; - try { - if (s_clientScreen == NULL) { - clientScreen = openClientScreen(); - s_client = openClient(ARG->m_name, - *ARG->m_serverAddress, clientScreen); - s_clientScreen = clientScreen; - LOG((CLOG_NOTE "started client")); - } - s_client->connect(); - updateStatus(); - return true; - } - catch (XScreenUnavailable& e) { - LOG((CLOG_WARN "cannot open secondary screen: %s", e.what())); - closeClientScreen(clientScreen); - updateStatus(CString("Cannot open secondary screen: ") + e.what()); - retryTime = e.getRetryTime(); - } - catch (XScreenOpenFailure& e) { - LOG((CLOG_CRIT "cannot open secondary screen: %s", e.what())); - closeClientScreen(clientScreen); - return false; - } - catch (XBase& e) { - LOG((CLOG_CRIT "failed to start client: %s", e.what())); - closeClientScreen(clientScreen); - return false; - } - - if (ARG->m_restartable) { - scheduleClientRestart(retryTime); - return true; - } - else { - // don't try again - return false; - } -} - -static -void -stopClient() -{ - closeClient(s_client); - closeClientScreen(s_clientScreen); - s_client = NULL; - s_clientScreen = NULL; -} - -static -int -mainLoop() -{ - // logging to files - CFileLogOutputter* fileLog = NULL; - - if (ARG->m_logFile != NULL) { - fileLog = new CFileLogOutputter(ARG->m_logFile); - - CLOG->insert(fileLog); - - LOG((CLOG_DEBUG1 "Logging to file (%s) enabled", ARG->m_logFile)); - } - - // create socket multiplexer. this must happen after daemonization - // on unix because threads evaporate across a fork(). - CSocketMultiplexer multiplexer; - - // create the event queue - CEventQueue eventQueue; - - // start the client. if this return false then we've failed and - // we shouldn't retry. - LOG((CLOG_DEBUG1 "starting client")); - if (!startClient()) { - return kExitFailed; - } - - // run event loop. if startClient() failed we're supposed to retry - // later. the timer installed by startClient() will take care of - // that. - CEvent event; - DAEMON_RUNNING(true); - EVENTQUEUE->getEvent(event); - while (event.getType() != CEvent::kQuit) { - EVENTQUEUE->dispatchEvent(event); - CEvent::deleteData(event); - EVENTQUEUE->getEvent(event); - } - DAEMON_RUNNING(false); - - // close down - LOG((CLOG_DEBUG1 "stopping client")); - stopClient(); - updateStatus(); - LOG((CLOG_NOTE "stopped client")); - - if (fileLog) { - CLOG->remove(fileLog); - delete fileLog; - } - - return kExitSuccess; -} - -static -int -daemonMainLoop(int, const char**) -{ -#if SYSAPI_WIN32 - CSystemLogger sysLogger(DAEMON_NAME, false); #else - CSystemLogger sysLogger(DAEMON_NAME, true); +#error Platform not supported. #endif - return mainLoop(); -} - -static -int -standardStartup(int argc, char** argv) -{ - if (!ARG->m_daemon) { - ARCH->showConsole(false); - } - - // parse command line - parse(argc, argv); - - // daemonize if requested - if (ARG->m_daemon) { - return ARCH->daemonize(DAEMON_NAME, &daemonMainLoop); - } - else { - return mainLoop(); - } -} - -static -int -run(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup) -{ - // general initialization - ARG->m_serverAddress = new CNetworkAddress; - ARG->m_pname = ARCH->getBasename(argv[0]); - - // install caller's output filter - if (outputter != NULL) { - CLOG->insert(outputter); - } - - // save log messages - CBufferedLogOutputter logBuffer(1000); - CLOG->insert(&logBuffer, true); - - // make the task bar receiver. the user can control this app - // through the task bar. - s_taskBarReceiver = createTaskBarReceiver(&logBuffer); - - // run - int result = startup(argc, argv); - - // done with task bar receiver - delete s_taskBarReceiver; - - // done with log buffer - CLOG->remove(&logBuffer); - - delete ARG->m_serverAddress; - return result; -} - - -// -// command line parsing -// - -#define BYE "\nTry `%s --help' for more information." - -static void (*bye)(int) = &exit; - -static -void -version() -{ - LOG((CLOG_PRINT "%s %s, protocol version %d.%d\n%s", - ARG->m_pname, - kVersion, - kProtocolMajorVersion, - kProtocolMinorVersion, - kCopyright)); -} - -static -void -help() -{ -#if WINAPI_XWINDOWS -# define USAGE_DISPLAY_ARG \ -" [--display ]" -# define USAGE_DISPLAY_INFO \ -" --display connect to the X server at \n" -#else -# define USAGE_DISPLAY_ARG -# define USAGE_DISPLAY_INFO -#endif - - LOG((CLOG_PRINT -"Usage: %s" -" [--daemon|--no-daemon]" -" [--debug ]" -USAGE_DISPLAY_ARG -" [--name ]" -" [--yscroll ]" -" [--restart|--no-restart]" -" " -"\n\n" -"Start the synergy mouse/keyboard sharing server.\n" -"\n" -" -d, --debug filter out log messages with priorty below level.\n" -" level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n" -" DEBUG, DEBUG1, DEBUG2.\n" -USAGE_DISPLAY_INFO -" -f, --no-daemon run the client in the foreground.\n" -"* --daemon run the client as a daemon.\n" -" -n, --name use screen-name instead the hostname to identify\n" -" ourself to the server.\n" -" --yscroll defines the vertical scrolling delta, which is\n" -" 120 by default.\n" -" -1, --no-restart do not try to restart the client if it fails for\n" -" some reason.\n" -"* --restart restart the client automatically if it fails.\n" -" -l --log write log messages to file.\n" -" -h, --help display this help and exit.\n" -" --version display version information and exit.\n" -"\n" -"* marks defaults.\n" -"\n" -"The server address is of the form: [][:]. The hostname\n" -"must be the address or hostname of the server. The port overrides the\n" -"default port, %d.\n" -"\n" -"Where log messages go depends on the platform and whether or not the\n" -"client is running as a daemon.", - ARG->m_pname, kDefaultPort)); - -} - -static -bool -isArg(int argi, int argc, const char* const* argv, - const char* name1, const char* name2, - int minRequiredParameters = 0) -{ - if ((name1 != NULL && strcmp(argv[argi], name1) == 0) || - (name2 != NULL && strcmp(argv[argi], name2) == 0)) { - // match. check args left. - if (argi + minRequiredParameters >= argc) { - LOG((CLOG_PRINT "%s: missing arguments for `%s'" BYE, - ARG->m_pname, argv[argi], ARG->m_pname)); - bye(kExitArgs); - } - return true; - } - - // no match - return false; -} - -static -void -parse(int argc, const char* const* argv) -{ - assert(ARG->m_pname != NULL); - assert(argv != NULL); - assert(argc >= 1); - - if(ARG->m_pname == NULL - || argv == NULL - || argc < 1) { - return; - } - - // set defaults - ARG->m_name = ARCH->getHostName(); - - // parse options - int i; - for (i = 1; i < argc; ++i) { - if (isArg(i, argc, argv, "-d", "--debug", 1)) { - // change logging level - ARG->m_logFilter = argv[++i]; - } - - else if (isArg(i, argc, argv, "-n", "--name", 1)) { - // save screen name - ARG->m_name = argv[++i]; - } - - else if (isArg(i, argc, argv, NULL, "--camp")) { - // ignore -- included for backwards compatibility - } - - else if (isArg(i, argc, argv, NULL, "--no-camp")) { - // ignore -- included for backwards compatibility - } - - else if (isArg(i, argc, argv, "-f", "--no-daemon")) { - // not a daemon - ARG->m_daemon = false; - } - - else if (isArg(i, argc, argv, NULL, "--daemon")) { - // daemonize - ARG->m_daemon = true; - } - -#if WINAPI_XWINDOWS - else if (isArg(i, argc, argv, "-display", "--display", 1)) { - // use alternative display - ARG->m_display = argv[++i]; - } -#endif - - else if (isArg(i, argc, argv, NULL, "--yscroll", 1)) { - // define scroll - ARG->m_yscroll = atoi(argv[++i]); - } - - else if (isArg(i, argc, argv, "-l", "--log", 1)) { - ARG->m_logFile = argv[++i]; - } - - else if (isArg(i, argc, argv, "-1", "--no-restart")) { - // don't try to restart - ARG->m_restartable = false; - } - - else if (isArg(i, argc, argv, NULL, "--restart")) { - // try to restart - ARG->m_restartable = true; - } - - else if (isArg(i, argc, argv, "-z", NULL)) { - ARG->m_backend = true; - } - - else if (isArg(i, argc, argv, "-h", "--help")) { - help(); - bye(kExitSuccess); - } - - else if (isArg(i, argc, argv, NULL, "--version")) { - version(); - bye(kExitSuccess); - } - - else if (isArg(i, argc, argv, "--", NULL)) { - // remaining arguments are not options - ++i; - break; - } - - else if (argv[i][0] == '-') { - LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, - ARG->m_pname, argv[i], ARG->m_pname)); - bye(kExitArgs); - } - - else { - // this and remaining arguments are not options - break; - } - } - - // exactly one non-option argument (server-address) - if (i == argc) { - LOG((CLOG_PRINT "%s: a server address or name is required" BYE, - ARG->m_pname, ARG->m_pname)); - bye(kExitArgs); - } - if (i + 1 != argc) { - LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, - ARG->m_pname, argv[i], ARG->m_pname)); - bye(kExitArgs); - } - - // save server address - try { - *ARG->m_serverAddress = CNetworkAddress(argv[i], kDefaultPort); - ARG->m_serverAddress->resolve(); - } - catch (XSocketAddress& e) { - // allow an address that we can't look up if we're restartable. - // we'll try to resolve the address each time we connect to the - // server. a bad port will never get better. patch by Brent - // Priddy. - if (!ARG->m_restartable || e.getError() == XSocketAddress::kBadPort) { - LOG((CLOG_PRINT "%s: %s" BYE, - ARG->m_pname, e.what(), ARG->m_pname)); - bye(kExitFailed); - } - } - - // increase default filter level for daemon. the user must - // explicitly request another level for a daemon. - if (ARG->m_daemon && ARG->m_logFilter == NULL) { -#if SYSAPI_WIN32 - if (CArchMiscWindows::isWindows95Family()) { - // windows 95 has no place for logging so avoid showing - // the log console window. - ARG->m_logFilter = "FATAL"; - } - else -#endif - { - ARG->m_logFilter = "NOTE"; - } - } - - // set log filter - if (!CLOG->setFilter(ARG->m_logFilter)) { - LOG((CLOG_PRINT "%s: unrecognized log level `%s'" BYE, - ARG->m_pname, ARG->m_logFilter, ARG->m_pname)); - bye(kExitArgs); - } - - // identify system - LOG((CLOG_INFO "%s Client on %s %s", kAppVersion, ARCH->getOSName().c_str(), ARCH->getPlatformName().c_str())); - -#ifdef WIN32 -#ifdef _AMD64_ - LOG((CLOG_WARN "This is an experimental x64 build of %s. Use it at your own risk.", kApplication)); -#endif -#endif -} - - -// -// platform dependent entry points -// - -#if SYSAPI_WIN32 -static bool s_hasImportantLogMessages = false; - -// -// CMessageBoxOutputter -// -// This class writes severe log messages to a message box -// - -class CMessageBoxOutputter : public ILogOutputter { -public: - CMessageBoxOutputter() { } - virtual ~CMessageBoxOutputter() { } - - // ILogOutputter overrides - virtual void open(const char*) { } - virtual void close() { } - virtual void show(bool) { } - virtual bool write(ELevel level, const char* message); - virtual const char* getNewline() const { return ""; } -}; - -bool -CMessageBoxOutputter::write(ELevel level, const char* message) -{ - // note any important messages the user may need to know about - if (level <= CLog::kWARNING) { - s_hasImportantLogMessages = true; - } - - // FATAL and PRINT messages get a dialog box if not running as - // backend. if we're running as a backend the user will have - // a chance to see the messages when we exit. - if (!ARG->m_backend && level <= CLog::kFATAL) { - MessageBox(NULL, message, ARG->m_pname, MB_OK | MB_ICONWARNING); - return false; - } - else { - return true; - } -} - -static -void -byeThrow(int x) -{ - CArchMiscWindows::daemonFailed(x); -} - -static -int -daemonNTMainLoop(int argc, const char** argv) -{ - parse(argc, argv); - ARG->m_backend = false; - return CArchMiscWindows::runDaemon(mainLoop); -} - -static -int -daemonNTStartup(int, char**) -{ - CSystemLogger sysLogger(DAEMON_NAME, false); - bye = &byeThrow; - return ARCH->daemonize(DAEMON_NAME, &daemonNTMainLoop); -} - -static -int -foregroundStartup(int argc, char** argv) -{ - ARCH->showConsole(false); - - // parse command line - parse(argc, argv); - - // never daemonize - return mainLoop(); -} - -static -void -showError(HINSTANCE instance, const char* title, UINT id, const char* arg) -{ - CString fmt = CMSWindowsUtil::getString(instance, id); - CString msg = CStringUtil::format(fmt.c_str(), arg); - MessageBox(NULL, msg.c_str(), title, MB_OK | MB_ICONWARNING); -} - -int WINAPI -WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) -{ - try { - CArchMiscWindows::setIcons((HICON)LoadImage(instance, - MAKEINTRESOURCE(IDI_SYNERGY), - IMAGE_ICON, - 32, 32, LR_SHARED), - (HICON)LoadImage(instance, - MAKEINTRESOURCE(IDI_SYNERGY), - IMAGE_ICON, - 16, 16, LR_SHARED)); - CArch arch(instance); - CMSWindowsScreen::init(instance); - CLOG; - CThread::getCurrentThread().setPriority(-14); - CArgs args; - - // set title on log window - ARCH->openConsole((CString(kAppVersion) + " " + "Client").c_str()); - - // 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. - StartupFunc startup = &standardStartup; - if (!CArchMiscWindows::isWindows95Family()) { - if (__argc <= 1) { - startup = &daemonNTStartup; - } - else { - startup = &foregroundStartup; - } - } - - // send PRINT and FATAL output to a message box - int result = run(__argc, __argv, new CMessageBoxOutputter, startup); - - // let user examine any messages if we're running as a backend - // by putting up a dialog box before exiting. - if (args.m_backend && s_hasImportantLogMessages) { - showError(instance, args.m_pname, IDS_FAILED, ""); - } - - delete CLOG; - return result; - } - catch (XBase& e) { - showError(instance, __argv[0], IDS_UNCAUGHT_EXCEPTION, e.what()); - //throw; - } - catch (XArch& e) { - showError(instance, __argv[0], IDS_INIT_FAILED, e.what().c_str()); - } - catch (...) { - showError(instance, __argv[0], IDS_UNCAUGHT_EXCEPTION, ""); - //throw; - } - return kExitFailed; -} - -#elif SYSAPI_UNIX int -main(int argc, char** argv) +main(int argc, char** argv) { - CArgs args; - try { - int result; - CArch arch; - CLOG; - CArgs args; - result = run(argc, argv, NULL, &standardStartup); - delete CLOG; - return result; - } - catch (XBase& e) { - LOG((CLOG_CRIT "Uncaught exception: %s\n", e.what())); - throw; - } - catch (XArch& e) { - LOG((CLOG_CRIT "Initialization failed: %s" BYE, e.what().c_str())); - return kExitFailed; - } - catch (...) { - LOG((CLOG_CRIT "Uncaught exception: \n")); - throw; - } + CClientApp app; + return app.run(argc, argv, createTaskBarReceiver); } - -#else - -#error no main() for platform - -#endif diff --git a/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp b/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp index 50bcc638..97ba1a36 100644 --- a/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp +++ b/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp @@ -21,10 +21,8 @@ #include "CArch.h" #include "CArchTaskBarWindows.h" #include "resource.h" - -extern CEvent::Type getReloadConfigEvent(); -extern CEvent::Type getForceReconnectEvent(); -extern CEvent::Type getResetServerEvent(); +#include "CArchMiscWindows.h" +#include "CMSWindowsScreen.h" // // CMSWindowsServerTaskBarReceiver @@ -378,3 +376,20 @@ CMSWindowsServerTaskBarReceiver::staticDlgProc(HWND hwnd, return (msg == WM_INITDIALOG) ? TRUE : FALSE; } } + +IArchTaskBarReceiver* +createTaskBarReceiver(const CBufferedLogOutputter* logBuffer) +{ + CArchMiscWindows::setIcons( + (HICON)LoadImage(CArchMiscWindows::instanceWin32(), + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 32, 32, LR_SHARED), + (HICON)LoadImage(CArchMiscWindows::instanceWin32(), + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 16, 16, LR_SHARED)); + + return new CMSWindowsServerTaskBarReceiver( + CMSWindowsScreen::getInstance(), logBuffer); +} diff --git a/cmd/synergys/COSXServerTaskBarReceiver.cpp b/cmd/synergys/COSXServerTaskBarReceiver.cpp index 8195b84f..4770478b 100644 --- a/cmd/synergys/COSXServerTaskBarReceiver.cpp +++ b/cmd/synergys/COSXServerTaskBarReceiver.cpp @@ -54,3 +54,9 @@ COSXServerTaskBarReceiver::getIcon() const { return NULL; } + +IArchTaskBarReceiver* +createTaskBarReceiver(const CBufferedLogOutputter* logBuffer) +{ + return new COSXServerTaskBarReceiver(logBuffer); +} diff --git a/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp b/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp index 861d2f8c..61db20df 100644 --- a/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp +++ b/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp @@ -54,3 +54,9 @@ CXWindowsServerTaskBarReceiver::getIcon() const { return NULL; } + +IArchTaskBarReceiver* +createTaskBarReceiver(const CBufferedLogOutputter* logBuffer) +{ + return new CXWindowsServerTaskBarReceiver(logBuffer); +} diff --git a/cmd/synergys/synergys.cpp b/cmd/synergys/synergys.cpp index dcf2da92..bee31152 100644 --- a/cmd/synergys/synergys.cpp +++ b/cmd/synergys/synergys.cpp @@ -12,1351 +12,21 @@ * GNU General Public License for more details. */ -#include "CClientListener.h" -#include "CClientProxy.h" -#include "CConfig.h" -#include "CPrimaryClient.h" -#include "CServer.h" -#include "CScreen.h" -#include "ProtocolTypes.h" -#include "Version.h" -#include "XScreen.h" -#include "CSocketMultiplexer.h" -#include "CTCPSocketFactory.h" -#include "XSocket.h" -#include "CThread.h" -#include "CEventQueue.h" -#include "CFunctionEventJob.h" -#include "CLog.h" -#include "CString.h" -#include "CStringUtil.h" -#include "LogOutputters.h" -#include "CArch.h" -#include "XArch.h" -#include "stdfstream.h" -#include +#include "CServerApp.h" -#define DAEMON_RUNNING(running_) #if WINAPI_MSWINDOWS -#include "CArchMiscWindows.h" -#include "CMSWindowsScreen.h" -#include "CMSWindowsUtil.h" #include "CMSWindowsServerTaskBarReceiver.h" -#include "resource.h" -#undef DAEMON_RUNNING -#define DAEMON_RUNNING(running_) CArchMiscWindows::daemonRunning(running_) #elif WINAPI_XWINDOWS -#include "CXWindowsScreen.h" #include "CXWindowsServerTaskBarReceiver.h" #elif WINAPI_CARBON -#include "COSXScreen.h" #include "COSXServerTaskBarReceiver.h" -#endif - -// platform dependent name of a daemon -#if SYSAPI_WIN32 -#define DAEMON_NAME "Synergy+ Server" -#elif SYSAPI_UNIX -#define DAEMON_NAME "synergys" -#endif - -// configuration file name -#if SYSAPI_WIN32 -#define USR_CONFIG_NAME "synergy.sgc" -#define SYS_CONFIG_NAME "synergy.sgc" -#elif SYSAPI_UNIX -#define USR_CONFIG_NAME ".synergy.conf" -#define SYS_CONFIG_NAME "synergy.conf" -#endif - -typedef int (*StartupFunc)(int, char**); -static void parse(int argc, const char* const* argv); -static bool loadConfig(const CString& pathname); -static void loadConfig(); - -// -// program arguments -// - -#define ARG CArgs::s_instance - -class CArgs { -public: - CArgs() : - m_pname(NULL), - m_backend(false), - m_restartable(true), - m_daemon(true), - m_configFile(), - m_logFilter(NULL), - m_logFile(NULL), - m_display(NULL), - m_synergyAddress(NULL), - m_config(NULL) - { s_instance = this; } - ~CArgs() { s_instance = NULL; } - -public: - static CArgs* s_instance; - const char* m_pname; - bool m_backend; - bool m_restartable; - bool m_daemon; - CString m_configFile; - const char* m_logFilter; - const char* m_logFile; - const char* m_display; - CString m_name; - CNetworkAddress* m_synergyAddress; - CConfig* m_config; -}; - -CArgs* CArgs::s_instance = NULL; - - -// -// platform dependent factories -// - -static -CScreen* -createScreen() -{ -#if WINAPI_MSWINDOWS - return new CScreen(new CMSWindowsScreen(true)); -#elif WINAPI_XWINDOWS - return new CScreen(new CXWindowsScreen(ARG->m_display, true)); -#elif WINAPI_CARBON - return new CScreen(new COSXScreen(true)); -#endif -} - -static -CServerTaskBarReceiver* -createTaskBarReceiver(const CBufferedLogOutputter* logBuffer) -{ -#if WINAPI_MSWINDOWS - return new CMSWindowsServerTaskBarReceiver( - CMSWindowsScreen::getInstance(), logBuffer); -#elif WINAPI_XWINDOWS - return new CXWindowsServerTaskBarReceiver(logBuffer); -#elif WINAPI_CARBON - return new COSXServerTaskBarReceiver(logBuffer); -#endif -} - - -// -// platform independent main -// - -enum EServerState { - kUninitialized, - kInitializing, - kInitializingToStart, - kInitialized, - kStarting, - kStarted -}; - -static EServerState s_serverState = kUninitialized; -static CServer* s_server = NULL; -static CScreen* s_serverScreen = NULL; -static CPrimaryClient* s_primaryClient = NULL; -static CClientListener* s_listener = NULL; -static CServerTaskBarReceiver* s_taskBarReceiver = NULL; -static CEvent::Type s_reloadConfigEvent = CEvent::kUnknown; -static CEvent::Type s_forceReconnectEvent = CEvent::kUnknown; -static CEvent::Type s_resetServerEvent = CEvent::kUnknown; -static bool s_suspended = false; -static CEventQueueTimer* s_timer = NULL; - -CEvent::Type -getReloadConfigEvent() -{ - return CEvent::registerTypeOnce(s_reloadConfigEvent, "reloadConfig"); -} - -CEvent::Type -getForceReconnectEvent() -{ - return CEvent::registerTypeOnce(s_forceReconnectEvent, "forceReconnect"); -} - -CEvent::Type -getResetServerEvent() -{ - return CEvent::registerTypeOnce(s_resetServerEvent, "resetServer"); -} - -static -void -updateStatus() -{ - s_taskBarReceiver->updateStatus(s_server, ""); -} - -static -void -updateStatus(const CString& msg) -{ - s_taskBarReceiver->updateStatus(s_server, msg); -} - -static -void -handleClientConnected(const CEvent&, void* vlistener) -{ - CClientListener* listener = reinterpret_cast(vlistener); - CClientProxy* client = listener->getNextClient(); - if (client != NULL) { - s_server->adoptClient(client); - updateStatus(); - } -} - -static -CClientListener* -openClientListener(const CNetworkAddress& address) -{ - CClientListener* listen = - new CClientListener(address, new CTCPSocketFactory, NULL); - EVENTQUEUE->adoptHandler(CClientListener::getConnectedEvent(), listen, - new CFunctionEventJob( - &handleClientConnected, listen)); - return listen; -} - -static -void -closeClientListener(CClientListener* listen) -{ - if (listen != NULL) { - EVENTQUEUE->removeHandler(CClientListener::getConnectedEvent(), listen); - delete listen; - } -} - -static -void -handleScreenError(const CEvent&, void*) -{ - LOG((CLOG_CRIT "error on screen")); - EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); -} - - -static void handleSuspend(const CEvent& event, void*); -static void handleResume(const CEvent& event, void*); - -static -CScreen* -openServerScreen() -{ - CScreen* screen = createScreen(); - EVENTQUEUE->adoptHandler(IScreen::getErrorEvent(), - screen->getEventTarget(), - new CFunctionEventJob( - &handleScreenError)); - EVENTQUEUE->adoptHandler(IScreen::getSuspendEvent(), - screen->getEventTarget(), - new CFunctionEventJob( - &handleSuspend)); - EVENTQUEUE->adoptHandler(IScreen::getResumeEvent(), - screen->getEventTarget(), - new CFunctionEventJob( - &handleResume)); - return screen; -} - -static -void -closeServerScreen(CScreen* screen) -{ - if (screen != NULL) { - EVENTQUEUE->removeHandler(IScreen::getErrorEvent(), - screen->getEventTarget()); - EVENTQUEUE->removeHandler(IScreen::getSuspendEvent(), - screen->getEventTarget()); - EVENTQUEUE->removeHandler(IScreen::getResumeEvent(), - screen->getEventTarget()); - delete screen; - } -} - -static -CPrimaryClient* -openPrimaryClient(const CString& name, CScreen* screen) -{ - LOG((CLOG_DEBUG1 "creating primary screen")); - return new CPrimaryClient(name, screen); -} - -static -void -closePrimaryClient(CPrimaryClient* primaryClient) -{ - delete primaryClient; -} - -static -void -handleNoClients(const CEvent&, void*) -{ - updateStatus(); -} - -static -void -handleClientsDisconnected(const CEvent&, void*) -{ - EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); -} - -static -CServer* -openServer(const CConfig& config, CPrimaryClient* primaryClient) -{ - CServer* server = new CServer(config, primaryClient); - EVENTQUEUE->adoptHandler(CServer::getDisconnectedEvent(), server, - new CFunctionEventJob(handleNoClients)); - return server; -} - -static -void -closeServer(CServer* server) -{ - if (server == NULL) { - return; - } - - // tell all clients to disconnect - server->disconnect(); - - // wait for clients to disconnect for up to timeout seconds - double timeout = 3.0; - CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(timeout, NULL); - EVENTQUEUE->adoptHandler(CEvent::kTimer, timer, - new CFunctionEventJob(handleClientsDisconnected)); - EVENTQUEUE->adoptHandler(CServer::getDisconnectedEvent(), server, - new CFunctionEventJob(handleClientsDisconnected)); - CEvent event; - EVENTQUEUE->getEvent(event); - while (event.getType() != CEvent::kQuit) { - EVENTQUEUE->dispatchEvent(event); - CEvent::deleteData(event); - EVENTQUEUE->getEvent(event); - } - EVENTQUEUE->removeHandler(CEvent::kTimer, timer); - EVENTQUEUE->deleteTimer(timer); - EVENTQUEUE->removeHandler(CServer::getDisconnectedEvent(), server); - - // done with server - delete server; -} - -static bool initServer(); -static bool startServer(); - -static -void -stopRetryTimer() -{ - if (s_timer != NULL) { - EVENTQUEUE->deleteTimer(s_timer); - EVENTQUEUE->removeHandler(CEvent::kTimer, NULL); - s_timer = NULL; - } -} - -static -void -retryHandler(const CEvent&, void*) -{ - // discard old timer - assert(s_timer != NULL); - stopRetryTimer(); - - // try initializing/starting the server again - switch (s_serverState) { - case kUninitialized: - case kInitialized: - case kStarted: - assert(0 && "bad internal server state"); - break; - - case kInitializing: - LOG((CLOG_DEBUG1 "retry server initialization")); - s_serverState = kUninitialized; - if (!initServer()) { - EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); - } - break; - - case kInitializingToStart: - LOG((CLOG_DEBUG1 "retry server initialization")); - s_serverState = kUninitialized; - if (!initServer()) { - EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); - } - else if (s_serverState == kInitialized) { - LOG((CLOG_DEBUG1 "starting server")); - if (!startServer()) { - EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); - } - } - break; - - case kStarting: - LOG((CLOG_DEBUG1 "retry starting server")); - s_serverState = kInitialized; - if (!startServer()) { - EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); - } - break; - } -} - -static -bool -initServer() -{ - // skip if already initialized or initializing - if (s_serverState != kUninitialized) { - return true; - } - - double retryTime; - CScreen* serverScreen = NULL; - CPrimaryClient* primaryClient = NULL; - try { - CString name = ARG->m_config->getCanonicalName(ARG->m_name); - serverScreen = openServerScreen(); - primaryClient = openPrimaryClient(name, serverScreen); - s_serverScreen = serverScreen; - s_primaryClient = primaryClient; - s_serverState = kInitialized; - updateStatus(); - return true; - } - catch (XScreenUnavailable& e) { - LOG((CLOG_WARN "cannot open primary screen: %s", e.what())); - closePrimaryClient(primaryClient); - closeServerScreen(serverScreen); - updateStatus(CString("cannot open primary screen: ") + e.what()); - retryTime = e.getRetryTime(); - } - catch (XScreenOpenFailure& e) { - LOG((CLOG_CRIT "cannot open primary screen: %s", e.what())); - closePrimaryClient(primaryClient); - closeServerScreen(serverScreen); - return false; - } - catch (XBase& e) { - LOG((CLOG_CRIT "failed to start server: %s", e.what())); - closePrimaryClient(primaryClient); - closeServerScreen(serverScreen); - return false; - } - - if (ARG->m_restartable) { - // install a timer and handler to retry later - assert(s_timer == NULL); - LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); - s_timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL); - EVENTQUEUE->adoptHandler(CEvent::kTimer, s_timer, - new CFunctionEventJob(&retryHandler, NULL)); - s_serverState = kInitializing; - return true; - } - else { - // don't try again - return false; - } -} - -static -bool -startServer() -{ - // skip if already started or starting - if (s_serverState == kStarting || s_serverState == kStarted) { - return true; - } - - // initialize if necessary - if (s_serverState != kInitialized) { - if (!initServer()) { - // hard initialization failure - return false; - } - if (s_serverState == kInitializing) { - // not ready to start - s_serverState = kInitializingToStart; - return true; - } - assert(s_serverState == kInitialized); - } - - double retryTime; - CClientListener* listener = NULL; - try { - listener = openClientListener(ARG->m_config->getSynergyAddress()); - s_server = openServer(*ARG->m_config, s_primaryClient); - s_listener = listener; - updateStatus(); - LOG((CLOG_NOTE "started server")); - s_serverState = kStarted; - return true; - } - catch (XSocketAddressInUse& e) { - LOG((CLOG_WARN "cannot listen for clients: %s", e.what())); - closeClientListener(listener); - updateStatus(CString("cannot listen for clients: ") + e.what()); - retryTime = 10.0; - } - catch (XBase& e) { - LOG((CLOG_CRIT "failed to start server: %s", e.what())); - closeClientListener(listener); - return false; - } - - if (ARG->m_restartable) { - // install a timer and handler to retry later - assert(s_timer == NULL); - LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); - s_timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL); - EVENTQUEUE->adoptHandler(CEvent::kTimer, s_timer, - new CFunctionEventJob(&retryHandler, NULL)); - s_serverState = kStarting; - return true; - } - else { - // don't try again - return false; - } -} - -static -void -stopServer() -{ - if (s_serverState == kStarted) { - closeClientListener(s_listener); - closeServer(s_server); - s_server = NULL; - s_listener = NULL; - s_serverState = kInitialized; - } - else if (s_serverState == kStarting) { - stopRetryTimer(); - s_serverState = kInitialized; - } - assert(s_server == NULL); - assert(s_listener == NULL); -} - -static -void -cleanupServer() -{ - stopServer(); - if (s_serverState == kInitialized) { - closePrimaryClient(s_primaryClient); - closeServerScreen(s_serverScreen); - s_primaryClient = NULL; - s_serverScreen = NULL; - s_serverState = kUninitialized; - } - else if (s_serverState == kInitializing || - s_serverState == kInitializingToStart) { - stopRetryTimer(); - s_serverState = kUninitialized; - } - assert(s_primaryClient == NULL); - assert(s_serverScreen == NULL); - assert(s_serverState == kUninitialized); -} - -static -void -handleSuspend(const CEvent&, void*) -{ - if (!s_suspended) { - LOG((CLOG_INFO "suspend")); - stopServer(); - s_suspended = true; - } -} - -static -void -handleResume(const CEvent&, void*) -{ - if (s_suspended) { - LOG((CLOG_INFO "resume")); - startServer(); - s_suspended = false; - } -} - -static -void -reloadSignalHandler(CArch::ESignal, void*) -{ - EVENTQUEUE->addEvent(CEvent(getReloadConfigEvent(), - IEventQueue::getSystemTarget())); -} - -static -void -reloadConfig(const CEvent&, void*) -{ - LOG((CLOG_DEBUG "reload configuration")); - if (loadConfig(ARG->m_configFile)) { - if (s_server != NULL) { - s_server->setConfig(*ARG->m_config); - } - LOG((CLOG_NOTE "reloaded configuration")); - } -} - -static -void -forceReconnect(const CEvent&, void*) -{ - if (s_server != NULL) { - s_server->disconnect(); - } -} - -// simply stops and starts the server in order to try and -// work around issues like the sticky meta keys problem, etc -static -void -resetServer(const CEvent&, void*) -{ - LOG((CLOG_DEBUG1 "resetting server")); - stopServer(); - cleanupServer(); - startServer(); -} - -static -int -mainLoop() -{ - // create socket multiplexer. this must happen after daemonization - // on unix because threads evaporate across a fork(). - CSocketMultiplexer multiplexer; - - // create the event queue - CEventQueue eventQueue; - - // logging to files - CFileLogOutputter* fileLog = NULL; - - if (ARG->m_logFile != NULL) { - fileLog = new CFileLogOutputter(ARG->m_logFile); - - CLOG->insert(fileLog); - - LOG((CLOG_DEBUG1 "Logging to file (%s) enabled", ARG->m_logFile)); - } - - // if configuration has no screens then add this system - // as the default - if (ARG->m_config->begin() == ARG->m_config->end()) { - ARG->m_config->addScreen(ARG->m_name); - } - - // set the contact address, if provided, in the config. - // otherwise, if the config doesn't have an address, use - // the default. - if (ARG->m_synergyAddress->isValid()) { - ARG->m_config->setSynergyAddress(*ARG->m_synergyAddress); - } - else if (!ARG->m_config->getSynergyAddress().isValid()) { - ARG->m_config->setSynergyAddress(CNetworkAddress(kDefaultPort)); - } - - // canonicalize the primary screen name - CString primaryName = ARG->m_config->getCanonicalName(ARG->m_name); - if (primaryName.empty()) { - LOG((CLOG_CRIT "unknown screen name `%s'", ARG->m_name.c_str())); - return kExitFailed; - } - - // start the server. if this return false then we've failed and - // we shouldn't retry. - LOG((CLOG_DEBUG1 "starting server")); - if (!startServer()) { - return kExitFailed; - } - - // handle hangup signal by reloading the server's configuration - ARCH->setSignalHandler(CArch::kHANGUP, &reloadSignalHandler, NULL); - EVENTQUEUE->adoptHandler(getReloadConfigEvent(), - IEventQueue::getSystemTarget(), - new CFunctionEventJob(&reloadConfig)); - - // handle force reconnect event by disconnecting clients. they'll - // reconnect automatically. - EVENTQUEUE->adoptHandler(getForceReconnectEvent(), - IEventQueue::getSystemTarget(), - new CFunctionEventJob(&forceReconnect)); - - // to work around the sticky meta keys problem, we'll give users - // the option to reset the state of synergys - EVENTQUEUE->adoptHandler(getResetServerEvent(), - IEventQueue::getSystemTarget(), - new CFunctionEventJob(&resetServer)); - - // run event loop. if startServer() failed we're supposed to retry - // later. the timer installed by startServer() will take care of - // that. - CEvent event; - DAEMON_RUNNING(true); - EVENTQUEUE->getEvent(event); - while (event.getType() != CEvent::kQuit) { - EVENTQUEUE->dispatchEvent(event); - CEvent::deleteData(event); - EVENTQUEUE->getEvent(event); - } - DAEMON_RUNNING(false); - - // close down - LOG((CLOG_DEBUG1 "stopping server")); - EVENTQUEUE->removeHandler(getForceReconnectEvent(), - IEventQueue::getSystemTarget()); - EVENTQUEUE->removeHandler(getReloadConfigEvent(), - IEventQueue::getSystemTarget()); - cleanupServer(); - updateStatus(); - LOG((CLOG_NOTE "stopped server")); - - if (fileLog) { - CLOG->remove(fileLog); - delete fileLog; - } - - return kExitSuccess; -} - -static -int -daemonMainLoop(int, const char**) -{ -#if SYSAPI_WIN32 - CSystemLogger sysLogger(DAEMON_NAME, false); #else - CSystemLogger sysLogger(DAEMON_NAME, true); +#error Platform not supported. #endif - return mainLoop(); -} - -static -int -standardStartup(int argc, char** argv) -{ - - // parse command line - parse(argc, argv); - - // load configuration - loadConfig(); - - // daemonize if requested - if (ARG->m_daemon) { - return ARCH->daemonize(DAEMON_NAME, &daemonMainLoop); - } - else { - return mainLoop(); - } -} - -static -int -run(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup) -{ - // general initialization - ARG->m_synergyAddress = new CNetworkAddress; - ARG->m_config = new CConfig; - ARG->m_pname = ARCH->getBasename(argv[0]); - - // install caller's output filter - if (outputter != NULL) { - CLOG->insert(outputter); - } - - // save log messages - CBufferedLogOutputter logBuffer(1000); - CLOG->insert(&logBuffer, true); - - // make the task bar receiver. the user can control this app - // through the task bar. - s_taskBarReceiver = createTaskBarReceiver(&logBuffer); - - // run - int result = startup(argc, argv); - - // done with task bar receiver - delete s_taskBarReceiver; - - // done with log buffer - CLOG->remove(&logBuffer); - - delete ARG->m_config; - delete ARG->m_synergyAddress; - return result; -} - - -// -// command line parsing -// - -#define BYE "\nTry `%s --help' for more information." - -static void (*bye)(int) = &exit; - -static -void -version() -{ - LOG((CLOG_PRINT -"%s %s, protocol version %d.%d\n" -"%s", - ARG->m_pname, - kVersion, - kProtocolMajorVersion, - kProtocolMinorVersion, - kCopyright)); -} - -static -void -help() -{ -#if WINAPI_XWINDOWS -# define USAGE_DISPLAY_ARG \ -" [--display ]" -# define USAGE_DISPLAY_INFO \ -" --display connect to the X server at \n" -#else -# define USAGE_DISPLAY_ARG -# define USAGE_DISPLAY_INFO -#endif - -#if SYSAPI_WIN32 - -# define PLATFORM_ARGS \ -" [--daemon|--no-daemon]" -# define PLATFORM_DESC -# define PLATFORM_EXTRA \ -"At least one command line argument is required. If you don't otherwise\n" \ -"need an argument use `--daemon'.\n" \ -"\n" - -#else - -# define PLATFORM_ARGS \ -" [--daemon|--no-daemon]" -# define PLATFORM_DESC -# define PLATFORM_EXTRA - -#endif - - LOG((CLOG_PRINT -"Usage: %s" -" [--address
]" -" [--config ]" -" [--debug ]" -USAGE_DISPLAY_ARG -" [--name ]" -" [--restart|--no-restart]" -PLATFORM_ARGS -"\n\n" -"Start the synergy mouse/keyboard sharing server.\n" -"\n" -" -a, --address
listen for clients on the given address.\n" -" -c, --config use the named configuration file instead.\n" -" -d, --debug filter out log messages with priorty below level.\n" -" level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n" -" DEBUG, DEBUG1, DEBUG2.\n" -USAGE_DISPLAY_INFO -" -f, --no-daemon run the server in the foreground.\n" -"* --daemon run the server as a daemon.\n" -" -n, --name use screen-name instead the hostname to identify\n" -" this screen in the configuration.\n" -" -1, --no-restart do not try to restart the server if it fails for\n" -" some reason.\n" -"* --restart restart the server automatically if it fails.\n" -" -l --log write log messages to file.\n" -PLATFORM_DESC -" -h, --help display this help and exit.\n" -" --version display version information and exit.\n" -"\n" -"* marks defaults.\n" -"\n" -PLATFORM_EXTRA -"The argument for --address is of the form: [][:]. The\n" -"hostname must be the address or hostname of an interface on the system.\n" -"The default is to listen on all interfaces. The port overrides the\n" -"default port, %d.\n" -"\n" -"If no configuration file pathname is provided then the first of the\n" -"following to load successfully sets the configuration:\n" -" %s\n" -" %s\n" -"If no configuration file can be loaded then the configuration uses its\n" -"defaults with just the server screen.\n" -"\n" -"Where log messages go depends on the platform and whether or not the\n" -"server is running as a daemon.", - ARG->m_pname, - kDefaultPort, - ARCH->concatPath( - ARCH->getUserDirectory(), - USR_CONFIG_NAME).c_str(), - ARCH->concatPath( - ARCH->getSystemDirectory(), - SYS_CONFIG_NAME).c_str())); -} - -static -bool -isArg(int argi, int argc, const char* const* argv, - const char* name1, const char* name2, - int minRequiredParameters = 0) -{ - if ((name1 != NULL && strcmp(argv[argi], name1) == 0) || - (name2 != NULL && strcmp(argv[argi], name2) == 0)) { - // match. check args left. - if (argi + minRequiredParameters >= argc) { - LOG((CLOG_PRINT "%s: missing arguments for `%s'" BYE, - ARG->m_pname, argv[argi], ARG->m_pname)); - bye(kExitArgs); - } - return true; - } - - // no match - return false; -} - -static -void -parse(int argc, const char* const* argv) -{ - assert(ARG->m_pname != NULL); - assert(argv != NULL); - assert(argc >= 1); - - // set defaults - ARG->m_name = ARCH->getHostName(); - - // parse options - int i = 1; - for (; i < argc; ++i) { - if (isArg(i, argc, argv, "-d", "--debug", 1)) { - // change logging level - ARG->m_logFilter = argv[++i]; - } - - else if (isArg(i, argc, argv, "-a", "--address", 1)) { - // save listen address - try { - *ARG->m_synergyAddress = CNetworkAddress(argv[i + 1], - kDefaultPort); - ARG->m_synergyAddress->resolve(); - } - catch (XSocketAddress& e) { - LOG((CLOG_PRINT "%s: %s" BYE, - ARG->m_pname, e.what(), ARG->m_pname)); - bye(kExitArgs); - } - ++i; - } - - else if (isArg(i, argc, argv, "-n", "--name", 1)) { - // save screen name - ARG->m_name = argv[++i]; - } - - else if (isArg(i, argc, argv, "-c", "--config", 1)) { - // save configuration file path - ARG->m_configFile = argv[++i]; - } - -#if WINAPI_XWINDOWS - else if (isArg(i, argc, argv, "-display", "--display", 1)) { - // use alternative display - ARG->m_display = argv[++i]; - } -#endif - - else if (isArg(i, argc, argv, "-f", "--no-daemon")) { - // not a daemon - ARG->m_daemon = false; - } - - else if (isArg(i, argc, argv, NULL, "--daemon")) { - // daemonize - ARG->m_daemon = true; - } - else if (isArg(i, argc, argv, "-l", "--log", 1)) { - // logging to file - ARG->m_logFile = argv[++i]; - } - - else if (isArg(i, argc, argv, "-1", "--no-restart")) { - // don't try to restart - ARG->m_restartable = false; - } - - else if (isArg(i, argc, argv, NULL, "--restart")) { - // try to restart - ARG->m_restartable = true; - } - - else if (isArg(i, argc, argv, "-z", NULL)) { - ARG->m_backend = true; - } - - else if (isArg(i, argc, argv, "-h", "--help")) { - help(); - bye(kExitSuccess); - } - - else if (isArg(i, argc, argv, NULL, "--version")) { - version(); - bye(kExitSuccess); - } - - else if (isArg(i, argc, argv, "--", NULL)) { - // remaining arguments are not options - ++i; - break; - } - - else if (argv[i][0] == '-') { - LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, - ARG->m_pname, argv[i], ARG->m_pname)); - bye(kExitArgs); - } - - else { - // this and remaining arguments are not options - break; - } - } - - // no non-option arguments are allowed - if (i != argc) { - LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, - ARG->m_pname, argv[i], ARG->m_pname)); - bye(kExitArgs); - } - - // increase default filter level for daemon. the user must - // explicitly request another level for a daemon. - if (ARG->m_daemon && ARG->m_logFilter == NULL) { -#if SYSAPI_WIN32 - if (CArchMiscWindows::isWindows95Family()) { - // windows 95 has no place for logging so avoid showing - // the log console window. - ARG->m_logFilter = "FATAL"; - } - else -#endif - { - ARG->m_logFilter = "NOTE"; - } - } - - // set log filter - if (!CLOG->setFilter(ARG->m_logFilter)) { - LOG((CLOG_PRINT "%s: unrecognized log level `%s'" BYE, - ARG->m_pname, ARG->m_logFilter, ARG->m_pname)); - bye(kExitArgs); - } - - // identify system - LOG((CLOG_INFO "%s Server on %s %s", kAppVersion, ARCH->getOSName().c_str(), ARCH->getPlatformName().c_str())); - -#ifdef WIN32 -#ifdef _AMD64_ - LOG((CLOG_WARN "This is an experimental x64 build of %s. Use it at your own risk.", kApplication)); -#endif -#endif -} - -static -bool -loadConfig(const CString& pathname) -{ - try { - // load configuration - LOG((CLOG_DEBUG "opening configuration \"%s\"", pathname.c_str())); - std::ifstream configStream(pathname.c_str()); - if (!configStream.is_open()) { - // report failure to open configuration as a debug message - // since we try several paths and we expect some to be - // missing. - LOG((CLOG_DEBUG "cannot open configuration \"%s\"", - pathname.c_str())); - return false; - } - configStream >> *ARG->m_config; - LOG((CLOG_DEBUG "configuration read successfully")); - return true; - } - catch (XConfigRead& e) { - // report error in configuration file - LOG((CLOG_ERR "cannot read configuration \"%s\": %s", - pathname.c_str(), e.what())); - } - return false; -} - -static -void -loadConfig() -{ - bool loaded = false; - - // load the config file, if specified - if (!ARG->m_configFile.empty()) { - loaded = loadConfig(ARG->m_configFile); - } - - // load the default configuration if no explicit file given - else { - // get the user's home directory - CString path = ARCH->getUserDirectory(); - if (!path.empty()) { - // complete path - path = ARCH->concatPath(path, USR_CONFIG_NAME); - - // now try loading the user's configuration - if (loadConfig(path)) { - loaded = true; - ARG->m_configFile = path; - } - } - if (!loaded) { - // try the system-wide config file - path = ARCH->getSystemDirectory(); - if (!path.empty()) { - path = ARCH->concatPath(path, SYS_CONFIG_NAME); - if (loadConfig(path)) { - loaded = true; - ARG->m_configFile = path; - } - } - } - } - - if (!loaded) { - LOG((CLOG_PRINT "%s: no configuration available", ARG->m_pname)); - bye(kExitConfig); - } -} - - -// -// platform dependent entry points -// - -#if SYSAPI_WIN32 - -static bool s_hasImportantLogMessages = false; - -// -// CMessageBoxOutputter -// -// This class writes severe log messages to a message box -// - -class CMessageBoxOutputter : public ILogOutputter { -public: - CMessageBoxOutputter() { } - virtual ~CMessageBoxOutputter() { } - - // ILogOutputter overrides - virtual void open(const char*) { } - virtual void close() { } - virtual void show(bool) { } - virtual bool write(ELevel level, const char* message); - virtual const char* getNewline() const { return ""; } -}; - -bool -CMessageBoxOutputter::write(ELevel level, const char* message) -{ - // note any important messages the user may need to know about - if (level <= CLog::kWARNING) { - s_hasImportantLogMessages = true; - } - - // FATAL and PRINT messages get a dialog box if not running as - // backend. if we're running as a backend the user will have - // a chance to see the messages when we exit. - if (!ARG->m_backend && level <= CLog::kFATAL) { - MessageBox(NULL, message, ARG->m_pname, MB_OK | MB_ICONWARNING); - return false; - } - else { - return true; - } -} - -static -void -byeThrow(int x) -{ - CArchMiscWindows::daemonFailed(x); -} - -static -int -daemonNTMainLoop(int argc, const char** argv) -{ - parse(argc, argv); - ARG->m_backend = false; - loadConfig(); - return CArchMiscWindows::runDaemon(mainLoop); -} - -static -int -daemonNTStartup(int, char**) -{ - CSystemLogger sysLogger(DAEMON_NAME, false); - bye = &byeThrow; - return ARCH->daemonize(DAEMON_NAME, &daemonNTMainLoop); -} - -static -int -foregroundStartup(int argc, char** argv) -{ - - // parse command line - parse(argc, argv); - - // load configuration - loadConfig(); - - // never daemonize - return mainLoop(); -} - -static -void -showError(HINSTANCE instance, const char* title, UINT id, const char* arg) -{ - CString fmt = CMSWindowsUtil::getString(instance, id); - CString msg = CStringUtil::format(fmt.c_str(), arg); - MessageBox(NULL, msg.c_str(), title, MB_OK | MB_ICONWARNING); -} - -int WINAPI -WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) -{ - try { - CArchMiscWindows::setIcons((HICON)LoadImage(instance, - MAKEINTRESOURCE(IDI_SYNERGY), - IMAGE_ICON, - 32, 32, LR_SHARED), - (HICON)LoadImage(instance, - MAKEINTRESOURCE(IDI_SYNERGY), - IMAGE_ICON, - 16, 16, LR_SHARED)); - CArch arch(instance); - CMSWindowsScreen::init(instance); - CLOG; - CThread::getCurrentThread().setPriority(-14); - CArgs args; - - // set title on log window - ARCH->openConsole((CString(kAppVersion) + " " + "Server").c_str()); - - // 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. - StartupFunc startup = &standardStartup; - if (!CArchMiscWindows::isWindows95Family()) { - if (__argc <= 1) { - startup = &daemonNTStartup; - } - else { - startup = &foregroundStartup; - } - } - - // send PRINT and FATAL output to a message box - int result = run(__argc, __argv, new CMessageBoxOutputter, startup); - - // let user examine any messages if we're running as a backend - // by putting up a dialog box before exiting. - if (args.m_backend && s_hasImportantLogMessages) { - showError(instance, args.m_pname, IDS_FAILED, ""); - } - - delete CLOG; - return result; - } - catch (XBase& e) { - showError(instance, __argv[0], IDS_UNCAUGHT_EXCEPTION, e.what()); - //throw; - } - catch (XArch& e) { - showError(instance, __argv[0], IDS_INIT_FAILED, e.what().c_str()); - } - catch (...) { - showError(instance, __argv[0], IDS_UNCAUGHT_EXCEPTION, ""); - //throw; - } - return kExitFailed; -} - -#elif SYSAPI_UNIX int -main(int argc, char** argv) +main(int argc, char** argv) { - CArgs args; - try { - int result; - CArch arch; - CLOG; - CArgs args; - result = run(argc, argv, NULL, &standardStartup); - delete CLOG; - return result; - } - catch (XBase& e) { - LOG((CLOG_CRIT "Uncaught exception: %s\n", e.what())); - throw; - } - catch (XArch& e) { - LOG((CLOG_CRIT "Initialization failed: %s" BYE, e.what().c_str())); - return kExitFailed; - } - catch (...) { - LOG((CLOG_CRIT "Uncaught exception: \n")); - throw; - } + CServerApp app; + return app.run(argc, argv, createTaskBarReceiver); } - -#else - -#error no main() for platform - -#endif diff --git a/gui/qsynergy.pro b/gui/qsynergy.pro index d0e8dfd0..f67e03af 100644 --- a/gui/qsynergy.pro +++ b/gui/qsynergy.pro @@ -83,3 +83,5 @@ release { RCC_DIR = tmp/release } +Debug:DESTDIR = ../bin/Debug +Release:DESTDIR = ../bin/Release diff --git a/gui/res/win/QSynergy.ico b/gui/res/win/QSynergy.ico index 89f965f4432c5f58054b9a1b0eae5bf6cbfd245d..fc2e41468ec60a88e0da4194f288a18572f3f36f 100644 GIT binary patch literal 287934 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 literal 8478 zcmeI1KWH3B6vm(a*e8S|%Z-~Dq__}vXV}UrV&mT6#z|>aLkbt^+yrSM;8FwvhV6t% zLQiZmzTr%%8baza{{iY$oC5(ZeHZtwQ9U1vh;!S!}zl^B0qm9a)se4 z$4!nI!GV?&yT9dtAbB1-&RoiHIHXs=+duJMuQ>Qqlb(9p`ug^E1I+I3?(XmJkD_Ir zwoR+XpoZ`7n_*F5!?$xa7_9y@9UB~QjWDbDj>v-IqX1iNmO=`Jq0y9JcvU(jnEQ^w z7Gb2>!%ze~pRL_3zyv-RkH?YkoZDF1+}w=gwY9a?KK%1QIIFneKdQ#~i{M`c0A3Tt z1K}(%xF*;nf_mc~2;;GW3Worjn~JKt$bbM9zM*5w5O5oY2+>S!bo#xVIM#9I(&{w)eJBUN zeJAtt^Rlq8Am`4VlM5Fv$i<5n<({SK{)Lx2 zcf5QpPsRUH%CArVl&9mdeD%YRB+58+ffXEO7-rZqY#FvpZQOaKIPAI@9d?J^VOO^T zdx5=RP+%{x(@BZF#9m@AF_hRnW6#)^mO0~`5j-PMhEvA(o2BQJp&>&8GBjjp$k32^Z0N|)k*N+oJ*YAjjs+pbjlo20;DbJG3~oSXt2F(BaVG(BaTwr9y{8gF}NugO<*r!=b^Up|o%I zGL(=!;g7MESF3|6au@Kd)Z{MERFJeFL4l@%+y!7k?t?N}3)s-$(csbG(csbG(O}C74J8^p8ax_G zG!TXZJ{mk4JQ_S22+e^V4IT|14ITlW{3XFWjvkUDhGXo|;VrguoO~qT%DsE{eVYbJUo4A)*c53emtZek(@;r+A&?7`%alYMX$a zY+S?5oN9)eBHqA@i;?Pjweae)tI(w2f&-E#+${e5S>63iAM`TagzjPcMcFJ$Y(hZaAL zK9Rw|H_|&9)FI_tfX*qMLxQ;r%W>h`ir$IeFQ=8D75`u1H(R)5quI_B_inZY z`w#h31SN9V5+-_1#7`aC(~P4ezM76pkkD<1n(})TrS!0)eWSx9y(K8_{d=_ISy@{N zT9R7PBfXOV?+~X=Zo;=qR3)1r*J?T?JqdqOQS0|g$Q4UqeS6A`CiJxElT+yOJM;;M z4u68)b!hW@=$O(;r2KxiS{sjsKb@U=ME;xEhFTKipMZQE#eS#RJDbrUyt}d2IpX6> za(ySP_)ug{dwv(qXfHHQN7`?RK8(?u08Lb>z>#22Rc4!-|>%e>pWH7s3Yo%I-~CHeBwOgyeWaz zp{~oYs#C06tRuuw-%(X(t$cW&zD$T!j`%zECiCoXIRyaW$EkIkxjg-ydip!{^nLpK z@IIZrF`MQ1m2Nxb=7&PqIET$NlxiPcZLX`ax&{w}LnJfkI1B@$JJyFz#P!+JH%|8{ t;Zdj_)sxDeTJ=5vdoUQ9?87Q{NCkzlT_5sH8R_#vpACFJzU}{8{tX4i2$ui= diff --git a/gui/res/win/QSynergy.rc b/gui/res/win/QSynergy.rc index 696cc6e6..f746d72f 100644 --- a/gui/res/win/QSynergy.rc +++ b/gui/res/win/QSynergy.rc @@ -1 +1 @@ -IDI_ICON1 ICON DISCARDABLE "res\win\QSynergy.ico" +IDI_ICON1 ICON DISCARDABLE "res\\win\\QSynergy.ico" diff --git a/gui/src/AppConfig.cpp b/gui/src/AppConfig.cpp index cfb4345f..e16f3c74 100644 --- a/gui/src/AppConfig.cpp +++ b/gui/src/AppConfig.cpp @@ -6,7 +6,7 @@ #if defined(Q_OS_WIN) const char AppConfig::m_SynergysName[] = "synergys.exe"; const char AppConfig::m_SynergycName[] = "synergyc.exe"; -const char AppConfig::m_SynergyProgramDir[] = "c:/program files/synergy/"; +const char AppConfig::m_SynergyProgramDir[] = "./"; #else const char AppConfig::m_SynergysName[] = "synergys"; const char AppConfig::m_SynergycName[] = "synergyc"; diff --git a/gui/src/MainWindow.cpp b/gui/src/MainWindow.cpp index 7b3b89c3..c676cfc0 100644 --- a/gui/src/MainWindow.cpp +++ b/gui/src/MainWindow.cpp @@ -224,7 +224,7 @@ void MainWindow::startSynergy() setSynergyProcess(new QProcess(this)); if ((synergyType() == synergyClient && !clientArgs(args, app)) - || synergyType() == synergyServer && !serverArgs(args, app)) + || (synergyType() == synergyServer && !serverArgs(args, app))) { stopSynergy(); return; diff --git a/gui/src/main.cpp b/gui/src/main.cpp index 3087fe0b..e3ff1c24 100644 --- a/gui/src/main.cpp +++ b/gui/src/main.cpp @@ -6,16 +6,16 @@ int main(int argc, char* argv[]) { - QCoreApplication::setOrganizationName("Fidra"); - QCoreApplication::setOrganizationDomain("www.fidra.de"); - QCoreApplication::setApplicationName("QSynergy"); + QCoreApplication::setOrganizationName("The Synergy+ Project"); + QCoreApplication::setOrganizationDomain("http://code.google.com/p/synergy-plus/"); + QCoreApplication::setApplicationName("Synergy+"); QSynergyApplication app(argc, argv); #if !defined(Q_OS_MAC) if (!QSystemTrayIcon::isSystemTrayAvailable()) { - QMessageBox::critical(NULL, "QSynergy", QObject::tr("There doesn't seem to be a system tray available. Quitting.")); + QMessageBox::critical(NULL, "Synergy+", QObject::tr("There doesn't seem to be a system tray available. Quitting.")); return -1; } diff --git a/lib/arch/CArch.cpp b/lib/arch/CArch.cpp index 6d0a8c07..7d31bf3e 100644 --- a/lib/arch/CArch.cpp +++ b/lib/arch/CArch.cpp @@ -14,6 +14,7 @@ #include "common.h" #include "CArch.h" +#include "CLog.h" #undef ARCH_CONSOLE #undef ARCH_DAEMON @@ -41,6 +42,7 @@ # include "CArchSystemWindows.h" # include "CArchTaskBarWindows.h" # include "CArchTimeWindows.h" +# include "CArchAppUtilWindows.h" #elif SYSAPI_UNIX # include "CArchConsoleUnix.h" # include "CArchDaemonUnix.h" @@ -55,6 +57,7 @@ # include "CArchSystemUnix.h" # include "CArchTaskBarXWindows.h" # include "CArchTimeUnix.h" +# include "CArchAppUtilUnix.h" #endif #if !defined(ARCH_CONSOLE) @@ -101,13 +104,17 @@ # error unsupported platform for time #endif +#if !defined(ARCH_APPUTIL) +# error unsupported platform for app util +#endif + // // CArch // CArch* CArch::s_instance = NULL; -CArch::CArch(ARCH_ARGS* args) +CArch::CArch() { // only once instance of CArch assert(s_instance == NULL); @@ -122,9 +129,10 @@ CArch::CArch(ARCH_ARGS* args) m_sleep = new ARCH_SLEEP; m_string = new ARCH_STRING; m_time = new ARCH_TIME; - m_console = new ARCH_CONSOLE(args); + m_console = new ARCH_CONSOLE; m_daemon = new ARCH_DAEMON; - m_taskbar = new ARCH_TASKBAR(args); + m_taskbar = new ARCH_TASKBAR; + m_appUtil = new ARCH_APPUTIL; #if SYSAPI_WIN32 CArchMiscWindows::init(); @@ -145,6 +153,7 @@ CArch::~CArch() delete m_file; delete m_system; delete m_mt; + delete m_appUtil; // no instance s_instance = NULL; @@ -182,12 +191,6 @@ CArch::writeConsole(const char* str) m_console->writeConsole(str); } -const char* -CArch::getNewlineForConsole() -{ - return m_console->getNewlineForConsole(); -} - void CArch::installDaemon(const char* name, const char* description, @@ -643,3 +646,27 @@ CArch::time() { return m_time->time(); } + +bool +CArch::parseArg(const int& argc, const char* const* argv, int& i) +{ + return m_appUtil->parseArg(argc, argv, i); +} + +void +CArch::adoptApp(CApp* app) +{ + m_appUtil->adoptApp(app); +} + +CApp& +CArch::app() const +{ + return m_appUtil->app(); +} + +int +CArch::run(int argc, char** argv, CreateTaskBarReceiverFunc createTaskBarReceiver) +{ + return m_appUtil->run(argc, argv, createTaskBarReceiver); +} diff --git a/lib/arch/CArch.h b/lib/arch/CArch.h index d5d5b9d3..a7bcf935 100644 --- a/lib/arch/CArch.h +++ b/lib/arch/CArch.h @@ -26,6 +26,7 @@ #include "IArchSystem.h" #include "IArchTaskBar.h" #include "IArchTime.h" +#include "IArchAppUtil.h" /*! \def ARCH @@ -33,9 +34,7 @@ This macro evaluates to the singleton CArch object. */ #define ARCH (CArch::getInstance()) -#define ARCH_ARGS void - -//! Delegating mplementation of architecture dependent interfaces +//! Delegating implementation of architecture dependent interfaces /*! This class is a centralized interface to all architecture dependent interface implementations (except miscellaneous functions). It @@ -55,9 +54,10 @@ class CArch : public IArchConsole, public IArchString, public IArchSystem, public IArchTaskBar, - public IArchTime { + public IArchTime, + public IArchAppUtil { public: - CArch(ARCH_ARGS* args = NULL); + CArch(); ~CArch(); // @@ -76,7 +76,6 @@ public: virtual void closeConsole(); virtual void showConsole(bool showIfEmpty); virtual void writeConsole(const char*); - virtual const char* getNewlineForConsole(); // IArchDaemon overrides virtual void installDaemon(const char* name, @@ -183,6 +182,12 @@ public: // IArchTime overrides virtual double time(); + + // IArchAppUtil overrides + virtual bool parseArg(const int& argc, const char* const* argv, int& i); + virtual void adoptApp(CApp* app); + virtual CApp& app() const; + virtual int run(int argc, char** argv, CreateTaskBarReceiverFunc createTaskBarReceiver); private: static CArch* s_instance; @@ -198,6 +203,7 @@ private: IArchSystem* m_system; IArchTaskBar* m_taskbar; IArchTime* m_time; + IArchAppUtil* m_appUtil; }; //! Convenience object to lock/unlock an arch mutex diff --git a/lib/arch/CArchAppUtil.cpp b/lib/arch/CArchAppUtil.cpp new file mode 100644 index 00000000..ec8b072a --- /dev/null +++ b/lib/arch/CArchAppUtil.cpp @@ -0,0 +1,55 @@ +/* + * 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 "CArchAppUtil.h" +#include "CApp.h" + +CArchAppUtil* CArchAppUtil::s_instance = nullptr; + +CArchAppUtil::CArchAppUtil() : +m_app(nullptr) +{ + s_instance = this; +} + +CArchAppUtil::~CArchAppUtil() +{ +} + +bool +CArchAppUtil::parseArg(const int& argc, const char* const* argv, int& i) +{ + // no common platform args (yet) + return false; +} + +void +CArchAppUtil::adoptApp(CApp* app) +{ + m_app = app; +} + +CApp& +CArchAppUtil::app() const +{ + assert(m_app != nullptr); + return *m_app; +} + +CArchAppUtil& +CArchAppUtil::instance() +{ + assert(s_instance != nullptr); + return *s_instance; +} diff --git a/lib/arch/CArchAppUtil.h b/lib/arch/CArchAppUtil.h new file mode 100644 index 00000000..5b74f648 --- /dev/null +++ b/lib/arch/CArchAppUtil.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#pragma once + +#include "IArchAppUtil.h" + +class CArchAppUtil : public IArchAppUtil { +public: + CArchAppUtil(); + virtual ~CArchAppUtil(); + + virtual bool parseArg(const int& argc, const char* const* argv, int& i); + virtual void adoptApp(CApp* app); + CApp& app() const; + + static CArchAppUtil& instance(); + +private: + CApp* m_app; + static CArchAppUtil* s_instance; +}; diff --git a/lib/arch/CArchAppUtilUnix.cpp b/lib/arch/CArchAppUtilUnix.cpp new file mode 100644 index 00000000..98527538 --- /dev/null +++ b/lib/arch/CArchAppUtilUnix.cpp @@ -0,0 +1,56 @@ +/* + * 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 "CArchAppUtilUnix.h" + +CArchAppUtilUnix::CArchAppUtilUnix() +{ +} + +CArchAppUtilUnix::~CArchAppUtilUnix() +{ +} + +bool +CArchAppUtilUnix::parseArg(const int& argc, const char* const* argv, int& i) +{ +#if WINAPI_XWINDOWS + if (app().isArg(i, argc, argv, "-display", "--display", 1)) { + // use alternative display + app().argsBase().m_display = argv[++i]; + } + + else { + // option not supported here + return false; + } + + return true; +#else + // no options for carbon + return false; +#endif +} + +int +standardStartupStatic(int argc, char** argv) +{ + return CArchAppUtil::instance().app().standardStartup(argc, argv); +} + +int +CArchAppUtilUnix::run(int argc, char** argv, CreateTaskBarReceiverFunc createTaskBarReceiver) +{ + return app().runInner(argc, argv, NULL, &standardStartupStatic, createTaskBarReceiver); +} diff --git a/lib/arch/CArchAppUtilUnix.h b/lib/arch/CArchAppUtilUnix.h new file mode 100644 index 00000000..a9ba730c --- /dev/null +++ b/lib/arch/CArchAppUtilUnix.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#pragma once + +#include "CArchAppUtil.h" + +#define ARCH_APPUTIL CArchAppUtilUnix + +class CArchAppUtilUnix : public CArchAppUtil { +public: + CArchAppUtilUnix(); + virtual ~CArchAppUtilUnix(); + + bool parseArg(const int& argc, const char* const* argv, int& i); + int run(int argc, char** argv, CreateTaskBarReceiverFunc createTaskBarReceiver); +}; diff --git a/lib/arch/CArchAppUtilWindows.cpp b/lib/arch/CArchAppUtilWindows.cpp new file mode 100644 index 00000000..872ed3c6 --- /dev/null +++ b/lib/arch/CArchAppUtilWindows.cpp @@ -0,0 +1,268 @@ +/* + * 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 "CArchAppUtilWindows.h" +#include "Version.h" +#include "CLog.h" +#include "XArchWindows.h" +#include "CArchMiscWindows.h" +#include "CApp.h" +#include "LogOutputters.h" +#include "CMSWindowsScreen.h" + +#include +#include +#include + +CArchAppUtilWindows::CArchAppUtilWindows() +{ +} + +CArchAppUtilWindows::~CArchAppUtilWindows() +{ +} + +bool +CArchAppUtilWindows::parseArg(const int& argc, const char* const* argv, int& i) +{ + if (app().isArg(i, argc, argv, NULL, "--service")) { + + const char* action = argv[++i]; + + if (_stricmp(action, "install") == 0) { + installService(); + } + else if (_stricmp(action, "uninstall") == 0) { + uninstallService(); + } + else if (_stricmp(action, "start") == 0) { + startService(); + } + else if (_stricmp(action, "stop") == 0) { + stopService(); + } + else { + LOG((CLOG_ERR "unknown service action: %s", action)); + app().m_bye(kExitArgs); + } + app().m_bye(kExitSuccess); + } + else { + // option not supported here + return false; + } + + return true; +} + +void +CArchAppUtilWindows::adoptApp(CApp* app) +{ + app->m_bye = &exitPause; + CArchAppUtil::adoptApp(app); +} + +CString +CArchAppUtilWindows::getServiceArgs() const +{ + std::stringstream argBuf; + for (int i = 1; i < __argc; i++) { + const char* arg = __argv[i]; + + // ignore service setup args + if (_stricmp(arg, "--service") == 0) { + // ignore and skip the next arg also (service action) + i++; + } + else { + argBuf << " " << __argv[i]; + } + } + return argBuf.str(); +} + +void +CArchAppUtilWindows::installService() +{ + CString args = getServiceArgs(); + + // get the path of this program + char thisPath[MAX_PATH]; + GetModuleFileName(CArchMiscWindows::instanceWin32(), thisPath, MAX_PATH); + + ARCH->installDaemon( + app().daemonName(), app().daemonInfo(), + thisPath, args.c_str(), NULL, true); + + LOG((CLOG_INFO "service '%s' installed with args: %s", + app().daemonName(), args != "" ? args.c_str() : "none" )); +} + +void +CArchAppUtilWindows::uninstallService() +{ + ARCH->uninstallDaemon(app().daemonName(), true); + LOG((CLOG_INFO "service '%s' uninstalled", app().daemonName())); +} + +void +CArchAppUtilWindows::startService() +{ + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // open the service + SC_HANDLE service = OpenService( + mgr, app().daemonName(), SERVICE_START); + + if (service == NULL) { + CloseServiceHandle(mgr); + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // start the service + if (StartService(service, 0, NULL)) { + LOG((CLOG_INFO "service '%s' started", app().daemonName())); + } + else { + throw XArchDaemonFailed(new XArchEvalWindows()); + } +} + +void +CArchAppUtilWindows::stopService() +{ + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // open the service + SC_HANDLE service = OpenService( + mgr, app().daemonName(), + SERVICE_STOP | SERVICE_QUERY_STATUS); + + if (service == NULL) { + CloseServiceHandle(mgr); + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // ask the service to stop, asynchronously + SERVICE_STATUS ss; + if (!ControlService(service, SERVICE_CONTROL_STOP, &ss)) { + DWORD dwErrCode = GetLastError(); + if (dwErrCode != ERROR_SERVICE_NOT_ACTIVE) { + LOG((CLOG_ERR "cannot stop service '%s'", app().daemonName())); + throw XArchDaemonFailed(new XArchEvalWindows()); + } + } + + LOG((CLOG_INFO "service '%s' stopping asyncronously", app().daemonName())); +} + +void +exitPause(int code) +{ + CString name; + CArchMiscWindows::getParentProcessName(name); + + // if the user did not launch from the command prompt (i.e. it was launched + // by double clicking, or through a debugger), allow user to read any error + // messages (instead of the window closing automatically). + if (name != "cmd.exe") { + std::cout << std::endl << "Press any key to exit..." << std::endl; + int c = _getch(); + } + + exit(code); +} + +static +int +mainLoopStatic() +{ + return CArchAppUtil::instance().app().mainLoop(); +} + +int +CArchAppUtilWindows::daemonNTMainLoop(int argc, const char** argv) +{ + app().parseArgs(argc, argv); + app().argsBase().m_backend = false; + app().loadConfig(); + return CArchMiscWindows::runDaemon(mainLoopStatic); +} + +void +CArchAppUtilWindows::byeThrow(int x) +{ + CArchMiscWindows::daemonFailed(x); +} + +int daemonNTMainLoopStatic(int argc, const char** argv) +{ + return CArchAppUtil::instance().app().daemonMainLoop(argc, argv); +} + +int +CArchAppUtilWindows::daemonNTStartup(int, char**) +{ + CSystemLogger sysLogger(app().daemonName(), false); + app().m_bye = &byeThrow; + return ARCH->daemonize(app().daemonName(), daemonNTMainLoopStatic); +} + +static +int +daemonNTStartupStatic(int argc, char** argv) +{ + return CArchAppUtilWindows::instance().daemonNTStartup(argc, argv); +} + +static +int +foregroundStartupStatic(int argc, char** argv) +{ + return CArchAppUtil::instance().app().foregroundStartup(argc, argv); +} + +int +CArchAppUtilWindows::run(int argc, char** argv, CreateTaskBarReceiverFunc createTaskBarReceiver) +{ + // record window instance for tray icon, etc + CArchMiscWindows::setInstanceWin32(GetModuleHandle(NULL)); + + CMSWindowsScreen::init(CArchMiscWindows::instanceWin32()); + CThread::getCurrentThread().setPriority(-14); + + StartupFunc startup; + if (CArchMiscWindows::wasLaunchedAsService()) { + startup = &daemonNTStartupStatic; + } else { + startup = &foregroundStartupStatic; + app().argsBase().m_daemon = false; + } + + return app().runInner(argc, argv, NULL, startup, createTaskBarReceiver); +} + +CArchAppUtilWindows& +CArchAppUtilWindows::instance() +{ + return (CArchAppUtilWindows&)CArchAppUtil::instance(); +} diff --git a/lib/arch/CArchAppUtilWindows.h b/lib/arch/CArchAppUtilWindows.h new file mode 100644 index 00000000..81d1b0d1 --- /dev/null +++ b/lib/arch/CArchAppUtilWindows.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#pragma once + +#include "CArchAppUtil.h" +#include "CString.h" + +#include "Windows.h" + +#define ARCH_APPUTIL CArchAppUtilWindows + +class CArchAppUtilWindows : public CArchAppUtil { +public: + CArchAppUtilWindows(); + virtual ~CArchAppUtilWindows(); + + // Gets the arguments to be used with a service. + CString getServiceArgs() const; + + // Install application as Windows service. + void installService(); + + // Uninstall a Windows service with matching daemon name. + void uninstallService(); + + // Start a Windows service with matching daemon name. + void startService(); + + // Stop a Windows service with matching daemon name. + void stopService(); + + // Will install, uninstall, start, or stop the service depending on arg. + void handleServiceArg(const char* serviceAction); + + bool parseArg(const int& argc, const char* const* argv, int& i); + + void adoptApp(CApp* app); + + int daemonNTStartup(int, char**); + + int daemonNTMainLoop(int argc, const char** argv); + + int run(int argc, char** argv, CreateTaskBarReceiverFunc createTaskBarReceiver); + + static void byeThrow(int x); + + static CArchAppUtilWindows& instance(); +}; + +// TODO: move to class +void exitPause(int code); diff --git a/lib/arch/CArchConsoleStd.cpp b/lib/arch/CArchConsoleStd.cpp new file mode 100644 index 00000000..e9e06c65 --- /dev/null +++ b/lib/arch/CArchConsoleStd.cpp @@ -0,0 +1,24 @@ +/* + * 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 "CArchConsoleStd.h" +#include + +void +CArchConsoleStd::writeConsole(const char* str) +{ + // TODO: we need to use cerr also somehow + std::cout << str << std::endl; + std::cout.flush(); +} \ No newline at end of file diff --git a/lib/arch/CArchConsoleStd.h b/lib/arch/CArchConsoleStd.h new file mode 100644 index 00000000..ac902721 --- /dev/null +++ b/lib/arch/CArchConsoleStd.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#pragma once + +#include "IArchConsole.h" + +//! Cross platform implementation of IArchConsole +class CArchConsoleStd : public IArchConsole { +public: + CArchConsoleStd() { } + virtual ~CArchConsoleStd() { } + + // IArchConsole overrides + virtual void openConsole(const char* title) { } + virtual void closeConsole() { } + virtual void showConsole(bool) { } + virtual void writeConsole(const char*); +}; diff --git a/lib/arch/CArchConsoleUnix.cpp b/lib/arch/CArchConsoleUnix.cpp index 88337150..59877a19 100644 --- a/lib/arch/CArchConsoleUnix.cpp +++ b/lib/arch/CArchConsoleUnix.cpp @@ -13,49 +13,7 @@ */ #include "CArchConsoleUnix.h" -#include -#include -// -// CArchConsoleUnix -// +CArchConsoleUnix::CArchConsoleUnix() { } -CArchConsoleUnix::CArchConsoleUnix(void*) -{ - // do nothing -} - -CArchConsoleUnix::~CArchConsoleUnix() -{ - // do nothing -} - -void -CArchConsoleUnix::openConsole(const char*) -{ - // do nothing -} - -void -CArchConsoleUnix::closeConsole() -{ - // do nothing -} - -void -CArchConsoleUnix::showConsole(bool) -{ - // do nothing -} - -void -CArchConsoleUnix::writeConsole(const char* str) -{ - fprintf(stderr, "%s", str); -} - -const char* -CArchConsoleUnix::getNewlineForConsole() -{ - return "\n"; -} +CArchConsoleUnix::~CArchConsoleUnix() { } diff --git a/lib/arch/CArchConsoleUnix.h b/lib/arch/CArchConsoleUnix.h index f93630bd..c8a737f3 100644 --- a/lib/arch/CArchConsoleUnix.h +++ b/lib/arch/CArchConsoleUnix.h @@ -12,25 +12,14 @@ * GNU General Public License for more details. */ -#ifndef CARCHCONSOLEUNIX_H -#define CARCHCONSOLEUNIX_H +#pragma once -#include "IArchConsole.h" +#include "CArchConsoleStd.h" #define ARCH_CONSOLE CArchConsoleUnix -//! Unix implementation of IArchConsole -class CArchConsoleUnix : public IArchConsole { +class CArchConsoleUnix : public CArchConsoleStd { public: - CArchConsoleUnix(void*); + CArchConsoleUnix(); virtual ~CArchConsoleUnix(); - - // IArchConsole overrides - virtual void openConsole(const char* title); - virtual void closeConsole(); - virtual void showConsole(bool); - virtual void writeConsole(const char*); - virtual const char* getNewlineForConsole(); }; - -#endif diff --git a/lib/arch/CArchConsoleWindows.cpp b/lib/arch/CArchConsoleWindows.cpp index 3f470e5e..1f721c5b 100644 --- a/lib/arch/CArchConsoleWindows.cpp +++ b/lib/arch/CArchConsoleWindows.cpp @@ -1,439 +1,19 @@ -/* - * 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 "CArchConsoleWindows.h" -#include "IArchMultithread.h" -#include "CArch.h" -#include "CArchMiscWindows.h" -#include - -#define SYNERGY_MSG_CONSOLE_OPEN WM_APP + 0x0021 -#define SYNERGY_MSG_CONSOLE_CLOSE WM_APP + 0x0022 -#define SYNERGY_MSG_CONSOLE_SHOW WM_APP + 0x0023 -#define SYNERGY_MSG_CONSOLE_WRITE WM_APP + 0x0024 -#define SYNERGY_MSG_CONSOLE_CLEAR WM_APP + 0x0025 -#define TWIPS_PER_POINT 20 - -// -// CArchConsoleWindows -// - -CArchConsoleWindows* CArchConsoleWindows::s_instance = NULL; -HINSTANCE CArchConsoleWindows::s_appInstance = NULL; - -CArchConsoleWindows::CArchConsoleWindows(void* appInstance) : - m_show(false), - m_maxLines(1000), - m_numCharacters(0), - m_maxCharacters(65536) -{ - // 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(&CArchConsoleWindows::threadEntry, this); - - // wait for child thread - while (!m_ready) { - ARCH->waitCondVar(m_condVar, m_mutex, -1.0); - } - - // ready - ARCH->unlockMutex(m_mutex); - -} - -CArchConsoleWindows::~CArchConsoleWindows() -{ - if (m_thread != NULL) { - PostMessage(m_hwnd, WM_QUIT, 0, 0); - ARCH->wait(m_thread, -1.0); - ARCH->closeThread(m_thread); - } - ARCH->closeCondVar(m_condVar); - ARCH->closeMutex(m_mutex); - s_instance = NULL; -} - -void -CArchConsoleWindows::openConsole(const char* title) -{ - SetWindowText(m_frame, title); - SendMessage(m_frame, SYNERGY_MSG_CONSOLE_OPEN, 0, 0); -} - -void -CArchConsoleWindows::closeConsole() -{ - SendMessage(m_frame, SYNERGY_MSG_CONSOLE_CLOSE, 0, 0); - SendMessage(m_frame, SYNERGY_MSG_CONSOLE_CLEAR, 0, 0); -} - -void -CArchConsoleWindows::showConsole(bool showIfEmpty) -{ - SendMessage(m_frame, SYNERGY_MSG_CONSOLE_SHOW, showIfEmpty ? 1 : 0, 0); -} - -void -CArchConsoleWindows::writeConsole(const char* str) -{ - SendMessage(m_frame, SYNERGY_MSG_CONSOLE_WRITE, - reinterpret_cast(str), 0); -} - -const char* -CArchConsoleWindows::getNewlineForConsole() -{ - return "\r\n"; -} - -void -CArchConsoleWindows::clearBuffer() -{ - m_buffer.clear(); - m_numCharacters = 0; - SetWindowText(m_hwnd, ""); -} - -void -CArchConsoleWindows::appendBuffer(const char* msg) -{ - bool wasEmpty = m_buffer.empty(); - - // get current selection - CHARRANGE selection; - SendMessage(m_hwnd, EM_EXGETSEL, 0, reinterpret_cast(&selection)); - - // remove tail of buffer - size_t removedCharacters = 0; - while (m_buffer.size() >= m_maxLines) { - removedCharacters += m_buffer.front().size(); - m_buffer.pop_front(); - } - - // remove lines from top of control - if (removedCharacters > 0) { - CHARRANGE range; - range.cpMin = 0; - range.cpMax = static_cast(removedCharacters); - SendMessage(m_hwnd, EM_EXSETSEL, 0, reinterpret_cast(&range)); - SendMessage(m_hwnd, EM_REPLACESEL, FALSE, reinterpret_cast("")); - - // adjust selection - if (selection.cpMin < static_cast(removedCharacters) || - selection.cpMax < static_cast(removedCharacters)) { - selection.cpMin = 0; - selection.cpMax = 0; - } - else { - selection.cpMin -= static_cast(removedCharacters); - selection.cpMax -= static_cast(removedCharacters); - } - - m_numCharacters -= removedCharacters; - } - - // append message - m_buffer.push_back(msg); - size_t newNumCharacters = m_numCharacters + m_buffer.back().size(); - - // add line to bottom of control - if (newNumCharacters > m_maxCharacters) { - m_maxCharacters = newNumCharacters; - SendMessage(m_hwnd, EM_EXLIMITTEXT, 0, m_maxCharacters); - } - CHARRANGE range; - range.cpMin = (LONG)m_numCharacters; - range.cpMax = (LONG)m_numCharacters; - SendMessage(m_hwnd, EM_EXSETSEL, 0, reinterpret_cast(&range)); - SendMessage(m_hwnd, EM_REPLACESEL, FALSE, - reinterpret_cast(m_buffer.back().c_str())); - - // adjust selection - bool atEnd = false; - if (selection.cpMax == static_cast(m_numCharacters)) { - selection.cpMin = static_cast(newNumCharacters); - selection.cpMax = static_cast(newNumCharacters); - atEnd = true; - } - - // restore the selection - SendMessage(m_hwnd, EM_EXSETSEL, 0, reinterpret_cast(&selection)); - if (atEnd) { - SendMessage(m_hwnd, EM_SCROLLCARET, 0, 0); - } - - if (wasEmpty && m_show) { - ShowWindow(m_frame, TRUE); - } - - m_numCharacters = newNumCharacters; -} - -void -CArchConsoleWindows::setSize(int width, int height) -{ - DWORD style = GetWindowLong(m_frame, GWL_STYLE); - DWORD exStyle = GetWindowLong(m_frame, GWL_EXSTYLE); - RECT rect; - rect.left = 100; - rect.top = 100; - rect.right = rect.left + width * m_wChar; - rect.bottom = rect.top + height * m_hChar; - AdjustWindowRectEx(&rect, style, FALSE, exStyle); - SetWindowPos(m_frame, NULL, 0, 0, rect.right - rect.left, - rect.bottom - rect.top, - SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER); -} - -LRESULT -CArchConsoleWindows::wndProc(HWND hwnd, - UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_CLOSE: - ShowWindow(m_frame, FALSE); - m_show = false; - return 0; - - case SYNERGY_MSG_CONSOLE_OPEN: - return 0; - - case SYNERGY_MSG_CONSOLE_CLOSE: - SendMessage(m_frame, WM_CLOSE, 0, 0); - m_show = false; - return 0; - - case SYNERGY_MSG_CONSOLE_SHOW: - m_show = true; - if (wParam != 0 || !m_buffer.empty()) { - ShowWindow(m_frame, TRUE); - } - return 0; - - case SYNERGY_MSG_CONSOLE_WRITE: - appendBuffer(reinterpret_cast(wParam)); - return 0; - - case SYNERGY_MSG_CONSOLE_CLEAR: - clearBuffer(); - return 0; - - case WM_SIZE: - if (hwnd == m_frame) { - MoveWindow(m_hwnd, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE); - } - break; - - case WM_SIZING: - if (hwnd == m_frame) { - // get window vs client area info - int wBase = 40 * m_wChar; - int hBase = 40 * m_hChar; - DWORD style = GetWindowLong(m_frame, GWL_STYLE); - DWORD exStyle = GetWindowLong(m_frame, GWL_EXSTYLE); - RECT rect; - rect.left = 100; - rect.top = 100; - rect.right = rect.left + wBase; - rect.bottom = rect.top + hBase; - AdjustWindowRectEx(&rect, style, FALSE, exStyle); - wBase = rect.right - rect.left - wBase; - hBase = rect.bottom - rect.top - hBase; - - // get closest size that's a multiple of the character size - RECT* newRect = (RECT*)lParam; - int width = (newRect->right - newRect->left - wBase) / m_wChar; - int height = (newRect->bottom - newRect->top - hBase) / m_hChar; - width = width * m_wChar + wBase; - height = height * m_hChar + hBase; - - // adjust sizing rect - switch (wParam) { - case WMSZ_LEFT: - case WMSZ_TOPLEFT: - case WMSZ_BOTTOMLEFT: - newRect->left = newRect->right - width; - break; - - case WMSZ_RIGHT: - case WMSZ_TOPRIGHT: - case WMSZ_BOTTOMRIGHT: - newRect->right = newRect->left + width; - break; - } - switch (wParam) { - case WMSZ_TOP: - case WMSZ_TOPLEFT: - case WMSZ_TOPRIGHT: - newRect->top = newRect->bottom - height; - break; - - case WMSZ_BOTTOM: - case WMSZ_BOTTOMLEFT: - case WMSZ_BOTTOMRIGHT: - newRect->bottom = newRect->top + height; - break; - } - return TRUE; - } - break; - - default: - break; - } - - return DefWindowProc(hwnd, msg, wParam, lParam); -} - -LRESULT CALLBACK -CArchConsoleWindows::staticWndProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - // forward the message - if (s_instance != NULL) { - return s_instance->wndProc(hwnd, msg, wParam, lParam); - } - else { - return DefWindowProc(hwnd, msg, wParam, lParam); - } -} - -void -CArchConsoleWindows::threadMainLoop() -{ - LoadLibrary("RICHED32.DLL"); - - // get the app icons - HICON largeIcon, smallIcon; - CArchMiscWindows::getIcons(largeIcon, smallIcon); - - // register a window class - WNDCLASSEX classInfo; - classInfo.cbSize = sizeof(classInfo); - classInfo.style = 0; - classInfo.lpfnWndProc = &CArchConsoleWindows::staticWndProc; - classInfo.cbClsExtra = 0; - classInfo.cbWndExtra = sizeof(CArchConsoleWindows*); - classInfo.hInstance = s_appInstance; - classInfo.hIcon = largeIcon; - classInfo.hCursor = NULL; - classInfo.hbrBackground = NULL; - classInfo.lpszMenuName = NULL; - classInfo.lpszClassName = TEXT("SynergyConsole"); - classInfo.hIconSm = smallIcon; - ATOM windowClass = RegisterClassEx(&classInfo); - - // create frame window - m_frame = CreateWindowEx(0, - reinterpret_cast(windowClass), - TEXT("Synergy Log"), - WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, CW_USEDEFAULT, 100, 100, - NULL, - NULL, - s_appInstance, - NULL); - - // create log window - m_hwnd = CreateWindowEx(0, - "RichEdit", - TEXT(""), - WS_CHILD | WS_VISIBLE | WS_VSCROLL | - ES_MULTILINE | ES_READONLY, - 0, 0, 1, 1, - m_frame, - (HMENU)1, - s_appInstance, - NULL); - - // select font and get info - HDC hdc = GetDC(m_hwnd); - HGDIOBJ oldFont = SelectObject(hdc, GetStockObject(ANSI_FIXED_FONT)); - TEXTMETRIC metrics; - GetTextMetrics(hdc, &metrics); - CHARFORMAT format; - format.cbSize = sizeof(format); - format.dwMask = CFM_CHARSET | CFM_COLOR | CFM_FACE | - CFM_OFFSET | CFM_SIZE | CFM_PROTECTED | - CFM_BOLD | CFM_ITALIC | - CFM_STRIKEOUT | CFM_UNDERLINE; - format.dwEffects = 0; - format.yHeight = metrics.tmHeight * TWIPS_PER_POINT; // this is in 1/1440 in (twips) - format.yOffset = 0; - format.crTextColor = RGB(0, 0, 0); - format.bCharSet = DEFAULT_CHARSET; - format.bPitchAndFamily = FIXED_PITCH | FF_MODERN; - GetTextFace(hdc, sizeof(format.szFaceName), format.szFaceName); - SelectObject(hdc, oldFont); - ReleaseDC(m_hwnd, hdc); - - // prep window - SendMessage(m_hwnd, EM_EXLIMITTEXT, 0, m_maxCharacters); - SendMessage(m_hwnd, EM_SETCHARFORMAT, 0, reinterpret_cast(&format)); - SendMessage(m_hwnd, EM_SETBKGNDCOLOR, 0, RGB(255, 255, 255)); - m_wChar = metrics.tmAveCharWidth; - m_hChar = metrics.tmHeight + metrics.tmExternalLeading; - setSize(80, 25); - - // signal ready - ARCH->lockMutex(m_mutex); - m_ready = true; - ARCH->broadcastCondVar(m_condVar); - ARCH->unlockMutex(m_mutex); - - // handle failure - if (m_hwnd == NULL) { - UnregisterClass(reinterpret_cast(windowClass), s_appInstance); - return; - } - - // main loop - MSG msg; - while (GetMessage(&msg, NULL, 0, 0)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - // clean up - DestroyWindow(m_hwnd); - UnregisterClass(reinterpret_cast(windowClass), s_appInstance); -} - -void* -CArchConsoleWindows::threadEntry(void* self) -{ - reinterpret_cast(self)->threadMainLoop(); - return NULL; -} +/* + * 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 "CArchConsoleWindows.h" + +CArchConsoleWindows::CArchConsoleWindows() { } + +CArchConsoleWindows::~CArchConsoleWindows() { } diff --git a/lib/arch/CArchConsoleWindows.h b/lib/arch/CArchConsoleWindows.h index 0d59e6ef..4a2a6ac4 100644 --- a/lib/arch/CArchConsoleWindows.h +++ b/lib/arch/CArchConsoleWindows.h @@ -12,66 +12,14 @@ * GNU General Public License for more details. */ -#ifndef CARCHCONSOLEWINDOWS_H -#define CARCHCONSOLEWINDOWS_H +#pragma once -#define WIN32_LEAN_AND_MEAN - -#include "IArchConsole.h" -#include "IArchMultithread.h" -#include "stddeque.h" -#include +#include "CArchConsoleStd.h" #define ARCH_CONSOLE CArchConsoleWindows -//! Win32 implementation of IArchConsole -class CArchConsoleWindows : public IArchConsole { +class CArchConsoleWindows : public CArchConsoleStd { public: - CArchConsoleWindows(void*); + CArchConsoleWindows(); virtual ~CArchConsoleWindows(); - - // IArchConsole overrides - virtual void openConsole(const char* title); - virtual void closeConsole(); - virtual void showConsole(bool showIfEmpty); - virtual void writeConsole(const char*); - virtual const char* getNewlineForConsole(); - -private: - void clearBuffer(); - void appendBuffer(const char*); - void setSize(int width, int height); - - LRESULT wndProc(HWND, UINT, WPARAM, LPARAM); - static LRESULT CALLBACK - staticWndProc(HWND, UINT, WPARAM, LPARAM); - void threadMainLoop(); - static void* threadEntry(void*); - -private: - typedef std::deque MessageBuffer; - - static CArchConsoleWindows* s_instance; - static HINSTANCE s_appInstance; - - // multithread data - CArchMutex m_mutex; - CArchCond m_condVar; - bool m_ready; - CArchThread m_thread; - - // child thread data - HWND m_frame; - HWND m_hwnd; - LONG m_wChar; - LONG m_hChar; - bool m_show; - - // messages - size_t m_maxLines; - size_t m_maxCharacters; - size_t m_numCharacters; - MessageBuffer m_buffer; }; - -#endif diff --git a/lib/arch/CArchDaemonWindows.cpp b/lib/arch/CArchDaemonWindows.cpp index c308dcb3..65aa3909 100644 --- a/lib/arch/CArchDaemonWindows.cpp +++ b/lib/arch/CArchDaemonWindows.cpp @@ -142,9 +142,12 @@ CArchDaemonWindows::installDaemon(const char* name, throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); } } + else { + // done with service (but only try to close if not null) + CloseServiceHandle(service); + } - // done with service and manager - CloseServiceHandle(service); + // done with manager CloseServiceHandle(mgr); // open the registry key for this service diff --git a/lib/arch/CArchMiscWindows.cpp b/lib/arch/CArchMiscWindows.cpp index b680bb18..49b576c0 100644 --- a/lib/arch/CArchMiscWindows.cpp +++ b/lib/arch/CArchMiscWindows.cpp @@ -1,439 +1,551 @@ -/* - * 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 "CArchMiscWindows.h" -#include "CArchDaemonWindows.h" - -#ifndef ES_SYSTEM_REQUIRED -#define ES_SYSTEM_REQUIRED ((DWORD)0x00000001) -#endif -#ifndef ES_DISPLAY_REQUIRED -#define ES_DISPLAY_REQUIRED ((DWORD)0x00000002) -#endif -#ifndef ES_CONTINUOUS -#define ES_CONTINUOUS ((DWORD)0x80000000) -#endif -typedef DWORD EXECUTION_STATE; - -// -// CArchMiscWindows -// - -CArchMiscWindows::CDialogs* CArchMiscWindows::s_dialogs = NULL; -DWORD CArchMiscWindows::s_busyState = 0; -CArchMiscWindows::STES_t CArchMiscWindows::s_stes = NULL; -HICON CArchMiscWindows::s_largeIcon = NULL; -HICON CArchMiscWindows::s_smallIcon = NULL; - -void -CArchMiscWindows::init() -{ - s_dialogs = new CDialogs; - isWindows95Family(); -} - -bool -CArchMiscWindows::isWindows95Family() -{ - static bool init = false; - static bool result = false; - - if (!init) { - OSVERSIONINFO version; - version.dwOSVersionInfoSize = sizeof(version); - if (GetVersionEx(&version) == 0) { - // cannot determine OS; assume windows 95 family - result = true; - } - else { - result = (version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); - } - init = true; - } - return result; -} - -bool -CArchMiscWindows::isWindowsModern() -{ - static bool init = false; - static bool result = false; - - if (!init) { - OSVERSIONINFO version; - version.dwOSVersionInfoSize = sizeof(version); - if (GetVersionEx(&version) == 0) { - // cannot determine OS; assume not modern - result = false; - } - else { - result = ((version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS && - version.dwMajorVersion == 4 && - version.dwMinorVersion > 0) || - (version.dwPlatformId == VER_PLATFORM_WIN32_NT && - version.dwMajorVersion > 4)); - } - init = true; - } - return result; -} - -void -CArchMiscWindows::setIcons(HICON largeIcon, HICON smallIcon) -{ - s_largeIcon = largeIcon; - s_smallIcon = smallIcon; -} - -void -CArchMiscWindows::getIcons(HICON& largeIcon, HICON& smallIcon) -{ - largeIcon = s_largeIcon; - smallIcon = s_smallIcon; -} - -int -CArchMiscWindows::runDaemon(RunFunc runFunc) -{ - return CArchDaemonWindows::runDaemon(runFunc); -} - -void -CArchMiscWindows::daemonRunning(bool running) -{ - CArchDaemonWindows::daemonRunning(running); -} - -void -CArchMiscWindows::daemonFailed(int result) -{ - CArchDaemonWindows::daemonFailed(result); -} - -UINT -CArchMiscWindows::getDaemonQuitMessage() -{ - return CArchDaemonWindows::getDaemonQuitMessage(); -} - -HKEY -CArchMiscWindows::openKey(HKEY key, const TCHAR* keyName) -{ - return openKey(key, keyName, false); -} - -HKEY -CArchMiscWindows::openKey(HKEY key, const TCHAR* const* keyNames) -{ - return openKey(key, keyNames, false); -} - -HKEY -CArchMiscWindows::addKey(HKEY key, const TCHAR* keyName) -{ - return openKey(key, keyName, true); -} - -HKEY -CArchMiscWindows::addKey(HKEY key, const TCHAR* const* keyNames) -{ - return openKey(key, keyNames, true); -} - -HKEY -CArchMiscWindows::openKey(HKEY key, const TCHAR* keyName, bool create) -{ - // ignore if parent is NULL - if (key == NULL) { - return NULL; - } - - // open next key - HKEY newKey; - LONG result = RegOpenKeyEx(key, keyName, 0, - KEY_WRITE | KEY_QUERY_VALUE, &newKey); - if (result != ERROR_SUCCESS && create) { - DWORD disp; - result = RegCreateKeyEx(key, keyName, 0, TEXT(""), - 0, KEY_WRITE | KEY_QUERY_VALUE, - NULL, &newKey, &disp); - } - if (result != ERROR_SUCCESS) { - RegCloseKey(key); - return NULL; - } - - // switch to new key - RegCloseKey(key); - return newKey; -} - -HKEY -CArchMiscWindows::openKey(HKEY key, const TCHAR* const* keyNames, bool create) -{ - for (size_t i = 0; key != NULL && keyNames[i] != NULL; ++i) { - // open next key - key = openKey(key, keyNames[i], create); - } - return key; -} - -void -CArchMiscWindows::closeKey(HKEY key) -{ - assert(key != NULL); - RegCloseKey(key); -} - -void -CArchMiscWindows::deleteKey(HKEY key, const TCHAR* name) -{ - assert(key != NULL); - assert(name != NULL); - RegDeleteKey(key, name); -} - -void -CArchMiscWindows::deleteValue(HKEY key, const TCHAR* name) -{ - assert(key != NULL); - assert(name != NULL); - RegDeleteValue(key, name); -} - -bool -CArchMiscWindows::hasValue(HKEY key, const TCHAR* name) -{ - DWORD type; - LONG result = RegQueryValueEx(key, name, 0, &type, NULL, NULL); - return (result == ERROR_SUCCESS && - (type == REG_DWORD || type == REG_SZ)); -} - -CArchMiscWindows::EValueType -CArchMiscWindows::typeOfValue(HKEY key, const TCHAR* name) -{ - DWORD type; - LONG result = RegQueryValueEx(key, name, 0, &type, NULL, NULL); - if (result != ERROR_SUCCESS) { - return kNO_VALUE; - } - switch (type) { - case REG_DWORD: - return kUINT; - - case REG_SZ: - return kSTRING; - - case REG_BINARY: - return kBINARY; - - default: - return kUNKNOWN; - } -} - -void -CArchMiscWindows::setValue(HKEY key, - const TCHAR* name, const std::string& value) -{ - assert(key != NULL); - assert(name != NULL); - RegSetValueEx(key, name, 0, REG_SZ, - reinterpret_cast(value.c_str()), - (DWORD)value.size() + 1); -} - -void -CArchMiscWindows::setValue(HKEY key, const TCHAR* name, DWORD value) -{ - assert(key != NULL); - assert(name != NULL); - RegSetValueEx(key, name, 0, REG_DWORD, - reinterpret_cast(&value), - sizeof(DWORD)); -} - -void -CArchMiscWindows::setValueBinary(HKEY key, - const TCHAR* name, const std::string& value) -{ - assert(key != NULL); - assert(name != NULL); - RegSetValueEx(key, name, 0, REG_BINARY, - reinterpret_cast(value.data()), - (DWORD)value.size()); -} - -std::string -CArchMiscWindows::readBinaryOrString(HKEY key, const TCHAR* name, DWORD type) -{ - // get the size of the string - DWORD actualType; - DWORD size = 0; - LONG result = RegQueryValueEx(key, name, 0, &actualType, NULL, &size); - if (result != ERROR_SUCCESS || actualType != type) { - return std::string(); - } - - // if zero size then return empty string - if (size == 0) { - return std::string(); - } - - // allocate space - char* buffer = new char[size]; - - // read it - result = RegQueryValueEx(key, name, 0, &actualType, - reinterpret_cast(buffer), &size); - if (result != ERROR_SUCCESS || actualType != type) { - delete[] buffer; - return std::string(); - } - - // clean up and return value - if (type == REG_SZ && buffer[size - 1] == '\0') { - // don't include terminating nul; std::string will add one. - --size; - } - std::string value(buffer, size); - delete[] buffer; - return value; -} - -std::string -CArchMiscWindows::readValueString(HKEY key, const TCHAR* name) -{ - return readBinaryOrString(key, name, REG_SZ); -} - -std::string -CArchMiscWindows::readValueBinary(HKEY key, const TCHAR* name) -{ - return readBinaryOrString(key, name, REG_BINARY); -} - -DWORD -CArchMiscWindows::readValueInt(HKEY key, const TCHAR* name) -{ - DWORD type; - DWORD value; - DWORD size = sizeof(value); - LONG result = RegQueryValueEx(key, name, 0, &type, - reinterpret_cast(&value), &size); - if (result != ERROR_SUCCESS || type != REG_DWORD) { - return 0; - } - return value; -} - -void -CArchMiscWindows::addDialog(HWND hwnd) -{ - s_dialogs->insert(hwnd); -} - -void -CArchMiscWindows::removeDialog(HWND hwnd) -{ - s_dialogs->erase(hwnd); -} - -bool -CArchMiscWindows::processDialog(MSG* msg) -{ - for (CDialogs::const_iterator index = s_dialogs->begin(); - index != s_dialogs->end(); ++index) { - if (IsDialogMessage(*index, msg)) { - return true; - } - } - return false; -} - -void -CArchMiscWindows::addBusyState(DWORD busyModes) -{ - s_busyState |= busyModes; - setThreadExecutionState(s_busyState); -} - -void -CArchMiscWindows::removeBusyState(DWORD busyModes) -{ - s_busyState &= ~busyModes; - setThreadExecutionState(s_busyState); -} - -void -CArchMiscWindows::setThreadExecutionState(DWORD busyModes) -{ - // look up function dynamically so we work on older systems - if (s_stes == NULL) { - HINSTANCE kernel = LoadLibrary("kernel32.dll"); - if (kernel != NULL) { - s_stes = reinterpret_cast(GetProcAddress(kernel, - "SetThreadExecutionState")); - } - if (s_stes == NULL) { - s_stes = &CArchMiscWindows::dummySetThreadExecutionState; - } - } - - // convert to STES form - EXECUTION_STATE state = 0; - if ((busyModes & kSYSTEM) != 0) { - state |= ES_SYSTEM_REQUIRED; - } - if ((busyModes & kDISPLAY) != 0) { - state |= ES_DISPLAY_REQUIRED; - } - if (state != 0) { - state |= ES_CONTINUOUS; - } - - // do it - s_stes(state); -} - -DWORD -CArchMiscWindows::dummySetThreadExecutionState(DWORD) -{ - // do nothing - return 0; -} - -void -CArchMiscWindows::wakeupDisplay() -{ - // We can't use ::setThreadExecutionState here because it sets - // ES_CONTINUOUS, which we don't want. - - if (s_stes == NULL) { - HINSTANCE kernel = LoadLibrary("kernel32.dll"); - if (kernel != NULL) { - s_stes = reinterpret_cast(GetProcAddress(kernel, - "SetThreadExecutionState")); - } - if (s_stes == NULL) { - s_stes = &CArchMiscWindows::dummySetThreadExecutionState; - } - } - - s_stes(ES_DISPLAY_REQUIRED); - - // restore the original execution states - setThreadExecutionState(s_busyState); +/* + * 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 "CArchMiscWindows.h" +#include "CArchDaemonWindows.h" +#include "CLog.h" + +#include +#pragma warning(disable: 4099) +#include +#pragma warning(default: 4099) +#include "Version.h" + +// parent process name for services in Vista +#define SERVICE_LAUNCHER "services.exe" + +#ifndef ES_SYSTEM_REQUIRED +#define ES_SYSTEM_REQUIRED ((DWORD)0x00000001) +#endif +#ifndef ES_DISPLAY_REQUIRED +#define ES_DISPLAY_REQUIRED ((DWORD)0x00000002) +#endif +#ifndef ES_CONTINUOUS +#define ES_CONTINUOUS ((DWORD)0x80000000) +#endif +typedef DWORD EXECUTION_STATE; + +// +// CArchMiscWindows +// + +CArchMiscWindows::CDialogs* CArchMiscWindows::s_dialogs = NULL; +DWORD CArchMiscWindows::s_busyState = 0; +CArchMiscWindows::STES_t CArchMiscWindows::s_stes = NULL; +HICON CArchMiscWindows::s_largeIcon = NULL; +HICON CArchMiscWindows::s_smallIcon = NULL; +HINSTANCE CArchMiscWindows::s_instanceWin32 = NULL; + +void +CArchMiscWindows::init() +{ + s_dialogs = new CDialogs; + isWindows95Family(); +} + +bool +CArchMiscWindows::isWindows95Family() +{ + static bool init = false; + static bool result = false; + + if (!init) { + OSVERSIONINFO version; + version.dwOSVersionInfoSize = sizeof(version); + if (GetVersionEx(&version) == 0) { + // cannot determine OS; assume windows 95 family + result = true; + } + else { + result = (version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); + } + init = true; + } + return result; +} + +bool +CArchMiscWindows::isWindowsModern() +{ + static bool init = false; + static bool result = false; + + if (!init) { + OSVERSIONINFO version; + version.dwOSVersionInfoSize = sizeof(version); + if (GetVersionEx(&version) == 0) { + // cannot determine OS; assume not modern + result = false; + } + else { + result = ((version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS && + version.dwMajorVersion == 4 && + version.dwMinorVersion > 0) || + (version.dwPlatformId == VER_PLATFORM_WIN32_NT && + version.dwMajorVersion > 4)); + } + init = true; + } + return result; +} + +void +CArchMiscWindows::setIcons(HICON largeIcon, HICON smallIcon) +{ + s_largeIcon = largeIcon; + s_smallIcon = smallIcon; +} + +void +CArchMiscWindows::getIcons(HICON& largeIcon, HICON& smallIcon) +{ + largeIcon = s_largeIcon; + smallIcon = s_smallIcon; +} + +int +CArchMiscWindows::runDaemon(RunFunc runFunc) +{ + return CArchDaemonWindows::runDaemon(runFunc); +} + +void +CArchMiscWindows::daemonRunning(bool running) +{ + CArchDaemonWindows::daemonRunning(running); +} + +void +CArchMiscWindows::daemonFailed(int result) +{ + CArchDaemonWindows::daemonFailed(result); +} + +UINT +CArchMiscWindows::getDaemonQuitMessage() +{ + return CArchDaemonWindows::getDaemonQuitMessage(); +} + +HKEY +CArchMiscWindows::openKey(HKEY key, const TCHAR* keyName) +{ + return openKey(key, keyName, false); +} + +HKEY +CArchMiscWindows::openKey(HKEY key, const TCHAR* const* keyNames) +{ + return openKey(key, keyNames, false); +} + +HKEY +CArchMiscWindows::addKey(HKEY key, const TCHAR* keyName) +{ + return openKey(key, keyName, true); +} + +HKEY +CArchMiscWindows::addKey(HKEY key, const TCHAR* const* keyNames) +{ + return openKey(key, keyNames, true); +} + +HKEY +CArchMiscWindows::openKey(HKEY key, const TCHAR* keyName, bool create) +{ + // ignore if parent is NULL + if (key == NULL) { + return NULL; + } + + // open next key + HKEY newKey; + LONG result = RegOpenKeyEx(key, keyName, 0, + KEY_WRITE | KEY_QUERY_VALUE, &newKey); + if (result != ERROR_SUCCESS && create) { + DWORD disp; + result = RegCreateKeyEx(key, keyName, 0, TEXT(""), + 0, KEY_WRITE | KEY_QUERY_VALUE, + NULL, &newKey, &disp); + } + if (result != ERROR_SUCCESS) { + RegCloseKey(key); + return NULL; + } + + // switch to new key + RegCloseKey(key); + return newKey; +} + +HKEY +CArchMiscWindows::openKey(HKEY key, const TCHAR* const* keyNames, bool create) +{ + for (size_t i = 0; key != NULL && keyNames[i] != NULL; ++i) { + // open next key + key = openKey(key, keyNames[i], create); + } + return key; +} + +void +CArchMiscWindows::closeKey(HKEY key) +{ + assert(key != NULL); + if (key==NULL) return; + RegCloseKey(key); +} + +void +CArchMiscWindows::deleteKey(HKEY key, const TCHAR* name) +{ + assert(key != NULL); + assert(name != NULL); + if (key==NULL || name==NULL) return; + RegDeleteKey(key, name); +} + +void +CArchMiscWindows::deleteValue(HKEY key, const TCHAR* name) +{ + assert(key != NULL); + assert(name != NULL); + if (key==NULL || name==NULL) return; + RegDeleteValue(key, name); +} + +bool +CArchMiscWindows::hasValue(HKEY key, const TCHAR* name) +{ + DWORD type; + LONG result = RegQueryValueEx(key, name, 0, &type, NULL, NULL); + return (result == ERROR_SUCCESS && + (type == REG_DWORD || type == REG_SZ)); +} + +CArchMiscWindows::EValueType +CArchMiscWindows::typeOfValue(HKEY key, const TCHAR* name) +{ + DWORD type; + LONG result = RegQueryValueEx(key, name, 0, &type, NULL, NULL); + if (result != ERROR_SUCCESS) { + return kNO_VALUE; + } + switch (type) { + case REG_DWORD: + return kUINT; + + case REG_SZ: + return kSTRING; + + case REG_BINARY: + return kBINARY; + + default: + return kUNKNOWN; + } +} + +void +CArchMiscWindows::setValue(HKEY key, + const TCHAR* name, const std::string& value) +{ + assert(key != NULL); + assert(name != NULL); + if(key ==NULL || name==NULL) return; // TODO: throw exception + RegSetValueEx(key, name, 0, REG_SZ, + reinterpret_cast(value.c_str()), + (DWORD)value.size() + 1); +} + +void +CArchMiscWindows::setValue(HKEY key, const TCHAR* name, DWORD value) +{ + assert(key != NULL); + assert(name != NULL); + if(key ==NULL || name==NULL) return; // TODO: throw exception + RegSetValueEx(key, name, 0, REG_DWORD, + reinterpret_cast(&value), + sizeof(DWORD)); +} + +void +CArchMiscWindows::setValueBinary(HKEY key, + const TCHAR* name, const std::string& value) +{ + assert(key != NULL); + assert(name != NULL); + if(key ==NULL || name==NULL) return; // TODO: throw exception + RegSetValueEx(key, name, 0, REG_BINARY, + reinterpret_cast(value.data()), + (DWORD)value.size()); +} + +std::string +CArchMiscWindows::readBinaryOrString(HKEY key, const TCHAR* name, DWORD type) +{ + // get the size of the string + DWORD actualType; + DWORD size = 0; + LONG result = RegQueryValueEx(key, name, 0, &actualType, NULL, &size); + if (result != ERROR_SUCCESS || actualType != type) { + return std::string(); + } + + // if zero size then return empty string + if (size == 0) { + return std::string(); + } + + // allocate space + char* buffer = new char[size]; + + // read it + result = RegQueryValueEx(key, name, 0, &actualType, + reinterpret_cast(buffer), &size); + if (result != ERROR_SUCCESS || actualType != type) { + delete[] buffer; + return std::string(); + } + + // clean up and return value + if (type == REG_SZ && buffer[size - 1] == '\0') { + // don't include terminating nul; std::string will add one. + --size; + } + std::string value(buffer, size); + delete[] buffer; + return value; +} + +std::string +CArchMiscWindows::readValueString(HKEY key, const TCHAR* name) +{ + return readBinaryOrString(key, name, REG_SZ); +} + +std::string +CArchMiscWindows::readValueBinary(HKEY key, const TCHAR* name) +{ + return readBinaryOrString(key, name, REG_BINARY); +} + +DWORD +CArchMiscWindows::readValueInt(HKEY key, const TCHAR* name) +{ + DWORD type; + DWORD value; + DWORD size = sizeof(value); + LONG result = RegQueryValueEx(key, name, 0, &type, + reinterpret_cast(&value), &size); + if (result != ERROR_SUCCESS || type != REG_DWORD) { + return 0; + } + return value; +} + +void +CArchMiscWindows::addDialog(HWND hwnd) +{ + s_dialogs->insert(hwnd); +} + +void +CArchMiscWindows::removeDialog(HWND hwnd) +{ + s_dialogs->erase(hwnd); +} + +bool +CArchMiscWindows::processDialog(MSG* msg) +{ + for (CDialogs::const_iterator index = s_dialogs->begin(); + index != s_dialogs->end(); ++index) { + if (IsDialogMessage(*index, msg)) { + return true; + } + } + return false; +} + +void +CArchMiscWindows::addBusyState(DWORD busyModes) +{ + s_busyState |= busyModes; + setThreadExecutionState(s_busyState); +} + +void +CArchMiscWindows::removeBusyState(DWORD busyModes) +{ + s_busyState &= ~busyModes; + setThreadExecutionState(s_busyState); +} + +void +CArchMiscWindows::setThreadExecutionState(DWORD busyModes) +{ + // look up function dynamically so we work on older systems + if (s_stes == NULL) { + HINSTANCE kernel = LoadLibrary("kernel32.dll"); + if (kernel != NULL) { + s_stes = reinterpret_cast(GetProcAddress(kernel, + "SetThreadExecutionState")); + } + if (s_stes == NULL) { + s_stes = &CArchMiscWindows::dummySetThreadExecutionState; + } + } + + // convert to STES form + EXECUTION_STATE state = 0; + if ((busyModes & kSYSTEM) != 0) { + state |= ES_SYSTEM_REQUIRED; + } + if ((busyModes & kDISPLAY) != 0) { + state |= ES_DISPLAY_REQUIRED; + } + if (state != 0) { + state |= ES_CONTINUOUS; + } + + // do it + s_stes(state); +} + +DWORD +CArchMiscWindows::dummySetThreadExecutionState(DWORD) +{ + // do nothing + return 0; +} + +void +CArchMiscWindows::wakeupDisplay() +{ + // We can't use ::setThreadExecutionState here because it sets + // ES_CONTINUOUS, which we don't want. + + if (s_stes == NULL) { + HINSTANCE kernel = LoadLibrary("kernel32.dll"); + if (kernel != NULL) { + s_stes = reinterpret_cast(GetProcAddress(kernel, + "SetThreadExecutionState")); + } + if (s_stes == NULL) { + s_stes = &CArchMiscWindows::dummySetThreadExecutionState; + } + } + + s_stes(ES_DISPLAY_REQUIRED); + + // restore the original execution states + setThreadExecutionState(s_busyState); +} + +bool +CArchMiscWindows::wasLaunchedAsService() +{ + CString name; + if (!getParentProcessName(name)) { + LOG((CLOG_ERR "cannot determine if process was launched as service")); + return false; + } + + return (name == SERVICE_LAUNCHER); +} + +bool +CArchMiscWindows::getParentProcessName(CString &name) +{ + PROCESSENTRY32 parentEntry; + if (!getParentProcessEntry(parentEntry)){ + LOG((CLOG_ERR "could not get entry for parent process")); + return false; + } + + name = parentEntry.szExeFile; + return true; +} + +BOOL WINAPI +CArchMiscWindows::getSelfProcessEntry(PROCESSENTRY32& entry) +{ + // get entry from current PID + return getProcessEntry(entry, GetCurrentProcessId()); +} + +BOOL WINAPI +CArchMiscWindows::getParentProcessEntry(PROCESSENTRY32& entry) +{ + // get the current process, so we can get parent PID + PROCESSENTRY32 selfEntry; + if (!getSelfProcessEntry(selfEntry)) { + return FALSE; + } + + // get entry from parent PID + return getProcessEntry(entry, selfEntry.th32ParentProcessID); +} + +BOOL WINAPI +CArchMiscWindows::getProcessEntry(PROCESSENTRY32& entry, DWORD processID) +{ + // first we need to take a snapshot of the running processes + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) { + LOG((CLOG_ERR "could not get process snapshot (error: %i)", + GetLastError())); + return FALSE; + } + + entry.dwSize = sizeof(PROCESSENTRY32); + + // get the first process, and if we can't do that then it's + // unlikely we can go any further + BOOL gotEntry = Process32First(snapshot, &entry); + if (!gotEntry) { + LOG((CLOG_ERR "could not get first process entry (error: %i)", + GetLastError())); + return FALSE; + } + + while(gotEntry) { + + if (entry.th32ProcessID == processID) { + // found current process + return TRUE; + } + + // now move on to the next entry (when we reach end, loop will stop) + gotEntry = Process32Next(snapshot, &entry); + } + + return FALSE; +} + +HINSTANCE +CArchMiscWindows::instanceWin32() +{ + assert(s_instanceWin32 != NULL); + return s_instanceWin32; +} + +void +CArchMiscWindows::setInstanceWin32(HINSTANCE instance) +{ + assert(instance != NULL); + s_instanceWin32 = instance; } \ No newline at end of file diff --git a/lib/arch/CArchMiscWindows.h b/lib/arch/CArchMiscWindows.h index 5260eaeb..dc1eca02 100644 --- a/lib/arch/CArchMiscWindows.h +++ b/lib/arch/CArchMiscWindows.h @@ -20,7 +20,9 @@ #include "common.h" #include "stdstring.h" #include "stdset.h" -#include +#include +#include +#include "CString.h" //! Miscellaneous win32 functions. class CArchMiscWindows { @@ -164,6 +166,16 @@ public: //! Briefly interrupt power saving static void wakeupDisplay(); + //! Returns true if this process was launched via NT service host. + static bool wasLaunchedAsService(); + + //! Returns true if we got the parent process name. + static bool getParentProcessName(CString &name); + + static HINSTANCE instanceWin32(); + + static void setInstanceWin32(HINSTANCE instance); + private: //! Open and return a registry key, closing the parent key static HKEY openKey(HKEY parent, const TCHAR* child, bool create); @@ -180,6 +192,10 @@ private: static DWORD WINAPI dummySetThreadExecutionState(DWORD); + static BOOL WINAPI getProcessEntry(PROCESSENTRY32& entry, DWORD processID); + static BOOL WINAPI getSelfProcessEntry(PROCESSENTRY32& entry); + static BOOL WINAPI getParentProcessEntry(PROCESSENTRY32& entry); + private: typedef std::set CDialogs; typedef DWORD (WINAPI *STES_t)(DWORD); @@ -189,6 +205,7 @@ private: static STES_t s_stes; static HICON s_largeIcon; static HICON s_smallIcon; + static HINSTANCE s_instanceWin32; }; #endif diff --git a/lib/arch/CArchTaskBarWindows.cpp b/lib/arch/CArchTaskBarWindows.cpp index 7ff2a2c1..8b0f6e8f 100644 --- a/lib/arch/CArchTaskBarWindows.cpp +++ b/lib/arch/CArchTaskBarWindows.cpp @@ -19,6 +19,7 @@ #include "XArch.h" #include #include +#include "CArchAppUtilWindows.h" static const UINT kAddReceiver = WM_USER + 10; static const UINT kRemoveReceiver = WM_USER + 11; @@ -31,17 +32,13 @@ static const UINT kFirstReceiverID = WM_USER + 14; // CArchTaskBarWindows* CArchTaskBarWindows::s_instance = NULL; -HINSTANCE CArchTaskBarWindows::s_appInstance = NULL; -CArchTaskBarWindows::CArchTaskBarWindows(void* appInstance) : +CArchTaskBarWindows::CArchTaskBarWindows() : 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(); @@ -437,7 +434,7 @@ CArchTaskBarWindows::threadMainLoop() classInfo.lpfnWndProc = &CArchTaskBarWindows::staticWndProc; classInfo.cbClsExtra = 0; classInfo.cbWndExtra = sizeof(CArchTaskBarWindows*); - classInfo.hInstance = s_appInstance; + classInfo.hInstance = instanceWin32(); classInfo.hIcon = NULL; classInfo.hCursor = NULL; classInfo.hbrBackground = NULL; @@ -454,7 +451,7 @@ CArchTaskBarWindows::threadMainLoop() 0, 0, 1, 1, NULL, NULL, - s_appInstance, + instanceWin32(), reinterpret_cast(this)); // signal ready @@ -465,7 +462,7 @@ CArchTaskBarWindows::threadMainLoop() // handle failure if (m_hwnd == NULL) { - UnregisterClass(reinterpret_cast(windowClass), s_appInstance); + UnregisterClass(reinterpret_cast(windowClass), instanceWin32()); return; } @@ -481,7 +478,7 @@ CArchTaskBarWindows::threadMainLoop() // clean up removeAllIcons(); DestroyWindow(m_hwnd); - UnregisterClass(reinterpret_cast(windowClass), s_appInstance); + UnregisterClass(reinterpret_cast(windowClass), instanceWin32()); } void* @@ -490,3 +487,8 @@ CArchTaskBarWindows::threadEntry(void* self) reinterpret_cast(self)->threadMainLoop(); return NULL; } + +HINSTANCE CArchTaskBarWindows::instanceWin32() +{ + return CArchMiscWindows::instanceWin32(); +} \ No newline at end of file diff --git a/lib/arch/CArchTaskBarWindows.h b/lib/arch/CArchTaskBarWindows.h index 67e9af17..82aefcb2 100644 --- a/lib/arch/CArchTaskBarWindows.h +++ b/lib/arch/CArchTaskBarWindows.h @@ -28,7 +28,7 @@ //! Win32 implementation of IArchTaskBar class CArchTaskBarWindows : public IArchTaskBar { public: - CArchTaskBarWindows(void*); + CArchTaskBarWindows(); virtual ~CArchTaskBarWindows(); //! Add a dialog window @@ -81,9 +81,10 @@ private: void threadMainLoop(); static void* threadEntry(void*); + HINSTANCE instanceWin32(); + private: static CArchTaskBarWindows* s_instance; - static HINSTANCE s_appInstance; // multithread data CArchMutex m_mutex; diff --git a/lib/arch/CArchTaskBarXWindows.cpp b/lib/arch/CArchTaskBarXWindows.cpp index 6934f271..075c7cba 100644 --- a/lib/arch/CArchTaskBarXWindows.cpp +++ b/lib/arch/CArchTaskBarXWindows.cpp @@ -18,7 +18,7 @@ // CArchTaskBarXWindows // -CArchTaskBarXWindows::CArchTaskBarXWindows(void*) +CArchTaskBarXWindows::CArchTaskBarXWindows() { // do nothing } diff --git a/lib/arch/CArchTaskBarXWindows.h b/lib/arch/CArchTaskBarXWindows.h index abf28012..0b74a817 100644 --- a/lib/arch/CArchTaskBarXWindows.h +++ b/lib/arch/CArchTaskBarXWindows.h @@ -22,7 +22,7 @@ //! X11 implementation of IArchTaskBar class CArchTaskBarXWindows : public IArchTaskBar { public: - CArchTaskBarXWindows(void*); + CArchTaskBarXWindows(); virtual ~CArchTaskBarXWindows(); // IArchTaskBar overrides diff --git a/lib/arch/IArchAppUtil.h b/lib/arch/IArchAppUtil.h new file mode 100644 index 00000000..8b79c86f --- /dev/null +++ b/lib/arch/IArchAppUtil.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#pragma once + +#include "IInterface.h" + +// TODO: replace with forward declaration if possible +// we need to decouple these classes! +#include "CApp.h" + +class IArchAppUtil : public IInterface { +public: + virtual bool parseArg(const int& argc, const char* const* argv, int& i) = 0; + virtual void adoptApp(CApp* app) = 0; + virtual CApp& app() const = 0; + virtual int run(int argc, char** argv, CreateTaskBarReceiverFunc createTaskBarReceiver) = 0; +}; diff --git a/lib/arch/IArchConsole.h b/lib/arch/IArchConsole.h index 2befb196..76e78853 100644 --- a/lib/arch/IArchConsole.h +++ b/lib/arch/IArchConsole.h @@ -58,13 +58,6 @@ public: */ virtual void writeConsole(const char*) = 0; - //! Returns the newline sequence for the console - /*! - Different consoles use different character sequences for newlines. - This method returns the appropriate newline sequence for the console. - */ - virtual const char* getNewlineForConsole() = 0; - //@} }; diff --git a/lib/arch/IArchTaskBarReceiver.h b/lib/arch/IArchTaskBarReceiver.h index 917f2fbf..e0dd9bff 100644 --- a/lib/arch/IArchTaskBarReceiver.h +++ b/lib/arch/IArchTaskBarReceiver.h @@ -18,6 +18,9 @@ #include "IInterface.h" #include "stdstring.h" +class IScreen; +class INode; + //! Interface for architecture dependent task bar event handling /*! This interface defines the task bar icon event handlers required @@ -84,6 +87,8 @@ public: */ virtual std::string getToolTip() const = 0; + virtual void updateStatus(INode*, const CString& errorMsg) = 0; + //@} }; diff --git a/lib/base/CLog.cpp b/lib/base/CLog.cpp index 783710fb..616545d9 100644 --- a/lib/base/CLog.cpp +++ b/lib/base/CLog.cpp @@ -1,319 +1,300 @@ -/* - * 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 "CLog.h" -#include "CString.h" -#include "CStringUtil.h" -#include "LogOutputters.h" -#include "CArch.h" -#include "Version.h" -#include -#include -#include -#include - -// names of priorities -static const char* g_priority[] = { - "FATAL", - "ERROR", - "WARNING", - "NOTE", - "INFO", - "DEBUG", - "DEBUG1", - "DEBUG2" - }; - -// number of priorities -static const int g_numPriority = (int)(sizeof(g_priority) / - sizeof(g_priority[0])); - -// the default priority -#if defined(NDEBUG) -static const int g_defaultMaxPriority = 4; -#else -static const int g_defaultMaxPriority = 5; -#endif - -// length of longest string in g_priority -static const int g_maxPriorityLength = 7; - -// length of suffix string (": ") -static const int g_prioritySuffixLength = 2; - -// amount of padded required to fill in the priority prefix -static const int g_priorityPad = g_maxPriorityLength + - g_prioritySuffixLength; - - -// -// CLog -// - -CLog* CLog::s_log = NULL; - -CLog::CLog() -{ - assert(s_log == NULL); - - // create mutex for multithread safe operation - m_mutex = ARCH->newMutex(); - - // other initalization - m_maxPriority = g_defaultMaxPriority; - m_maxNewlineLength = 0; - insert(new CConsoleLogOutputter); -} - -CLog::~CLog() -{ - // clean up - for (COutputterList::iterator index = m_outputters.begin(); - index != m_outputters.end(); ++index) { - delete *index; - } - for (COutputterList::iterator index = m_alwaysOutputters.begin(); - index != m_alwaysOutputters.end(); ++index) { - delete *index; - } - ARCH->closeMutex(m_mutex); - s_log = NULL; -} - -CLog* -CLog::getInstance() -{ - // note -- not thread safe; client must initialize log safely - if (s_log == NULL) { - s_log = new CLog; - } - return s_log; -} - -void -CLog::print(const char* file, int line, const char* fmt, ...) const -{ - // check if fmt begins with a priority argument - int priority = 4; - if (fmt[0] == '%' && fmt[1] == 'z') { - priority = fmt[2] - '\060'; - fmt += 3; - } - - // done if below priority threshold - if (priority > getFilter()) { - return; - } - - // compute prefix padding length - char stack[1024]; - - // compute suffix padding length - int sPad = m_maxNewlineLength; - - // print to buffer, leaving space for a newline at the end and prefix - // at the beginning. - char* buffer = stack; - int len = (int)(sizeof(stack) / sizeof(stack[0])); - while (true) { - // try printing into the buffer - va_list args; - va_start(args, fmt); - int n = ARCH->vsnprintf(buffer, len - sPad, fmt, args); - va_end(args); - - // if the buffer wasn't big enough then make it bigger and try again - if (n < 0 || n > (int)len) { - if (buffer != stack) { - delete[] buffer; - } - len *= 2; - buffer = new char[len]; - } - - // if the buffer was big enough then continue - else { - break; - } - } - - // print the prefix to the buffer. leave space for priority label. - if (file != NULL) { - char message[2048]; - struct tm *tm; - char tmp[220]; - time_t t; - time(&t); - tm = localtime(&t); - sprintf(tmp, "%04i-%02i-%02iT%02i:%02i:%02i", tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - //strcpy(msg, tmp); - - - sprintf(message, "%s %s: %s\n\t%s,%d", tmp, g_priority[priority], buffer, file, line); -// buffer[pPad - 1] = ' '; - - // discard file and line if priority < 0 - /*if (priority < 0) { - message += pPad - g_priorityPad; - } - */ - // output buffer - output(priority, message); - } else { - output(priority, buffer); - } - - // clean up - if (buffer != stack) { - delete[] buffer; - } -} - -void -CLog::insert(ILogOutputter* outputter, bool alwaysAtHead) -{ - assert(outputter != NULL); - assert(outputter->getNewline() != NULL); - - CArchMutexLock lock(m_mutex); - if (alwaysAtHead) { - m_alwaysOutputters.push_front(outputter); - } - else { - m_outputters.push_front(outputter); - } - int newlineLength = (int)strlen(outputter->getNewline()); - if (newlineLength > m_maxNewlineLength) { - m_maxNewlineLength = newlineLength; - } - outputter->open(kAppVersion); - - // Issue 41 - // don't show log unless user requests it, as some users find this - // feature irritating (i.e. when they lose network connectivity). - // in windows the log window can be displayed by selecting "show log" - // from the synergy system tray icon. - // if this causes problems for other architectures, then a different - // work around should be attempted. - //outputter->show(false); -} - -void -CLog::remove(ILogOutputter* outputter) -{ - CArchMutexLock lock(m_mutex); - m_outputters.remove(outputter); - m_alwaysOutputters.remove(outputter); -} - -void -CLog::pop_front(bool alwaysAtHead) -{ - CArchMutexLock lock(m_mutex); - COutputterList* list = alwaysAtHead ? &m_alwaysOutputters : &m_outputters; - if (!list->empty()) { - delete list->front(); - list->pop_front(); - } -} - -bool -CLog::setFilter(const char* maxPriority) -{ - if (maxPriority != NULL) { - for (int i = 0; i < g_numPriority; ++i) { - if (strcmp(maxPriority, g_priority[i]) == 0) { - setFilter(i); - return true; - } - } - return false; - } - return true; -} - -void -CLog::setFilter(int maxPriority) -{ - CArchMutexLock lock(m_mutex); - m_maxPriority = maxPriority; -} - -int -CLog::getFilter() const -{ - CArchMutexLock lock(m_mutex); - return m_maxPriority; -} - -void -CLog::output(int priority, char* msg) const -{ - assert(priority >= -1 && priority < g_numPriority); - assert(msg != NULL); - - // insert priority label - //int n = -g_prioritySuffixLength; - /* - if (priority >= 0) { - - - n = strlen(g_priority[priority]); - strcpy(msg + g_maxPriorityLength - n, g_priority[priority]); - msg[g_maxPriorityLength + 0] = ':'; - msg[g_maxPriorityLength + 1] = ' '; - msg[g_maxPriorityLength + 1] = ' '; - - - } -*/ - // find end of message - //char* end = msg + g_priorityPad + strlen(msg + g_priorityPad); - int len = (int)strlen(msg); - char* tmp = new char[len+m_maxNewlineLength+1]; - char* end = tmp + len; - strcpy(tmp, msg); - - // write to each outputter - CArchMutexLock lock(m_mutex); - for (COutputterList::const_iterator index = m_alwaysOutputters.begin(); - index != m_alwaysOutputters.end(); - ++index) { - // get outputter - ILogOutputter* outputter = *index; - - // put an appropriate newline at the end - strcpy(end, outputter->getNewline()); - - // write message - outputter->write(static_cast(priority), - tmp /*+ g_maxPriorityLength - n*/); - } - for (COutputterList::const_iterator index = m_outputters.begin(); - index != m_outputters.end(); ++index) { - // get outputter - ILogOutputter* outputter = *index; - - // put an appropriate newline at the end - strcpy(end, outputter->getNewline()); - - // write message and break out of loop if it returns false - if (!outputter->write(static_cast(priority), - tmp /*+ g_maxPriorityLength - n*/)) { - break; - } - } - - delete[] tmp; -} +/* + * 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 "CLog.h" +#include "CString.h" +#include "CStringUtil.h" +#include "LogOutputters.h" +#include "CArch.h" +#include "Version.h" +#include +#include +#include +#include + +// names of priorities +static const char* g_priority[] = { + "FATAL", + "ERROR", + "WARNING", + "NOTE", + "INFO", + "DEBUG", + "DEBUG1", + "DEBUG2", + "DEBUG3", + "DEBUG4", + "DEBUG5" + }; + +// number of priorities +static const int g_numPriority = (int)(sizeof(g_priority) / sizeof(g_priority[0])); + +// the default priority +#if defined(NDEBUG) +static const int g_defaultMaxPriority = 4; +#else +static const int g_defaultMaxPriority = 5; +#endif + +// length of longest string in g_priority +static const int g_maxPriorityLength = 7; + +// length of suffix string (": ") +static const int g_prioritySuffixLength = 2; + +// amount of padded required to fill in the priority prefix +static const int g_priorityPad = g_maxPriorityLength + + g_prioritySuffixLength; + + +// +// CLog +// + +CLog* CLog::s_log = NULL; + +CLog::CLog() +{ + assert(s_log == NULL); + + // create mutex for multithread safe operation + m_mutex = ARCH->newMutex(); + + // other initalization + m_maxPriority = g_defaultMaxPriority; + m_maxNewlineLength = 0; + insert(new CConsoleLogOutputter); +} + +CLog::~CLog() +{ + // clean up + for (COutputterList::iterator index = m_outputters.begin(); + index != m_outputters.end(); ++index) { + delete *index; + } + for (COutputterList::iterator index = m_alwaysOutputters.begin(); + index != m_alwaysOutputters.end(); ++index) { + delete *index; + } + ARCH->closeMutex(m_mutex); + s_log = NULL; +} + +CLog* +CLog::getInstance() +{ + // note -- not thread safe; client must initialize log safely + if (s_log == NULL) { + s_log = new CLog; + } + return s_log; +} + +const char* +CLog::getFilterName() const +{ + return getFilterName(getFilter()); +} + +const char* +CLog::getFilterName(int level) const +{ + return g_priority[level]; +} + +void +CLog::print(const char* file, int line, const char* fmt, ...) +{ + // check if fmt begins with a priority argument + ELevel priority = kINFO; + if (fmt[0] == '%' && fmt[1] == 'z') { + + // 060 in octal is 0 (48 in decimal), so subtracting this converts ascii + // number it a true number. we could use atoi instead, but this is how + // it was done originally. + priority = (ELevel)(fmt[2] - '\060'); // TODO: fix this shit + + // move the pointer on past the debug priority char + fmt += 3; + } + + // done if below priority threshold + if (priority > getFilter()) { + return; + } + + // compute prefix padding length + char stack[1024]; + + // compute suffix padding length + int sPad = m_maxNewlineLength; + + // print to buffer, leaving space for a newline at the end and prefix + // at the beginning. + char* buffer = stack; + int len = (int)(sizeof(stack) / sizeof(stack[0])); + while (true) { + // try printing into the buffer + va_list args; + va_start(args, fmt); + int n = ARCH->vsnprintf(buffer, len - sPad, fmt, args); + va_end(args); + + // if the buffer wasn't big enough then make it bigger and try again + if (n < 0 || n > (int)len) { + if (buffer != stack) { + delete[] buffer; + } + len *= 2; + buffer = new char[len]; + } + + // if the buffer was big enough then continue + else { + break; + } + } + + // print the prefix to the buffer. leave space for priority label. + if (file != NULL) { + char message[2048]; + struct tm *tm; + char tmp[220]; + time_t t; + time(&t); + tm = localtime(&t); + sprintf(tmp, "%04i-%02i-%02iT%02i:%02i:%02i", tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + //strcpy(msg, tmp); + + + sprintf(message, "%s %s: %s\n\t%s,%d", tmp, g_priority[priority], buffer, file, line); +// buffer[pPad - 1] = ' '; + + // discard file and line if priority < 0 + /*if (priority < 0) { + message += pPad - g_priorityPad; + } + */ + // output buffer + output(priority, message); + } else { + output(priority, buffer); + } + + // clean up + if (buffer != stack) { + delete[] buffer; + } +} + +void +CLog::insert(ILogOutputter* outputter, bool alwaysAtHead) +{ + assert(outputter != NULL); + + CArchMutexLock lock(m_mutex); + if (alwaysAtHead) { + m_alwaysOutputters.push_front(outputter); + } + else { + m_outputters.push_front(outputter); + } + + outputter->open(kAppVersion); + + // Issue 41 + // don't show log unless user requests it, as some users find this + // feature irritating (i.e. when they lose network connectivity). + // in windows the log window can be displayed by selecting "show log" + // from the synergy system tray icon. + // if this causes problems for other architectures, then a different + // work around should be attempted. + //outputter->show(false); +} + +void +CLog::remove(ILogOutputter* outputter) +{ + CArchMutexLock lock(m_mutex); + m_outputters.remove(outputter); + m_alwaysOutputters.remove(outputter); +} + +void +CLog::pop_front(bool alwaysAtHead) +{ + CArchMutexLock lock(m_mutex); + COutputterList* list = alwaysAtHead ? &m_alwaysOutputters : &m_outputters; + if (!list->empty()) { + delete list->front(); + list->pop_front(); + } +} + +bool +CLog::setFilter(const char* maxPriority) +{ + if (maxPriority != NULL) { + for (int i = 0; i < g_numPriority; ++i) { + if (strcmp(maxPriority, g_priority[i]) == 0) { + setFilter(i); + return true; + } + } + return false; + } + return true; +} + +void +CLog::setFilter(int maxPriority) +{ + CArchMutexLock lock(m_mutex); + m_maxPriority = maxPriority; +} + +int +CLog::getFilter() const +{ + CArchMutexLock lock(m_mutex); + return m_maxPriority; +} + +void +CLog::output(ELevel priority, char* msg) +{ + assert(priority >= -1 && priority < g_numPriority); + assert(msg != NULL); + if (!msg) return; + + CArchMutexLock lock(m_mutex); + + COutputterList::const_iterator i; + + for (i = m_alwaysOutputters.begin(); i != m_alwaysOutputters.end(); ++i) { + + // write to outputter + (*i)->write(priority, msg); + } + + for (i = m_outputters.begin(); i != m_outputters.end(); ++i) { + + // write to outputter and break out of loop if it returns false + if (!(*i)->write(priority, msg)) { + break; + } + } +} \ No newline at end of file diff --git a/lib/base/CLog.h b/lib/base/CLog.h index 391480e2..06cff03a 100644 --- a/lib/base/CLog.h +++ b/lib/base/CLog.h @@ -19,10 +19,12 @@ #include "IArchMultithread.h" #include "stdlist.h" #include +#include "CArch.h" #define CLOG (CLog::getInstance()) class ILogOutputter; +class CThread; //! Logging facility /*! @@ -44,8 +46,11 @@ public: kNOTE, //!< For messages about notable events kINFO, //!< For informational messages kDEBUG, //!< For important debugging messages - kDEBUG1, //!< For more detailed debugging messages - kDEBUG2 //!< For even more detailed debugging messages + kDEBUG1, //!< For verbosity +1 debugging messages + kDEBUG2, //!< For verbosity +2 debugging messages + kDEBUG3, //!< For verbosity +3 debugging messages + kDEBUG4, //!< For verbosity +4 debugging messages + kDEBUG5 //!< For verbosity +5 debugging messages }; ~CLog(); @@ -109,20 +114,28 @@ public: neither the file nor the line are printed. */ void print(const char* file, int line, - const char* format, ...) const; + const char* format, ...); //! Get the minimum priority level. int getFilter() const; + //! Get the filter name of the current filter level. + const char* getFilterName() const; + + //! Get the filter name of a specified filter level. + const char* getFilterName(int level) const; + //! Get the singleton instance of the log static CLog* getInstance(); + //! Get the console filter level (messages above this are not sent to console). + int getConsoleMaxLevel() const { return kDEBUG1; } //@} private: CLog(); - void output(int priority, char* msg) const; + void output(ELevel priority, char* msg); private: typedef std::list COutputterList; @@ -189,8 +202,13 @@ otherwise it expands to a call that doesn't. #define CLOG_TRACE __FILE__, __LINE__, #endif -#define CLOG_PRINT CLOG_TRACE "%z\057" -#define CLOG_CRIT CLOG_TRACE "%z\060" +// the CLOG_* defines are line and file plus %z and an octal number (060=0, +// 071=9), but the limitation is that once we run out of numbers at either +// end, then we resort to using non-numerical chars. this still works (since +// to deduce the number we subtract octal \060, so '/' is -1, and ':' is 10 + +#define CLOG_PRINT CLOG_TRACE "%z\057" // char is '/' +#define CLOG_CRIT CLOG_TRACE "%z\060" // char is '0' #define CLOG_ERR CLOG_TRACE "%z\061" #define CLOG_WARN CLOG_TRACE "%z\062" #define CLOG_NOTE CLOG_TRACE "%z\063" @@ -198,5 +216,8 @@ otherwise it expands to a call that doesn't. #define CLOG_DEBUG CLOG_TRACE "%z\065" #define CLOG_DEBUG1 CLOG_TRACE "%z\066" #define CLOG_DEBUG2 CLOG_TRACE "%z\067" +#define CLOG_DEBUG3 CLOG_TRACE "%z\070" +#define CLOG_DEBUG4 CLOG_TRACE "%z\071" // char is '9' +#define CLOG_DEBUG5 CLOG_TRACE "%z\072" // char is ':' #endif diff --git a/lib/base/ILogOutputter.h b/lib/base/ILogOutputter.h index 2be4dcc9..c75c643c 100644 --- a/lib/base/ILogOutputter.h +++ b/lib/base/ILogOutputter.h @@ -63,18 +63,6 @@ public: */ virtual bool write(ELevel level, const char* message) = 0; - //@} - //! @name accessors - //@{ - - //! Returns the newline sequence for the outputter - /*! - Different outputters use different character sequences for newlines. - This method returns the appropriate newline sequence for this - outputter. - */ - virtual const char* getNewline() const = 0; - //@} }; diff --git a/lib/base/LogOutputters.cpp b/lib/base/LogOutputters.cpp index 84bb8d85..9aaaae84 100644 --- a/lib/base/LogOutputters.cpp +++ b/lib/base/LogOutputters.cpp @@ -14,6 +14,7 @@ #include "LogOutputters.h" #include "CArch.h" +#include "TMethodJob.h" #include // @@ -54,12 +55,6 @@ CStopLogOutputter::write(ELevel, const char*) return false; } -const char* -CStopLogOutputter::getNewline() const -{ - return ""; -} - // // CConsoleLogOutputter @@ -67,12 +62,10 @@ CStopLogOutputter::getNewline() const CConsoleLogOutputter::CConsoleLogOutputter() { - // do nothing } CConsoleLogOutputter::~CConsoleLogOutputter() { - // do nothing } void @@ -94,16 +87,16 @@ CConsoleLogOutputter::show(bool showIfEmpty) } bool -CConsoleLogOutputter::write(ELevel, const char* msg) +CConsoleLogOutputter::write(ELevel level, const char* msg) { ARCH->writeConsole(msg); - return true; + return true; // wtf? } -const char* -CConsoleLogOutputter::getNewline() const +void +CConsoleLogOutputter::flush() { - return ARCH->getNewlineForConsole(); + } @@ -170,13 +163,6 @@ CSystemLogOutputter::write(ELevel level, const char* msg) return true; } -const char* -CSystemLogOutputter::getNewline() const -{ - return ""; -} - - // // CSystemLogger // @@ -261,12 +247,6 @@ CBufferedLogOutputter::write(ELevel, const char* message) return true; } -const char* -CBufferedLogOutputter::getNewline() const -{ - return ""; -} - // // CFileLogOutputter @@ -287,17 +267,11 @@ CFileLogOutputter::~CFileLogOutputter() m_handle.close(); } -const char* -CFileLogOutputter::getNewline() const -{ - return "\n"; -} - bool CFileLogOutputter::write(ILogOutputter::ELevel level, const char *message) { if (m_handle.is_open() && m_handle.fail() != true) { - m_handle << message; + m_handle << message << std::endl; // write buffer to file m_handle.flush(); diff --git a/lib/base/LogOutputters.h b/lib/base/LogOutputters.h index 9d7629f4..f08b5108 100644 --- a/lib/base/LogOutputters.h +++ b/lib/base/LogOutputters.h @@ -19,8 +19,11 @@ #include "ILogOutputter.h" #include "CString.h" #include "stddeque.h" +#include "CThread.h" +#include #include + //! Stop traversing log chain outputter /*! This outputter performs no output and returns false from \c write(), @@ -37,7 +40,6 @@ public: virtual void close(); virtual void show(bool showIfEmpty); virtual bool write(ELevel level, const char* message); - virtual const char* getNewline() const; }; //! Write log to console @@ -55,7 +57,7 @@ public: virtual void close(); virtual void show(bool showIfEmpty); virtual bool write(ELevel level, const char* message); - virtual const char* getNewline() const; + virtual void flush(); }; //! Write log to file @@ -74,7 +76,6 @@ public: virtual void close(); virtual void show(bool showIfEmpty); virtual bool write(ELevel level, const char* message); - virtual const char* getNewline() const; private: std::ofstream m_handle; }; @@ -93,7 +94,6 @@ public: virtual void close(); virtual void show(bool showIfEmpty); virtual bool write(ELevel level, const char* message); - virtual const char* getNewline() const; }; //! Write log to system log only @@ -144,8 +144,6 @@ public: virtual void close(); virtual void show(bool showIfEmpty); virtual bool write(ELevel level, const char* message); - virtual const char* getNewline() const; - private: UInt32 m_maxBufferSize; CBuffer m_buffer; diff --git a/lib/client/CClient.cpp b/lib/client/CClient.cpp index b19781e2..a118cb6a 100644 --- a/lib/client/CClient.cpp +++ b/lib/client/CClient.cpp @@ -390,10 +390,10 @@ CClient::sendEvent(CEvent::Type type, void* data) void CClient::sendConnectionFailedEvent(const char* msg) { - CFailInfo* info = (CFailInfo*)malloc(sizeof(CFailInfo) + strlen(msg)); - info->m_retry = true; - strcpy(info->m_what, msg); - sendEvent(getConnectionFailedEvent(), info); + CFailInfo* info = new CFailInfo(msg); + info->m_retry = true; + CEvent event(getConnectionFailedEvent(), getEventTarget(), info, CEvent::kDontFreeData); + EVENTQUEUE->addEvent(event); } void @@ -549,7 +549,8 @@ CClient::handleConnectionFailed(const CEvent& event, void*) delete m_stream; m_stream = NULL; LOG((CLOG_DEBUG1 "connection failed")); - sendConnectionFailedEvent(info->m_what); + sendConnectionFailedEvent(info->m_what.c_str()); + delete info; } void diff --git a/lib/client/CClient.h b/lib/client/CClient.h index 4bd11c66..066fcc61 100644 --- a/lib/client/CClient.h +++ b/lib/client/CClient.h @@ -18,6 +18,7 @@ #include "IClient.h" #include "IClipboard.h" #include "CNetworkAddress.h" +#include "INode.h" class CEventQueueTimer; class CScreen; @@ -31,12 +32,13 @@ class IStreamFilterFactory; /*! This class implements the top-level client algorithms for synergy. */ -class CClient : public IClient { +class CClient : public IClient, public INode { public: class CFailInfo { public: + CFailInfo(const char* what) : m_retry(false), m_what(what) { } bool m_retry; - char m_what[1]; + CString m_what; }; /*! diff --git a/lib/common/common.h b/lib/common/common.h index 0d7818ca..a470bbb5 100644 --- a/lib/common/common.h +++ b/lib/common/common.h @@ -98,6 +98,10 @@ // this one's a little too aggressive # pragma warning(disable: 4127) // conditional expression is constant + // Code Analysis +# pragma warning(disable: 6011) + + // emitted incorrectly under release build in some circumstances # if defined(NDEBUG) # pragma warning(disable: 4702) // unreachable code @@ -129,6 +133,15 @@ // define NULL #include +// we don't want to use NULL since it's old and nasty, so replace any +// usages with nullptr (warning: this could break many things). +// if not c++0x yet, future proof code by allowing use of nullptr +#ifdef nullptr +#define NULL nullptr +#else +#define nullptr NULL +#endif + // make assert available since we use it a lot #include #include diff --git a/lib/net/CTCPListenSocket.cpp b/lib/net/CTCPListenSocket.cpp index 938bf95a..62c43a83 100644 --- a/lib/net/CTCPListenSocket.cpp +++ b/lib/net/CTCPListenSocket.cpp @@ -101,9 +101,9 @@ CTCPListenSocket::getEventTarget() const IDataSocket* CTCPListenSocket::accept() { + IDataSocket* socket = NULL; try { - IDataSocket* socket = - new CTCPSocket(ARCH->acceptSocket(m_socket, NULL)); + socket = new CTCPSocket(ARCH->acceptSocket(m_socket, NULL)); if (socket != NULL) { CSocketMultiplexer::getInstance()->addSocket(this, new TSocketMultiplexerMethodJob( @@ -113,8 +113,17 @@ CTCPListenSocket::accept() return socket; } catch (XArchNetwork&) { + if (socket != NULL) { + delete socket; + } return NULL; } + catch (std::exception &ex) { + if (socket != NULL) { + delete socket; + } + throw ex; + } } ISocketMultiplexerJob* diff --git a/lib/net/CTCPSocket.cpp b/lib/net/CTCPSocket.cpp index f83e24ea..2b88f7a2 100644 --- a/lib/net/CTCPSocket.cpp +++ b/lib/net/CTCPSocket.cpp @@ -346,11 +346,9 @@ CTCPSocket::newJob() void CTCPSocket::sendConnectionFailedEvent(const char* msg) { - CConnectionFailedInfo* info = (CConnectionFailedInfo*)malloc( - sizeof(CConnectionFailedInfo) + strlen(msg)); - strcpy(info->m_what, msg); + CConnectionFailedInfo* info = new CConnectionFailedInfo(msg); EVENTQUEUE->addEvent(CEvent(getConnectionFailedEvent(), - getEventTarget(), info)); + getEventTarget(), info, CEvent::kDontFreeData)); } void diff --git a/lib/net/IDataSocket.h b/lib/net/IDataSocket.h index d760d4ab..21bef9b1 100644 --- a/lib/net/IDataSocket.h +++ b/lib/net/IDataSocket.h @@ -17,6 +17,7 @@ #include "ISocket.h" #include "IStream.h" +#include "CString.h" //! Data stream socket interface /*! @@ -27,8 +28,8 @@ class IDataSocket : public ISocket, public IStream { public: class CConnectionFailedInfo { public: - // pointer to a string describing the failure - char m_what[1]; + CConnectionFailedInfo(const char* what) : m_what(what) { } + CString m_what; }; //! @name manipulators diff --git a/lib/platform/CMSWindowsDesks.cpp b/lib/platform/CMSWindowsDesks.cpp index 1905f122..994692c4 100644 --- a/lib/platform/CMSWindowsDesks.cpp +++ b/lib/platform/CMSWindowsDesks.cpp @@ -86,7 +86,7 @@ // CMSWindowsDesks::CMSWindowsDesks( - bool isPrimary, HINSTANCE hookLibrary, + bool isPrimary, bool noHooks, HINSTANCE hookLibrary, const IScreenSaver* screensaver, IJob* updateKeys) : m_isPrimary(isPrimary), m_is95Family(CArchMiscWindows::isWindows95Family()), @@ -361,7 +361,7 @@ void CMSWindowsDesks::queryHookLibrary(HINSTANCE hookLibrary) { // look up functions - if (m_isPrimary) { + if (m_isPrimary && !m_noHooks) { m_install = (InstallFunc)GetProcAddress(hookLibrary, "install"); m_uninstall = (UninstallFunc)GetProcAddress(hookLibrary, "uninstall"); m_installScreensaver = @@ -736,7 +736,7 @@ CMSWindowsDesks::deskThread(void* vdesk) continue; case SYNERGY_MSG_SWITCH: - if (m_isPrimary) { + if (m_isPrimary && !m_noHooks) { m_uninstall(); if (m_screensaverNotify) { m_uninstallScreensaver(); @@ -816,11 +816,13 @@ CMSWindowsDesks::deskThread(void* vdesk) break; case SYNERGY_MSG_SCREENSAVER: - if (msg.wParam != 0) { - m_installScreensaver(); - } - else { - m_uninstallScreensaver(); + if (!m_noHooks) { + if (msg.wParam != 0) { + m_installScreensaver(); + } + else { + m_uninstallScreensaver(); + } } break; diff --git a/lib/platform/CMSWindowsDesks.h b/lib/platform/CMSWindowsDesks.h index f0388f98..1771d576 100644 --- a/lib/platform/CMSWindowsDesks.h +++ b/lib/platform/CMSWindowsDesks.h @@ -59,7 +59,7 @@ public: updated in a thread attached to the current desk. \p hookLibrary must be a handle to the hook library. */ - CMSWindowsDesks(bool isPrimary, HINSTANCE hookLibrary, + CMSWindowsDesks(bool isPrimary, bool noHooks, HINSTANCE hookLibrary, const IScreenSaver* screensaver, IJob* updateKeys); ~CMSWindowsDesks(); @@ -241,6 +241,9 @@ private: // true if screen is being used as a primary screen, false otherwise bool m_isPrimary; + // true if hooks are not to be installed (useful for debugging) + bool m_noHooks; + // true if windows 95/98/me bool m_is95Family; diff --git a/lib/platform/CMSWindowsScreen.cpp b/lib/platform/CMSWindowsScreen.cpp index ada627d0..87162feb 100644 --- a/lib/platform/CMSWindowsScreen.cpp +++ b/lib/platform/CMSWindowsScreen.cpp @@ -77,8 +77,9 @@ HINSTANCE CMSWindowsScreen::s_instance = NULL; CMSWindowsScreen* CMSWindowsScreen::s_screen = NULL; -CMSWindowsScreen::CMSWindowsScreen(bool isPrimary) : +CMSWindowsScreen::CMSWindowsScreen(bool isPrimary, bool noHooks) : m_isPrimary(isPrimary), + m_noHooks(noHooks), m_is95Family(CArchMiscWindows::isWindows95Family()), m_isOnScreen(m_isPrimary), m_class(0), @@ -118,7 +119,8 @@ CMSWindowsScreen::CMSWindowsScreen(bool isPrimary) : m_hookLibrary = openHookLibrary("synrgyhk"); } m_screensaver = new CMSWindowsScreenSaver(); - m_desks = new CMSWindowsDesks(m_isPrimary, + m_desks = new CMSWindowsDesks( + m_isPrimary, m_noHooks, m_hookLibrary, m_screensaver, new TMethodJob(this, &CMSWindowsScreen::updateKeysCB)); @@ -491,7 +493,7 @@ void CMSWindowsScreen::saveMousePosition(SInt32 x, SInt32 y) { m_xCursor = x; m_yCursor = y; - LOG((CLOG_DEBUG2 "saved mouse position for next delta: %+d,%+d", x,y)); + LOG((CLOG_DEBUG5 "saved mouse position for next delta: %+d,%+d", x,y)); } UInt32 @@ -763,6 +765,7 @@ CMSWindowsScreen::createBlankCursor() const // create a transparent cursor int cw = GetSystemMetrics(SM_CXCURSOR); int ch = GetSystemMetrics(SM_CYCURSOR); + UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)]; UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)]; memset(cursorAND, 0xff, ch * ((cw + 31) >> 2)); @@ -916,7 +919,7 @@ bool CMSWindowsScreen::onPreDispatchPrimary(HWND, UINT message, WPARAM wParam, LPARAM lParam) { - LOG((CLOG_DEBUG2 "handling pre-dispatch primary")); + LOG((CLOG_DEBUG5 "handling pre-dispatch primary")); // handle event switch (message) { @@ -1298,8 +1301,8 @@ CMSWindowsScreen::onMouseMove(SInt32 mx, SInt32 my) SInt32 x = mx - m_xCursor; SInt32 y = my - m_yCursor; - LOG((CLOG_DEBUG2 - "handling mouse move; delta motion calc: %+d=(%+d - %+d),%+d=(%+d - %+d)", + LOG((CLOG_DEBUG3 + "mouse move - motion delta: %+d=(%+d - %+d),%+d=(%+d - %+d)", x, mx, m_xCursor, y, my, m_yCursor)); // ignore if the mouse didn't move or if message posted prior @@ -1324,7 +1327,7 @@ CMSWindowsScreen::onMouseMove(SInt32 mx, SInt32 my) // center on the server screen. if we don't do this, then the mouse // will always try to return to the original entry point on the // secondary screen. - LOG((CLOG_DEBUG2 "warping server cursor to center: %+d,%+d", m_xCenter, m_yCenter)); + LOG((CLOG_DEBUG5 "warping server cursor to center: %+d,%+d", m_xCenter, m_yCenter)); warpCursorNoFlush(m_xCenter, m_yCenter); // examine the motion. if it's about the distance diff --git a/lib/platform/CMSWindowsScreen.h b/lib/platform/CMSWindowsScreen.h index 629b6c0a..6d29bf96 100644 --- a/lib/platform/CMSWindowsScreen.h +++ b/lib/platform/CMSWindowsScreen.h @@ -32,7 +32,7 @@ class CThread; //! Implementation of IPlatformScreen for Microsoft Windows class CMSWindowsScreen : public CPlatformScreen { public: - CMSWindowsScreen(bool isPrimary); + CMSWindowsScreen(bool isPrimary, bool noHooks); virtual ~CMSWindowsScreen(); //! @name manipulators @@ -216,6 +216,9 @@ private: // true if screen is being used as a primary screen, false otherwise bool m_isPrimary; + // true if hooks are not to be installed (useful for debugging) + bool m_noHooks; + // true if windows 95/98/me bool m_is95Family; diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp index b9baf99b..7907b662 100644 --- a/lib/server/CServer.cpp +++ b/lib/server/CServer.cpp @@ -293,7 +293,7 @@ CServer::adoptClient(CBaseClientProxy* client) // send notification CServer::CScreenConnectedInfo* info = - CServer::CScreenConnectedInfo::alloc(getName(client)); + new CServer::CScreenConnectedInfo(getName(client)); EVENTQUEUE->addEvent(CEvent(CServer::getConnectedEvent(), m_primaryClient->getEventTarget(), info)); } @@ -1638,7 +1638,7 @@ CServer::onMouseUp(ButtonID id) bool CServer::onMouseMovePrimary(SInt32 x, SInt32 y) { - LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y)); + LOG((CLOG_DEBUG4 "onMouseMovePrimary %d,%d", x, y)); // mouse move on primary (server's) screen if (m_active != m_primaryClient) { @@ -2133,22 +2133,6 @@ CServer::CSwitchInDirectionInfo::alloc(EDirection direction) return info; } - -// -// CServer::CScreenConnectedInfo -// - -CServer::CScreenConnectedInfo* -CServer::CScreenConnectedInfo::alloc(const CString& screen) -{ - CScreenConnectedInfo* info = - (CScreenConnectedInfo*)malloc(sizeof(CScreenConnectedInfo) + - screen.size()); - strcpy(info->m_screen, screen.c_str()); - return info; -} - - // // CServer::CKeyboardBroadcastInfo // diff --git a/lib/server/CServer.h b/lib/server/CServer.h index 36cf5e83..03171bf5 100644 --- a/lib/server/CServer.h +++ b/lib/server/CServer.h @@ -25,6 +25,7 @@ #include "stdmap.h" #include "stdset.h" #include "stdvector.h" +#include "INode.h" class CBaseClientProxy; class CEventQueueTimer; @@ -35,7 +36,7 @@ class CInputFilter; /*! This class implements the top-level server algorithms for synergy. */ -class CServer { +class CServer : public INode { public: //! Lock cursor to screen data class CLockCursorToScreenInfo { @@ -70,11 +71,10 @@ public: //! Screen connected data class CScreenConnectedInfo { public: - static CScreenConnectedInfo* alloc(const CString& screen); + CScreenConnectedInfo(CString screen) : m_screen(screen) { } public: - // this is a C-string; this type is a variable size structure - char m_screen[1]; + CString m_screen; // was char[1] }; //! Keyboard broadcast data diff --git a/lib/synergy/CApp.cpp b/lib/synergy/CApp.cpp new file mode 100644 index 00000000..cd69aea5 --- /dev/null +++ b/lib/synergy/CApp.cpp @@ -0,0 +1,273 @@ +/* +* 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 "CApp.h" +#include "CLog.h" +#include "Version.h" +#include "ProtocolTypes.h" +#include "CArch.h" +#include "XBase.h" +#include "XArch.h" +#include "CArchMiscWindows.h" +#include "LogOutputters.h" + +#include +#include + +CApp* CApp::s_instance = nullptr; + +CApp::CApp(CArgsBase* args) : +m_args(args), +m_bye(&exit), +s_taskBarReceiver(NULL), +s_suspended(false) +{ + assert(s_instance == nullptr); + s_instance = this; +} + +CApp::~CApp() +{ + delete m_args; +} + +CApp::CArgsBase::CArgsBase() : +m_daemon(true), +m_backend(false), +m_restartable(true), +m_noHooks(false), +m_pname(NULL), +m_logFilter(NULL), +m_logFile(NULL), +m_display(NULL) +{ +} + +CApp::CArgsBase::~CArgsBase() +{ +} + +bool +CApp::isArg( + int argi, int argc, const char* const* argv, + const char* name1, const char* name2, + int minRequiredParameters) +{ + if ((name1 != NULL && strcmp(argv[argi], name1) == 0) || + (name2 != NULL && strcmp(argv[argi], name2) == 0)) { + // match. check args left. + if (argi + minRequiredParameters >= argc) { + LOG((CLOG_PRINT "%s: missing arguments for `%s'" BYE, + argsBase().m_pname, argv[argi], argsBase().m_pname)); + m_bye(kExitArgs); + } + return true; + } + + // no match + return false; +} + +bool +CApp::parseArg(const int& argc, const char* const* argv, int& i) +{ + if (ARCH->parseArg(argc, argv, i)) { + // handled by platform util + return true; + } + + else if (isArg(i, argc, argv, "-d", "--debug", 1)) { + // change logging level + argsBase().m_logFilter = argv[++i]; + } + + else if (isArg(i, argc, argv, "-l", "--log", 1)) { + argsBase().m_logFile = argv[++i]; + } + + else if (isArg(i, argc, argv, "-f", "--no-daemon")) { + // not a daemon + argsBase().m_daemon = false; + } + + else if (isArg(i, argc, argv, NULL, "--daemon")) { + // daemonize + argsBase().m_daemon = true; + } + + else if (isArg(i, argc, argv, "-n", "--name", 1)) { + // save screen name + argsBase().m_name = argv[++i]; + } + + else if (isArg(i, argc, argv, "-1", "--no-restart")) { + // don't try to restart + argsBase().m_restartable = false; + } + + else if (isArg(i, argc, argv, NULL, "--restart")) { + // try to restart + argsBase().m_restartable = true; + } + + else if (isArg(i, argc, argv, "-z", NULL)) { + argsBase().m_backend = true; + } + + else if (isArg(i, argc, argv, NULL, "--no-hooks")) { + argsBase().m_noHooks = true; + } + + else if (isArg(i, argc, argv, "-h", "--help")) { + help(); + m_bye(kExitSuccess); + } + + else if (isArg(i, argc, argv, NULL, "--version")) { + version(); + m_bye(kExitSuccess); + } + + else { + // option not supported here + return false; + } + + return true; +} + +void +CApp::parseArgs(int argc, const char* const* argv, int& i) +{ + // about these use of assert() here: + // previously an /analyze warning was displayed if we only used assert and + // did not return on failure. however, this warning does not appear to show + // any more (could be because new compiler args have been added). + // the asserts are programmer benefit only; the os should never pass 0 args, + // because the first is always the binary name. the only way assert would + // evaluate to true, is if this parse function were implemented incorrectly, + // which is unlikely because it's old code and has a specific use. + // we should avoid using anything other than assert here, because it will + // look like important code, which it's not really. + assert(argsBase().m_pname != NULL); + assert(argv != NULL); + assert(argc >= 1); + + // set defaults + argsBase().m_name = ARCH->getHostName(); + + // parse options + for (i = 1; i < argc; ++i) { + + if (parseArg(argc, argv, i)) { + continue; + } + + else if (isArg(i, argc, argv, "--", NULL)) { + // remaining arguments are not options + ++i; + break; + } + + else if (argv[i][0] == '-') { + std::cerr << "Unrecognized option: " << argv[i] << std::endl; + m_bye(kExitArgs); + } + + else { + // this and remaining arguments are not options + break; + } + } + + // increase default filter level for daemon. the user must + // explicitly request another level for a daemon. + if (argsBase().m_daemon && argsBase().m_logFilter == NULL) { + argsBase().m_logFilter = "NOTE"; + } +} + +void +CApp::version() +{ + char buffer[500]; + sprintf( + buffer, + "%s %s, protocol version %d.%d\n%s", + argsBase().m_pname, + kVersion, + kProtocolMajorVersion, + kProtocolMinorVersion, + kCopyright + ); + + std::cout << buffer << std::endl; +} + +int +CApp::run(int argc, char** argv, CreateTaskBarReceiverFunc createTaskBarReceiver) +{ +#if SYSAPI_WIN32 + // record window instance for tray icon, etc + CArchMiscWindows::setInstanceWin32(GetModuleHandle(NULL)); +#endif + + CArch arch; + + // install application in to arch + ARCH->adoptApp(this); + + // create an instance of log + CLOG; + + int result; + try { + result = ARCH->run(argc, argv, createTaskBarReceiver); + } + catch (XBase& e) { + LOG((CLOG_CRIT "Uncaught exception: %s\n", e.what())); + result = kExitFailed; + } + catch (XArch& e) { + LOG((CLOG_CRIT "Initialization failed: %s" BYE, e.what().c_str(), argsBase().m_pname)); + result = kExitFailed; + } + catch (std::exception& e) { + LOG((CLOG_CRIT "Uncaught exception: %s\n", e.what())); + result = kExitFailed; + } + catch (...) { + LOG((CLOG_CRIT "Uncaught exception: \n")); + result = kExitFailed; + } + + delete CLOG; + + // not sure i like what's going on here; m_bye could call exit, but it also does + // some other stuff - if we don't return then we get compiler warning (and it's + // not good practice anyway), but the return will never get hit. + m_bye(result); + return result; +} + +int +CApp::daemonMainLoop(int, const char**) +{ +#if SYSAPI_WIN32 + CSystemLogger sysLogger(daemonName(), false); +#else + CSystemLogger sysLogger(daemonName(), true); +#endif + return mainLoop(); +} diff --git a/lib/synergy/CApp.h b/lib/synergy/CApp.h new file mode 100644 index 00000000..c13d0ec3 --- /dev/null +++ b/lib/synergy/CApp.h @@ -0,0 +1,105 @@ +/* +* 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. +*/ + +#pragma once + +#include "common.h" +#include "CString.h" + +class IArchTaskBarReceiver; +class CBufferedLogOutputter; +class ILogOutputter; + +typedef IArchTaskBarReceiver* (*CreateTaskBarReceiverFunc)(const CBufferedLogOutputter*); +typedef int (*StartupFunc)(int, char**); + +class CApp { +public: + class CArgsBase { + public: + CArgsBase(); + virtual ~CArgsBase(); + bool m_daemon; + bool m_backend; + bool m_restartable; + bool m_noHooks; + const char* m_pname; + const char* m_logFilter; + const char* m_logFile; + const char* m_display; + CString m_name; + }; + + CApp(CArgsBase* args); + virtual ~CApp(); + + // Returns args that are common between server and client. + CArgsBase& argsBase() const { return *m_args; } + + // Prints the current compiled version. + virtual void version(); + + // Prints help specific to client or server. + virtual void help() = 0; + + // Parse command line arguments. + virtual void parseArgs(int argc, const char* const* argv) = 0; + + int run(int argc, char** argv, CreateTaskBarReceiverFunc createTaskBarReceiver); + + int daemonMainLoop(int, const char**); + + virtual void loadConfig() = 0; + virtual bool loadConfig(const CString& pathname) = 0; + virtual int mainLoop() = 0; + virtual int foregroundStartup(int argc, char** argv) = 0; + virtual int standardStartup(int argc, char** argv) = 0; + virtual int runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup, CreateTaskBarReceiverFunc createTaskBarReceiver) = 0; + + // Name of the daemon (used for Unix and Windows). + virtual const char* daemonName() const = 0; + + // A description of the daemon (used only on Windows). + virtual const char* daemonInfo() const = 0; + + // Function pointer for function to exit immediately. + // TODO: this is old C code - use inheritance to normalize + void (*m_bye)(int); + + // Returns true if argv[argi] is equal to name1 or name2. + bool isArg(int argi, int argc, const char* const* argv, + const char* name1, const char* name2, + int minRequiredParameters = 0); + + static CApp& instance() { assert(s_instance != nullptr); return *s_instance; } + + bool s_suspended; + IArchTaskBarReceiver* s_taskBarReceiver; + +protected: + virtual void parseArgs(int argc, const char* const* argv, int &i); + virtual bool parseArg(const int& argc, const char* const* argv, int& i); + +private: + CArgsBase* m_args; + static CApp* s_instance; +}; + +#define BYE "\nTry `%s --help' for more information." + +#if WINAPI_MSWINDOWS +#define DAEMON_RUNNING(running_) CArchMiscWindows::daemonRunning(running_) +#else +#define DAEMON_RUNNING(running_) +#endif diff --git a/lib/synergy/CClientApp.cpp b/lib/synergy/CClientApp.cpp new file mode 100644 index 00000000..fb92dbc2 --- /dev/null +++ b/lib/synergy/CClientApp.cpp @@ -0,0 +1,616 @@ +/* +* 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 "CClientApp.h" +#include "CLog.h" +#include "CArch.h" +#include "XSocket.h" +#include "Version.h" +#include "ProtocolTypes.h" +#include "CString.h" +#include "CScreen.h" +#include "CEvent.h" +#include "CClient.h" +#include "CNetworkAddress.h" +#include "IArchTaskBarReceiver.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include "CTCPSocketFactory.h" +#include "XScreen.h" +#include "LogOutputters.h" +#include "CSocketMultiplexer.h" +#include "CEventQueue.h" + +#if SYSAPI_WIN32 +#include "CArchMiscWindows.h" +#endif + +#if WINAPI_MSWINDOWS +#include "CMSWindowsScreen.h" +#elif WINAPI_XWINDOWS +#include "CXWindowsScreen.h" +#elif WINAPI_CARBON +#include "COSXScreen.h" +#endif + +#include +#include + +#define RETRY_TIME 1.0 + +CClientApp::CClientApp() : +CApp(new CArgs()), +s_client(NULL), +s_clientScreen(NULL) +{ +} + +CClientApp::~CClientApp() +{ +} + +CClientApp::CArgs::CArgs() : +m_yscroll(0), +m_serverAddress(NULL) +{ +} + +CClientApp::CArgs::~CArgs() +{ +} + +bool +CClientApp::parseArg(const int& argc, const char* const* argv, int& i) +{ + if (CApp::parseArg(argc, argv, i)) { + // found common arg + return true; + } + + else if (isArg(i, argc, argv, NULL, "--camp")) { + // ignore -- included for backwards compatibility + } + + else if (isArg(i, argc, argv, NULL, "--no-camp")) { + // ignore -- included for backwards compatibility + } + + else if (isArg(i, argc, argv, NULL, "--yscroll", 1)) { + // define scroll + args().m_yscroll = atoi(argv[++i]); + } + + else { + // option not supported here + return false; + } + + // argument was valid + return true; +} + +void +CClientApp::parseArgs(int argc, const char* const* argv) +{ + // asserts values, sets defaults, and parses args + int i; + CApp::parseArgs(argc, argv, i); + + // exactly one non-option argument (server-address) + if (i == argc) { + LOG((CLOG_PRINT "%s: a server address or name is required" BYE, + args().m_pname, args().m_pname)); + m_bye(kExitArgs); + } + if (i + 1 != argc) { + LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, + args().m_pname, argv[i], args().m_pname)); + m_bye(kExitArgs); + } + + // save server address + try { + *args().m_serverAddress = CNetworkAddress(argv[i], kDefaultPort); + args().m_serverAddress->resolve(); + } + catch (XSocketAddress& e) { + // allow an address that we can't look up if we're restartable. + // we'll try to resolve the address each time we connect to the + // server. a bad port will never get better. patch by Brent + // Priddy. + if (!args().m_restartable || e.getError() == XSocketAddress::kBadPort) { + LOG((CLOG_PRINT "%s: %s" BYE, + args().m_pname, e.what(), args().m_pname)); + m_bye(kExitFailed); + } + } + + // set log filter + if (!CLOG->setFilter(args().m_logFilter)) { + LOG((CLOG_PRINT "%s: unrecognized log level `%s'" BYE, + args().m_pname, args().m_logFilter, args().m_pname)); + m_bye(kExitArgs); + } + + // identify system + LOG((CLOG_INFO "%s Client on %s %s", kAppVersion, ARCH->getOSName().c_str(), ARCH->getPlatformName().c_str())); + +#ifdef WIN32 +#ifdef _AMD64_ + LOG((CLOG_WARN "This is an experimental x64 build of %s. Use it at your own risk.", kApplication)); +#endif +#endif + + if (CLOG->getFilter() > CLOG->getConsoleMaxLevel()) { + if (args().m_logFile == NULL) { + LOG((CLOG_WARN "log messages above %s are NOT sent to console (use file logging)", + CLOG->getFilterName(CLOG->getConsoleMaxLevel()))); + } + } +} + +void +CClientApp::help() +{ +#if WINAPI_XWINDOWS +# define USAGE_DISPLAY_ARG \ + " [--display ]" +# define USAGE_DISPLAY_INFO \ + " --display connect to the X server at \n" +#else +# define USAGE_DISPLAY_ARG +# define USAGE_DISPLAY_INFO +#endif + + char buffer[2000]; + sprintf( + buffer, + "Usage: %s" + " [--daemon|--no-daemon]" + " [--debug ]" + USAGE_DISPLAY_ARG + " [--name ]" + " [--yscroll ]" + " [--restart|--no-restart]" + " " + "\n\n" + "Start the synergy mouse/keyboard sharing server.\n" + "\n" + " -d, --debug filter out log messages with priorty below level.\n" + " level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n" + " DEBUG, DEBUG1, DEBUG2.\n" + USAGE_DISPLAY_INFO + " -f, --no-daemon run the client in the foreground.\n" + "* --daemon run the client as a daemon.\n" + " -n, --name use screen-name instead the hostname to identify\n" + " ourself to the server.\n" + " --yscroll defines the vertical scrolling delta, which is\n" + " 120 by default.\n" + " -1, --no-restart do not try to restart the client if it fails for\n" + " some reason.\n" + "* --restart restart the client automatically if it fails.\n" + " -l --log write log messages to file.\n" + " -h, --help display this help and exit.\n" + " --version display version information and exit.\n" + "\n" + "* marks defaults.\n" + "\n" + "The server address is of the form: [][:]. The hostname\n" + "must be the address or hostname of the server. The port overrides the\n" + "default port, %d.\n" + "\n" + "Where log messages go depends on the platform and whether or not the\n" + "client is running as a daemon.", + args().m_pname, kDefaultPort + ); + + std::cout << buffer << std::endl; +} + +const char* +CClientApp::daemonName() const +{ +#if SYSAPI_WIN32 + return "Synergy+ Client"; +#elif SYSAPI_UNIX + return "synergyc"; +#endif +} + +const char* +CClientApp::daemonInfo() const +{ +#if SYSAPI_WIN32 + return "Allows another computer to share it's keyboard and mouse with this computer."; +#elif SYSAPI_UNIX + return ""; +#endif +} + +CScreen* +CClientApp::createScreen() +{ +#if WINAPI_MSWINDOWS + return new CScreen(new CMSWindowsScreen(false, args().m_noHooks)); +#elif WINAPI_XWINDOWS + return new CScreen(new CXWindowsScreen(args().m_display, false, args().m_yscroll)); +#elif WINAPI_CARBON + return new CScreen(new COSXScreen(false)); +#endif +} + +void +CClientApp::updateStatus() +{ + s_taskBarReceiver->updateStatus(s_client, ""); +} + + +void +CClientApp::updateStatus(const CString& msg) +{ + s_taskBarReceiver->updateStatus(s_client, msg); +} + + +void +CClientApp::resetRestartTimeout() +{ + // retry time can nolonger be changed + //s_retryTime = 0.0; +} + + +double +CClientApp::nextRestartTimeout() +{ + // retry at a constant rate (Issue 52) + return RETRY_TIME; + + /* + // choose next restart timeout. we start with rapid retries + // then slow down. + if (s_retryTime < 1.0) { + s_retryTime = 1.0; + } + else if (s_retryTime < 3.0) { + s_retryTime = 3.0; + } + else { + s_retryTime = 5.0; + } + return s_retryTime; + */ +} + + +void +CClientApp::handleScreenError(const CEvent&, void*) +{ + LOG((CLOG_CRIT "error on screen")); + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + + +CScreen* +CClientApp::openClientScreen() +{ + CScreen* screen = createScreen(); + EVENTQUEUE->adoptHandler(IScreen::getErrorEvent(), + screen->getEventTarget(), + new TMethodEventJob( + this, &CClientApp::handleScreenError)); + return screen; +} + + +void +CClientApp::closeClientScreen(CScreen* screen) +{ + if (screen != NULL) { + EVENTQUEUE->removeHandler(IScreen::getErrorEvent(), + screen->getEventTarget()); + delete screen; + } +} + + +void +CClientApp::handleClientRestart(const CEvent&, void* vtimer) +{ + // discard old timer + CEventQueueTimer* timer = reinterpret_cast(vtimer); + EVENTQUEUE->deleteTimer(timer); + EVENTQUEUE->removeHandler(CEvent::kTimer, timer); + + // reconnect + startClient(); +} + + +void +CClientApp::scheduleClientRestart(double retryTime) +{ + // install a timer and handler to retry later + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, timer, + new TMethodEventJob(this, &CClientApp::handleClientRestart, timer)); +} + + +void +CClientApp::handleClientConnected(const CEvent&, void*) +{ + LOG((CLOG_NOTE "connected to server")); + resetRestartTimeout(); + updateStatus(); +} + + +void +CClientApp::handleClientFailed(const CEvent& e, void*) +{ + CClient::CFailInfo* info = + reinterpret_cast(e.getData()); + + updateStatus(CString("Failed to connect to server: ") + info->m_what); + if (!args().m_restartable || !info->m_retry) { + LOG((CLOG_ERR "failed to connect to server: %s", info->m_what.c_str())); + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + else { + LOG((CLOG_WARN "failed to connect to server: %s", info->m_what.c_str())); + if (!s_suspended) { + scheduleClientRestart(nextRestartTimeout()); + } + } + delete info; +} + + +void +CClientApp::handleClientDisconnected(const CEvent&, void*) +{ + LOG((CLOG_NOTE "disconnected from server")); + if (!args().m_restartable) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + else if (!s_suspended) { + s_client->connect(); + } + updateStatus(); +} + + +CClient* +CClientApp::openClient(const CString& name, const CNetworkAddress& address, CScreen* screen) +{ + CClient* client = new CClient( + name, address, new CTCPSocketFactory, NULL, screen); + + try { + EVENTQUEUE->adoptHandler( + CClient::getConnectedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, &CClientApp::handleClientConnected)); + + EVENTQUEUE->adoptHandler( + CClient::getConnectionFailedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, &CClientApp::handleClientFailed)); + + EVENTQUEUE->adoptHandler( + CClient::getDisconnectedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, &CClientApp::handleClientDisconnected)); + + } catch (std::bad_alloc &ba) { + delete client; + throw ba; + } + + return client; +} + + +void +CClientApp::closeClient(CClient* client) +{ + if (client == NULL) { + return; + } + + EVENTQUEUE->removeHandler(CClient::getConnectedEvent(), client); + EVENTQUEUE->removeHandler(CClient::getConnectionFailedEvent(), client); + EVENTQUEUE->removeHandler(CClient::getDisconnectedEvent(), client); + delete client; +} + +int +CClientApp::foregroundStartup(int argc, char** argv) +{ + // parse command line + parseArgs(argc, argv); + + // never daemonize + return mainLoop(); +} + +bool +CClientApp::startClient() +{ + double retryTime; + CScreen* clientScreen = NULL; + try { + if (s_clientScreen == NULL) { + clientScreen = openClientScreen(); + s_client = openClient(args().m_name, + *args().m_serverAddress, clientScreen); + s_clientScreen = clientScreen; + LOG((CLOG_NOTE "started client")); + } + s_client->connect(); + updateStatus(); + return true; + } + catch (XScreenUnavailable& e) { + LOG((CLOG_WARN "cannot open secondary screen: %s", e.what())); + closeClientScreen(clientScreen); + updateStatus(CString("Cannot open secondary screen: ") + e.what()); + retryTime = e.getRetryTime(); + } + catch (XScreenOpenFailure& e) { + LOG((CLOG_CRIT "cannot open secondary screen: %s", e.what())); + closeClientScreen(clientScreen); + return false; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start client: %s", e.what())); + closeClientScreen(clientScreen); + return false; + } + + if (args().m_restartable) { + scheduleClientRestart(retryTime); + return true; + } + else { + // don't try again + return false; + } +} + + +void +CClientApp::stopClient() +{ + closeClient(s_client); + closeClientScreen(s_clientScreen); + s_client = NULL; + s_clientScreen = NULL; +} + + +int +CClientApp::mainLoop() +{ + // logging to files + CFileLogOutputter* fileLog = NULL; + + if (args().m_logFile != NULL) { + fileLog = new CFileLogOutputter(args().m_logFile); + + CLOG->insert(fileLog); + + LOG((CLOG_DEBUG1 "Logging to file (%s) enabled", args().m_logFile)); + } + + // create socket multiplexer. this must happen after daemonization + // on unix because threads evaporate across a fork(). + CSocketMultiplexer multiplexer; + + // create the event queue + CEventQueue eventQueue; + + // start the client. if this return false then we've failed and + // we shouldn't retry. + LOG((CLOG_DEBUG1 "starting client")); + if (!startClient()) { + return kExitFailed; + } + + // run event loop. if startClient() failed we're supposed to retry + // later. the timer installed by startClient() will take care of + // that. + CEvent event; + DAEMON_RUNNING(true); + EVENTQUEUE->getEvent(event); + while (event.getType() != CEvent::kQuit) { + EVENTQUEUE->dispatchEvent(event); + CEvent::deleteData(event); + EVENTQUEUE->getEvent(event); + } + DAEMON_RUNNING(false); + + // close down + LOG((CLOG_DEBUG1 "stopping client")); + stopClient(); + updateStatus(); + LOG((CLOG_NOTE "stopped client")); + + if (fileLog) { + CLOG->remove(fileLog); + delete fileLog; + } + + return kExitSuccess; +} + +static +int +daemonMainLoopStatic(int argc, const char** argv) +{ + return CClientApp::instance().daemonMainLoop(argc, argv); +} + +int +CClientApp::standardStartup(int argc, char** argv) +{ + if (!args().m_daemon) { + ARCH->showConsole(false); + } + + // parse command line + parseArgs(argc, argv); + + // daemonize if requested + if (args().m_daemon) { + return ARCH->daemonize(daemonName(), &daemonMainLoopStatic); + } + else { + return mainLoop(); + } +} + +int +CClientApp::runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup, CreateTaskBarReceiverFunc createTaskBarReceiver) +{ + // general initialization + args().m_serverAddress = new CNetworkAddress; + args().m_pname = ARCH->getBasename(argv[0]); + + // install caller's output filter + if (outputter != NULL) { + CLOG->insert(outputter); + } + + // save log messages + // use heap memory because CLog deletes outputters on destruction + CBufferedLogOutputter* logBuffer = new CBufferedLogOutputter(1000); + CLOG->insert(logBuffer, true); + + // make the task bar receiver. the user can control this app + // through the task bar. + s_taskBarReceiver = createTaskBarReceiver(logBuffer); + + // run + int result = startup(argc, argv); + + // done with task bar receiver + delete s_taskBarReceiver; + + delete args().m_serverAddress; + return result; +} diff --git a/lib/synergy/CClientApp.h b/lib/synergy/CClientApp.h new file mode 100644 index 00000000..39c67bce --- /dev/null +++ b/lib/synergy/CClientApp.h @@ -0,0 +1,84 @@ +/* +* 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. +*/ + +#pragma once + +#include "CApp.h" + +class CScreen; +class CEvent; +class CClient; +class CNetworkAddress; + +class CClientApp : public CApp { +public: + class CArgs : public CApp::CArgsBase { + public: + CArgs(); + ~CArgs(); + + public: + int m_yscroll; + CNetworkAddress* m_serverAddress; + }; + + CClientApp(); + virtual ~CClientApp(); + + // Parse client specific command line arguments. + void parseArgs(int argc, const char* const* argv); + + // Prints help specific to client. + void help(); + + // Returns arguments that are common and for client. + CArgs& args() const { return (CArgs&)argsBase(); } + + const char* daemonName() const; + const char* daemonInfo() const; + + // TODO: move to server only (not supported on client) + void loadConfig() { } + bool loadConfig(const CString& pathname) { return false; } + + int foregroundStartup(int argc, char** argv); + int standardStartup(int argc, char** argv); + int runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup, CreateTaskBarReceiverFunc createTaskBarReceiver); + CScreen* createScreen(); + void updateStatus(); + void updateStatus(const CString& msg); + void resetRestartTimeout(); + double nextRestartTimeout(); + void handleScreenError(const CEvent&, void*); + CScreen* openClientScreen(); + void closeClientScreen(CScreen* screen); + void handleClientRestart(const CEvent&, void* vtimer); + void scheduleClientRestart(double retryTime); + void handleClientConnected(const CEvent&, void*); + void handleClientFailed(const CEvent& e, void*); + void handleClientDisconnected(const CEvent&, void*); + CClient* openClient(const CString& name, const CNetworkAddress& address, CScreen* screen); + void closeClient(CClient* client); + bool startClient(); + void stopClient(); + int mainLoop(); + + static CClientApp& instance() { return (CClientApp&)CApp::instance(); } + +private: + CClient* s_client; + CScreen* s_clientScreen; + + virtual bool parseArg(const int& argc, const char* const* argv, int& i); +}; diff --git a/cmd/synergyc/CClientTaskBarReceiver.cpp b/lib/synergy/CClientTaskBarReceiver.cpp similarity index 100% rename from cmd/synergyc/CClientTaskBarReceiver.cpp rename to lib/synergy/CClientTaskBarReceiver.cpp diff --git a/cmd/synergyc/CClientTaskBarReceiver.h b/lib/synergy/CClientTaskBarReceiver.h similarity index 89% rename from cmd/synergyc/CClientTaskBarReceiver.h rename to lib/synergy/CClientTaskBarReceiver.h index 0e3440e1..92f4b352 100644 --- a/cmd/synergyc/CClientTaskBarReceiver.h +++ b/lib/synergy/CClientTaskBarReceiver.h @@ -17,8 +17,8 @@ #include "CString.h" #include "IArchTaskBarReceiver.h" - -class CClient; +#include "LogOutputters.h" +#include "CClient.h" //! Implementation of IArchTaskBarReceiver for the synergy server class CClientTaskBarReceiver : public IArchTaskBarReceiver { @@ -35,6 +35,8 @@ public: */ void updateStatus(CClient*, const CString& errorMsg); + void updateStatus(INode* n, const CString& errorMsg) { updateStatus((CClient*)n, errorMsg); } + //@} // IArchTaskBarReceiver overrides @@ -80,4 +82,6 @@ private: CString m_server; }; +IArchTaskBarReceiver* createTaskBarReceiver(const CBufferedLogOutputter* logBuffer); + #endif diff --git a/lib/synergy/CKeyMap.cpp b/lib/synergy/CKeyMap.cpp index 87e64d7f..38effdaf 100644 --- a/lib/synergy/CKeyMap.cpp +++ b/lib/synergy/CKeyMap.cpp @@ -98,7 +98,7 @@ CKeyMap::addKeyEntry(const KeyItem& item) // add item list entries.push_back(items); - LOG((CLOG_DEBUG1 "add key: %04x %d %03x %04x (%04x %04x %04x)%s", newItem.m_id, newItem.m_group, newItem.m_button, newItem.m_client, newItem.m_required, newItem.m_sensitive, newItem.m_generates, newItem.m_dead ? " dead" : "")); + LOG((CLOG_DEBUG3 "add key: %04x %d %03x %04x (%04x %04x %04x)%s", newItem.m_id, newItem.m_group, newItem.m_button, newItem.m_client, newItem.m_required, newItem.m_sensitive, newItem.m_generates, newItem.m_dead ? " dead" : "")); } void diff --git a/lib/synergy/CServerApp.cpp b/lib/synergy/CServerApp.cpp new file mode 100644 index 00000000..2db1af73 --- /dev/null +++ b/lib/synergy/CServerApp.cpp @@ -0,0 +1,969 @@ +/* +* 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 "CServerApp.h" +#include "CLog.h" +#include "CArch.h" +#include "XSocket.h" +#include "Version.h" +#include "IEventQueue.h" +#include "CServer.h" +#include "CClientListener.h" +#include "CClientProxy.h" +#include "TMethodEventJob.h" +#include "CServerTaskBarReceiver.h" +#include "CPrimaryClient.h" +#include "CScreen.h" +#include "CSocketMultiplexer.h" +#include "CEventQueue.h" +#include "LogOutputters.h" +#include "CFunctionEventJob.h" + +#if SYSAPI_WIN32 +#include "CArchMiscWindows.h" +#endif + +#if WINAPI_MSWINDOWS +#include "CMSWindowsScreen.h" +#elif WINAPI_XWINDOWS +#include "CXWindowsScreen.h" +#elif WINAPI_CARBON +#include "COSXScreen.h" +#endif + +#include +#include +#include +#include "XScreen.h" +#include "CTCPSocketFactory.h" + +CEvent::Type CServerApp::s_reloadConfigEvent = CEvent::kUnknown; + +CServerApp::CServerApp() : +CApp(new CArgs()), +s_server(NULL), +s_forceReconnectEvent(CEvent::kUnknown), +s_resetServerEvent(CEvent::kUnknown), +s_serverState(kUninitialized), +s_serverScreen(NULL), +s_primaryClient(NULL), +s_listener(NULL), +s_timer(NULL) +{ +} + +CServerApp::~CServerApp() +{ +} + +CServerApp::CArgs::CArgs() : +m_synergyAddress(NULL), +m_config(NULL) +{ +} + +CServerApp::CArgs::~CArgs() +{ +} + +bool +CServerApp::parseArg(const int& argc, const char* const* argv, int& i) +{ + if (CApp::parseArg(argc, argv, i)) { + // found common arg + return true; + } + + else if (isArg(i, argc, argv, "-a", "--address", 1)) { + // save listen address + try { + *args().m_synergyAddress = CNetworkAddress(argv[i + 1], + kDefaultPort); + args().m_synergyAddress->resolve(); + } + catch (XSocketAddress& e) { + LOG((CLOG_PRINT "%s: %s" BYE, + args().m_pname, e.what(), args().m_pname)); + m_bye(kExitArgs); + } + ++i; + } + + else if (isArg(i, argc, argv, "-c", "--config", 1)) { + // save configuration file path + args().m_configFile = argv[++i]; + } + + else { + // option not supported here + return false; + } + + // argument was valid + return true; +} + +void +CServerApp::parseArgs(int argc, const char* const* argv) +{ + // asserts values, sets defaults, and parses args + int i; + CApp::parseArgs(argc, argv, i); + + // no non-option arguments are allowed + if (i != argc) { + LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, + args().m_pname, argv[i], args().m_pname)); + m_bye(kExitArgs); + } + +#if SYSAPI_WIN32 + // if user wants to run as daemon, but process not launched from service launcher... + if (args().m_daemon && !CArchMiscWindows::wasLaunchedAsService()) { + LOG((CLOG_ERR "cannot launch as daemon if process not started through " + "service host (use '--service start' argument instead)")); + m_bye(kExitArgs); + } +#endif + + // set log filter + if (!CLOG->setFilter(args().m_logFilter)) { + LOG((CLOG_PRINT "%s: unrecognized log level `%s'" BYE, + args().m_pname, args().m_logFilter, args().m_pname)); + m_bye(kExitArgs); + } + + // identify system + LOG((CLOG_INFO "%s Server on %s %s", kAppVersion, ARCH->getOSName().c_str(), ARCH->getPlatformName().c_str())); + +#ifdef WIN32 +#ifdef _AMD64_ + LOG((CLOG_WARN "This is an experimental x64 build of %s. Use it at your own risk.", kApplication)); +#endif +#endif + + if (CLOG->getFilter() > CLOG->getConsoleMaxLevel()) { + if (args().m_logFile == NULL) { + LOG((CLOG_WARN "log messages above %s are NOT sent to console (use file logging)", + CLOG->getFilterName(CLOG->getConsoleMaxLevel()))); + } + } +} + +void +CServerApp::help() +{ +#if WINAPI_XWINDOWS +# define USAGE_DISPLAY_ARG \ + " [--display ]" +# define USAGE_DISPLAY_INFO \ + " --display connect to the X server at \n" +#else +# define USAGE_DISPLAY_ARG +# define USAGE_DISPLAY_INFO +#endif + +#if SYSAPI_WIN32 + +# define PLATFORM_ARGS \ + " [--daemon|--no-daemon]" +# define PLATFORM_DESC +# define PLATFORM_EXTRA \ + "At least one command line argument is required. If you don't otherwise\n" \ + "need an argument use `--daemon'.\n" \ + "\n" + +#else + +# define PLATFORM_ARGS \ + " [--daemon|--no-daemon]" +# define PLATFORM_DESC +# define PLATFORM_EXTRA + +#endif + + char buffer[2000]; + sprintf( + buffer, + "Usage: %s" + " [--address
]" + " [--config ]" + " [--debug ]" + USAGE_DISPLAY_ARG + " [--name ]" + " [--restart|--no-restart]" + PLATFORM_ARGS + "\n\n" + "Start the synergy mouse/keyboard sharing server.\n" + "\n" + " -a, --address
listen for clients on the given address.\n" + " -c, --config use the named configuration file instead.\n" + " -d, --debug filter out log messages with priorty below level.\n" + " level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n" + " DEBUG, DEBUG1, DEBUG2.\n" + USAGE_DISPLAY_INFO + " -f, --no-daemon run the server in the foreground.\n" + "* --daemon run the server as a daemon.\n" + " -n, --name use screen-name instead the hostname to identify\n" + " this screen in the configuration.\n" + " -1, --no-restart do not try to restart the server if it fails for\n" + " some reason.\n" + "* --restart restart the server automatically if it fails.\n" + " -l --log write log messages to file.\n" + PLATFORM_DESC + " -h, --help display this help and exit.\n" + " --version display version information and exit.\n" + "\n" + "* marks defaults.\n" + "\n" + PLATFORM_EXTRA + "The argument for --address is of the form: [][:]. The\n" + "hostname must be the address or hostname of an interface on the system.\n" + "The default is to listen on all interfaces. The port overrides the\n" + "default port, %d.\n" + "\n" + "If no configuration file pathname is provided then the first of the\n" + "following to load successfully sets the configuration:\n" + " %s\n" + " %s\n" + "If no configuration file can be loaded then the configuration uses its\n" + "defaults with just the server screen.\n" + "\n" + "Where log messages go depends on the platform and whether or not the\n" + "server is running as a daemon.", + args().m_pname, kDefaultPort, + ARCH->concatPath(ARCH->getUserDirectory(), USR_CONFIG_NAME).c_str(), + ARCH->concatPath(ARCH->getSystemDirectory(), SYS_CONFIG_NAME).c_str() + ); + + std::cout << buffer << std::endl; +} + +void +CServerApp::reloadSignalHandler(CArch::ESignal, void*) +{ + EVENTQUEUE->addEvent(CEvent(getReloadConfigEvent(), + IEventQueue::getSystemTarget())); +} + +void +CServerApp::reloadConfig(const CEvent&, void*) +{ + LOG((CLOG_DEBUG "reload configuration")); + if (loadConfig(args().m_configFile)) { + if (s_server != NULL) { + s_server->setConfig(*args().m_config); + } + LOG((CLOG_NOTE "reloaded configuration")); + } +} + +void +CServerApp::loadConfig() +{ + bool loaded = false; + + // load the config file, if specified + if (!args().m_configFile.empty()) { + loaded = loadConfig(args().m_configFile); + } + + // load the default configuration if no explicit file given + else { + // get the user's home directory + CString path = ARCH->getUserDirectory(); + if (!path.empty()) { + // complete path + path = ARCH->concatPath(path, USR_CONFIG_NAME); + + // now try loading the user's configuration + if (loadConfig(path)) { + loaded = true; + args().m_configFile = path; + } + } + if (!loaded) { + // try the system-wide config file + path = ARCH->getSystemDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, SYS_CONFIG_NAME); + if (loadConfig(path)) { + loaded = true; + args().m_configFile = path; + } + } + } + } + + if (!loaded) { + LOG((CLOG_PRINT "%s: no configuration available", args().m_pname)); + m_bye(kExitConfig); + } +} + +bool +CServerApp::loadConfig(const CString& pathname) +{ + try { + // load configuration + LOG((CLOG_DEBUG "opening configuration \"%s\"", pathname.c_str())); + std::ifstream configStream(pathname.c_str()); + if (!configStream.is_open()) { + // report failure to open configuration as a debug message + // since we try several paths and we expect some to be + // missing. + LOG((CLOG_DEBUG "cannot open configuration \"%s\"", + pathname.c_str())); + return false; + } + configStream >> *args().m_config; + LOG((CLOG_DEBUG "configuration read successfully")); + return true; + } + catch (XConfigRead& e) { + // report error in configuration file + LOG((CLOG_ERR "cannot read configuration \"%s\": %s", + pathname.c_str(), e.what())); + } + return false; +} + +CEvent::Type +CServerApp::getReloadConfigEvent() +{ + return CEvent::registerTypeOnce(s_reloadConfigEvent, "reloadConfig"); +} + +void +CServerApp::forceReconnect(const CEvent&, void*) +{ + if (s_server != NULL) { + s_server->disconnect(); + } +} + +CEvent::Type +CServerApp::getForceReconnectEvent() +{ + return CEvent::registerTypeOnce(s_forceReconnectEvent, "forceReconnect"); +} + +CEvent::Type +CServerApp::getResetServerEvent() +{ + return CEvent::registerTypeOnce(s_resetServerEvent, "resetServer"); +} + +void +CServerApp::handleClientConnected(const CEvent&, void* vlistener) +{ + CClientListener* listener = reinterpret_cast(vlistener); + CClientProxy* client = listener->getNextClient(); + if (client != NULL) { + s_server->adoptClient(client); + updateStatus(); + } +} + +void +CServerApp::handleClientsDisconnected(const CEvent&, void*) +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + +void +CServerApp::closeServer(CServer* server) +{ + if (server == NULL) { + return; + } + + // tell all clients to disconnect + server->disconnect(); + + // wait for clients to disconnect for up to timeout seconds + double timeout = 3.0; + CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(timeout, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, timer, + new TMethodEventJob(this, &CServerApp::handleClientsDisconnected)); + EVENTQUEUE->adoptHandler(CServer::getDisconnectedEvent(), server, + new TMethodEventJob(this, &CServerApp::handleClientsDisconnected)); + CEvent event; + EVENTQUEUE->getEvent(event); + while (event.getType() != CEvent::kQuit) { + EVENTQUEUE->dispatchEvent(event); + CEvent::deleteData(event); + EVENTQUEUE->getEvent(event); + } + EVENTQUEUE->removeHandler(CEvent::kTimer, timer); + EVENTQUEUE->deleteTimer(timer); + EVENTQUEUE->removeHandler(CServer::getDisconnectedEvent(), server); + + // done with server + delete server; +} + +void +CServerApp::stopRetryTimer() +{ + if (s_timer != NULL) { + EVENTQUEUE->deleteTimer(s_timer); + EVENTQUEUE->removeHandler(CEvent::kTimer, NULL); + s_timer = NULL; + } +} + +void +CServerApp::updateStatus() +{ + s_taskBarReceiver->updateStatus(s_server, ""); +} + +void CServerApp::updateStatus( const CString& msg ) +{ + s_taskBarReceiver->updateStatus(s_server, msg); +} + +void +CServerApp::closeClientListener(CClientListener* listen) +{ + if (listen != NULL) { + EVENTQUEUE->removeHandler(CClientListener::getConnectedEvent(), listen); + delete listen; + } +} + +void +CServerApp::stopServer() +{ + if (s_serverState == kStarted) { + closeClientListener(s_listener); + closeServer(s_server); + s_server = NULL; + s_listener = NULL; + s_serverState = kInitialized; + } + else if (s_serverState == kStarting) { + stopRetryTimer(); + s_serverState = kInitialized; + } + assert(s_server == NULL); + assert(s_listener == NULL); +} + +void +CServerApp::closePrimaryClient(CPrimaryClient* primaryClient) +{ + delete primaryClient; +} + +void +CServerApp::closeServerScreen(CScreen* screen) +{ + if (screen != NULL) { + EVENTQUEUE->removeHandler(IScreen::getErrorEvent(), + screen->getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getSuspendEvent(), + screen->getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getResumeEvent(), + screen->getEventTarget()); + delete screen; + } +} + +void CServerApp::cleanupServer() +{ + stopServer(); + if (s_serverState == kInitialized) { + closePrimaryClient(s_primaryClient); + closeServerScreen(s_serverScreen); + s_primaryClient = NULL; + s_serverScreen = NULL; + s_serverState = kUninitialized; + } + else if (s_serverState == kInitializing || + s_serverState == kInitializingToStart) { + stopRetryTimer(); + s_serverState = kUninitialized; + } + assert(s_primaryClient == NULL); + assert(s_serverScreen == NULL); + assert(s_serverState == kUninitialized); +} + +void +CServerApp::retryHandler(const CEvent&, void*) +{ + // discard old timer + assert(s_timer != NULL); + stopRetryTimer(); + + // try initializing/starting the server again + switch (s_serverState) { + case kUninitialized: + case kInitialized: + case kStarted: + assert(0 && "bad internal server state"); + break; + + case kInitializing: + LOG((CLOG_DEBUG1 "retry server initialization")); + s_serverState = kUninitialized; + if (!initServer()) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + break; + + case kInitializingToStart: + LOG((CLOG_DEBUG1 "retry server initialization")); + s_serverState = kUninitialized; + if (!initServer()) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + else if (s_serverState == kInitialized) { + LOG((CLOG_DEBUG1 "starting server")); + if (!startServer()) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + } + break; + + case kStarting: + LOG((CLOG_DEBUG1 "retry starting server")); + s_serverState = kInitialized; + if (!startServer()) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + break; + } +} + +bool CServerApp::initServer() +{ + // skip if already initialized or initializing + if (s_serverState != kUninitialized) { + return true; + } + + double retryTime; + CScreen* serverScreen = NULL; + CPrimaryClient* primaryClient = NULL; + try { + CString name = args().m_config->getCanonicalName(args().m_name); + serverScreen = openServerScreen(); + primaryClient = openPrimaryClient(name, serverScreen); + s_serverScreen = serverScreen; + s_primaryClient = primaryClient; + s_serverState = kInitialized; + updateStatus(); + return true; + } + catch (XScreenUnavailable& e) { + LOG((CLOG_WARN "cannot open primary screen: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + updateStatus(CString("cannot open primary screen: ") + e.what()); + retryTime = e.getRetryTime(); + } + catch (XScreenOpenFailure& e) { + LOG((CLOG_CRIT "cannot open primary screen: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + return false; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start server: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + return false; + } + + if (args().m_restartable) { + // install a timer and handler to retry later + assert(s_timer == NULL); + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + s_timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, s_timer, + new TMethodEventJob(this, &CServerApp::retryHandler)); + s_serverState = kInitializing; + return true; + } + else { + // don't try again + return false; + } +} + +CScreen* CServerApp::openServerScreen() +{ + CScreen* screen = createScreen(); + EVENTQUEUE->adoptHandler(IScreen::getErrorEvent(), + screen->getEventTarget(), + new TMethodEventJob( + this, &CServerApp::handleScreenError)); + EVENTQUEUE->adoptHandler(IScreen::getSuspendEvent(), + screen->getEventTarget(), + new TMethodEventJob( + this, &CServerApp::handleSuspend)); + EVENTQUEUE->adoptHandler(IScreen::getResumeEvent(), + screen->getEventTarget(), + new TMethodEventJob( + this, &CServerApp::handleResume)); + return screen; +} + +bool +CServerApp::startServer() +{ + // skip if already started or starting + if (s_serverState == kStarting || s_serverState == kStarted) { + return true; + } + + // initialize if necessary + if (s_serverState != kInitialized) { + if (!initServer()) { + // hard initialization failure + return false; + } + if (s_serverState == kInitializing) { + // not ready to start + s_serverState = kInitializingToStart; + return true; + } + assert(s_serverState == kInitialized); + } + + double retryTime; + CClientListener* listener = NULL; + try { + listener = openClientListener(args().m_config->getSynergyAddress()); + s_server = openServer(*args().m_config, s_primaryClient); + s_listener = listener; + updateStatus(); + LOG((CLOG_NOTE "started server")); + s_serverState = kStarted; + return true; + } + catch (XSocketAddressInUse& e) { + LOG((CLOG_WARN "cannot listen for clients: %s", e.what())); + closeClientListener(listener); + updateStatus(CString("cannot listen for clients: ") + e.what()); + retryTime = 10.0; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start server: %s", e.what())); + closeClientListener(listener); + return false; + } + + if (args().m_restartable) { + // install a timer and handler to retry later + assert(s_timer == NULL); + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + s_timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, s_timer, + new TMethodEventJob(this, &CServerApp::retryHandler)); + s_serverState = kStarting; + return true; + } + else { + // don't try again + return false; + } +} + +CScreen* +CServerApp::createScreen() +{ +#if WINAPI_MSWINDOWS + return new CScreen(new CMSWindowsScreen(true, args().m_noHooks)); +#elif WINAPI_XWINDOWS + return new CScreen(new CXWindowsScreen(args().m_display, true)); +#elif WINAPI_CARBON + return new CScreen(new COSXScreen(true)); +#endif +} + +CPrimaryClient* +CServerApp::openPrimaryClient(const CString& name, CScreen* screen) +{ + LOG((CLOG_DEBUG1 "creating primary screen")); + return new CPrimaryClient(name, screen); + +} + +void +CServerApp::handleScreenError(const CEvent&, void*) +{ + LOG((CLOG_CRIT "error on screen")); + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + +void +CServerApp::handleSuspend(const CEvent&, void*) +{ + if (!s_suspended) { + LOG((CLOG_INFO "suspend")); + stopServer(); + s_suspended = true; + } +} + +void +CServerApp::handleResume(const CEvent&, void*) +{ + if (s_suspended) { + LOG((CLOG_INFO "resume")); + startServer(); + s_suspended = false; + } +} + +CClientListener* +CServerApp::openClientListener(const CNetworkAddress& address) +{ + CClientListener* listen = + new CClientListener(address, new CTCPSocketFactory, NULL); + EVENTQUEUE->adoptHandler(CClientListener::getConnectedEvent(), listen, + new TMethodEventJob( + this, &CServerApp::handleClientConnected, listen)); + return listen; +} + +CServer* +CServerApp::openServer(const CConfig& config, CPrimaryClient* primaryClient) +{ + CServer* server = new CServer(config, primaryClient); + + try { + EVENTQUEUE->adoptHandler( + CServer::getDisconnectedEvent(), server, + new TMethodEventJob(this, &CServerApp::handleNoClients)); + + } catch (std::bad_alloc &ba) { + delete server; + throw ba; + } + + return server; +} + +void CServerApp::handleNoClients( const CEvent&, void* ) +{ + updateStatus(); +} + +int CServerApp::mainLoop() +{ + // create socket multiplexer. this must happen after daemonization + // on unix because threads evaporate across a fork(). + CSocketMultiplexer multiplexer; + + // create the event queue + CEventQueue eventQueue; + + // logging to files + CFileLogOutputter* fileLog = NULL; + + if (args().m_logFile != NULL) { + fileLog = new CFileLogOutputter(args().m_logFile); + + CLOG->insert(fileLog); + + LOG((CLOG_DEBUG1 "Logging to file (%s) enabled", args().m_logFile)); + } + + // if configuration has no screens then add this system + // as the default + if (args().m_config->begin() == args().m_config->end()) { + args().m_config->addScreen(args().m_name); + } + + // set the contact address, if provided, in the config. + // otherwise, if the config doesn't have an address, use + // the default. + if (args().m_synergyAddress->isValid()) { + args().m_config->setSynergyAddress(*args().m_synergyAddress); + } + else if (!args().m_config->getSynergyAddress().isValid()) { + args().m_config->setSynergyAddress(CNetworkAddress(kDefaultPort)); + } + + // canonicalize the primary screen name + CString primaryName = args().m_config->getCanonicalName(args().m_name); + if (primaryName.empty()) { + LOG((CLOG_CRIT "unknown screen name `%s'", args().m_name.c_str())); + return kExitFailed; + } + + // start the server. if this return false then we've failed and + // we shouldn't retry. + LOG((CLOG_DEBUG1 "starting server")); + if (!startServer()) { + return kExitFailed; + } + + // handle hangup signal by reloading the server's configuration + ARCH->setSignalHandler(CArch::kHANGUP, &reloadSignalHandler, NULL); + EVENTQUEUE->adoptHandler(getReloadConfigEvent(), + IEventQueue::getSystemTarget(), + new TMethodEventJob(this, &CServerApp::reloadConfig)); + + // handle force reconnect event by disconnecting clients. they'll + // reconnect automatically. + EVENTQUEUE->adoptHandler(getForceReconnectEvent(), + IEventQueue::getSystemTarget(), + new TMethodEventJob(this, &CServerApp::forceReconnect)); + + // to work around the sticky meta keys problem, we'll give users + // the option to reset the state of synergys + EVENTQUEUE->adoptHandler(getResetServerEvent(), + IEventQueue::getSystemTarget(), + new TMethodEventJob(this, &CServerApp::resetServer)); + + // run event loop. if startServer() failed we're supposed to retry + // later. the timer installed by startServer() will take care of + // that. + CEvent event; + DAEMON_RUNNING(true); + EVENTQUEUE->getEvent(event); + while (event.getType() != CEvent::kQuit) { + EVENTQUEUE->dispatchEvent(event); + CEvent::deleteData(event); + EVENTQUEUE->getEvent(event); + } + DAEMON_RUNNING(false); + + // close down + LOG((CLOG_DEBUG1 "stopping server")); + EVENTQUEUE->removeHandler(getForceReconnectEvent(), + IEventQueue::getSystemTarget()); + EVENTQUEUE->removeHandler(getReloadConfigEvent(), + IEventQueue::getSystemTarget()); + cleanupServer(); + updateStatus(); + LOG((CLOG_NOTE "stopped server")); + + if (fileLog) { + CLOG->remove(fileLog); + delete fileLog; + } + + return kExitSuccess; +} + +void CServerApp::resetServer(const CEvent&, void*) +{ + LOG((CLOG_DEBUG1 "resetting server")); + stopServer(); + cleanupServer(); + startServer(); +} + +int +CServerApp::runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup, CreateTaskBarReceiverFunc createTaskBarReceiver) +{ + // general initialization + args().m_synergyAddress = new CNetworkAddress; + args().m_config = new CConfig; + args().m_pname = ARCH->getBasename(argv[0]); + + // install caller's output filter + if (outputter != NULL) { + CLOG->insert(outputter); + } + + // save log messages + // use heap memory because CLog deletes outputters on destruction + CBufferedLogOutputter* logBuffer = new CBufferedLogOutputter(1000); + CLOG->insert(logBuffer, true); + + // make the task bar receiver. the user can control this app + // through the task bar. + s_taskBarReceiver = createTaskBarReceiver(logBuffer); + + // run + int result = startup(argc, argv); + + // done with task bar receiver + delete s_taskBarReceiver; + + delete args().m_config; + delete args().m_synergyAddress; + return result; +} + +int daemonMainLoopStatic(int argc, const char** argv) { + return CServerApp::instance().daemonMainLoop(argc, argv); +} + +int +CServerApp::standardStartup(int argc, char** argv) +{ + // parse command line + parseArgs(argc, argv); + + // load configuration + loadConfig(); + + // daemonize if requested + if (args().m_daemon) { + return ARCH->daemonize(daemonName(), daemonMainLoopStatic); + } + else { + return mainLoop(); + } +} + +int +CServerApp::foregroundStartup(int argc, char** argv) +{ + // parse command line + parseArgs(argc, argv); + + // load configuration + loadConfig(); + + // never daemonize + return mainLoop(); +} + +static +int +mainLoopStatic() +{ + return CServerApp::instance().mainLoop(); +} + +const char* +CServerApp::daemonName() const +{ +#if SYSAPI_WIN32 + return "Synergy+ Server"; +#elif SYSAPI_UNIX + return "synergys"; +#endif +} + +const char* +CServerApp::daemonInfo() const +{ +#if SYSAPI_WIN32 + return "Shares this computers mouse and keyboard with other computers."; +#elif SYSAPI_UNIX + return ""; +#endif +} + diff --git a/lib/synergy/CServerApp.h b/lib/synergy/CServerApp.h new file mode 100644 index 00000000..2dda6ff7 --- /dev/null +++ b/lib/synergy/CServerApp.h @@ -0,0 +1,130 @@ +/* +* 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. +*/ + +#pragma once + +#include "CApp.h" +#include "CString.h" +#include "CConfig.h" +#include "CNetworkAddress.h" +#include "CArch.h" +#include "IArchMultithread.h" + +enum EServerState { + kUninitialized, + kInitializing, + kInitializingToStart, + kInitialized, + kStarting, + kStarted +}; + +class CServer; +class CScreen; +class CClientListener; +class CEventQueueTimer; +class ILogOutputter; + +class CServerApp : public CApp { +public: + class CArgs : public CApp::CArgsBase { + public: + CArgs(); + ~CArgs(); + + public: + CString m_configFile; + CNetworkAddress* m_synergyAddress; + CConfig* m_config; + }; + + CServerApp(); + virtual ~CServerApp(); + + // Parse server specific command line arguments. + void parseArgs(int argc, const char* const* argv); + + // Prints help specific to server. + void help(); + + // Returns arguments that are common and for server. + CArgs& args() const { return (CArgs&)argsBase(); } + + const char* daemonName() const; + const char* daemonInfo() const; + + // TODO: Document these functions. + static void reloadSignalHandler(CArch::ESignal, void*); + static CEvent::Type getReloadConfigEvent(); + + void reloadConfig(const CEvent&, void*); + void loadConfig(); + bool loadConfig(const CString& pathname); + void forceReconnect(const CEvent&, void*); + CEvent::Type getForceReconnectEvent(); + void resetServer(const CEvent&, void*); + CEvent::Type getResetServerEvent(); + void handleClientConnected(const CEvent&, void* vlistener); + void handleClientsDisconnected(const CEvent&, void*); + void closeServer(CServer* server); + void stopRetryTimer(); + void updateStatus(); + void updateStatus(const CString& msg); + void closeClientListener(CClientListener* listen); + void stopServer(); + void closePrimaryClient(CPrimaryClient* primaryClient); + void closeServerScreen(CScreen* screen); + void cleanupServer(); + bool initServer(); + void retryHandler(const CEvent&, void*); + CScreen* openServerScreen(); + CScreen* createScreen(); + CPrimaryClient* openPrimaryClient(const CString& name, CScreen* screen); + void handleScreenError(const CEvent&, void*); + void handleSuspend(const CEvent&, void*); + void handleResume(const CEvent&, void*); + CClientListener* openClientListener(const CNetworkAddress& address); + CServer* openServer(const CConfig& config, CPrimaryClient* primaryClient); + void handleNoClients(const CEvent&, void*); + bool startServer(); + int mainLoop(); + int runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup, CreateTaskBarReceiverFunc createTaskBarReceiver); + int standardStartup(int argc, char** argv); + int foregroundStartup(int argc, char** argv); + + static CServerApp& instance() { return (CServerApp&)CApp::instance(); } + + // TODO: change s_ to m_ + CServer* s_server; + static CEvent::Type s_reloadConfigEvent; + CEvent::Type s_forceReconnectEvent; + CEvent::Type s_resetServerEvent; + EServerState s_serverState; + CScreen* s_serverScreen; + CPrimaryClient* s_primaryClient; + CClientListener* s_listener; + CEventQueueTimer* s_timer; + +private: + virtual bool parseArg(const int& argc, const char* const* argv, int& i); +}; + +// configuration file name +#if SYSAPI_WIN32 +#define USR_CONFIG_NAME "synergy.sgc" +#define SYS_CONFIG_NAME "synergy.sgc" +#elif SYSAPI_UNIX +#define USR_CONFIG_NAME ".synergy.conf" +#define SYS_CONFIG_NAME "synergy.conf" +#endif diff --git a/cmd/synergys/CServerTaskBarReceiver.cpp b/lib/synergy/CServerTaskBarReceiver.cpp similarity index 87% rename from cmd/synergys/CServerTaskBarReceiver.cpp rename to lib/synergy/CServerTaskBarReceiver.cpp index 6555b214..632c9a8d 100644 --- a/cmd/synergys/CServerTaskBarReceiver.cpp +++ b/lib/synergy/CServerTaskBarReceiver.cpp @@ -131,3 +131,21 @@ CServerTaskBarReceiver::getToolTip() const return ""; } } + +CEvent::Type +CServerTaskBarReceiver::getReloadConfigEvent() +{ + return CServerApp::instance().getReloadConfigEvent(); +} + +CEvent::Type +CServerTaskBarReceiver::getForceReconnectEvent() +{ + return CServerApp::instance().getForceReconnectEvent(); +} + +CEvent::Type +CServerTaskBarReceiver::getResetServerEvent() +{ + return CServerApp::instance().getResetServerEvent(); +} diff --git a/cmd/synergys/CServerTaskBarReceiver.h b/lib/synergy/CServerTaskBarReceiver.h similarity index 84% rename from cmd/synergys/CServerTaskBarReceiver.h rename to lib/synergy/CServerTaskBarReceiver.h index d6ec8571..66b15534 100644 --- a/cmd/synergys/CServerTaskBarReceiver.h +++ b/lib/synergy/CServerTaskBarReceiver.h @@ -18,8 +18,9 @@ #include "CString.h" #include "IArchTaskBarReceiver.h" #include "stdvector.h" - -class CServer; +#include "CEvent.h" +#include "CServerApp.h" +#include "CServer.h" //! Implementation of IArchTaskBarReceiver for the synergy server class CServerTaskBarReceiver : public IArchTaskBarReceiver { @@ -36,6 +37,8 @@ public: */ void updateStatus(CServer*, const CString& errorMsg); + void updateStatus(INode* n, const CString& errorMsg) { updateStatus((CServer*)n, errorMsg); } + //@} // IArchTaskBarReceiver overrides @@ -79,10 +82,17 @@ protected: */ virtual void onStatusChanged(CServer* server); +protected: + CEvent::Type getReloadConfigEvent(); + CEvent::Type getForceReconnectEvent(); + CEvent::Type getResetServerEvent(); + private: EState m_state; CString m_errorMessage; CClients m_clients; }; +IArchTaskBarReceiver* createTaskBarReceiver(const CBufferedLogOutputter* logBuffer); + #endif diff --git a/lib/synergy/INode.h b/lib/synergy/INode.h new file mode 100644 index 00000000..de4ecbd9 --- /dev/null +++ b/lib/synergy/INode.h @@ -0,0 +1,19 @@ +/* + * 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 "IInterface.h" + +class INode : IInterface { + +};