From 958fa80d1d7ec091a1dba77aa80f00b6226cd1dc Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Fri, 27 Feb 2009 11:54:59 +0000 Subject: [PATCH] Initial commit of the synergy trunk sources from sf.net --- AUTHORS | 1 + COPYING | 286 + ChangeLog | 11453 ++++++++++++++++ INSTALL | 1 + Makefile.am | 97 + Makefile.win | 145 + NEWS | 1 + README | 21 + acinclude.m4 | 571 + cmd/Makefile.am | 27 + cmd/launcher/CAddScreen.cpp | 427 + cmd/launcher/CAddScreen.h | 74 + cmd/launcher/CAdvancedOptions.cpp | 269 + cmd/launcher/CAdvancedOptions.h | 80 + cmd/launcher/CAutoStart.cpp | 361 + cmd/launcher/CAutoStart.h | 90 + cmd/launcher/CGlobalOptions.cpp | 281 + cmd/launcher/CGlobalOptions.h | 67 + cmd/launcher/CHotkeyOptions.cpp | 1938 +++ cmd/launcher/CHotkeyOptions.h | 227 + cmd/launcher/CInfo.cpp | 111 + cmd/launcher/CInfo.h | 57 + cmd/launcher/CScreensLinks.cpp | 855 ++ cmd/launcher/CScreensLinks.h | 138 + cmd/launcher/LaunchUtil.cpp | 261 + cmd/launcher/LaunchUtil.h | 61 + cmd/launcher/Makefile.am | 75 + cmd/launcher/Makefile.win | 101 + cmd/launcher/launcher.cpp | 755 + cmd/launcher/launcher.rc | 617 + cmd/launcher/resource.h | 186 + cmd/launcher/synergy.ico | Bin 0 -> 8478 bytes cmd/synergyc/CClientTaskBarReceiver.cpp | 136 + cmd/synergyc/CClientTaskBarReceiver.h | 83 + .../CMSWindowsClientTaskBarReceiver.cpp | 346 + .../CMSWindowsClientTaskBarReceiver.h | 64 + cmd/synergyc/COSXClientTaskBarReceiver.cpp | 56 + cmd/synergyc/COSXClientTaskBarReceiver.h | 35 + .../CXWindowsClientTaskBarReceiver.cpp | 56 + cmd/synergyc/CXWindowsClientTaskBarReceiver.h | 35 + cmd/synergyc/Makefile.am | 98 + cmd/synergyc/Makefile.win | 89 + cmd/synergyc/resource.h | 37 + cmd/synergyc/synergyc.cpp | 910 ++ cmd/synergyc/synergyc.ico | Bin 0 -> 8478 bytes cmd/synergyc/synergyc.rc | 141 + cmd/synergyc/tb_error.ico | Bin 0 -> 318 bytes cmd/synergyc/tb_idle.ico | Bin 0 -> 318 bytes cmd/synergyc/tb_run.ico | Bin 0 -> 318 bytes cmd/synergyc/tb_wait.ico | Bin 0 -> 318 bytes .../CMSWindowsServerTaskBarReceiver.cpp | 374 + .../CMSWindowsServerTaskBarReceiver.h | 64 + cmd/synergys/COSXServerTaskBarReceiver.cpp | 56 + cmd/synergys/COSXServerTaskBarReceiver.h | 35 + cmd/synergys/CServerTaskBarReceiver.cpp | 133 + cmd/synergys/CServerTaskBarReceiver.h | 88 + .../CXWindowsServerTaskBarReceiver.cpp | 56 + cmd/synergys/CXWindowsServerTaskBarReceiver.h | 35 + cmd/synergys/Makefile.am | 98 + cmd/synergys/Makefile.win | 89 + cmd/synergys/resource.h | 40 + cmd/synergys/synergys.cpp | 1312 ++ cmd/synergys/synergys.ico | Bin 0 -> 8478 bytes cmd/synergys/synergys.rc | 146 + cmd/synergys/tb_error.ico | Bin 0 -> 318 bytes cmd/synergys/tb_idle.ico | Bin 0 -> 318 bytes cmd/synergys/tb_run.ico | Bin 0 -> 318 bytes cmd/synergys/tb_wait.ico | Bin 0 -> 318 bytes config/config.guess | 1327 ++ config/config.sub | 1410 ++ config/depcomp | 411 + config/install-sh | 251 + config/missing | 283 + config/mkinstalldirs | 40 + configure.in | 289 + dist/Makefile.am | 26 + dist/nullsoft/Makefile.am | 24 + dist/nullsoft/Makefile.win | 63 + dist/nullsoft/dosify.c | 99 + dist/nullsoft/synergy.nsi | 179 + dist/rpm/Makefile.am | 22 + dist/rpm/synergy.spec.in | 66 + doc/Makefile.am | 49 + doc/PORTING | 419 + doc/about.html | 55 + doc/authors.html | 72 + doc/autostart.html | 428 + doc/banner.html | 16 + doc/border.html | 14 + doc/compiling.html | 112 + doc/configuration.html | 686 + doc/contact.html | 44 + doc/developer.html | 81 + doc/doxygen.cfg.in | 898 ++ doc/faq.html | 266 + doc/history.html | 30 + doc/home.html | 61 + doc/images/logo.gif | Bin 0 -> 1827 bytes doc/images/warp.gif | Bin 0 -> 68841 bytes doc/index.html | 33 + doc/license.html | 313 + doc/news.html | 599 + doc/roadmap.html | 92 + doc/running.html | 394 + doc/security.html | 55 + doc/synergy.css | 166 + doc/tips.html | 81 + doc/toc.html | 43 + doc/todo.html | 70 + doc/trouble.html | 204 + examples/synergy.conf | 37 + lib/Makefile.am | 34 + lib/arch/CArch.cpp | 639 + lib/arch/CArch.h | 218 + lib/arch/CArchConsoleUnix.cpp | 60 + lib/arch/CArchConsoleUnix.h | 36 + lib/arch/CArchConsoleWindows.cpp | 438 + lib/arch/CArchConsoleWindows.h | 77 + lib/arch/CArchDaemonNone.cpp | 66 + lib/arch/CArchDaemonNone.h | 47 + lib/arch/CArchDaemonUnix.cpp | 78 + lib/arch/CArchDaemonUnix.h | 33 + lib/arch/CArchDaemonWindows.cpp | 770 ++ lib/arch/CArchDaemonWindows.h | 134 + lib/arch/CArchFileUnix.cpp | 98 + lib/arch/CArchFileUnix.h | 36 + lib/arch/CArchFileWindows.cpp | 132 + lib/arch/CArchFileWindows.h | 36 + lib/arch/CArchLogUnix.cpp | 79 + lib/arch/CArchLogUnix.h | 35 + lib/arch/CArchLogWindows.cpp | 90 + lib/arch/CArchLogWindows.h | 41 + lib/arch/CArchMiscWindows.cpp | 416 + lib/arch/CArchMiscWindows.h | 191 + lib/arch/CArchMultithreadPosix.cpp | 806 ++ lib/arch/CArchMultithreadPosix.h | 113 + lib/arch/CArchMultithreadWindows.cpp | 699 + lib/arch/CArchMultithreadWindows.h | 115 + lib/arch/CArchNetworkBSD.cpp | 974 ++ lib/arch/CArchNetworkBSD.h | 101 + lib/arch/CArchNetworkWinsock.cpp | 934 ++ lib/arch/CArchNetworkWinsock.h | 99 + lib/arch/CArchSleepUnix.cpp | 88 + lib/arch/CArchSleepUnix.h | 32 + lib/arch/CArchSleepWindows.cpp | 57 + lib/arch/CArchSleepWindows.h | 32 + lib/arch/CArchStringUnix.cpp | 29 + lib/arch/CArchStringUnix.h | 39 + lib/arch/CArchStringWindows.cpp | 34 + lib/arch/CArchStringWindows.h | 39 + lib/arch/CArchSystemUnix.cpp | 50 + lib/arch/CArchSystemUnix.h | 32 + lib/arch/CArchSystemWindows.cpp | 86 + lib/arch/CArchSystemWindows.h | 32 + lib/arch/CArchTaskBarWindows.cpp | 492 + lib/arch/CArchTaskBarWindows.h | 110 + lib/arch/CArchTaskBarXWindows.cpp | 47 + lib/arch/CArchTaskBarXWindows.h | 34 + lib/arch/CArchTimeUnix.cpp | 47 + lib/arch/CArchTimeUnix.h | 32 + lib/arch/CArchTimeWindows.cpp | 86 + lib/arch/CArchTimeWindows.h | 32 + lib/arch/CMultibyte.cpp | 219 + lib/arch/IArchConsole.h | 71 + lib/arch/IArchDaemon.h | 107 + lib/arch/IArchFile.h | 64 + lib/arch/IArchLog.h | 73 + lib/arch/IArchMultithread.h | 272 + lib/arch/IArchNetwork.h | 279 + lib/arch/IArchSleep.h | 43 + lib/arch/IArchString.h | 68 + lib/arch/IArchSystem.h | 39 + lib/arch/IArchTaskBar.h | 60 + lib/arch/IArchTaskBarReceiver.h | 90 + lib/arch/IArchTime.h | 40 + lib/arch/Makefile.am | 118 + lib/arch/Makefile.win | 84 + lib/arch/XArch.cpp | 33 + lib/arch/XArch.h | 170 + lib/arch/XArchUnix.cpp | 33 + lib/arch/XArchUnix.h | 34 + lib/arch/XArchWindows.cpp | 127 + lib/arch/XArchWindows.h | 52 + lib/arch/vsnprintf.cpp | 61 + lib/base/CEvent.cpp | 98 + lib/base/CEvent.h | 123 + lib/base/CEventQueue.cpp | 526 + lib/base/CEventQueue.h | 123 + lib/base/CFunctionEventJob.cpp | 40 + lib/base/CFunctionEventJob.h | 38 + lib/base/CFunctionJob.cpp | 39 + lib/base/CFunctionJob.h | 38 + lib/base/CLog.cpp | 293 + lib/base/CLog.h | 202 + lib/base/CPriorityQueue.h | 136 + lib/base/CSimpleEventQueueBuffer.cpp | 97 + lib/base/CSimpleEventQueueBuffer.h | 49 + lib/base/CStopwatch.cpp | 126 + lib/base/CStopwatch.h | 108 + lib/base/CString.h | 25 + lib/base/CStringUtil.cpp | 194 + lib/base/CStringUtil.h | 77 + lib/base/CUnicode.cpp | 779 ++ lib/base/CUnicode.h | 143 + lib/base/IEventJob.h | 32 + lib/base/IEventQueue.cpp | 43 + lib/base/IEventQueue.h | 213 + lib/base/IEventQueueBuffer.h | 94 + lib/base/IJob.h | 30 + lib/base/ILogOutputter.h | 81 + lib/base/LogOutputters.cpp | 267 + lib/base/LogOutputters.h | 132 + lib/base/Makefile.am | 62 + lib/base/Makefile.win | 77 + lib/base/TMethodEventJob.h | 70 + lib/base/TMethodJob.h | 67 + lib/base/XBase.cpp | 69 + lib/base/XBase.h | 121 + lib/client/CClient.cpp | 662 + lib/client/CClient.h | 198 + lib/client/CServerProxy.cpp | 816 ++ lib/client/CServerProxy.h | 115 + lib/client/Makefile.am | 40 + lib/client/Makefile.win | 63 + lib/common/BasicTypes.h | 86 + lib/common/IInterface.h | 31 + lib/common/MacOSXPrecomp.h | 8 + lib/common/Makefile.am | 48 + lib/common/Makefile.win | 53 + lib/common/Version.cpp | 22 + lib/common/Version.h | 45 + lib/common/common.h | 135 + lib/common/stdbitset.h | 17 + lib/common/stddeque.h | 17 + lib/common/stdfstream.h | 18 + lib/common/stdistream.h | 43 + lib/common/stdlist.h | 17 + lib/common/stdmap.h | 17 + lib/common/stdostream.h | 21 + lib/common/stdpost.h | 17 + lib/common/stdpre.h | 27 + lib/common/stdset.h | 17 + lib/common/stdsstream.h | 371 + lib/common/stdstring.h | 17 + lib/common/stdvector.h | 17 + lib/io/CStreamBuffer.cpp | 142 + lib/io/CStreamBuffer.h | 78 + lib/io/CStreamFilter.cpp | 113 + lib/io/CStreamFilter.h | 70 + lib/io/IStream.cpp | 60 + lib/io/IStream.h | 157 + lib/io/IStreamFilterFactory.h | 36 + lib/io/Makefile.am | 41 + lib/io/Makefile.win | 63 + lib/io/XIO.cpp | 47 + lib/io/XIO.h | 48 + lib/mt/CCondVar.cpp | 81 + lib/mt/CCondVar.h | 224 + lib/mt/CLock.cpp | 38 + lib/mt/CLock.h | 48 + lib/mt/CMutex.cpp | 53 + lib/mt/CMutex.h | 78 + lib/mt/CThread.cpp | 183 + lib/mt/CThread.h | 209 + lib/mt/Makefile.am | 42 + lib/mt/Makefile.win | 64 + lib/mt/XMT.cpp | 25 + lib/mt/XMT.h | 29 + lib/mt/XThread.h | 36 + lib/net/CNetworkAddress.cpp | 208 + lib/net/CNetworkAddress.h | 122 + lib/net/CSocketMultiplexer.cpp | 359 + lib/net/CSocketMultiplexer.h | 111 + lib/net/CTCPListenSocket.cpp | 134 + lib/net/CTCPListenSocket.h | 51 + lib/net/CTCPSocket.cpp | 542 + lib/net/CTCPSocket.h | 86 + lib/net/CTCPSocketFactory.cpp | 43 + lib/net/CTCPSocketFactory.h | 31 + lib/net/IDataSocket.cpp | 51 + lib/net/IDataSocket.h | 90 + lib/net/IListenSocket.cpp | 28 + lib/net/IListenSocket.h | 62 + lib/net/ISocket.cpp | 28 + lib/net/ISocket.h | 69 + lib/net/ISocketFactory.h | 42 + lib/net/ISocketMultiplexerJob.h | 75 + lib/net/Makefile.am | 54 + lib/net/Makefile.win | 74 + lib/net/TSocketMultiplexerMethodJob.h | 108 + lib/net/XSocket.cpp | 113 + lib/net/XSocket.h | 96 + lib/platform/CMSWindowsClipboard.cpp | 209 + lib/platform/CMSWindowsClipboard.h | 104 + .../CMSWindowsClipboardAnyTextConverter.cpp | 145 + .../CMSWindowsClipboardAnyTextConverter.h | 56 + .../CMSWindowsClipboardBitmapConverter.cpp | 146 + .../CMSWindowsClipboardBitmapConverter.h | 35 + .../CMSWindowsClipboardHTMLConverter.cpp | 107 + .../CMSWindowsClipboardHTMLConverter.h | 44 + .../CMSWindowsClipboardTextConverter.cpp | 55 + .../CMSWindowsClipboardTextConverter.h | 36 + .../CMSWindowsClipboardUTF16Converter.cpp | 55 + .../CMSWindowsClipboardUTF16Converter.h | 36 + lib/platform/CMSWindowsDesks.cpp | 1033 ++ lib/platform/CMSWindowsDesks.h | 296 + lib/platform/CMSWindowsEventQueueBuffer.cpp | 138 + lib/platform/CMSWindowsEventQueueBuffer.h | 44 + lib/platform/CMSWindowsKeyState.cpp | 1420 ++ lib/platform/CMSWindowsKeyState.h | 216 + lib/platform/CMSWindowsScreen.cpp | 1738 +++ lib/platform/CMSWindowsScreen.h | 308 + lib/platform/CMSWindowsScreenSaver.cpp | 498 + lib/platform/CMSWindowsScreenSaver.h | 92 + lib/platform/CMSWindowsUtil.cpp | 75 + lib/platform/CMSWindowsUtil.h | 38 + lib/platform/COSXClipboard.cpp | 220 + lib/platform/COSXClipboard.h | 94 + .../COSXClipboardAnyTextConverter.cpp | 85 + lib/platform/COSXClipboardAnyTextConverter.h | 52 + lib/platform/COSXClipboardTextConverter.cpp | 88 + lib/platform/COSXClipboardTextConverter.h | 41 + lib/platform/COSXClipboardUTF16Converter.cpp | 50 + lib/platform/COSXClipboardUTF16Converter.h | 36 + lib/platform/COSXEventQueueBuffer.cpp | 124 + lib/platform/COSXEventQueueBuffer.h | 40 + lib/platform/COSXKeyState.cpp | 1237 ++ lib/platform/COSXKeyState.h | 235 + lib/platform/COSXScreen.cpp | 1699 +++ lib/platform/COSXScreen.h | 252 + lib/platform/COSXScreenSaver.cpp | 175 + lib/platform/COSXScreenSaver.h | 54 + lib/platform/COSXScreenSaverUtil.h | 39 + lib/platform/COSXScreenSaverUtil.m | 81 + lib/platform/CSynergyHook.cpp | 1110 ++ lib/platform/CSynergyHook.h | 81 + lib/platform/CXWindowsClipboard.cpp | 1507 ++ lib/platform/CXWindowsClipboard.h | 376 + .../CXWindowsClipboardAnyBitmapConverter.cpp | 187 + .../CXWindowsClipboardAnyBitmapConverter.h | 59 + .../CXWindowsClipboardBMPConverter.cpp | 139 + lib/platform/CXWindowsClipboardBMPConverter.h | 39 + .../CXWindowsClipboardHTMLConverter.cpp | 62 + .../CXWindowsClipboardHTMLConverter.h | 41 + .../CXWindowsClipboardTextConverter.cpp | 74 + .../CXWindowsClipboardTextConverter.h | 41 + .../CXWindowsClipboardUCS2Converter.cpp | 62 + .../CXWindowsClipboardUCS2Converter.h | 41 + .../CXWindowsClipboardUTF8Converter.cpp | 61 + .../CXWindowsClipboardUTF8Converter.h | 41 + lib/platform/CXWindowsEventQueueBuffer.cpp | 208 + lib/platform/CXWindowsEventQueueBuffer.h | 57 + lib/platform/CXWindowsKeyState.cpp | 826 ++ lib/platform/CXWindowsKeyState.h | 155 + lib/platform/CXWindowsScreen.cpp | 1901 +++ lib/platform/CXWindowsScreen.h | 229 + lib/platform/CXWindowsScreenSaver.cpp | 598 + lib/platform/CXWindowsScreenSaver.h | 164 + lib/platform/CXWindowsUtil.cpp | 1766 +++ lib/platform/CXWindowsUtil.h | 185 + lib/platform/Makefile.am | 123 + lib/platform/Makefile.win | 119 + lib/platform/OSXScreenSaverControl.h | 36 + lib/server/CBaseClientProxy.cpp | 52 + lib/server/CBaseClientProxy.h | 85 + lib/server/CClientListener.cpp | 194 + lib/server/CClientListener.h | 76 + lib/server/CClientProxy.cpp | 81 + lib/server/CClientProxy.h | 113 + lib/server/CClientProxy1_0.cpp | 498 + lib/server/CClientProxy1_0.h | 100 + lib/server/CClientProxy1_1.cpp | 55 + lib/server/CClientProxy1_1.h | 33 + lib/server/CClientProxy1_2.cpp | 39 + lib/server/CClientProxy1_2.h | 30 + lib/server/CClientProxy1_3.cpp | 114 + lib/server/CClientProxy1_3.h | 47 + lib/server/CClientProxyUnknown.cpp | 287 + lib/server/CClientProxyUnknown.h | 83 + lib/server/CConfig.cpp | 2277 +++ lib/server/CConfig.h | 536 + lib/server/CInputFilter.cpp | 1066 ++ lib/server/CInputFilter.h | 344 + lib/server/CPrimaryClient.cpp | 257 + lib/server/CPrimaryClient.h | 145 + lib/server/CServer.cpp | 2176 +++ lib/server/CServer.h | 464 + lib/server/Makefile.am | 60 + lib/server/Makefile.win | 83 + lib/synergy/CClipboard.cpp | 114 + lib/synergy/CClipboard.h | 70 + lib/synergy/CKeyMap.cpp | 1330 ++ lib/synergy/CKeyMap.h | 491 + lib/synergy/CKeyState.cpp | 886 ++ lib/synergy/CKeyState.h | 216 + lib/synergy/CPacketStreamFilter.cpp | 190 + lib/synergy/CPacketStreamFilter.h | 55 + lib/synergy/CPlatformScreen.cpp | 106 + lib/synergy/CPlatformScreen.h | 109 + lib/synergy/CProtocolUtil.cpp | 538 + lib/synergy/CProtocolUtil.h | 95 + lib/synergy/CScreen.cpp | 488 + lib/synergy/CScreen.h | 304 + lib/synergy/ClipboardTypes.h | 41 + lib/synergy/IClient.h | 175 + lib/synergy/IClipboard.cpp | 155 + lib/synergy/IClipboard.h | 168 + lib/synergy/IKeyState.cpp | 176 + lib/synergy/IKeyState.h | 174 + lib/synergy/IPlatformScreen.h | 210 + lib/synergy/IPrimaryScreen.cpp | 178 + lib/synergy/IPrimaryScreen.h | 206 + lib/synergy/IScreen.cpp | 60 + lib/synergy/IScreen.h | 112 + lib/synergy/IScreenSaver.h | 74 + lib/synergy/ISecondaryScreen.h | 58 + lib/synergy/KeyTypes.cpp | 204 + lib/synergy/KeyTypes.h | 306 + lib/synergy/Makefile.am | 71 + lib/synergy/Makefile.win | 87 + lib/synergy/MouseTypes.h | 35 + lib/synergy/OptionTypes.h | 92 + lib/synergy/ProtocolTypes.cpp | 47 + lib/synergy/ProtocolTypes.h | 308 + lib/synergy/XScreen.cpp | 53 + lib/synergy/XScreen.h | 61 + lib/synergy/XSynergy.cpp | 104 + lib/synergy/XSynergy.h | 105 + win32util/autodep.cpp | 149 + 429 files changed, 96848 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 Makefile.win create mode 100644 NEWS create mode 100644 README create mode 100644 acinclude.m4 create mode 100644 cmd/Makefile.am create mode 100644 cmd/launcher/CAddScreen.cpp create mode 100644 cmd/launcher/CAddScreen.h create mode 100644 cmd/launcher/CAdvancedOptions.cpp create mode 100644 cmd/launcher/CAdvancedOptions.h create mode 100644 cmd/launcher/CAutoStart.cpp create mode 100644 cmd/launcher/CAutoStart.h create mode 100644 cmd/launcher/CGlobalOptions.cpp create mode 100644 cmd/launcher/CGlobalOptions.h create mode 100644 cmd/launcher/CHotkeyOptions.cpp create mode 100644 cmd/launcher/CHotkeyOptions.h create mode 100644 cmd/launcher/CInfo.cpp create mode 100644 cmd/launcher/CInfo.h create mode 100644 cmd/launcher/CScreensLinks.cpp create mode 100644 cmd/launcher/CScreensLinks.h create mode 100644 cmd/launcher/LaunchUtil.cpp create mode 100644 cmd/launcher/LaunchUtil.h create mode 100644 cmd/launcher/Makefile.am create mode 100644 cmd/launcher/Makefile.win create mode 100644 cmd/launcher/launcher.cpp create mode 100644 cmd/launcher/launcher.rc create mode 100644 cmd/launcher/resource.h create mode 100644 cmd/launcher/synergy.ico create mode 100644 cmd/synergyc/CClientTaskBarReceiver.cpp create mode 100644 cmd/synergyc/CClientTaskBarReceiver.h create mode 100644 cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp create mode 100644 cmd/synergyc/CMSWindowsClientTaskBarReceiver.h create mode 100644 cmd/synergyc/COSXClientTaskBarReceiver.cpp create mode 100644 cmd/synergyc/COSXClientTaskBarReceiver.h create mode 100644 cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp create mode 100644 cmd/synergyc/CXWindowsClientTaskBarReceiver.h create mode 100644 cmd/synergyc/Makefile.am create mode 100644 cmd/synergyc/Makefile.win create mode 100644 cmd/synergyc/resource.h create mode 100644 cmd/synergyc/synergyc.cpp create mode 100644 cmd/synergyc/synergyc.ico create mode 100644 cmd/synergyc/synergyc.rc create mode 100644 cmd/synergyc/tb_error.ico create mode 100644 cmd/synergyc/tb_idle.ico create mode 100644 cmd/synergyc/tb_run.ico create mode 100644 cmd/synergyc/tb_wait.ico create mode 100644 cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp create mode 100644 cmd/synergys/CMSWindowsServerTaskBarReceiver.h create mode 100644 cmd/synergys/COSXServerTaskBarReceiver.cpp create mode 100644 cmd/synergys/COSXServerTaskBarReceiver.h create mode 100644 cmd/synergys/CServerTaskBarReceiver.cpp create mode 100644 cmd/synergys/CServerTaskBarReceiver.h create mode 100644 cmd/synergys/CXWindowsServerTaskBarReceiver.cpp create mode 100644 cmd/synergys/CXWindowsServerTaskBarReceiver.h create mode 100644 cmd/synergys/Makefile.am create mode 100644 cmd/synergys/Makefile.win create mode 100644 cmd/synergys/resource.h create mode 100644 cmd/synergys/synergys.cpp create mode 100644 cmd/synergys/synergys.ico create mode 100644 cmd/synergys/synergys.rc create mode 100644 cmd/synergys/tb_error.ico create mode 100644 cmd/synergys/tb_idle.ico create mode 100644 cmd/synergys/tb_run.ico create mode 100644 cmd/synergys/tb_wait.ico create mode 100644 config/config.guess create mode 100644 config/config.sub create mode 100644 config/depcomp create mode 100644 config/install-sh create mode 100644 config/missing create mode 100644 config/mkinstalldirs create mode 100644 configure.in create mode 100644 dist/Makefile.am create mode 100644 dist/nullsoft/Makefile.am create mode 100644 dist/nullsoft/Makefile.win create mode 100644 dist/nullsoft/dosify.c create mode 100644 dist/nullsoft/synergy.nsi create mode 100644 dist/rpm/Makefile.am create mode 100644 dist/rpm/synergy.spec.in create mode 100644 doc/Makefile.am create mode 100644 doc/PORTING create mode 100644 doc/about.html create mode 100644 doc/authors.html create mode 100644 doc/autostart.html create mode 100644 doc/banner.html create mode 100644 doc/border.html create mode 100644 doc/compiling.html create mode 100644 doc/configuration.html create mode 100644 doc/contact.html create mode 100644 doc/developer.html create mode 100644 doc/doxygen.cfg.in create mode 100644 doc/faq.html create mode 100644 doc/history.html create mode 100644 doc/home.html create mode 100644 doc/images/logo.gif create mode 100644 doc/images/warp.gif create mode 100644 doc/index.html create mode 100644 doc/license.html create mode 100644 doc/news.html create mode 100644 doc/roadmap.html create mode 100644 doc/running.html create mode 100644 doc/security.html create mode 100644 doc/synergy.css create mode 100644 doc/tips.html create mode 100644 doc/toc.html create mode 100644 doc/todo.html create mode 100644 doc/trouble.html create mode 100644 examples/synergy.conf create mode 100644 lib/Makefile.am create mode 100644 lib/arch/CArch.cpp create mode 100644 lib/arch/CArch.h create mode 100644 lib/arch/CArchConsoleUnix.cpp create mode 100644 lib/arch/CArchConsoleUnix.h create mode 100644 lib/arch/CArchConsoleWindows.cpp create mode 100644 lib/arch/CArchConsoleWindows.h create mode 100644 lib/arch/CArchDaemonNone.cpp create mode 100644 lib/arch/CArchDaemonNone.h create mode 100644 lib/arch/CArchDaemonUnix.cpp create mode 100644 lib/arch/CArchDaemonUnix.h create mode 100644 lib/arch/CArchDaemonWindows.cpp create mode 100644 lib/arch/CArchDaemonWindows.h create mode 100644 lib/arch/CArchFileUnix.cpp create mode 100644 lib/arch/CArchFileUnix.h create mode 100644 lib/arch/CArchFileWindows.cpp create mode 100644 lib/arch/CArchFileWindows.h create mode 100644 lib/arch/CArchLogUnix.cpp create mode 100644 lib/arch/CArchLogUnix.h create mode 100644 lib/arch/CArchLogWindows.cpp create mode 100644 lib/arch/CArchLogWindows.h create mode 100644 lib/arch/CArchMiscWindows.cpp create mode 100644 lib/arch/CArchMiscWindows.h create mode 100644 lib/arch/CArchMultithreadPosix.cpp create mode 100644 lib/arch/CArchMultithreadPosix.h create mode 100644 lib/arch/CArchMultithreadWindows.cpp create mode 100644 lib/arch/CArchMultithreadWindows.h create mode 100644 lib/arch/CArchNetworkBSD.cpp create mode 100644 lib/arch/CArchNetworkBSD.h create mode 100644 lib/arch/CArchNetworkWinsock.cpp create mode 100644 lib/arch/CArchNetworkWinsock.h create mode 100644 lib/arch/CArchSleepUnix.cpp create mode 100644 lib/arch/CArchSleepUnix.h create mode 100644 lib/arch/CArchSleepWindows.cpp create mode 100644 lib/arch/CArchSleepWindows.h create mode 100644 lib/arch/CArchStringUnix.cpp create mode 100644 lib/arch/CArchStringUnix.h create mode 100644 lib/arch/CArchStringWindows.cpp create mode 100644 lib/arch/CArchStringWindows.h create mode 100644 lib/arch/CArchSystemUnix.cpp create mode 100644 lib/arch/CArchSystemUnix.h create mode 100644 lib/arch/CArchSystemWindows.cpp create mode 100644 lib/arch/CArchSystemWindows.h create mode 100644 lib/arch/CArchTaskBarWindows.cpp create mode 100644 lib/arch/CArchTaskBarWindows.h create mode 100644 lib/arch/CArchTaskBarXWindows.cpp create mode 100644 lib/arch/CArchTaskBarXWindows.h create mode 100644 lib/arch/CArchTimeUnix.cpp create mode 100644 lib/arch/CArchTimeUnix.h create mode 100644 lib/arch/CArchTimeWindows.cpp create mode 100644 lib/arch/CArchTimeWindows.h create mode 100644 lib/arch/CMultibyte.cpp create mode 100644 lib/arch/IArchConsole.h create mode 100644 lib/arch/IArchDaemon.h create mode 100644 lib/arch/IArchFile.h create mode 100644 lib/arch/IArchLog.h create mode 100644 lib/arch/IArchMultithread.h create mode 100644 lib/arch/IArchNetwork.h create mode 100644 lib/arch/IArchSleep.h create mode 100644 lib/arch/IArchString.h create mode 100644 lib/arch/IArchSystem.h create mode 100644 lib/arch/IArchTaskBar.h create mode 100644 lib/arch/IArchTaskBarReceiver.h create mode 100644 lib/arch/IArchTime.h create mode 100644 lib/arch/Makefile.am create mode 100644 lib/arch/Makefile.win create mode 100644 lib/arch/XArch.cpp create mode 100644 lib/arch/XArch.h create mode 100644 lib/arch/XArchUnix.cpp create mode 100644 lib/arch/XArchUnix.h create mode 100644 lib/arch/XArchWindows.cpp create mode 100644 lib/arch/XArchWindows.h create mode 100644 lib/arch/vsnprintf.cpp create mode 100644 lib/base/CEvent.cpp create mode 100644 lib/base/CEvent.h create mode 100644 lib/base/CEventQueue.cpp create mode 100644 lib/base/CEventQueue.h create mode 100644 lib/base/CFunctionEventJob.cpp create mode 100644 lib/base/CFunctionEventJob.h create mode 100644 lib/base/CFunctionJob.cpp create mode 100644 lib/base/CFunctionJob.h create mode 100644 lib/base/CLog.cpp create mode 100644 lib/base/CLog.h create mode 100644 lib/base/CPriorityQueue.h create mode 100644 lib/base/CSimpleEventQueueBuffer.cpp create mode 100644 lib/base/CSimpleEventQueueBuffer.h create mode 100644 lib/base/CStopwatch.cpp create mode 100644 lib/base/CStopwatch.h create mode 100644 lib/base/CString.h create mode 100644 lib/base/CStringUtil.cpp create mode 100644 lib/base/CStringUtil.h create mode 100644 lib/base/CUnicode.cpp create mode 100644 lib/base/CUnicode.h create mode 100644 lib/base/IEventJob.h create mode 100644 lib/base/IEventQueue.cpp create mode 100644 lib/base/IEventQueue.h create mode 100644 lib/base/IEventQueueBuffer.h create mode 100644 lib/base/IJob.h create mode 100644 lib/base/ILogOutputter.h create mode 100644 lib/base/LogOutputters.cpp create mode 100644 lib/base/LogOutputters.h create mode 100644 lib/base/Makefile.am create mode 100644 lib/base/Makefile.win create mode 100644 lib/base/TMethodEventJob.h create mode 100644 lib/base/TMethodJob.h create mode 100644 lib/base/XBase.cpp create mode 100644 lib/base/XBase.h create mode 100644 lib/client/CClient.cpp create mode 100644 lib/client/CClient.h create mode 100644 lib/client/CServerProxy.cpp create mode 100644 lib/client/CServerProxy.h create mode 100644 lib/client/Makefile.am create mode 100644 lib/client/Makefile.win create mode 100644 lib/common/BasicTypes.h create mode 100644 lib/common/IInterface.h create mode 100644 lib/common/MacOSXPrecomp.h create mode 100644 lib/common/Makefile.am create mode 100644 lib/common/Makefile.win create mode 100644 lib/common/Version.cpp create mode 100644 lib/common/Version.h create mode 100644 lib/common/common.h create mode 100644 lib/common/stdbitset.h create mode 100644 lib/common/stddeque.h create mode 100644 lib/common/stdfstream.h create mode 100644 lib/common/stdistream.h create mode 100644 lib/common/stdlist.h create mode 100644 lib/common/stdmap.h create mode 100644 lib/common/stdostream.h create mode 100644 lib/common/stdpost.h create mode 100644 lib/common/stdpre.h create mode 100644 lib/common/stdset.h create mode 100644 lib/common/stdsstream.h create mode 100644 lib/common/stdstring.h create mode 100644 lib/common/stdvector.h create mode 100644 lib/io/CStreamBuffer.cpp create mode 100644 lib/io/CStreamBuffer.h create mode 100644 lib/io/CStreamFilter.cpp create mode 100644 lib/io/CStreamFilter.h create mode 100644 lib/io/IStream.cpp create mode 100644 lib/io/IStream.h create mode 100644 lib/io/IStreamFilterFactory.h create mode 100644 lib/io/Makefile.am create mode 100644 lib/io/Makefile.win create mode 100644 lib/io/XIO.cpp create mode 100644 lib/io/XIO.h create mode 100644 lib/mt/CCondVar.cpp create mode 100644 lib/mt/CCondVar.h create mode 100644 lib/mt/CLock.cpp create mode 100644 lib/mt/CLock.h create mode 100644 lib/mt/CMutex.cpp create mode 100644 lib/mt/CMutex.h create mode 100644 lib/mt/CThread.cpp create mode 100644 lib/mt/CThread.h create mode 100644 lib/mt/Makefile.am create mode 100644 lib/mt/Makefile.win create mode 100644 lib/mt/XMT.cpp create mode 100644 lib/mt/XMT.h create mode 100644 lib/mt/XThread.h create mode 100644 lib/net/CNetworkAddress.cpp create mode 100644 lib/net/CNetworkAddress.h create mode 100644 lib/net/CSocketMultiplexer.cpp create mode 100644 lib/net/CSocketMultiplexer.h create mode 100644 lib/net/CTCPListenSocket.cpp create mode 100644 lib/net/CTCPListenSocket.h create mode 100644 lib/net/CTCPSocket.cpp create mode 100644 lib/net/CTCPSocket.h create mode 100644 lib/net/CTCPSocketFactory.cpp create mode 100644 lib/net/CTCPSocketFactory.h create mode 100644 lib/net/IDataSocket.cpp create mode 100644 lib/net/IDataSocket.h create mode 100644 lib/net/IListenSocket.cpp create mode 100644 lib/net/IListenSocket.h create mode 100644 lib/net/ISocket.cpp create mode 100644 lib/net/ISocket.h create mode 100644 lib/net/ISocketFactory.h create mode 100644 lib/net/ISocketMultiplexerJob.h create mode 100644 lib/net/Makefile.am create mode 100644 lib/net/Makefile.win create mode 100644 lib/net/TSocketMultiplexerMethodJob.h create mode 100644 lib/net/XSocket.cpp create mode 100644 lib/net/XSocket.h create mode 100644 lib/platform/CMSWindowsClipboard.cpp create mode 100644 lib/platform/CMSWindowsClipboard.h create mode 100644 lib/platform/CMSWindowsClipboardAnyTextConverter.cpp create mode 100644 lib/platform/CMSWindowsClipboardAnyTextConverter.h create mode 100644 lib/platform/CMSWindowsClipboardBitmapConverter.cpp create mode 100644 lib/platform/CMSWindowsClipboardBitmapConverter.h create mode 100644 lib/platform/CMSWindowsClipboardHTMLConverter.cpp create mode 100644 lib/platform/CMSWindowsClipboardHTMLConverter.h create mode 100644 lib/platform/CMSWindowsClipboardTextConverter.cpp create mode 100644 lib/platform/CMSWindowsClipboardTextConverter.h create mode 100644 lib/platform/CMSWindowsClipboardUTF16Converter.cpp create mode 100644 lib/platform/CMSWindowsClipboardUTF16Converter.h create mode 100644 lib/platform/CMSWindowsDesks.cpp create mode 100644 lib/platform/CMSWindowsDesks.h create mode 100644 lib/platform/CMSWindowsEventQueueBuffer.cpp create mode 100644 lib/platform/CMSWindowsEventQueueBuffer.h create mode 100644 lib/platform/CMSWindowsKeyState.cpp create mode 100644 lib/platform/CMSWindowsKeyState.h create mode 100644 lib/platform/CMSWindowsScreen.cpp create mode 100644 lib/platform/CMSWindowsScreen.h create mode 100644 lib/platform/CMSWindowsScreenSaver.cpp create mode 100644 lib/platform/CMSWindowsScreenSaver.h create mode 100644 lib/platform/CMSWindowsUtil.cpp create mode 100644 lib/platform/CMSWindowsUtil.h create mode 100644 lib/platform/COSXClipboard.cpp create mode 100644 lib/platform/COSXClipboard.h create mode 100644 lib/platform/COSXClipboardAnyTextConverter.cpp create mode 100644 lib/platform/COSXClipboardAnyTextConverter.h create mode 100644 lib/platform/COSXClipboardTextConverter.cpp create mode 100644 lib/platform/COSXClipboardTextConverter.h create mode 100644 lib/platform/COSXClipboardUTF16Converter.cpp create mode 100644 lib/platform/COSXClipboardUTF16Converter.h create mode 100644 lib/platform/COSXEventQueueBuffer.cpp create mode 100644 lib/platform/COSXEventQueueBuffer.h create mode 100644 lib/platform/COSXKeyState.cpp create mode 100644 lib/platform/COSXKeyState.h create mode 100644 lib/platform/COSXScreen.cpp create mode 100644 lib/platform/COSXScreen.h create mode 100644 lib/platform/COSXScreenSaver.cpp create mode 100644 lib/platform/COSXScreenSaver.h create mode 100644 lib/platform/COSXScreenSaverUtil.h create mode 100644 lib/platform/COSXScreenSaverUtil.m create mode 100644 lib/platform/CSynergyHook.cpp create mode 100644 lib/platform/CSynergyHook.h create mode 100644 lib/platform/CXWindowsClipboard.cpp create mode 100644 lib/platform/CXWindowsClipboard.h create mode 100644 lib/platform/CXWindowsClipboardAnyBitmapConverter.cpp create mode 100644 lib/platform/CXWindowsClipboardAnyBitmapConverter.h create mode 100644 lib/platform/CXWindowsClipboardBMPConverter.cpp create mode 100644 lib/platform/CXWindowsClipboardBMPConverter.h create mode 100644 lib/platform/CXWindowsClipboardHTMLConverter.cpp create mode 100644 lib/platform/CXWindowsClipboardHTMLConverter.h create mode 100644 lib/platform/CXWindowsClipboardTextConverter.cpp create mode 100644 lib/platform/CXWindowsClipboardTextConverter.h create mode 100644 lib/platform/CXWindowsClipboardUCS2Converter.cpp create mode 100644 lib/platform/CXWindowsClipboardUCS2Converter.h create mode 100644 lib/platform/CXWindowsClipboardUTF8Converter.cpp create mode 100644 lib/platform/CXWindowsClipboardUTF8Converter.h create mode 100644 lib/platform/CXWindowsEventQueueBuffer.cpp create mode 100644 lib/platform/CXWindowsEventQueueBuffer.h create mode 100644 lib/platform/CXWindowsKeyState.cpp create mode 100644 lib/platform/CXWindowsKeyState.h create mode 100644 lib/platform/CXWindowsScreen.cpp create mode 100644 lib/platform/CXWindowsScreen.h create mode 100644 lib/platform/CXWindowsScreenSaver.cpp create mode 100644 lib/platform/CXWindowsScreenSaver.h create mode 100644 lib/platform/CXWindowsUtil.cpp create mode 100644 lib/platform/CXWindowsUtil.h create mode 100644 lib/platform/Makefile.am create mode 100644 lib/platform/Makefile.win create mode 100644 lib/platform/OSXScreenSaverControl.h create mode 100644 lib/server/CBaseClientProxy.cpp create mode 100644 lib/server/CBaseClientProxy.h create mode 100644 lib/server/CClientListener.cpp create mode 100644 lib/server/CClientListener.h create mode 100644 lib/server/CClientProxy.cpp create mode 100644 lib/server/CClientProxy.h create mode 100644 lib/server/CClientProxy1_0.cpp create mode 100644 lib/server/CClientProxy1_0.h create mode 100644 lib/server/CClientProxy1_1.cpp create mode 100644 lib/server/CClientProxy1_1.h create mode 100644 lib/server/CClientProxy1_2.cpp create mode 100644 lib/server/CClientProxy1_2.h create mode 100644 lib/server/CClientProxy1_3.cpp create mode 100644 lib/server/CClientProxy1_3.h create mode 100644 lib/server/CClientProxyUnknown.cpp create mode 100644 lib/server/CClientProxyUnknown.h create mode 100644 lib/server/CConfig.cpp create mode 100644 lib/server/CConfig.h create mode 100644 lib/server/CInputFilter.cpp create mode 100644 lib/server/CInputFilter.h create mode 100644 lib/server/CPrimaryClient.cpp create mode 100644 lib/server/CPrimaryClient.h create mode 100644 lib/server/CServer.cpp create mode 100644 lib/server/CServer.h create mode 100644 lib/server/Makefile.am create mode 100644 lib/server/Makefile.win create mode 100644 lib/synergy/CClipboard.cpp create mode 100644 lib/synergy/CClipboard.h create mode 100644 lib/synergy/CKeyMap.cpp create mode 100644 lib/synergy/CKeyMap.h create mode 100644 lib/synergy/CKeyState.cpp create mode 100644 lib/synergy/CKeyState.h create mode 100644 lib/synergy/CPacketStreamFilter.cpp create mode 100644 lib/synergy/CPacketStreamFilter.h create mode 100644 lib/synergy/CPlatformScreen.cpp create mode 100644 lib/synergy/CPlatformScreen.h create mode 100644 lib/synergy/CProtocolUtil.cpp create mode 100644 lib/synergy/CProtocolUtil.h create mode 100644 lib/synergy/CScreen.cpp create mode 100644 lib/synergy/CScreen.h create mode 100644 lib/synergy/ClipboardTypes.h create mode 100644 lib/synergy/IClient.h create mode 100644 lib/synergy/IClipboard.cpp create mode 100644 lib/synergy/IClipboard.h create mode 100644 lib/synergy/IKeyState.cpp create mode 100644 lib/synergy/IKeyState.h create mode 100644 lib/synergy/IPlatformScreen.h create mode 100644 lib/synergy/IPrimaryScreen.cpp create mode 100644 lib/synergy/IPrimaryScreen.h create mode 100644 lib/synergy/IScreen.cpp create mode 100644 lib/synergy/IScreen.h create mode 100644 lib/synergy/IScreenSaver.h create mode 100644 lib/synergy/ISecondaryScreen.h create mode 100644 lib/synergy/KeyTypes.cpp create mode 100644 lib/synergy/KeyTypes.h create mode 100644 lib/synergy/Makefile.am create mode 100644 lib/synergy/Makefile.win create mode 100644 lib/synergy/MouseTypes.h create mode 100644 lib/synergy/OptionTypes.h create mode 100644 lib/synergy/ProtocolTypes.cpp create mode 100644 lib/synergy/ProtocolTypes.h create mode 100644 lib/synergy/XScreen.cpp create mode 100644 lib/synergy/XScreen.h create mode 100644 lib/synergy/XSynergy.cpp create mode 100644 lib/synergy/XSynergy.h create mode 100644 win32util/autodep.cpp diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..ea648387 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +See doc/authors.html. diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..ce0fb161 --- /dev/null +++ b/COPYING @@ -0,0 +1,286 @@ +Synergy is copyright (C) 2002-2007 Chris Schoeneman. +Synergy is distributed under the following license. + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE +IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE +COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR +IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED +TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY +WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED +ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, +SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF +THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT +LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..5b101ca1 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,11453 @@ +2007/08/22 22:33:43 crs +all.dsp +ChangeLog +cmd/exec.dsp +cmd/launcher/launcher.dsp +cmd/launcher/launcher.rc +cmd/launcher/Makefile.am +cmd/launcher/nmake.mak +cmd/synergyc/Makefile.am +cmd/synergyc/nmake.mak +cmd/synergyc/synergyc.cpp +cmd/synergyc/synergyc.dsp +cmd/synergyc/synergyc.rc +cmd/synergys/Makefile.am +cmd/synergys/nmake.mak +cmd/synergys/synergys.cpp +cmd/synergys/synergys.dsp +cmd/synergys/synergys.rc +COPYING +dist/nullsoft/installer.dsp +dist/nullsoft/installer.mak +dist/nullsoft/Makefile.am +dist/nullsoft/nmake.mak +dist/nullsoft/synergy.nsi +doc/authors.html +lib/arch/arch.dsp +lib/arch/CArchDaemonNone.cpp +lib/arch/Makefile.am +lib/arch/nmake.mak +lib/base/base.dsp +lib/base/Makefile.am +lib/base/nmake.mak +lib/client/client.dsp +lib/client/Makefile.am +lib/client/nmake.mak +lib/common/common.dsp +lib/common/Makefile.am +lib/common/nmake.mak +lib/io/io.dsp +lib/io/Makefile.am +lib/io/nmake.mak +lib/mt/Makefile.am +lib/mt/mt.dsp +lib/mt/nmake.mak +lib/net/CSocketMultiplexer.cpp +lib/net/CTCPSocket.cpp +lib/net/Makefile.am +lib/net/net.dsp +lib/net/nmake.mak +lib/platform/CMSWindowsScreen.cpp +lib/platform/CSynergyHook.cpp +lib/platform/Makefile.am +lib/platform/makehook.dsp +lib/platform/nmake.mak +lib/platform/platform.dsp +lib/platform/synrgyhk.dsp +lib/server/Makefile.am +lib/server/nmake.mak +lib/server/server.dsp +lib/synergy/libsynergy.dsp +lib/synergy/Makefile.am +lib/synergy/nmake.mak +Makefile.am +nmake.mak +synergy.dsw +synergy.xcode/project.pbxproj +win32util/autodep.cpp + +Applied patch by maruel: +- Fixed taking the address of begin() on an empty std::vector. +- Fixed nsis makefile to use %ProgramFiles% environment variable. +- Fixed nsis makefile to pass the output directory and file to makensis. +- Fixed synergy.nsi to get the files from the output directory. That + enables a debug build of the installer. +- Fixes to compile under VS2005. + +I did not apply VS2005 project files, instead adding nmake files. nmake is +pretty weak but the makefiles can be modified without having visual studio. +Also modified the .rc files to not use winres.h. This plus nmake means +synergy can now be built using the freely downloadable Microsoft Windows +SDK for Vista, available from microsoft's web site. This change removes +all of the old VC++6 project files in favor of the nmake files. It also +removes the XCode project in favor of ./configure and make. + +All of the nmake files are named nmake.mak. Only the top level makefile +is directly useful (the rest are included by it) so all builds are from +the top level directory. nmake knows the following targets: + + all: build synergy.exe, synergyc.exe and synergys.exe + clean: remove all intermediate files, keep programs + clobber: clean and remove programs + installer: build programs and an installer + debug: build a debug version of 'all' + release: build a release version of 'all' + debug-installer: build an installer of the debug build + release-installer: build an installer of the release build + +The default build version is release so 'all' and 'installer' will build +a release version. The installer itself never has debug symbols, just +the stuff it installs. The default target is 'all'. To build use: + + nmake /nologo /f nmake.mak + +VC++ and VisualStudio users may need to manually run vcvars.bat in a +command.exe or cmd.exe window before invoking nmake. The Window 98/Me +command.exe may not handle potentially long command lines; I haven't +tried to verify if that works. + +---------- +2007/08/22 21:42:09 crs +lib/platform/COSXKeyState.cpp + +Allow input scripts other than roman on OS X. + +---------- +2007/06/22 19:17:24 crs +lib/platform/CXWindowsClipboard.cpp + +Applied patch 1731039. Fixes a bug in testing if X clipboard +was owned at a given time. + +---------- +2006/04/02 12:16:23 crs +lib/platform/CXWindowsKeyState.cpp + +Fixed non-XKB handling of Mode_switch. There were two problems. +First the XkbLockGroup function was being called even for non-XKB +layouts. (And was being included in the compile even if the XKB +headers were not available!) Second, modifiers were assumed to +exist only the group in which a keysym was found and when looking +for modifiers for a key we only check modifiers in the same group. +So keys that needed Mode_switch (in group 1) would check to see if +Mode_switch was explictly mapped in group 1. It never is so that +would never work. Now we handle implicitly mapped modifiers. I +hadn't noticed this problem because my system (like most others) +uses XKB and when I forced non-XKB, the keys I tested worked anyway +through Multi_key. + +---------- +2006/04/01 22:25:33 crs +lib/platform/CXWindowsKeyState.cpp +lib/platform/CXWindowsKeyState.h +lib/platform/CXWindowsScreen.cpp + +Fixed autorepeat on X11. Was autorepeating all keys sent from +server. Now autorepeating only those keys which are locally +configured in X to autorepeat. + +---------- +2006/04/01 21:37:24 crs +lib/platform/CMSWindowsKeyState.cpp + +Fixed autorepeat on win32 clients. Was synthesizing a key release +for each repeat. Win32 wants only repeat press events. + +---------- +2006/04/01 21:36:50 crs +cmd/launcher/CScreensLinks.cpp + +Fixed two bugs in screens and link dialog. First, the link controls +were not updated when reopening the dialog. Second, a change in any +link edit control would be discarded if the user pressed enter. + +---------- +2006/04/01 21:35:10 crs +cmd/launcher/CHotkeyOptions.cpp + +Fixed crash when creating a new hotkey but picking a key or mouse +button combination before clicking OK. + +---------- +2006/04/01 21:30:43 crs +configure.in +lib/arch/CArchNetworkBSD.cpp +lib/common/common.h + +Removed use of alloca() from unix and portable code. It's still +in win32 code but i don't have to play guessing games about +whether it's there or not on that platform. + +---------- +2006/04/01 17:53:27 crs +synergy.xcode/project.pbxproj + +Added new files to Xcode project. + +---------- +2006/04/01 17:41:59 crs +lib/client/CServerProxy.cpp +lib/net/CTCPSocket.cpp +lib/server/CClientProxy1_0.cpp + +Added more debugging output for network problems. + +---------- +2006/04/01 17:01:56 crs +lib/platform/COSXKeyState.cpp + +Fixed keymapping on OSX. Was checking keyboard tables for all +modifier combinations, including right-handed modifiers. It +turns out OSX doesn't set up the tables correctly for those +modifiers and acts if they have no effect. Since OSX never +generates right-handed modifiers this isn't too surprising. + +---------- +2006/04/01 15:32:19 crs +lib/server/CBaseClientProxy.cpp +lib/server/CBaseClientProxy.h +lib/server/CClientProxy.cpp +lib/server/CClientProxy.h +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/server/Makefile.am +lib/server/server.dsp + +Added new class to allow the server to keep information that every +screen has. The first such info is the cursor position when last +jumping from the screen. Using a hotkey to jump to a screen now +puts the cursor where it was the last time the user jumped from +that screen. + +---------- +2006/04/01 14:51:22 crs +lib/server/CInputFilter.cpp + +Fixed bug in reloading configurations. Was losing hotkeys. + +---------- +2006/04/01 14:49:41 crs +lib/platform/CXWindowsKeyState.cpp + +Fixed bug when recollecting the keyboard map on non-XKB keyboards. +Wasn't reseting a key map. It's unlikely anyone ever hit this bug. + +---------- +2006/04/01 13:39:09 crs +lib/platform/CXWindowsClipboard.cpp +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreenSaver.cpp + +Fixed several uses of CXWindowsUtil::CErrorLock that take a flag. +Was checking the flag before destroying the lock object. That +doesn't reliably work because the X protocol is asynchronous. The +lock object ensures that the flag is correctly set in its d'tor by +synchronizing with the server. The X11 hotkey (un)registration was +one place where this was done. There were places in the X11 +screensaver and clipboard handling, too. + +---------- +2006/04/01 12:55:17 crs +lib/synergy/CKeyState.cpp + +Fixed failure to clear the state of which keys are pressed in +fakeAllKeysUp(). This fixes problems with synergy clients +thinking some keys are still down, causing weird key behavior. + +---------- +2006/04/01 12:53:31 crs +lib/common/Version.h + +Changed version to 1.3.1. + +---------- +2006/03/21 21:54:16 crs +lib/platform/CXWindowsUtil.cpp + +Add all #defines for including keysyms that we use. The #defines in +X11/keysym.h vary from platform to platform and cannot be relied on. + +---------- +2006/03/21 21:42:53 crs +lib/common/Version.h + +Changed version to 1.3.0. + +---------- +2006/03/21 21:38:52 crs +cmd/launcher/Makefile.am + +Added new files to makefile. + +---------- +2006/03/21 21:38:02 crs +cmd/launcher/CAutoStart.cpp +cmd/launcher/CAutoStart.h +cmd/launcher/CHotkeyOptions.cpp +cmd/launcher/CHotkeyOptions.h +cmd/launcher/launcher.cpp +cmd/launcher/launcher.dsp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +cmd/synergyc/synergyc.dsp +cmd/synergys/synergys.dsp +lib/arch/arch.dsp +lib/base/base.dsp +lib/client/client.dsp +lib/common/common.dsp +lib/io/io.dsp +lib/mt/mt.dsp +lib/net/net.dsp +lib/platform/platform.dsp +lib/platform/synrgyhk.dsp +lib/server/CConfig.cpp +lib/server/CInputFilter.cpp +lib/server/CInputFilter.h +lib/server/server.dsp +lib/synergy/IKeyState.cpp +lib/synergy/IKeyState.h +lib/synergy/IPrimaryScreen.cpp +lib/synergy/IPrimaryScreen.h +lib/synergy/libsynergy.dsp + +Added a hot key dialog to the win32 launcher, providing full support +for all features of hot keys. This required a bunch of small changes +to CInputFilter and stuff used by it (mainly getter methods). + +The hot key dialog uses dynamic_cast<> to determine the kinds of +conditions and actions for each rule in the configuration. This +required enabling RTTI in the project files. + +Also changed the 'Start' button to start the synergy service if +installed (i.e. synergy is configured to start when the computer +starts). + +---------- +2006/03/21 21:37:59 crs +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsKeyState.h + +Changed AltGr handling on win32. Previously the server would send +ctrl and alt up events when sending a key that used AltGr then send +matching down events. Now we just clear the ctrl and alt bits in +the mask sent with the key; clients will temporarily release the +ctrl and alt modifiers as necessary to yield the key. If the key +doesn't need AltGr then the ctrl and alt bits are kept. We also +used to reserve the right alt key for AltGr even if the keyboard +layout didn't use it that way. This has been removed. The keyboard +mapping now presses the ctrl and alt keys for AltGr rather than use +the right alt. + +Also made getKeyID() a public method. + +---------- +2006/03/21 21:37:57 crs +lib/platform/CMSWindowsScreen.cpp +lib/platform/COSXScreen.cpp +lib/platform/CXWindowsScreen.cpp + +Improved log output when registering hot keys. + +---------- +2006/03/21 21:37:53 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp + +Added "server" and "client" to synergy log windows. + +---------- +2006/03/21 21:37:52 crs +lib/synergy/CKeyMap.cpp + +Fixed bug in keyboard mapping when releasing modifiers. This caused +a problem with AltGr on win32. The left alt key was being +(synthetically) released but it was the right alt key that was down. + +---------- +2006/03/21 21:37:49 crs +doc/configuration.html + +Documentation fix. + +---------- +2006/03/20 23:13:11 crs +doc/images/warp.gif + +Replaced animated GIF demonstrating cursor warp with one that +doesn't show the cursor in the region between the monitors. +This is the original GIF with frames removed. Supplied by +user Brian A. + +---------- +2006/03/18 19:17:46 crs +synergy.xcode/project.pbxproj + +Updated Xcode project to build universal binaries. The PPC build +uses the 10.2.8 SDK while the i386 build uses the 10.4u SDK. Also +removed the "Default" configuration; "Development" is now the +default. + +---------- +2006/03/18 16:39:43 crs +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h +lib/platform/COSXScreen.cpp + +Improved handling of option key on OS X servers. Previously it +was always treated as the super modifier. Now it's treated as +AltGr if it generates a glyph, though AltGr is not sent to the +clients. So if option+key produces a glyph then key is sent +without the option or AltGr modifiers. If option+key does not +produce a glpyh then option is sent as the super modifier. +Note that combining an option+key combination that would produce +a glyph with the command or control modifiers will cause it to +not produce a glyph. In that case we send option as the super +modifier and we send the glyph that would've been produced on +the server had the option key not been pressed. So option+s +sends the beta key id with no modifiers but command+option+s +sends the "s" key id with the alt and super modifiers. + +This seems to handle the user expectations. However some users +may expect option+L to produce win+L on win32 clients. These +same users probably expect option+? to produce an upside down +question mark. But these two expectations are fundamentally at +odds. We cannot satisfy everyone because OS X doesn't have +enough modifier keys. + +---------- +2006/03/18 13:20:18 crs +lib/server/CInputFilter.cpp + +Fixed mousebutton condition. Wasn't working if num lock, caps lock +or scroll lock was on. + +---------- +2006/03/18 12:05:34 crs +lib/platform/CXWindowsScreen.cpp + +Applied patch from Jaco Kroon to fix a problem with mouse focus +on X11. + +---------- +2006/03/18 11:54:40 crs +doc/index.html + +Added support for index.html?child#anchor syntax. This will open +the index.html page then child in the page frame and jump to anchor. + +---------- +2006/03/12 20:24:43 crs +cmd/launcher/CAutoStart.cpp +cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp +cmd/synergyc/synergyc.cpp +cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp +cmd/synergys/synergys.cpp +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchConsoleUnix.cpp +lib/arch/CArchConsoleUnix.h +lib/arch/CArchConsoleWindows.cpp +lib/arch/CArchConsoleWindows.h +lib/arch/CArchLogUnix.cpp +lib/arch/CArchLogUnix.h +lib/arch/CArchLogWindows.cpp +lib/arch/CArchLogWindows.h +lib/arch/IArchConsole.h +lib/arch/IArchLog.h +lib/base/CLog.cpp +lib/base/ILogOutputter.h +lib/base/LogOutputters.cpp +lib/base/LogOutputters.h + +Added show() method to console and logs. + +---------- +2006/03/12 20:24:14 crs +dist/nullsoft/synergy.nsi + +Updated windows installer to install new documentation pages and +to put a shortcut on the desktop. + +---------- +2006/03/12 20:23:46 crs +cmd/launcher/synergy.ico +cmd/synergyc/synergyc.ico +cmd/synergyc/tb_error.ico +cmd/synergyc/tb_idle.ico +cmd/synergyc/tb_run.ico +cmd/synergyc/tb_wait.ico +cmd/synergys/synergys.ico +cmd/synergys/tb_error.ico +cmd/synergys/tb_idle.ico +cmd/synergys/tb_run.ico +cmd/synergys/tb_wait.ico + +Updated icons on win32. + +---------- +2006/03/12 12:42:18 crs +doc/configuration.html +doc/faq.html +doc/running.html +doc/trouble.html + +More documentation fixes. + +---------- +2006/03/12 12:19:02 crs +doc/configuration.html +doc/faq.html +doc/Makefile.am +doc/toc.html +doc/trouble.html + +Added a page with typical problems and solutions. + +---------- +2006/03/12 10:25:15 crs +doc/tips.html + +More documentation fixes. + +---------- +2006/03/12 10:20:14 crs +doc/about.html +doc/configuration.html +doc/faq.html + +Documentation fixes. + +---------- +2006/03/12 09:34:16 crs +doc/toc.html + +Fixed link in table of contents. + +---------- +2006/03/11 15:01:00 crs +doc/banner.html +doc/border.html +doc/index.html +doc/synergy.css + +Adjusted how the border under the banner is drawn. + +---------- +2006/03/11 14:49:38 crs +doc/images/logo.gif + +Updated logo. + +---------- +2006/03/11 14:42:00 crs +doc/about.html +doc/authors.html +doc/autostart.html +doc/banner.html +doc/compiling.html +doc/configuration.html +doc/contact.html +doc/developer.html +doc/faq.html +doc/history.html +doc/home.html +doc/images/logo.gif +doc/images/warp.gif +doc/index.html +doc/license.html +doc/Makefile.am +doc/news.html +doc/roadmap.html +doc/running.html +doc/security.html +doc/synergy.css +doc/tips.html +doc/toc.html +doc/todo.html + +Updated documentation pages. They're now the web site pages except +they now use frames. + +---------- +2006/03/08 20:07:09 crs +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsDesks.h +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsKeyState.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h +lib/platform/CSynergyHook.cpp +lib/platform/CSynergyHook.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/server/CInputFilter.cpp +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/synergy/CKeyMap.cpp +lib/synergy/CKeyState.cpp +lib/synergy/CKeyState.h +lib/synergy/CPlatformScreen.h +lib/synergy/CScreen.cpp +lib/synergy/CScreen.h +lib/synergy/IPlatformScreen.h +lib/synergy/IPrimaryScreen.cpp +lib/synergy/IPrimaryScreen.h + +Added beginnings of support for synthesizing keystrokes on server's +screen. It's partly implemented on win32; it just needs to track +the modifier keys and adjust them as appropriate when synthesizing +keys. It's not implemented on X11 or OS X. It's also currently +disabled (in CPrimaryClient.cpp). + +---------- +2006/03/08 20:05:38 crs +cmd/synergyc/CClientTaskBarReceiver.cpp +cmd/synergyc/CClientTaskBarReceiver.h +cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp +cmd/synergyc/resource.h +cmd/synergyc/synergyc.cpp +cmd/synergyc/synergyc.rc +cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp +cmd/synergys/CServerTaskBarReceiver.cpp +cmd/synergys/resource.h +cmd/synergys/synergys.cpp +cmd/synergys/synergys.rc +lib/arch/CArch.cpp +lib/arch/CArchConsoleUnix.cpp +lib/arch/CArchConsoleUnix.h +lib/arch/CArchConsoleWindows.cpp +lib/arch/CArchConsoleWindows.h +lib/arch/CArchMiscWindows.cpp +lib/arch/CArchMiscWindows.h +lib/base/CLog.cpp +lib/base/LogOutputters.cpp +lib/base/LogOutputters.h +lib/client/CClient.cpp +lib/client/CClient.h +lib/common/Version.cpp + +Replaced using win32 console for log with a dialog containing a rich +edit control. The user can close this window without quiting synergy +and can reopen the window using the tray icon menu. Also added menu +items to switch the current logging level. + +---------- +2006/02/22 19:21:21 crs +lib/platform/COSXScreen.cpp + +Removed bogus logging call. + +---------- +2006/02/20 19:46:47 crs +cmd/launcher/launcher.cpp +cmd/launcher/LaunchUtil.cpp +cmd/launcher/LaunchUtil.h + +Fixed infinite loop of error dialogs in win32 launcher. + +---------- +2006/02/20 12:59:20 crs +doc/faq.html + +Added firewall info to faq12. Added faq19, a discussion of not taking +the foreground on win32. + +---------- +2006/02/20 12:46:18 crs +doc/authors.html +doc/autostart.html +doc/compiling.html +doc/configuration.html +doc/developer.html +doc/faq.html +doc/history.html +doc/index.html +doc/license.html +doc/news.html +doc/running.html +doc/security.html +doc/tips.html +doc/todo.html + +Changed !DOCTYPE to HTML 4.0 (from 3.2). + +---------- +2006/02/20 12:21:34 crs +doc/configuration.html +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/server/CInputFilter.cpp +lib/server/CInputFilter.h +lib/server/CServer.cpp +lib/server/CServer.h + +Hot key overhaul. Added support for multiple actions per hot key. +Actions and conditions are now idempotent so they no longer track +the active state (on, off, toggled, etc). Actions can be assigned +to the activation or deactivation of a condition, basically a hot +key or mouse button being pressed (activation) or released +(deactivation). The keystroke and mousebutton actions map to both +and the new keyDown, keyUp, mouseDown and mouseUp map to one or the +other. The lock cursor to screen action now takes a mode: on, off +or toggle and the corresponding event state is respected by the +server. Removed the modifiers action. Mouse button actions now use +the new kKeySetModifiers and kKeyClearModifiers keys to set/reset +modifier state directly on the client. + +Conditions and actions are much simpler now that they're idempotent +and so is CInputFilter. Refactored CRule into a class since there's +now more to a rule than a condition and action. + +---------- +2006/02/20 11:29:41 crs +lib/synergy/CKeyMap.cpp +lib/synergy/CKeyMap.h +lib/synergy/CKeyState.cpp + +Added support for kKeySetModifiers and kKeyClearModifiers keys. +The former activates the given modifiers and the latter deactivates +them. + +---------- +2006/02/20 11:25:44 crs +lib/synergy/KeyTypes.h + +Added special keys for setting/clearing modifiers. The intent +is to use these to set/reset modifiers for mouse button hot key +actions. + +---------- +2006/02/19 21:01:08 crs +lib/platform/COSXScreen.cpp + +Fixed OS X to send current keyboard modifiers with mouse button events. + +---------- +2006/02/19 13:26:54 crs +lib/client/CClient.cpp +lib/client/CClient.h + +Fixed two clipboard problems. + +First, synergy would blow an assert given the following sequence +of events: + enter client screen A + A takes clipboard + enter server screen B + B takes clipboard + clipboard on A changes (while we're not on the screen) + enter A + enter B +On entering B we find that the clipboard sender is not the owner +of the clipboard. This is because when A changed the clipboard +while we were on B, B ignored the grab from A. A now thinks it +owns the clipboard (even though B does). So when we leave A the +second time, it sends the clipboard (which contains what B sent +it) to B. The assertion is blown because B owns the clipboard +but A sends a clipboard with a valid sequence number. The fix +is simply that clients must reset their internal clipboard +ownership flag when told to set the clipboard by the server. + +Second, synergy clients would fail to send the clipboard to the +server given the following sequence of events: + enter client A + A takes the clipboard + enter screen B + B takes the clipboard + enter A + A takes the clipboard with the same contents as before +In an effort to reduce bandwidth, clients don't send unchanged +clipboard contents. Clients were incorrectly treating this +case as unchanged contents when, in fact, the contents are those +set by B not those of A. Clients now handle this case. + +---------- +2006/02/19 13:13:55 crs +lib/server/CServer.cpp + +Fixed error in log message. Was trying to report the sender of +a clipboard but was reporting the owner of the clipboard. + +---------- +2006/02/16 22:12:37 crs +lib/platform/CXWindowsKeyState.cpp +lib/platform/CXWindowsKeyState.h + +Added a hack to work around how VMware handles modifier keys on X11 +when a virtual machine has focus. VMware removes all of the modifier +mappings so synergy doesn't know about any modifiers. The hack is to +use the last known good modifiers when there aren't any active +modifiers. + +---------- +2006/02/14 18:10:12 crs +lib/server/CServer.cpp + +Made switch in direction hot keys use the cursor's current position +when calculating what the neighbor is. This only affects layouts +using fractional edges. + +---------- +2006/02/12 16:49:16 crs +synergy.xcode/project.pbxproj + +Updated Xcode project. + +---------- +2006/02/12 16:40:02 crs +doc/configuration.html +lib/server/CServer.cpp +lib/server/CServer.h +lib/synergy/KeyTypes.cpp + +Removed onCommandKey() from server. This was used only for handling +the ScrollLock key to lock the cursor to the screen but this has been +obsoleted by the hotkey support for locking the cursor. Also, +ScrollLock is now only added as a hotkey to lock the cursor if the +user hasn't configured some other hotkey to lock the cursor. + +---------- +2006/02/12 16:22:41 crs +doc/configuration.html +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/server/CInputFilter.cpp +lib/server/CServer.cpp +lib/server/CServer.h +lib/synergy/IKeyState.cpp +lib/synergy/IKeyState.h + +Added support for directing hotkey key actions to a particular screen +or screens or to broadcast to all screens. However, key action +keystrokes are never sent to the server screen. This should be fixed. + +---------- +2006/02/12 16:16:11 crs +doc/configuration.html +lib/synergy/CKeyMap.cpp +lib/synergy/CKeyMap.h +lib/synergy/KeyTypes.cpp +lib/synergy/KeyTypes.h +lib/synergy/libsynergy.dsp +lib/synergy/Makefile.am +lib/synergy/mkspecialkeynames.pl +lib/synergy/SpecialKeyNameMap.h + +Moved and restructed key name maps. Also added names for all +non-alphanumeric ASCII characters and added support for \uXXXX +unicode character codes. + +---------- +2006/02/12 14:47:23 crs +lib/synergy/CKeyState.cpp + +Now allowing fake key presses from server with a server button ID +of 0. Hot keys that synthesize key events use a server button ID +of 0. Note that other code will prevent a client from processing +a hotkey press while another hotkey is pressed. The nature of +hotkeys should ensure that never happens except for modifier only +hotkeys. Worry about that later. + +---------- +2006/02/12 12:06:50 crs +lib/platform/COSXScreen.cpp + +Fixed 2 axis scrolling on OS X. + +---------- +2006/02/12 11:53:35 crs +lib/client/CClient.cpp +lib/client/CClient.h +lib/client/CServerProxy.cpp +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsDesks.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/server/CClientProxy.h +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CClientProxy1_3.cpp +lib/server/CClientProxy1_3.h +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/synergy/CPlatformScreen.h +lib/synergy/CScreen.cpp +lib/synergy/CScreen.h +lib/synergy/IClient.h +lib/synergy/IPlatformScreen.h +lib/synergy/IPrimaryScreen.cpp +lib/synergy/IPrimaryScreen.h +lib/synergy/ISecondaryScreen.h +lib/synergy/ProtocolTypes.cpp +lib/synergy/ProtocolTypes.h + +Added support for horizontal scrolling. + +---------- +2006/02/12 10:08:49 crs +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h + +Applied patch from stkamp@users.sf.net that clamps mouse positions +to valid areas on OS X. It also improves using the local keyboard +with the remote mouse on OS X. + +---------- +2006/02/11 20:01:42 crs +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsKeyState.h +lib/platform/CMSWindowsScreen.cpp + +Fixed synthesis of ctrl+alt+del and handling of AltGr on win32 +client. + +---------- +2006/02/11 20:00:32 crs +lib/server/server.dsp + +Added CClientProxy1_3 to project. + +---------- +2006/02/06 19:27:45 crs +lib/server/CConfig.cpp + +Fixed handling of comments when parsing the configuration. +Had changed leading whitespace stripping which broke it. + +---------- +2006/02/05 19:42:55 crs +synergy.xcode/project.pbxproj + +Updated Xcode project. + +---------- +2006/02/05 18:48:35 crs +lib/server/CClientProxy1_3.cpp + +Fixed warning. + +---------- +2006/02/05 18:02:47 crs +lib/server/CInputFilter.cpp + +Fixed updates of input filters when configuration is changed. + +---------- +2006/02/05 17:55:45 crs +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_3.cpp + +More fixes for server side keep alives. + +---------- +2006/02/05 17:55:19 crs +lib/server/CServer.cpp + +Fixed sending of options to client. Wasn't reseting options first. + +---------- +2006/02/05 17:39:20 crs +lib/client/CServerProxy.cpp + +Fixed memory bug in releasing keep alive timer. + +---------- +2006/02/05 17:36:17 crs +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CClientProxy1_3.cpp +lib/server/CClientProxy1_3.h +lib/server/CClientProxyUnknown.cpp + +Fixed server side handling of keep alives. + +---------- +2006/02/05 17:34:14 crs +lib/client/CServerProxy.cpp +lib/client/CServerProxy.h + +Fixed client side handling of keep alives. + +---------- +2006/02/05 16:56:00 crs +lib/synergy/ProtocolTypes.h + +Added comment for protocol version 1.3. + +---------- +2006/02/05 16:54:39 crs +lib/client/CServerProxy.cpp +lib/client/CServerProxy.h +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CClientProxy1_3.cpp +lib/server/CClientProxy1_3.h +lib/server/Makefile.am +lib/synergy/ProtocolTypes.cpp +lib/synergy/ProtocolTypes.h + +Deprecated heartbeat and added keep alive to replace it. While a +heartbeat was generated by clients and sent to the server, a keep +alive is sent from the server and echoed by the client. This +checks both directions of the connection. Either side will hang +up if the other hasn't been heard from in a reasonable amount of +time. This fixes a problem where clients would not hang up on +an unavailable server. + +---------- +2006/02/05 16:29:01 crs +cmd/launcher/Makefile.am + +Added CInfo files to makefile. + +---------- +2006/02/05 15:30:49 crs +lib/synergy/CKeyState.cpp + +Fixed handling of modifier keys that are held down while leaving +a client screen. Was correctly releasing those keys on the client +but wasn't reseting the active modifier state so as soon as a key +was pressed after reentering the client the modifiers were +reactivated. However, the user could only see the problem by +typing on the local keyboard because the modifier was correctly +adjusted for keys from the server. Now reseting modifier state +when leaving a client screen. + +---------- +2006/02/05 14:47:59 crs +cmd/launcher/CInfo.cpp +cmd/launcher/CInfo.h +cmd/launcher/launcher.cpp +cmd/launcher/launcher.dsp +cmd/launcher/launcher.rc +cmd/launcher/LaunchUtil.cpp +cmd/launcher/LaunchUtil.h +cmd/launcher/resource.h + +Added two features to the win32 launcher. First there's now a +info dialog which reports some useful information. The other is +that configuration files are now re-read when the application is +activated and the file's modification time has changed. This +should help when users are hand editing the configuration file +while the launcher is running. + +---------- +2006/02/05 14:45:39 crs +lib/server/CConfig.cpp + +Fixed a bug in writing configuration files with fractional edges. + +---------- +2006/02/05 14:43:17 crs +cmd/launcher/CAddScreen.cpp +cmd/launcher/CScreensLinks.cpp +cmd/launcher/CScreensLinks.h +cmd/launcher/launcher.rc +cmd/launcher/resource.h +doc/configuration.html +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/server/CServer.cpp +lib/server/CServer.h + +Added support for fractional edge links. This allows users to +configure a portion of an edge to map to a portion of another +edge, rather than linking entire edges. This does not allow +users to split a (presumably multimonitor) screen to allow +switching on reaching an interior edge. + +---------- +2006/02/01 21:34:28 crs +lib/synergy/CKeyMap.cpp + +Fixed parsing of modifier plus single character keystroke and +mousebutton entries in the configuration. Was discarding single +characters. + +---------- +2006/02/01 21:20:46 crs +lib/platform/COSXKeyState.cpp + +Fixed OS X keypad enter key. + +---------- +2006/01/29 20:50:54 crs +lib/server/CConfig.cpp +lib/server/CInputFilter.cpp +lib/server/CInputFilter.h +lib/server/CServer.cpp +lib/server/CServer.h + +Added support for performing an action when a screen connects. +Also added support for jumping to the screen that just connected. +The line 'connect() = switchToScreen()' in the global options will +cause the cursor to jump to a screen as soon as it connects. This +isn't especially useful but serves as an example. + +---------- +2006/01/29 19:56:31 crs +lib/platform/CXWindowsScreen.cpp + +Fixed X11 hot key grabbing. Previously was sensitive to CapsLock, +NumLock and ScrollLock and is now insensitive to all of those. + +---------- +2006/01/29 17:54:08 crs +lib/platform/COSXScreen.cpp + +Changed input suppress delay to 0. This might be right or it +might need to be a small positive value like 0.1. + +---------- +2006/01/29 15:52:44 crs +lib/server/CConfig.h +lib/server/CInputFilter.cpp +lib/server/CInputFilter.h +lib/server/CServer.cpp + +Fixed support for ScrollLock to lock the cursor to the screen. +It now works via the hotkey mechanism. + +---------- +2006/01/29 15:51:59 crs +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsKeyState.h + +Fixed failure to run on win95 family. Was linked against +ToUnicodeEx which is not available on that family. Now looking +up that symbol at run time. + +---------- +2006/01/29 15:50:29 crs +lib/platform/COSXScreen.cpp + +Fixed minor bug in call to CGSetLocalEventsSuppressionInterval. + +---------- +2006/01/29 13:26:48 crs +lib/platform/CSynergyHook.cpp + +Win32 reports VK_RSHIFT as an extended key, which it is not. +We now correct this which fixes the right shift key when using +win32 servers. + +---------- +2006/01/29 12:47:31 crs +lib/synergy/CKeyMap.cpp + +Fixed handling of modifiers when synthesizing a modifier. We +were previously trying to match the desired modifier state +when synthesizing any key, even if some of those modifiers +weren't required (i.e. the key was insensitive to the state +of some of those modifiers). We no longer try to match the +state of non-required modifiers because doing so can break +the synthesis of some keys, particularly modifiers. For +example, previously pressing Control with NumLock on would +turn NumLock off, press Control, then turn NumLock back on. +If this Control was used with Alt on win32 to effect AltGr +then AltGr would not take effect. On X11 the Shift key was +improperly combined with NumLock, causing mouse keys to take +effect. + +---------- +2006/01/29 12:37:30 crs +lib/common/Version.h + +Changed to version 1.2.8. + +---------- +2005/12/18 18:00:56 crs +synergy.xcode/project.pbxproj + +Added new files to xcode project. + +---------- +2005/12/18 17:35:57 crs +doc/configuration.html +doc/news.html + +More documentation for 1.2.7. + +---------- +2005/12/18 16:51:52 crs +doc/news.html + +Fixed error in documentation. + +---------- +2005/12/18 16:15:42 crs +lib/platform/CSynergyHook.cpp + +Removed use of function from C standard library in the hook DLL. + +---------- +2005/12/18 16:11:15 crs +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h + +Added workaround for not receiving drag events for any mouse buttons +besides 1 and 2 (left and right). That appears to be a limitation +of the OS so we simply start polling the position while any buttons +besides 1 and 2 are down. Code based on a patch by Brian Kendall. + +---------- +2005/12/18 15:29:25 crs +lib/common/Version.h + +Changed version to 1.2.7. + +---------- +2005/12/18 15:12:39 crs +lib/platform/COSXKeyState.cpp + +Fixed mapping of unicode key layouts. Was discarding some +characters (backspace and return, in particular) that should +not have been discarded. + +---------- +2005/12/18 10:49:23 crs +cmd/launcher/launcher.rc +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsKeyState.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/server/CInputFilter.cpp +lib/server/CInputFilter.h +lib/server/CServer.cpp +lib/server/server.dsp +lib/synergy/CKeyMap.cpp +lib/synergy/CKeyMap.h +lib/synergy/IKeyState.cpp +lib/synergy/IKeyState.h +lib/synergy/IPrimaryScreen.cpp +lib/synergy/IPrimaryScreen.h +lib/synergy/libsynergy.dsp +lib/synergy/SpecialKeyNameMap.h +synergy.dsw + +Added support for hot keys on win32. Also fixed memory leaks in +CInputFilter and changed CConfig to write hot keys in its +CInputFilter object to the options section. + +---------- +2005/12/15 18:57:38 crs +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h + +Fixed hot keys on OS X when on a secondary screen. This worked +in the original patch but broken during the modify/merge. It +was broken because we turn off all hot key processing by the OS +while on a secondary screen. The fix is to check on key press +and release if the key and modifier state matches a hot key. + +This is known to be broken on hot key release if you press the +hot key then change the modifiers before releasing the provoking +key. + +---------- +2005/12/14 21:51:01 crs +lib/platform/CXWindowsKeyState.cpp +lib/platform/CXWindowsKeyState.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h + +Added support for hotkeys on X11. + +---------- +2005/12/14 21:50:49 crs +lib/server/CServer.cpp + +When jumping to another screen was computing the center using the +size of the active screen instead of the size of the destination +screen. + +---------- +2005/12/14 21:50:09 crs +lib/server/CConfig.cpp + +Switched to ctype.h from cctype to fix linux build. + +---------- +2005/12/14 09:33:25 crs +lib/base/CLog.cpp +lib/base/CLog.h +lib/base/CStringUtil.cpp +lib/base/CStringUtil.h + +Fixed bug in CStringUtil::vsprint. Was reusing va_list across +calls to vnsprintf, which is not allowed. Modified CStringUtil +and CLog to avoid doing that. This should fix a crash when +using --help on some platforms. Tthe --help message was the +only message long enough to exceed the static buffer length +which caused a va_list to get reused. + +---------- +2005/12/14 08:43:36 crs +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/server/CInputFilter.cpp +lib/server/CInputFilter.h +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/server/Makefile.am +lib/synergy/CKeyState.cpp +lib/synergy/CKeyState.h +lib/synergy/CPlatformScreen.h +lib/synergy/CScreen.cpp +lib/synergy/CScreen.h +lib/synergy/IPlatformScreen.h +lib/synergy/IPrimaryScreen.cpp +lib/synergy/IPrimaryScreen.h +lib/synergy/Makefile.am +lib/synergy/mkspecialkeynames.pl +lib/synergy/SpecialKeyNameMap.h + +Checkpoint of configurable hotkey support. Authored by Lorenz +Schori, modified and merged by me. Hotkeys are only implemented +on OS X so far; this change breaks the build on linux and win32. + +---------- +2005/11/29 21:22:02 crs +lib/platform/CXWindowsKeyState.cpp + +Fixed bug in X11 keyboard code. Wasn't checking for a NULL pointer. + +---------- +2005/11/28 22:12:49 crs +doc/running.html + +Added a little more detail to the documentation for for OS X users +not familiar with the shell. + +---------- +2005/11/28 20:59:39 crs +doc/autostart.html + +Added X11 autostart info for kdm. + +---------- +2005/11/27 16:41:06 crs +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsScreen.cpp +lib/platform/CSynergyHook.cpp +lib/synergy/CKeyMap.cpp +lib/synergy/CKeyState.cpp + +Fixed several win32 keyboard bugs. + +---------- +2005/11/27 16:30:50 crs +lib/synergy/CKeyMap.cpp +lib/synergy/CKeyState.cpp +lib/synergy/CKeyState.h + +Fixed a couple of problems with key event synthesis. + +---------- +2005/11/25 18:19:28 crs +lib/platform/CXWindowsScreenSaver.cpp +lib/platform/CXWindowsScreenSaver.h + +Now sending a more significant fake mouse motion to xscreensaver +to keep it from activating. xscreensaver since 2.21 requires a +motion of at least 10 pixels to be considered significant. + +---------- +2005/11/25 14:42:30 crs +lib/platform/COSXClipboard.cpp +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h + +Added periodic check for clipboard change on OS X. We're polling +because there doesn't seem to be any event to notify of a clipboard +change. + +---------- +2005/11/24 11:00:39 crs +cmd/launcher/launcher.cpp + +Fixed bug in win32 GUI. If synergy wasn't previously installed for +autostart then the GUI would incorrectly install it for autostart. +It was incorrectly installed in such a way that synergy would think +it wasn't installed and it would not function when the service +manager tried to start it. + +---------- +2005/11/20 22:34:06 crs +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsScreen.cpp +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h +lib/platform/CXWindowsScreen.cpp +lib/synergy/CKeyMap.cpp +lib/synergy/CKeyMap.h +lib/synergy/CKeyState.cpp +lib/synergy/CKeyState.h +lib/synergy/CPlatformScreen.cpp +lib/synergy/CPlatformScreen.h +lib/synergy/CScreen.cpp +lib/synergy/IKeyState.h +lib/synergy/IPlatformScreen.h + +Converted OS X to new keyboard handling. Two known problems +with OS X: the thorn character is mapped incorrectly (because +OS X lies) and we're using every keyboard layout, not just the +enabled one, because we have no idea how to detect if a layout +is enabled or not. The latter problem only causes some startup +slowness. + +---------- +2005/11/20 22:29:01 crs +configure.in +lib/arch/CMultibyte.cpp + +Added support for converting clipboard data to Latin-1 encoding +if the default encoding is ASCII by switching to the en_US locale +at startup. + +---------- +2005/11/14 18:35:07 crs +lib/synergy/CKeyState.h + +Removed unnecessary headers. + +---------- +2005/11/13 17:08:45 crs +lib/synergy/CKeyMap.cpp +lib/synergy/CKeyMap.h + +Added improved support for key combinations intended to perform a +keyboard shortcut rather than synthesize a particular key. + +---------- +2005/11/13 12:52:16 crs +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CXWindowsKeyState.cpp +lib/platform/CXWindowsKeyState.h +lib/platform/CXWindowsScreen.cpp +lib/synergy/CKeyMap.cpp +lib/synergy/CKeyMap.h + +Finished X11 non-XKB keyboard mapping. Also added a convenience +function to CKeyMap used by both the X11 and win32 implemenetations. + +---------- +2005/11/13 10:31:32 crs +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsKeyState.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h +lib/platform/CXWindowsKeyState.cpp +lib/platform/CXWindowsKeyState.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsUtil.cpp +lib/platform/CXWindowsUtil.h +lib/synergy/CKeyMap.cpp +lib/synergy/CKeyMap.h +lib/synergy/CKeyState.cpp +lib/synergy/CKeyState.h +lib/synergy/CPlatformScreen.cpp +lib/synergy/CPlatformScreen.h +lib/synergy/CScreen.cpp +lib/synergy/CScreen.h +lib/synergy/IKeyState.h +lib/synergy/IPlatformScreen.h +lib/synergy/KeyTypes.h +lib/synergy/libsynergy.dsp +lib/synergy/Makefile.am + +Checkpointing keyboard handling refactoring. This is a major change +to the client side handling of key events. This change converts the +X11 and win32 handling but not the OS X handling. + +The new class CKeyMap contains the information necessary to convert +a synergy key event to the platform events necessary to synthesize +the key. Platforms fill in a CKeyMap whenever their keyboard layouts +change using the addKeyEntry(), calling it once for each unique way +to synthesize a particular KeyID on the keyboard. The CKeyMap takes +it from there, first adding dead-key combinations and some other keys, +then doing the translation from KeyID to platform keystroke sequences. +Platforms no longer need to implement the KeyID to keystroke sequence +conversion, simplifying the platform implementations. + +This change also supports multiple keyboard layouts, typically used +to support various languages. If a key is not available on the active +layout but is on a different installed layout then synergy will switch +layouts automatically. + +The X11 version now fully supports the XKB extension. This should fix +problems with Mode_switch vs ISO_Level3_Shift. Non-XKB support is +incomplete in this checkpoint. + +---------- +2005/11/12 11:43:54 crs +lib/platform/CMSWindowsScreen.cpp + +Disabled code to periodically reinstall synergy in the clipboard +chain. It was causing an infinite recursion in the WM_DRAWCLIPBOARD +message handler. + +---------- +2005/11/12 11:28:25 crs +lib/platform/CMSWindowsScreenSaver.cpp + +Changed a couple of logging messages to DEBUG2 from DEBUG. + +---------- +2005/11/12 11:27:55 crs +cmd/launcher/CAutoStart.cpp +cmd/launcher/CAutoStart.h +cmd/launcher/launcher.cpp + +Fixed saving of autostart configuration in win32 launcher. It +was erroring out when the user did not have permission to write +to that portion of the registry. Now it will only write the +configuration if synergy is installed for autostart. This is +still not quite right since it'll still error out if some other +user with sufficient permission has installed the autostart and +this user doesn't have enough permission. + +---------- +2005/11/12 11:22:34 crs +lib/common/Version.h + +Changed version to 1.2.6. + +---------- +2005/11/01 19:58:21 crs +acinclude.m4 + +Restored pthread signal configuration checks accidentally removed +in previous version. + +---------- +2005/10/12 21:37:58 crs +doc/running.html + +Updated setup documentation. + +---------- +2005/10/11 21:12:18 crs +lib/arch/CArchMultithreadPosix.cpp + +Fixed warning in posix multithread code. + +---------- +2005/10/11 20:59:29 crs +acinclude.m4 + +Updated autoconf script for detecting pthreads. + +---------- +2005/10/06 21:45:01 crs +configure.in +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h + +Added initial XKB code. This only checks for the extension in +the configuration, queries the extension at runtime and handles +mapping notify events. Still need to use the extension to get +the keyboard map. + +---------- +2005/10/02 17:40:56 crs +lib/platform/CXWindowsClipboard.cpp +lib/platform/CXWindowsClipboard.h +lib/platform/CXWindowsUtil.cpp +lib/platform/CXWindowsUtil.h + +Fixed X11 clipboard handling on 64-bit systems. Also fixed a +problem with detecting when a clipboard owner says a format is +unavailable. + +---------- +2005/10/01 19:26:57 crs +lib/server/CServer.cpp + +Fixed unnecessary comparison to NULL that chokes some compilers. + +---------- +2005/10/01 19:23:38 crs +cmd/launcher/CAdvancedOptions.cpp +cmd/launcher/CAutoStart.cpp +cmd/launcher/CAutoStart.h +cmd/launcher/launcher.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +dist/nullsoft/synergy.nsi +lib/arch/CArchDaemonWindows.cpp +lib/arch/CArchMiscWindows.cpp +lib/arch/CArchMiscWindows.h +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/CMSWindowsScreenSaver.cpp +lib/platform/CSynergyHook.cpp +lib/synergy/CKeyState.cpp +lib/synergy/CKeyState.h +lib/synergy/CPlatformScreen.cpp +lib/synergy/CPlatformScreen.h +lib/synergy/CScreen.cpp +lib/synergy/IKeyState.h +lib/synergy/IPlatformScreen.h + +Fixed several win32 bugs. Removed the 'Save' button from the +launcher; configuration is now saved transparently. Autostart +configuration is now saved transparently whenever it changes +and autostart is enabled. Configuration is now saved when the +debug logging level changes. + +Now releasing synthetically pressed keys when leaving the client. +The existing code was checking for key down using a local key +button id then trying to release it using the same id as a server +key button id. + +Now looking up a local button id for key events that have a scan +code of 0. These are sent by some programs that synthesize events. + +Now periodically reinstalling the clipboard chain viewer. It appears +that the chain is breaking sometimes (probably due a badly behaved +or killed app) but there's no way to detect that so this is a +workaround. + +Now stopping and deleting the autostart services during uninstall. + +---------- +2005/10/01 18:55:59 crs +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h + +Added support for apple's mighty mouse. We now correctly handle +button 4 (and 5 if it exists). We now detect the mighty mouse's +scroll ball and convert vertical motion into scroll wheel events. +Also changed handling of scroll wheel scaling; the mac works +differently from other platforms so it's not perfect. + +---------- +2005/10/01 15:01:15 crs +cmd/launcher/Makefile.am + +Added new files to makefiles. + +---------- +2005/09/26 20:58:53 crs +lib/server/CConfig.cpp + +Fixed bug in output of configuration files. Was writing bottom-right +corner as "bottom-left" instead of "bottom-right". + +---------- +2005/09/26 20:55:24 crs +cmd/launcher/CAddScreen.cpp +cmd/launcher/CAddScreen.h +cmd/launcher/CScreensLinks.cpp +cmd/launcher/CScreensLinks.h +cmd/launcher/launcher.cpp +cmd/launcher/launcher.dsp +cmd/launcher/launcher.rc +cmd/launcher/resource.h + +Refactored code in win32 launcher GUI. + +---------- +2005/08/22 19:40:34 crs +lib/common/Version.h + +Changed version to 1.2.5. + +---------- +2005/08/07 10:32:54 crs +doc/autostart.html + +Updated OS X autostart documentation. + +---------- +2005/08/07 10:32:19 crs +lib/arch/CArchMultithreadPosix.cpp +lib/arch/IArchMultithread.h + +Added support for SIGUSR2. Not using it yet, though. + +---------- +2005/08/03 21:03:49 crs +cmd/synergyc/synergyc.cpp + +Client now quits after failing to connect if not restartable. +Was failing to quit. + +---------- +2005/08/03 20:14:42 crs +cmd/synergyc/Makefile.am +cmd/synergys/Makefile.am +configure.in +lib/arch/Makefile.am +lib/platform/Makefile.am + +Changed how we export MACOSX_DEPLOYMENT_TARGET to a hopefully more +portable solution. + +---------- +2005/08/02 21:42:12 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp + +Fixed --daemon fix when compiled on unix. + +---------- +2005/08/02 20:57:52 crs +cmd/launcher/CGlobalOptions.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsDesks.h +lib/platform/CMSWindowsScreen.cpp +lib/server/CConfig.cpp +lib/synergy/OptionTypes.h + +Added option to suppress grabbing of the foreground window on win32 +synergy servers. Grabbing the foreground window prevents certain +apps from messing up low level keyboard events but doing that can +break certain workflow, particularly with games. Since the option +is only for win32 and it's in the launcher GUI and I hope to get rid +of it someday, there is no configuration documentation for it. + +---------- +2005/08/02 20:55:35 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp + +Fixed '--daemon' command line option on Windows NT family client and +server. Those platforms now ignore --daemon and --no-daemon like +they should. + +---------- +2005/08/01 22:34:05 crs +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsKeyState.h +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h +lib/platform/CXWindowsKeyState.cpp +lib/platform/CXWindowsKeyState.h +lib/server/CPrimaryClient.cpp +lib/synergy/CKeyState.h +lib/synergy/CPlatformScreen.cpp +lib/synergy/CPlatformScreen.h +lib/synergy/CScreen.cpp +lib/synergy/CScreen.h +lib/synergy/IKeyState.h +lib/synergy/IPlatformScreen.h + +Fixed scroll-lock screen locking on linux servers. An earlier +change made the function that returns modifier state return the +shadowed state not the current state. On X windows we don't +shadow the state when on the primary screen so the synergy server +would never see the scroll-lock state turned on until it left the +primary screen. As a result scroll-lock would not lock to the +primary screen but would lock you to whatever secondary screen +you ended up on. + +Changed win32 and OSX versions to work like the linux version. +Win32 used to work anyway because we constantly shadow keyboard +state on that. OSX did not query current keyboard state at all +before and it still doesn't; this needs to be fixed. + +---------- +2005/08/01 22:29:48 crs +lib/synergy/libsynergy.dsp + +Added ProtocolTypes.cpp to VC++ project. + +---------- +2005/08/01 21:02:07 crs +lib/common/common.h +synergy.xcode/project.pbxproj + +Changes to get gcc3.3, 10.2.8 SDK builds working on OS X. + +---------- +2005/07/31 22:50:10 crs +configure.in +lib/platform/COSXScreenSaver.cpp +lib/platform/COSXScreenSaver.mm +lib/platform/COSXScreenSaverUtil.h +lib/platform/COSXScreenSaverUtil.m +lib/platform/Makefile.am + +Removed Objective-C++ file. Now using an Objective-C file instead +which works better with autoconf/automake. + +---------- +2005/07/31 22:49:03 crs +lib/synergy/Makefile.am +lib/synergy/ProtocolTypes.cpp +lib/synergy/ProtocolTypes.h + +Fixed warnings on gcc 4.0. + +---------- +2005/07/31 22:48:30 crs +lib/common/Version.h + +Changed version to 1.2.4. + +---------- +2005/07/26 21:37:41 crs +lib/common/Version.h + +Changed version to 1.2.3. + +---------- +2005/07/26 21:37:27 crs +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsDesks.h + +Now disabling the win32 foreground window on the server when +leaving the server screen. This prevents the system from +messing up keyboard events. The console (command prompt) and +remote desktop are known to interfere with the shift key when +they have the focus. This introduces a bug where the window +that was the foreground is raised to the top of the stacking +order when the cursor enters the server screen. This should +only be a problem for Mouse-X (activation follows mouse) users. +I don't know how Mouse-X changes the foreground/active window +without raising it; if I did I'd fix this bug. + +---------- +2005/07/26 21:24:13 crs +doc/configuration.html + +Fixed typo in documentation. + +---------- +2005/07/26 19:58:37 crs +cmd/launcher/launcher.cpp + +Now defaulting to WARNING as the default debug level on win32. +This is a workaround for NOTE and higher always popping up the +command window, which can't be hidden or closed. When the +user clicks 'Test', the debug level is forced to be at least +INFO to workaround this workaround. Future versions will do +away with the command window altogether so this weirdness isn't +too much of a problem. + +---------- +2005/07/26 19:50:44 crs +doc/faq.html +doc/tips.html + +Improved documentation of how to get ctrl+alt+pause working on +Windows NT,2k,XP. + +---------- +2005/07/21 21:25:51 crs +lib/common/common.h + +Forcing use of select() rather than poll() on OS X. Wasn't +getting a read event on a socket when the remote side closed +down when using poll(). Probably a bug in synergy code but +I couldn't find it. + +---------- +2005/07/21 21:24:28 crs +lib/arch/CArchNetworkBSD.cpp + +Fixed "resource temporarily unavailable" exception on OS X when +using the unblock pipe. + +---------- +2005/07/20 22:09:05 crs +cmd/launcher/launcher.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +cmd/synergys/synergys.cpp +doc/configuration.html +lib/client/CClient.cpp +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/synergy/OptionTypes.h + +Added "dead" corners support. The cursor can't switch screens +from a dead corner. + +---------- +2005/07/19 20:43:51 crs +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h + +Applied patch (from Lorenz Schori) to fix getting the keyboard +layout. It previously was failing for non-Apple keyboards. + +---------- +2005/07/19 20:23:29 crs +lib/platform/COSXKeyState.cpp + +Replaced NULL with 0 in arithmetic expression. + +---------- +2005/05/08 11:40:26 crs +lib/platform/CMSWindowsKeyState.cpp + +Applied patch 1113363 to support the kanji key on win32. + +---------- +2005/05/08 11:08:12 crs +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkBSD.h +lib/arch/CArchNetworkWinsock.cpp +lib/arch/CArchNetworkWinsock.h +lib/arch/IArchNetwork.h +lib/net/CTCPListenSocket.cpp + +Added support for SO_REUSEADDR. It is always enabled. + +---------- +2005/05/08 11:00:42 crs +cmd/synergys/synergys.cpp + +Fixed bug in retrying to initialize or start the server. Was +waiting the retry interval then doing nothing. + +---------- +2005/04/29 21:27:10 crs +lib/common/common.h +lib/platform/COSXScreen.cpp +synergy.xcode/project.pbxproj + +A few OSX build fixes. + +---------- +2005/04/28 22:05:51 crs +lib/common/common.h + +Now setting HAVE_SOCKLEN_T to 1 when on OSX and _SOCKLEN_T is +defined and HAVE_CONFIG_H isn't is set. + +---------- +2005/04/28 22:04:23 crs +cmd/synergyc/Makefile.am +cmd/synergys/Makefile.am +configure.in +lib/arch/Makefile.am +lib/platform/Makefile.am + +Added support for MACOSX_DEPLOYMENT_TARGET. It's set to 10.2. + +---------- +2005/04/28 22:02:47 crs +cmd/synergys/synergys.cpp +lib/client/CClient.cpp + +Now reporting sleep suspend/resume at INFO level. + +---------- +2005/04/25 22:12:04 crs +cmd/synergyc/Makefile.am +cmd/synergyc/synergyc.cpp +cmd/synergys/Makefile.am +cmd/synergys/synergys.cpp +configure.in +lib/base/CEvent.cpp +lib/base/CEvent.h +lib/base/CEventQueue.cpp +lib/client/CClient.cpp +lib/client/CClient.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h +lib/platform/COSXScreenSaver.cpp +lib/platform/COSXScreenSaver.h +lib/platform/COSXScreenSaver.mm +lib/platform/Makefile.am +lib/platform/OSXScreenSaverControl.h +lib/server/CServer.cpp +lib/synergy/IScreen.cpp +lib/synergy/IScreen.h +synergy.xcode/project.pbxproj + +Added support on OS X for screensaver synchronization, +sleeping and fast user switching. OS X Server also now +captures global hot keys (cmd+tab, F9, etc.) and sends them +to the client. Changes mostly due to lorenz schori. Some +bug fixes (in lorenz's code and in synergy) and integration +into automake by crs (had to add support for Objective C++ +files) and the XCode project. + +This change also adds flags to events. Flags can cause events +to be dispatched immediately and to not free event data. Both +features are used in the new code. + +This change adds events on IScreen for notification of the +system going to sleep or waking up (or the user's session +being somehow suspended and restored). CClient and synergys +now listen for and respond to these events. CMSWindowsScreen +used to use IJobs for handling these events synchronously. It +now uses the new IScreen events and delivers them immediately. + +---------- +2005/01/26 18:45:45 crs +lib/common/Version.h + +Changed version to 1.2.2. + +---------- +2005/01/26 18:43:33 crs +lib/platform/COSXKeyState.cpp + +Fixed bug in handling modifier keys on OS X clients. Was applying +modifiers to modifiers yielding, for example: ctrl down, ctrl down, +ctrl up for press of the control key. The first down and the up were +there because we were applying the control modifier to the control +key. + +---------- +2005/01/26 18:41:28 crs +lib/platform/CXWindowsKeyState.cpp + +Fixed handling of ISO_Level3_Shift. We now prefer ISO_Level3_Shift +over Mode_switch if it's mapped to any key. ISO_Level3_Shift +replaces Mode_switch in newer versions of X and Mode_switch does +nothing, so we have to use ISO_Level3_Shift if it's there. + +---------- +2005/01/04 19:29:58 crs +lib/platform/COSXKeyState.cpp + +Fixed bug in OS X server key translation which pretty much broke any +keystroke with a modifier key. + +---------- +2005/01/01 20:52:43 crs +doc/compiling.html + +Merged documentation fixes. + +---------- +2005/01/01 20:19:42 crs +doc/running.html + +Merged documentation fix from mainline. + +---------- +2004/12/30 13:28:51 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp +doc/authors.html +lib/client/CClient.cpp +lib/net/CNetworkAddress.cpp +lib/net/CNetworkAddress.h +lib/server/CConfig.cpp + +Adapted and applied patch by Brent Priddy for re-resolving the server +hostname on each connection. This allows the client to startup +without being able to resolve the server's hostname. It also lets +it handle changes in the server's address, a typical scenario when +the client is a laptop moving between networks. + +---------- +2004/12/30 12:10:47 crs +lib/platform/CMSWindowsKeyState.cpp +lib/synergy/KeyTypes.h + +Added Henkan key. Patch from rniitani at sourceforge.net. + +---------- +2004/12/30 11:54:23 crs +doc/authors.html +lib/platform/CXWindowsScreen.cpp + +Applied patch from Tom Chadwick to synthesize PageUp/PageDown on +X servers that don't support the mouse wheel. + +---------- +2004/12/29 21:12:05 crs +lib/platform/CMSWindowsScreen.cpp + +Now ignoring 4th and 5th mouse buttons if they don't exist. Was +previously querying their state, sometimes getting the wrong +answer from the OS that they were down, which prevented switching +screens. + +---------- +2004/12/29 21:10:49 crs +lib/platform/CMSWindowsKeyState.cpp + +Fixed handling of number pad number and decimal point keys when +NumLock is on on client on windows 95 family. + +---------- +2004/12/29 17:53:44 crs +lib/platform/CXWindowsScreen.cpp + +Added support for ISO_Level3_Shift on X windows server. It's +treated as if it were Mode_switch. + +---------- +2004/12/29 17:07:08 crs +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h + +Added support for unicode keyboard layouts on OS X. + +---------- +2004/12/29 17:06:49 crs +lib/platform/COSXScreen.cpp + +Removed calls to show/hide mouse because they only work if we've +taken exclusive access to the display and we don't do that. + +---------- +2004/12/29 17:06:00 crs +lib/platform/COSXEventQueueBuffer.cpp +lib/platform/COSXEventQueueBuffer.h + +Fixed leak of event objects on OS X. + +---------- +2004/12/29 17:00:17 crs +//depot/project/synergy-web/Makefile +//depot/project/synergy-web/autostart.htmls +//depot/project/synergy-web/synergy.css +doc/autostart.html +doc/synergy.css + +Added Mac OS X autostart documentation from Tor Slettnes (tor@slett.net). + +---------- +2004/11/12 15:50:04 crs +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h + +Now suppressing shift key when caps-lock is enabled on OSX. This +fixes handling of, say, Command+N with caps-lock enabled which +was being synthesized as Command+Shift+N. + +---------- +2004/11/11 19:23:14 crs +lib/arch/CArchMultithreadPosix.cpp + +Fixed a serious flaw in wrapper for posix condition variable wait +function. Because synergy doesn't use posix cancellation, it +cannot wake up a thread waiting on a condition variable. So +the wrapper would wake up periodically to test if the thread was +cancelled (according to synergy's cancellation state) then go +back to waiting. Well the condition could be signalled while +we're testing and be lost and we'd never return from the wait. +So now we wake up after a maximum timeout and return to the +caller. The caller must check for this spurious wakeup but all +callers should do this anyway so we're okay there. + +---------- +2004/11/11 19:17:03 crs +lib/net/CSocketMultiplexer.cpp +lib/net/CSocketMultiplexer.h + +Changed scheme used to lock the socket multiplexer's job list. +I think the new scheme is easier to understand. It should have +exactly the same behavior. + +---------- +2004/11/10 21:00:30 crs +lib/mt/CCondVar.h + +Made condition variable data volatile. This will hopefully fix +an strange deadlock seen on OSX. The CSocketMultiplexer deadlocks +with two threads, one waiting for m_polling to become false and +the other waiting for m_pollable to become true. The weird part +is that they're both false so the first thread should proceed. +It either didn't receive the broadcast when m_polling went to +false or it's not really checking the actual value of that flag. +I can't see how the former is possible and this change fixes the +latter. + +---------- +2004/11/10 19:11:33 crs +lib/platform/CXWindowsUtil.cpp + +Fixed typo. + +---------- +2004/11/09 20:05:33 crs +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h + +Fixed modifier handling on OSX client. Had hardcoded one set of +modifiers for all keys for testing purposes and forgotton to fix +that. Now choosing required modifiers per key. This fixes +shift+arrow keys suppressing the shift key and, i think, the +option key not working. + +---------- +2004/11/09 18:42:47 crs +lib/common/Version.h + +Changed version to 1.1.10. + +---------- +2004/11/09 18:38:14 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp + +Added synergy version number to first log message. + +---------- +2004/11/09 18:31:54 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp + +Moved log message that prints system info to after installation +of user requested log level so it can be filtered. + +---------- +2004/11/06 16:29:06 crs +lib/client/CServerProxy.cpp + +Attempt to workaround laggy mouse on OS X with linux as server. + +---------- +2004/11/06 16:13:52 crs +lib/arch/CArchMiscWindows.cpp +lib/arch/CArchMiscWindows.h +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsDesks.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h + +Fixed screensaver detection on XP. + +---------- +2004/11/06 16:13:01 crs +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsKeyState.h + +Fixed handling of number pad keys with num-lock off. Was +synthesizing events for the numbers even with num-lock off. Now +synthesizing the cursor movements. + +---------- +2004/11/06 16:11:39 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp + +Fixed console appearing when running synergy as a service. This +was introduced with the change to print system info to the start +of the log. This message was printed before the service installed +the log handler that directs messages to the event log. + +---------- +2004/11/04 21:26:43 crs +lib/platform/CXWindowsKeyState.cpp +lib/platform/CXWindowsKeyState.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/platform/CXWindowsUtil.cpp +lib/platform/CXWindowsUtil.h + +Added support for X11 compose key (Multi_key). This change fixes +the handling of compose key sequences. The key presses were +suppressed but not the corresponding releases, confusing the +clients. It also adds support for generating keysyms via the +compose key if the necessary dead keys or Mode_switch are not +available. + +---------- +2004/11/02 20:50:36 crs +doc/running.html + +Added documentation for -display option. + +---------- +2004/11/02 20:43:55 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h + +Added -display option for X11 version. + +---------- +2004/11/01 22:26:52 crs +lib/platform/CSynergyHook.cpp + +Reverted change to detach threads in hook DLL. It was breaking +double clicking. + +---------- +2004/11/01 22:26:02 crs +cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp +cmd/synergys/resource.h +cmd/synergys/synergys.rc + +Added tray menu item on win32 to force clients to reconnect. + +---------- +2004/11/01 22:25:39 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp +configure.in +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchFileWindows.cpp +lib/arch/CArchSystemWindows.cpp +lib/arch/CArchSystemWindows.h +lib/arch/IArchSystem.h +lib/arch/Makefile.am +lib/arch/arch.dsp + +Added operating system identification log message for debugging +purposes. + +---------- +2004/11/01 22:10:34 crs +lib/platform/CMSWindowsDesks.cpp + +Added debugging output to check window class of active window +when leaving screen. This may help determine how to avoid +DOS command prompt windows being in the foreground when leaving +the screen since they suppress handling of the shift key. + +---------- +2004/11/01 18:26:29 crs +lib/platform/CMSWindowsEventQueueBuffer.cpp + +Fixed synergy quiting when powerdvd stops playing a DVD. This may +fix some other bugs that involve unexpectedly quiting. The problem +was that synergy would (cleanly) quit when receiving an event with +a message id of 0 when not running as a service. + +---------- +2004/11/01 18:24:37 crs +lib/platform/CMSWindowsDesks.cpp + +Fixed multimon support for win NT/2000/XP running as client. Mouse +would jump between two points. + +---------- +2004/11/01 18:22:45 crs +lib/platform/CMSWindowsScreenSaver.cpp + +Fixed a resource leak. Also fixed the detection of the screen saver +closing on windows 2000 and XP. + +---------- +2004/11/01 18:21:00 crs +cmd/launcher/CAdvancedOptions.cpp +cmd/launcher/CAdvancedOptions.h +cmd/launcher/launcher.rc +cmd/launcher/resource.h + +Added option to set the listen address via the win32 GUI. This +allows the user to specify one (and only one) network interface +to listen on. + +---------- +2004/10/30 16:41:36 crs +lib/platform/CXWindowsKeyState.cpp + +Changed X11 key mapping to fallback to the modifier keysym with +the opposite handedness if the desired handedness is missing. +For example, if Alt_R is unmapped as it often is on keyboards +with Mode_switch, the client will use Alt_L if it is mapped +when told to synthesize Alt_R. This should fix handling of +AltGr sent from a win32 server. AltGr is literally just +Control_L and Alt_R and, previously, an X11 client without +Alt_R mapped would only simulate Control_L. This does not +address the fact that the user may really just want the Alt +modifier; there are already hueristics to attempt to figure +that out. + +---------- +2004/10/30 16:16:32 crs +configure.in +lib/platform/CXWindowsScreenSaver.cpp + +Improved X extension detection in configure and added handling +of dpms.h headers that don't have function prototypes. + +---------- +2004/10/28 21:40:56 crs +configure.in +lib/arch/CArchNetworkBSD.h +lib/common/stdistream.h +lib/common/stdostream.h +lib/common/stdsstream.h +lib/platform/CXWindowsClipboard.h +lib/platform/CXWindowsEventQueueBuffer.h +lib/platform/CXWindowsKeyState.cpp +lib/platform/CXWindowsKeyState.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/platform/CXWindowsScreenSaver.cpp +lib/platform/CXWindowsScreenSaver.h +lib/platform/CXWindowsUtil.h + +Fixed bugs in configuration. Wasn't doing configuration for DPMS +and Xinerama correctly. Also was using '#if defined(...)' instead +of '#if ...' for testing configure macros in some places. This +yields the wrong answer if the macro is set to 0, which means +missing/disabled. + +---------- +2004/10/27 21:46:22 crs +lib/arch/CArchFileUnix.cpp + +Fixed use of freed memory. + +---------- +2004/10/27 21:38:05 crs +lib/platform/CSynergyHook.cpp + +Now detaching hook thread after event processing. This may fix +problems with the Alt key being synthetically down when using +the back and forward bindings on a mouse with extra buttons. + +---------- +2004/10/27 21:29:19 crs +lib/platform/CSynergyHook.cpp + +Fixed bug in mouse wheel handling. Was reacting with mouse wheel +events when receiving any event with message == 0 when the system +doesn't use old style mouse wheel events. Some programs (especially +the flash plugin) would send events with message == 0 causing +undesired wheel scrolling. + +---------- +2004/10/27 21:22:36 crs +lib/platform/COSXScreen.cpp + +Fixed problem with multimonitor on OS X. The bug was simply that +the cursor wasn't being parked in the center of the main screen +but instead at the center of the total display surface. This could +place it off or dangerously close to the edge of the transparent +window that covers the main screen and prevent synergy from capturing +mouse motion. + +---------- +2004/10/24 18:18:21 crs +lib/platform/CXWindowsScreen.cpp + +Added eject key mapping. + +---------- +2004/10/24 18:18:11 crs +lib/platform/CXWindowsUtil.cpp + +Fixed comment. + +---------- +2004/10/24 18:18:01 crs +lib/platform/CXWindowsKeyState.cpp +lib/synergy/CKeyState.cpp +lib/synergy/CKeyState.h + +Fixed dead key and AltGr+shift handling on X windows. Also fixed +bug with decomposing characters that have no direct key mapping +but do have a direct key mapping for the character with the opposite +case. + +---------- +2004/10/24 18:15:33 crs +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h +lib/platform/COSXScreen.cpp + +Made OS X key mapping dynamic based on current key layout. It +now includes full support for dead keys and non-ascii glyph keys. + +---------- +2004/10/24 18:14:18 crs +lib/synergy/KeyTypes.h + +Added eject and sleep key IDs. + +---------- +2004/10/24 18:12:38 crs +lib/platform/CMSWindowsKeyState.cpp + +Added VK_SLEEP. + +---------- +2004/10/23 19:43:37 crs +lib/synergy/CKeyState.cpp +lib/synergy/CKeyState.h +lib/synergy/CScreen.cpp + +Previous half-duplex fix fixed secondary screens with half +duplex keys but broke primary screens. This fixes both and +also ensures that the primary screen updates its shadow toggle +modifier state when leaving so the secondary screens get the +correct toggle modifier state. Together these fix some strange +inconsistencies in toggle state across screens. + +---------- +2004/10/23 18:40:31 crs +lib/synergy/CKeyState.cpp + +Fixed bug in half-duplex keys. Was updating their toggled state +om every release as well as press. + +---------- +2004/10/13 20:39:22 crs +doc/configuration.html + +Fixed typo in the documentation of configuration options. + +---------- +2004/09/29 21:59:26 crs +cmd/synergyc/CClientTaskBarReceiver.cpp +cmd/synergyc/CClientTaskBarReceiver.h +cmd/synergys/CServerTaskBarReceiver.cpp +cmd/synergys/CServerTaskBarReceiver.h +configure.in +lib/arch/CArchMultithreadPosix.cpp +lib/base/CEventQueue.cpp +lib/base/CEventQueue.h +lib/base/IEventQueue.h +lib/io/CStreamFilter.cpp +lib/io/CStreamFilter.h +lib/io/IStream.h +lib/net/CTCPSocket.cpp +lib/net/CTCPSocket.h +lib/net/IDataSocket.h +lib/server/CClientListener.cpp +lib/synergy/CPacketStreamFilter.cpp +lib/synergy/CPacketStreamFilter.h + +Removed recursive mutexes. Simplified stream filters as a side +effect. Removed -D_BSD_SOURCE and -D_XOPEN_SOURCE=500 from +compile since they're not longer necessary. + +---------- +2004/09/28 22:25:35 crs +lib/server/CConfig.cpp + +Now accepting screen names that end in a dot since many OS X +users have misconfigured their systems to have hostnames that +end in a dot. + +---------- +2004/09/28 22:19:24 crs +dist/nullsoft/installer.mak + +Fixed error in win32 installer packaging. + +---------- +2004/09/28 22:19:11 crs +cmd/launcher/launcher.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +doc/configuration.html +doc/faq.html +lib/server/CConfig.cpp +lib/synergy/CScreen.cpp + +Added half-duplex option for scroll lock key. + +---------- +2004/09/27 21:54:49 crs +lib/platform/CXWindowsScreen.cpp +lib/synergy/IPlatformScreen.h + +Fixed compile on gcc 3.4 and later. gcc started doing access checks +for class visibility on pointers to member function 'using the +qualifying scope of the name itself.' what this means is if method +'prot' is declared protected in class A and B inherits from A then +a method in B cannot use &A::prot but can use &B::prot. Synergy +now does this in the one place it had not. + +---------- +2004/09/27 21:23:47 crs +lib/arch/CArchMultithreadPosix.cpp + +Worked around minor gcc 3.3.2 -O3 compiler bug. + +---------- +2004/09/27 21:04:37 crs +cmd/synergys/synergys.cpp + +Server now reports configuration file errors as ERROR level log +messages rather than DEBUG level. Missing files are still reported +as DEBUG level messages since we expect some to be missing. + +---------- +2004/09/27 20:53:54 crs +configure.in +lib/common/Version.h + +Changed version to 1.1.9. Changed configure.in to get version +number from lib/common/Version.h so it only has to be changed +there. + +---------- +2004/08/05 20:42:51 crs +dist/nullsoft/synergy.nsi + +Fixed missing license file and PORTING in win32 installer build. + +---------- +2004/08/04 22:48:30 crs +synergy.xcode/project.pbxproj + +Removed cross-compile setting from project. + +---------- +2004/08/04 22:48:08 crs +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h +lib/platform/COSXScreen.cpp +lib/synergy/KeyTypes.h + +Fixed handling of auto-repeat, ctrl + character key, and button +to KeyCode translation. + +---------- +2004/08/03 22:02:57 crs +lib/platform/COSXKeyState.cpp + +Fixed space key on OS X server -> client. + +---------- +2004/08/03 21:14:30 crs +lib/arch/CArchMultithreadPosix.cpp + +Fixed warnings in deployment build style on OS X. + +---------- +2004/08/01 17:37:28 crs +doc/faq.html +doc/index.html +doc/running.html + +Fixed errors in new docs markup. + +---------- +2004/08/01 17:37:11 crs +dist/nullsoft/synergy.nsi + +Updated win32 installer to use new docs. + +---------- +2004/08/01 17:36:53 crs +COPYING + +Reverted COPYING so win32 installer build can use it. + +---------- +2004/08/01 16:31:47 crs +Makefile.am +dist/nullsoft/installer.mak +dist/nullsoft/synergy.nsi +dist/rpm/synergy.spec.in + +Updated packagers to handle new documentation. + +---------- +2004/08/01 16:04:21 crs +AUTHORS +COPYING +INSTALL +NEWS + +Added files required by automake. They simply reference the +corresponding HTML file. + +---------- +2004/08/01 16:00:18 crs +AUTHORS +BUGS +COPYING +FAQ +HISTORY +INSTALL +Makefile.am +NEWS +PORTING +README +TODO +configure.in +doc/Makefile.am +doc/PORTING +doc/authors.html +doc/autostart.html +doc/compiling.html +doc/configuration.html +doc/developer.html +doc/faq.html +doc/history.html +doc/index.html +doc/license.html +doc/news.html +doc/running.html +doc/security.html +doc/synergy.css +doc/tips.html +doc/todo.html + +Updated documentation. Converted most documation to HTML. + +---------- +2004/07/31 14:34:02 crs +INSTALL +PORTING +README +TODO + +Documentation changes. + +---------- +2004/07/31 11:19:39 crs +acinclude.m4 +lib/arch/CArchNetworkBSD.cpp +lib/platform/CXWindowsEventQueueBuffer.cpp + +Now using instead of . Also added a bit +to autoconf to ensure we don't use poll on OS X. + +---------- +2004/07/29 22:11:27 crs +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h + +Fixed handling of modifier keys on OS X. Also made OS X client +ignore small mouse wheel events (which seem to get sent by some +win32 systems). Other platforms were already ignoring them. + +---------- +2004/07/29 22:09:28 crs +cmd/synergys/synergys.cpp + +Worked around bug in ifstream::operator!() on OS X. + +---------- +2004/07/29 21:59:26 crs +lib/platform/CSynergyHook.cpp + +Fixed handling of ctrl and alt keys when using low level hooks. +They were being discarded so the server wouldn't correctly send +ctrl, alt, or AltGr to clients. + +---------- +2004/07/29 21:57:42 crs +lib/platform/CMSWindowsScreenSaver.cpp + +Added comment about a problem detecting the screen saver. + +---------- +2004/07/29 21:53:30 crs +lib/platform/CMSWindowsKeyState.cpp + +Worked around bogus key mapping on 95/98/me where multiple virtual +keys are mapped to the same button. For example, the backslash +virtual key shares a button with some other virtual key on british +english key mappings. Synergy could end up using the wrong virtual +key. In the given case, the other virtual key produced no character +at all. To determine which virtual key should really be mapped to +a button we map the button back to a virtual key and see if it's the +virtual key we started with. + +Also fixed mapping of pause key. Previously, windows+pause sent to +a win32 client wouldn't bring up system properties like it should. + +---------- +2004/07/29 21:50:17 crs +lib/platform/CMSWindowsDesks.cpp + +Synergy now steals window activation when using low level hooks +and a console window is the active window. This is to work around +console windows preventing the hook from detecting the shift key. + +---------- +2004/07/29 21:48:40 crs +lib/net/CTCPSocket.cpp + +Worked around bug/weirdness on OS X. It's reporting that a +non-blocking connect is available for writing (i.e. the connection +was successful) when the connection has actually failed. This +caused synergy to rapidly retry connecting. This change makes +synergy check for a connection error whether one was reported or +not. Thankfully, OS X does correctly set the socket error state +when it bogusly reports a successful connection so we can tell the +connection failed. + +---------- +2004/07/28 21:54:39 crs +lib/net/CTCPSocket.cpp + +Added workaround for apparent bug in OS X 10.3.4. If you started +a synergy client on that OS and pointed it at a system that wasn't +listening for connections then instead of the connection attempt +failing with 'connection refused' the system would claim the +connection succeeded. A subsequent read would reveal the problem +and synergy would "disconnect" and retry, causing the CPU to spin. +The system does correctly set the socket error state so this +workaround checks for socket errors when connecting whether or not +select reports an error state. + +Also, sometimes the system doesn't claim success but doesn't report +an error. Synergy eventually times out these attempts. + +---------- +2004/07/25 14:18:50 crs +configure.in +lib/common/Version.h + +Changed version to 1.1.8. + +---------- +2004/06/22 21:11:14 crs +lib/platform/CXWindowsScreen.cpp + +Disable key event capture on X11. This was going to be used to +detect synergy hotkeys but a design flaw in X11 makes it problematic +with many applications. We'll have to fall back to the more +traditional XGrabKey when the time comes. + +---------- +2004/06/16 21:07:48 crs +synergy.xcode/project.pbxproj + +Added NDEBUG to and removed debugging symbols from XCode deployment +project. + +---------- +2004/06/13 17:11:19 crs +ChangeLog +NEWS + +Updated documentation. + +---------- +2004/06/12 20:48:04 crs +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsDesks.h +lib/platform/CMSWindowsScreenSaver.cpp +lib/platform/CMSWindowsScreenSaver.h + +(Maybe) fixed a problem detecting when win32 screen saver started. + +---------- +2004/06/12 20:46:35 crs +lib/arch/CMultibyte.cpp + +Fixed bug in converting wide characters to multibyte. + +---------- +2004/06/10 21:25:09 crs +lib/client/CClient.cpp + +Fixed assertion failure when client connection fails immediately. + +---------- +2004/06/10 19:56:35 crs +lib/arch/CArchNetworkBSD.cpp + +Changed O_NDELAY to O_NONBLOCK. On some versions of Unix, read +return 0 when O_NDELAY is set and there is nothing to read. We +want the O_NONBLOCK behavior where read returns -1 and sets +errno to EAGAIN when there is nothing to read. + +---------- +2004/06/10 19:42:01 crs +lib/common/Makefile.am + +Added OS X precompiled header file for XCode compiles. + +---------- +2004/06/10 19:39:07 crs +cmd/launcher/CAutoStart.cpp + +Removed dependency of service on Browser. Browser isn't always +available and, if it's not, synergy won't start. Users may have +to use an IP server address instead of a hostname since the +service may start before the service that resolves hostnames. +If I knew what that service was I'd depend on it instead. + +---------- +2004/06/10 19:32:40 crs +configure.in +lib/common/Version.h + +Changed version to 1.1.7. + +---------- +2004/06/07 21:06:49 crs +lib/platform/CXWindowsEventQueueBuffer.cpp + +Failed to reset flag in X11 event queue buffer and that could cause +multiple threads to access the X display connection simultaneously +which causes synergy to die. + +---------- +2004/05/26 19:23:32 crs +lib/common/MacOSXPrecomp.h +lib/common/common.h +lib/platform/COSXClipboardTextConverter.cpp +lib/platform/COSXClipboardTextConverter.h +lib/platform/COSXEventQueueBuffer.cpp +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h +synergy.xcode/project.pbxproj + +Merged Bertrand's OS X changes. Also added support for mouse wheel +on OS X server. + +---------- +2004/05/18 20:32:13 crs +lib/platform/CMSWindowsScreen.cpp + +If the server manages to detect ctrl+alt+del it will no longer send +that to the client. If it did then the user could see the effect of +ctrl+alt+del on both the server and client which we never want. The +user can use ctrl+alt+pause to emulate ctrl+alt+del on the client. + +---------- +2004/05/18 20:26:48 crs +lib/platform/CXWindowsEventQueueBuffer.cpp +lib/platform/CXWindowsEventQueueBuffer.h + +Fixed bug that could allow multiple threads to simultaneously access +the X11 display connection. The only problematic method was +CXWindowsEventQueueBuffer::addEvent given that the other event queue +methods are only called from the main thread. + +---------- +2004/05/17 21:55:55 crs +cmd/synergyc/synergyc.cpp + +Fixed logging of connection to server. Was DEBUG now NOTE. + +---------- +2004/05/17 21:55:38 crs +lib/platform/CMSWindowsKeyState.cpp + +Fixed ctrl+alt+del emulation on win32 server. It was mapping +VK_DELETE to the keypad delete key. This key is not interpreted +on the client as causing ctrl+alt+del. + +---------- +2004/05/16 18:04:36 crs +lib/client/CServerProxy.cpp +lib/server/CClientProxy1_0.cpp +lib/synergy/ProtocolTypes.h + +Fixed handling of screen resolution changes. + +---------- +2004/05/16 18:03:36 crs +cmd/launcher/CAutoStart.cpp + +Changed (win NT) service to depend on the 'Browser' service to +ensure correct startup order. + +---------- +2004/05/16 18:02:49 crs +all.dsp +cmd/exec.dsp +cmd/launcher/launcher.dsp +cmd/synergyc/synergyc.dsp +cmd/synergys/synergys.dsp +dist/nullsoft/installer.dsp +dist/nullsoft/synergy.nsi +lib/arch/arch.dsp +lib/base/base.dsp +lib/client/client.dsp +lib/common/common.dsp +lib/io/io.dsp +lib/mt/mt.dsp +lib/net/net.dsp +lib/platform/makehook.dsp +lib/platform/platform.dsp +lib/platform/synrgyhk.dsp +lib/server/server.dsp +lib/synergy/libsynergy.dsp + +Changed VC++ projects to put release targets in ./build, debug +targets in ./debug, and intermediate files under ./gen. + +---------- +2004/05/15 19:44:05 crs +configure.in +lib/common/Version.h + +Changed version to 1.1.6. + +---------- +2004/05/15 19:43:33 crs +lib/platform/CMSWindowsScreen.cpp + +Avoided duplicate logging of screen size on win32. + +---------- +2004/05/15 19:41:46 crs +Makefile.am +configure.in +lib/common/common.h +lib/platform/COSXClipboard.cpp +lib/platform/COSXClipboard.h +lib/platform/COSXClipboardAnyTextConverter.cpp +lib/platform/COSXClipboardAnyTextConverter.h +lib/platform/COSXClipboardTextConverter.cpp +lib/platform/COSXClipboardTextConverter.h +lib/platform/COSXClipboardUTF16Converter.cpp +lib/platform/COSXClipboardUTF16Converter.h +lib/platform/COSXEventQueueBuffer.cpp +lib/platform/COSXEventQueueBuffer.h +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h +lib/platform/Makefile.am +synergy.xcode/project.pbxproj + +Added bertrand landry hetu's mac OS X port to date. + +---------- +2004/05/12 20:28:00 crs +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h + +Now restoring input focus when entering the screen to the window +that had the focus when the screen was left. + +---------- +2004/05/12 19:50:58 crs +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkWinsock.cpp + +Fixed thread reference leak in network code. + +---------- +2004/05/12 19:12:28 crs +configure.in + +Added configure option to enable debug builds. If not enabled then +asserts are disabled. + +---------- +2004/05/12 18:54:03 crs +lib/platform/CXWindowsClipboardBMPConverter.cpp + +Fixed build error in gcc 3.3. + +---------- +2004/05/26 19:23:32 crs +lib/common/MacOSXPrecomp.h +lib/common/common.h +lib/platform/COSXClipboardTextConverter.cpp +lib/platform/COSXClipboardTextConverter.h +lib/platform/COSXEventQueueBuffer.cpp +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h +synergy.xcode/project.pbxproj + +Merged Bertrand's OS X changes. Also added support for mouse wheel +on OS X server. + +---------- +2004/05/18 20:32:13 crs +lib/platform/CMSWindowsScreen.cpp + +If the server manages to detect ctrl+alt+del it will no longer send +that to the client. If it did then the user could see the effect of +ctrl+alt+del on both the server and client which we never want. The +user can use ctrl+alt+pause to emulate ctrl+alt+del on the client. + +---------- +2004/05/18 20:26:48 crs +lib/platform/CXWindowsEventQueueBuffer.cpp +lib/platform/CXWindowsEventQueueBuffer.h + +Fixed bug that could allow multiple threads to simultaneously access +the X11 display connection. The only problematic method was +CXWindowsEventQueueBuffer::addEvent given that the other event queue +methods are only called from the main thread. + +---------- +2004/05/17 21:55:55 crs +cmd/synergyc/synergyc.cpp + +Fixed logging of connection to server. Was DEBUG now NOTE. + +---------- +2004/05/17 21:55:38 crs +lib/platform/CMSWindowsKeyState.cpp + +Fixed ctrl+alt+del emulation on win32 server. It was mapping +VK_DELETE to the keypad delete key. This key is not interpreted +on the client as causing ctrl+alt+del. + +---------- +2004/05/16 18:04:36 crs +lib/client/CServerProxy.cpp +lib/server/CClientProxy1_0.cpp +lib/synergy/ProtocolTypes.h + +Fixed handling of screen resolution changes. + +---------- +2004/05/16 18:03:36 crs +cmd/launcher/CAutoStart.cpp + +Changed (win NT) service to depend on the 'Browser' service to +ensure correct startup order. + +---------- +2004/05/16 18:02:49 crs +all.dsp +cmd/exec.dsp +cmd/launcher/launcher.dsp +cmd/synergyc/synergyc.dsp +cmd/synergys/synergys.dsp +dist/nullsoft/installer.dsp +dist/nullsoft/synergy.nsi +lib/arch/arch.dsp +lib/base/base.dsp +lib/client/client.dsp +lib/common/common.dsp +lib/io/io.dsp +lib/mt/mt.dsp +lib/net/net.dsp +lib/platform/makehook.dsp +lib/platform/platform.dsp +lib/platform/synrgyhk.dsp +lib/server/server.dsp +lib/synergy/libsynergy.dsp + +Changed VC++ projects to put release targets in ./build, debug +targets in ./debug, and intermediate files under ./gen. + +---------- +2004/05/15 19:44:05 crs +configure.in +lib/common/Version.h + +Changed version to 1.1.6. + +---------- +2004/05/15 19:43:33 crs +lib/platform/CMSWindowsScreen.cpp + +Avoided duplicate logging of screen size on win32. + +---------- +2004/05/15 19:41:46 crs +Makefile.am +configure.in +lib/common/common.h +lib/platform/COSXClipboard.cpp +lib/platform/COSXClipboard.h +lib/platform/COSXClipboardAnyTextConverter.cpp +lib/platform/COSXClipboardAnyTextConverter.h +lib/platform/COSXClipboardTextConverter.cpp +lib/platform/COSXClipboardTextConverter.h +lib/platform/COSXClipboardUTF16Converter.cpp +lib/platform/COSXClipboardUTF16Converter.h +lib/platform/COSXEventQueueBuffer.cpp +lib/platform/COSXEventQueueBuffer.h +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h +lib/platform/Makefile.am +synergy.xcode/project.pbxproj + +Added bertrand landry hetu's mac OS X port to date. + +---------- +2004/05/12 20:28:00 crs +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h + +Now restoring input focus when entering the screen to the window +that had the focus when the screen was left. + +---------- +2004/05/12 19:50:58 crs +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkWinsock.cpp + +Fixed thread reference leak in network code. + +---------- +2004/05/12 19:12:28 crs +configure.in + +Added configure option to enable debug builds. If not enabled then +asserts are disabled. + +---------- +2004/05/12 18:54:03 crs +lib/platform/CXWindowsClipboardBMPConverter.cpp + +Fixed build error in gcc 3.3. + +---------- +2004/05/04 20:45:06 crs +cmd/launcher/CGlobalOptions.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h + +Added GUI for relative mouse moves option on win32. + +---------- +2004/05/04 19:44:51 crs +configure.in + +Configured default mac to build for X windows instead of the incomplete +carbon implementation. + +---------- +2004/05/04 19:37:46 crs +lib/net/CTCPSocket.cpp + +Fixed bug in TCP socket that caused a busy loop in the socket +multiplexer. That caused a lock up on windows when quitting +the server with a client connected. + +---------- +2004/05/03 21:14:01 crs +lib/platform/CXWindowsClipboardAnyBitmapConverter.cpp +lib/platform/CXWindowsClipboardBMPConverter.cpp + +Fixed X11 BMP and other bitmap conversion. Had data alignment +problems. + +---------- +2004/05/02 21:31:19 crs +lib/base/CUnicode.cpp +lib/platform/CXWindowsClipboard.cpp +lib/platform/CXWindowsClipboardAnyBitmapConverter.cpp +lib/platform/CXWindowsClipboardAnyBitmapConverter.h +lib/platform/CXWindowsClipboardBMPConverter.cpp +lib/platform/CXWindowsClipboardBMPConverter.h +lib/platform/CXWindowsClipboardHTMLConverter.cpp +lib/platform/CXWindowsClipboardHTMLConverter.h +lib/platform/Makefile.am +lib/synergy/IClipboard.h + +Added image/bmp and text/html support to X11. + +---------- +2004/05/02 16:13:11 crs +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h + +Relative mouse motion for OS X. + +---------- +2004/05/02 16:06:04 crs +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h + +Used MouseKeys accessibility function to show the mouse cursor +on a secondary screen when there's no physical mouse attached to +the system. Kinda flaky when a mouse is attached or detached but +seems to work well enough when the device is not attached to start +with and not attached while running synergy. + +---------- +2004/05/02 16:01:59 crs +cmd/launcher/CAutoStart.cpp +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchDaemonNone.cpp +lib/arch/CArchDaemonNone.h +lib/arch/CArchDaemonWindows.cpp +lib/arch/CArchDaemonWindows.h +lib/arch/IArchDaemon.h + +Added support for daemon startup dependencies. Made synergy +dependent on NetBT, which I hope is right. + +---------- +2004/05/02 16:00:45 crs +lib/synergy/IClipboard.h + +Fixed comment about canonical bitmap clipboard format. + +---------- +2004/05/02 08:04:48 crs +lib/platform/CMSWindowsClipboard.cpp +lib/platform/CMSWindowsClipboardBitmapConverter.cpp +lib/platform/CMSWindowsClipboardBitmapConverter.h +lib/platform/CMSWindowsClipboardHTMLConverter.cpp +lib/platform/CMSWindowsClipboardHTMLConverter.h +lib/platform/platform.dsp +lib/server/server.dsp +lib/synergy/IClipboard.h + +Added win32 clipboard support for images and HTML. Still need X11 +support. + +---------- +2004/05/02 08:04:15 crs +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsDesks.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h + +Added relative mouse move support to win32. + +---------- +2004/05/02 08:03:49 crs +lib/client/CServerProxy.cpp +lib/client/CServerProxy.h + +Forgot to change the client to handle relative moves. + +---------- +2004/05/01 16:10:09 crs +lib/platform/CXWindowsClipboard.cpp +lib/platform/CXWindowsUtil.cpp +lib/platform/CXWindowsUtil.h + +X11 clipboard logging now also prints atom names, not just numbers. + +---------- +2004/05/01 15:19:53 crs +lib/server/CClientProxy1_2.cpp +lib/server/CClientProxy1_2.h + +Added files forgotten in previous checkin. + +---------- +2004/05/01 15:18:59 crs +lib/client/CClient.cpp +lib/client/CClient.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/server/CClientProxy.h +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CClientProxyUnknown.cpp +lib/server/CConfig.cpp +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/server/Makefile.am +lib/synergy/CPlatformScreen.h +lib/synergy/CScreen.cpp +lib/synergy/CScreen.h +lib/synergy/IClient.h +lib/synergy/IPlatformScreen.h +lib/synergy/ISecondaryScreen.h +lib/synergy/OptionTypes.h +lib/synergy/ProtocolTypes.h + +Added support for a global relative mouse motion option. When true +and on a secondary screen and locked to the screen (via scroll lock) +mouse motion is sent as motion deltas. When true and scroll lock +is toggled off the mouse is warped to the secondary screen's center +so the server knows where it is. This option is intended to support +games and other programs that repeatedly warp the mouse to the center +of the screen. This change adds general and X11 support but not +win32. The option name is "relativeMouseMoves". + +---------- +2004/05/01 12:11:28 crs +configure.in +lib/arch/CArchNetworkBSD.cpp +lib/common/common.h + +Better fixes for compiling on FreeBSD and OpenBSD. + +---------- +2004/05/01 11:01:40 crs +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkBSD.h +lib/common/common.h + +Fixes for FreeBSD. + +---------- +2004/05/01 10:12:06 crs +acinclude.m4 +configure.in +lib/arch/CArchNetworkBSD.cpp +lib/platform/CXWindowsScreenSaver.cpp + +Fixes to compile on solaris 9 using g++. + +---------- +2004/05/01 08:56:24 crs +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h + +Fixed regression where cursor wasn't locked to screen when a mouse +button is down on win32. + +---------- +2004/05/01 08:54:42 crs +lib/arch/CMultibyte.cpp + +Fixed type cast warnings. + +---------- +2004/04/13 19:39:04 crs +configure.in +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchStringUnix.cpp +lib/arch/CArchStringUnix.h +lib/arch/CArchStringWindows.cpp +lib/arch/CArchStringWindows.h +lib/arch/CMultibyte.cpp +lib/arch/CMultibyteEmu.cpp +lib/arch/CMultibyteOS.cpp +lib/arch/IArchString.h +lib/arch/Makefile.am +lib/arch/arch.dsp +lib/base/CUnicode.cpp + +Removed use of mbrtowc, wcrtomb, and mbsinit. Many platforms +didn't support them and the emulated versions were just as good +except for a performance problem with excessive locking and +unlocking of a mutex. So this also changes IArchString to +provide string rather than character conversion so we can lock +the mutex once per string rather than once per character. + +---------- +2004/04/11 20:01:18 crs +cmd/launcher/Makefile.am + +Oops, broke build in launcher on non-win32 platforms. + +---------- +2004/04/11 19:43:16 crs +cmd/launcher/Makefile.am +lib/platform/Makefile.am + +More changes for MSYS/MinGW builds. Added makefile for launcher. +Still need hook library and resource compiling. + +---------- +2004/04/11 19:15:09 crs +cmd/launcher/launcher.rc +cmd/synergyc/synergyc.rc +cmd/synergys/synergys.cpp +cmd/synergys/synergys.rc +configure.in +lib/arch/CArchDaemonWindows.cpp +lib/arch/CArchMultithreadWindows.cpp +lib/arch/CArchNetworkWinsock.cpp +lib/arch/CArchStringWindows.cpp +lib/arch/CArchTaskBarWindows.cpp +lib/arch/CMultibyte.cpp +lib/arch/XArchWindows.cpp +lib/arch/arch.dsp +lib/common/common.h +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreenSaver.cpp +lib/platform/CSynergyHook.cpp +lib/synergy/CProtocolUtil.cpp + +Preliminary support for MSYS/MinGW builds. Doesn't yet build +CSynergyHook as a DLL and does not compile or link in the +resources for the binaries. + +---------- +2004/04/11 14:58:08 crs +PORTING +cmd/synergyc/COSXClientTaskBarReceiver.cpp +cmd/synergyc/COSXClientTaskBarReceiver.h +cmd/synergyc/Makefile.am +cmd/synergyc/synergyc.cpp +cmd/synergys/COSXServerTaskBarReceiver.cpp +cmd/synergys/COSXServerTaskBarReceiver.h +cmd/synergys/Makefile.am +cmd/synergys/synergys.cpp +configure.in +lib/arch/CArch.cpp +lib/arch/CArchDaemonUnix.cpp +lib/arch/CArchFileUnix.cpp +lib/arch/CArchImpl.cpp +lib/arch/CArchNetworkBSD.cpp +lib/arch/CMultibyte.cpp +lib/arch/CMultibyteOS.cpp +lib/arch/Makefile.am +lib/arch/vsnprintf.cpp +lib/base/CPriorityQueue.h +lib/common/BasicTypes.h +lib/common/common.h +lib/platform/COSXClipboard.cpp +lib/platform/COSXClipboard.h +lib/platform/COSXEventQueueBuffer.cpp +lib/platform/COSXEventQueueBuffer.h +lib/platform/COSXKeyState.cpp +lib/platform/COSXKeyState.h +lib/platform/COSXScreen.cpp +lib/platform/COSXScreen.h +lib/platform/COSXScreenSaver.cpp +lib/platform/COSXScreenSaver.h +lib/platform/CSynergyHook.h +lib/platform/CXWindowsEventQueueBuffer.cpp +lib/platform/CXWindowsScreen.cpp +lib/platform/Makefile.am + +Updates to support OS X. This improves support for building on +multiple systems with automake, with X Windows and Carbon window +system APIs supported. It's also a starting port for supporting +win32 builds using mingw. OS X support is incomplete; the tree +will compile and link but the binaries will not function. + +---------- +2004/04/06 22:09:38 crs +lib/arch/CArchMultithreadPosix.cpp + +Added missing initialization of mutex attribute call. + +---------- +2004/04/05 21:23:44 crs +lib/server/CServer.cpp + +Fixed bug in handling rejection of screen with name that's already +in use. The client was being correctly rejected but the already +connected client was being forcefully disconnected too because the +client to disconnect was found by looking up the client by name. +We now instead look up the client by IClient*. + +---------- +2004/04/05 21:10:06 crs +lib/platform/CSynergyHook.cpp +lib/server/CServer.cpp +lib/server/CServer.h + +Added workaround for win32 low-level mouse hook position weirdness. +The low-level hook can report mouse positions outside the boundaries +of the screen and bogus retrograde motion. This messes up switch on +double tap. This change attempts to detect and suppress the bogus +events. + +---------- +2004/04/05 21:08:49 crs +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h + +Made hook debug logging print at DEBUG1 rather than INFO level. + +---------- +2004/04/04 12:12:32 crs +Makefile.am +cmd/Makefile.am +cmd/launcher/Makefile.am +cmd/synergyc/Makefile.am +cmd/synergys/Makefile.am +dist/Makefile.am +dist/nullsoft/Makefile.am +dist/rpm/Makefile.am +lib/Makefile.am +lib/arch/Makefile.am +lib/base/Makefile.am +lib/client/Makefile.am +lib/common/Makefile.am +lib/io/Makefile.am +lib/mt/Makefile.am +lib/net/Makefile.am +lib/platform/Makefile.am +lib/server/Makefile.am +lib/synergy/Makefile.am + +Removed DEPTH, VDEPTH, and VPATH from makefiles. + +---------- +2004/04/04 12:12:30 crs +configure.in +lib/common/Version.h + +Changed version to 1.1.5. + +---------- +2004/03/31 22:30:49 crs +dist/nullsoft/synergy.nsi + +Minor win32 installer tweaks. + +---------- +2004/03/31 22:15:13 crs +lib/arch/CArchTaskBarWindows.cpp +lib/arch/CArchTaskBarWindows.h +lib/arch/IArchTaskBar.h + +Reverted task bar code to 1.0.15 version. That used a window in +its own thread for handling messages. It seems to fix most of +the task bar bugs but there's still an hourglass cursor on NT +when using the popup menu. + +---------- +2004/03/31 22:14:15 crs +lib/arch/CArchNetworkWinsock.cpp + +Fixed lookup of hosts by name on win32. + +---------- +2004/03/31 22:14:01 crs +cmd/launcher/launcher.rc + +Make screen drop down lists longer in the launcher. They're now +long enough for the scroll bar to show up properly (with the +thumb) and they have enough space for 6 screens without needing +the scroll bar. + +---------- +2004/03/31 22:12:53 crs +lib/server/CServer.cpp + +Fixed failure to initialize double tap and wait to switch timeouts. + +---------- +2004/03/30 18:55:58 crs +lib/synergy/CKeyState.cpp + +Fixed crash bug in CKeyState. Would deference bogus pointer in +isModifierActive if there's an unmapped toggle modifier. + +---------- +2004/03/30 18:54:56 crs +configure.in +lib/common/Version.h + +Changed version to 1.1.4. This time changing the version before +making any other changes. + +---------- +2004/03/28 14:53:01 crs +lib/platform/CXWindowsKeyState.cpp + +Added ISO_Level3_Shift as a synonym for Mode_switch. I've no idea +if this will work as hoped but I've seen documentation that XFree +4.3 uses ISO_Level3_Shift rather than Mode_switch. + +---------- +2004/03/28 14:07:58 crs +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsKeyState.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/CSynergyHook.cpp +lib/platform/CSynergyHook.h +lib/synergy/CKeyState.h + +Fixed keyboard handling on windows 95 family. + +---------- +2004/03/28 14:07:37 crs +lib/platform/Makefile.am + +Updated makefile to reflect renaming of platform files for win32. + +---------- +2004/03/28 14:06:40 crs +lib/platform/CMSWindowsScreenSaver.cpp + +Fixed windows 95 family screen saver stuff. + +---------- +2004/03/28 14:05:52 crs +lib/client/CServerProxy.cpp + +Changed debug logging of key IDs to use hex. + +---------- +2004/03/28 14:05:31 crs +cmd/launcher/CAutoStart.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +lib/arch/CArchDaemonWindows.cpp + +Fixed bugs in installing per-user startup programs on windows 95 +family. + +---------- +2004/03/26 20:59:26 crs +lib/platform/CMSWindowsDesks.cpp +lib/platform/CMSWindowsDesks.h +lib/platform/CMSWindowsDesktop.cpp +lib/platform/CMSWindowsDesktop.h +lib/platform/CMSWindowsKeyMapper.cpp +lib/platform/CMSWindowsKeyMapper.h +lib/platform/CMSWindowsKeyState.cpp +lib/platform/CMSWindowsKeyState.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/CSynergyHook.cpp +lib/platform/CXWindowsKeyState.cpp +lib/platform/CXWindowsKeyState.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/platform/platform.dsp +lib/server/CServer.cpp +lib/server/CServer.h +lib/synergy/CKeyState.cpp +lib/synergy/CKeyState.h +lib/synergy/CPlatformScreen.cpp +lib/synergy/CPlatformScreen.h +lib/synergy/CScreen.cpp +lib/synergy/IKeyState.h +lib/synergy/IPlatformScreen.h +lib/synergy/ISecondaryScreen.h +lib/synergy/libsynergy.dsp + +Converted win32 to new keyboard state tracking design. Also +changed locking to screen so that keys no longer count (only +mouse buttons and scroll lock toggled on). This is to deal +with the unreliability of key event reporting which can leave +us locked to a screen with no key physically pressed. The +result of this is that clients get key repeats and releases +without the corresponding key press. CKeyState handles this +by discarding repeat/release events on keys it hasn't seen go +down. Also made a few other minor fixes to win32 keyboard +handling. + +---------- +2004/03/26 20:59:21 crs +lib/arch/CArchMiscWindows.cpp + +Fixed handling of reading strings from the registry. This was +broken when support for binary data was added. The terminating +NUL was included in the string as a character (that's in addition +to the terminating NUL added by std::string). + +---------- +2004/03/21 20:01:41 crs +lib/client/CClient.cpp +lib/platform/CXWindowsKeyMapper.cpp +lib/platform/CXWindowsKeyMapper.h +lib/platform/CXWindowsKeyState.cpp +lib/platform/CXWindowsKeyState.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/platform/Makefile.am +lib/server/CPrimaryClient.cpp +lib/synergy/CKeyState.cpp +lib/synergy/CKeyState.h +lib/synergy/CPlatformScreen.cpp +lib/synergy/CPlatformScreen.h +lib/synergy/CScreen.cpp +lib/synergy/CScreen.h +lib/synergy/IKeyState.cpp +lib/synergy/IKeyState.h +lib/synergy/IPlatformScreen.h +lib/synergy/IPrimaryScreen.cpp +lib/synergy/IPrimaryScreen.h +lib/synergy/ISecondaryScreen.h +lib/synergy/Makefile.am + +Checkpoint. Converted X11 to new keyboard state tracking design. +This new design is simpler. For keyboard support, clients need only +implement 4 virtual methods on a class derived from CKeyState and +one trivial method in the class derived from CPlatformScreen, which +is now the superclass of platform screens instead of IPlatformScreen. +Keyboard methods have been removed from IPlatformScreen, IPrimaryScreen +and ISecondaryScreen. Also, all keyboard state tracking is now in +exactly one place (the CKeyState subclass) rather than in CScreen, +the platform screen, and the key mapper. Still need to convert Win32. + +---------- +2004/03/17 20:59:25 crs +lib/platform/CMSWindowsKeyMapper.cpp +lib/platform/CMSWindowsKeyMapper.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/synergy/CScreen.cpp +lib/synergy/CScreen.h +lib/synergy/IKeyState.h +lib/synergy/libsynergy.dsp + +Updated keyboard handling on win32. Still needs some work to +avoid shadowing key state in multiple places. Also got locked +to screen and reported key appeared to be wrong. + +---------- +2004/03/14 17:55:53 crs +lib/platform/CXWindowsKeyMapper.cpp +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/synergy/CScreen.cpp +lib/synergy/CScreen.h +lib/synergy/IKeyState.h +lib/synergy/IPlatformScreen.cpp +lib/synergy/IPlatformScreen.h +lib/synergy/IPrimaryScreen.cpp +lib/synergy/IPrimaryScreen.h +lib/synergy/Makefile.am + +Changed how key state is tracked on X11. Now updating key state +on every key press and release so we don't have to updateKeys() +in isLockedToScreen(). However, if any key appears to be down +we still call updateKeys() to double check that it's really down. +If not we note the spurious lock and don't lock to the screen. + +---------- +2004/03/14 17:50:37 crs +lib/io/IStream.h + +Fixed doxygen formatting error. + +---------- +2004/03/13 19:01:27 crs +lib/platform/CMSWindowsScreen.cpp + +Improved handling of active window on win32. Synergy no longer +takes activation so the previously active window doesn't pop to +the top of the window stack when it regains activation. One +drawback of this is that the mouse cursor isn't shown when +a window (other than synergy's) is activated. However, synergy +does detect mouse motion as before and shows the cursor when it +sees any. + +---------- +2004/03/13 18:58:20 crs +lib/platform/CMSWindowsScreen.h + +Fixed error in previous submit. + +---------- +2004/03/13 17:16:24 crs +lib/client/CClient.cpp +lib/client/CClient.h +lib/client/CServerProxy.cpp +lib/client/CServerProxy.h + +Fixed handling of handshake complete. Was posting an event for it +but making direct calls for other messages from the server. This +could cause messages to be handled out of order. Now making a +direct call for handshake complete. + +---------- +2004/03/13 17:14:32 crs +lib/platform/CMSWindowsEventQueueBuffer.cpp + +Fixed win32 taskbar icon event handling. Wasn't responding to +messages sent via SendMessage (rather than PostMessage). + +---------- +2004/03/13 17:13:55 crs +cmd/synergyc/resource.h +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp +lib/arch/CArchMiscWindows.cpp +lib/arch/CArchMiscWindows.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/CMSWindowsScreenSaver.cpp +lib/platform/CMSWindowsScreenSaver.h + +Added win32 support for power management. + +---------- +2004/03/10 22:03:01 crs +configure.in +lib/platform/CXWindowsScreenSaver.cpp +lib/platform/CXWindowsScreenSaver.h + +Added support for DPMS in X11 screen saver. DPMS is the extension +that allows you to power down the display. Previously, synergy +would not power on the display if DPMS was enabled and activated +and xscreensaver was not running. It also wouldn't disable DPMS +so the display would power down normally on a synergy client if +there was no input activity. + +---------- +2004/03/10 20:35:03 crs +acinclude.m4 +lib/arch/CArchNetworkBSD.cpp + +Added check for inet_aton and a simple implementation for platforms +that are missing it. + +---------- +2004/03/08 21:18:36 crs +configure.in +lib/platform/CXWindowsKeyMapper.cpp + +Removed dependency on X11/XF86keysym.h. There are several versions +of that file in existance, not all of which have all the symbols we +require and none of which provide any convenient means of telling +what groups of symbols they define. + +---------- +2004/03/08 20:53:32 crs +lib/platform/CMSWindowsKeyMapper.cpp +lib/platform/CMSWindowsKeyMapper.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/CSynergyHook.cpp +lib/server/CServer.cpp + +Win32 fixes. Fixed slightly off cursor positioning when using +absolute mouse_event(). Improved keyboard handling: now using +keyboard layout of last foreground window when leaving server +so users can meaningfully choose the locale, moved dead key +handling into hook library so there should be no more race +conditions involving the keyboard dead key buffer, simplified +keyboard and cursor handling by using a full screen transparent +window when not using low level hooks, fixed error in restoring +buffered dead key when checking for dead keys. This hopefully +fixes all known keyboard bugs on win32. + +---------- +2004/03/08 20:45:53 crs +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkBSD.h + +Typecasting fix to compile on old solaris. + +---------- +2004/03/06 16:20:08 crs +lib/platform/CXWindowsScreen.cpp +lib/server/CServer.cpp + +Server now disables jump zones when scroll lock is active. + +---------- +2004/02/29 21:34:30 crs +lib/platform/CMSWindowsEventQueueBuffer.cpp + +Fixed processing of events. Was waking up on a sent (rather than +posted) message but then blocking in GetMessage() which handles +the sent message directly. No longer blocking on sent messages. + +---------- +2004/02/29 21:33:20 crs +lib/arch/CArchNetworkWinsock.cpp + +Fixed handling of winsock connect event. Was always immediately +indicating socket had connected. + +---------- +2004/02/29 21:32:00 crs +lib/platform/CMSWindowsScreen.cpp + +Fixed cursor hiding on win32. Still fails occassionally. + +---------- +2004/02/29 21:31:24 crs +cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp +cmd/synergys/resource.h +cmd/synergys/synergys.cpp +cmd/synergys/synergys.rc + +Added reload configuration menu item to win32 task bar. + +---------- +2004/02/29 17:36:32 crs +lib/arch/IArchMultithread.h + +Fixed comment. + +---------- +2004/02/29 17:29:01 crs +lib/arch/CArchMultithreadWindows.h + +Switched to doxygen comments. + +---------- +2004/02/29 17:28:51 crs +lib/client/CClient.cpp +lib/client/CClient.h +lib/server/CClientProxy.cpp +lib/server/CClientProxy.h +lib/server/CServer.cpp +lib/synergy/IScreen.cpp +lib/synergy/IScreen.h + +Moved clipboard changed event to CClientProxy because only it +and CServer use it. CServerProxy instead makes a direct call +to CClient, like it does for most other messages. + +---------- +2004/02/29 16:48:22 crs +lib/arch/CArchMultithreadPosix.cpp +lib/arch/CArchMultithreadPosix.h +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkBSD.h + +Fixed BSD unblockPollSocket(). Was signaling to break out of +poll() but there was a race condition where the thread trying +to unblock poll() could send the signal before the polling +thread had entered poll(). Now using a pipe and polling on +that and the client's sockets, and just writing a byte into +the pipe to unblock poll. This persists until the next call +to poll() so we might force poll() to return once unnecessarily +but that's not a problem. This change makes the BSD code +similar to the winsock code, which uses a winsock event instead +of a pipe. + +---------- +2004/02/29 16:11:17 crs +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkBSD.h +lib/arch/CArchNetworkWinsock.cpp +lib/arch/CArchNetworkWinsock.h +lib/arch/IArchNetwork.h +lib/arch/XArch.h +lib/net/CTCPListenSocket.cpp +lib/net/CTCPSocket.cpp + +Made all arch sockets non-blocking. + +---------- +2004/02/28 17:51:55 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp + +Enabled running at high priority on windows. + +---------- +2004/02/28 17:49:29 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchConsoleWindows.cpp +lib/arch/CArchMultithreadPosix.cpp +lib/arch/CArchMultithreadPosix.h +lib/arch/CArchMultithreadWindows.cpp +lib/arch/CArchMultithreadWindows.h +lib/arch/IArchMultithread.h +lib/base/CEventQueue.cpp + +Generalized signal handling. Now handling SIGHUP in addition +to SIGINT and SIGTERM. Setup SIGHUP to reload the server's +configuration. + +---------- +2004/02/28 16:06:00 crs +lib/base/CLog.cpp + +Fixed incorrect accumulation of newlines in log. + +---------- +2004/02/28 16:00:54 crs +lib/client/CServerProxy.cpp + +Now using first set options message as end of handshake. + +---------- +2004/02/28 12:30:52 crs +lib/platform/CMSWindowsUtil.cpp +lib/platform/CMSWindowsUtil.h + +Added missing files. + +---------- +2004/02/28 12:24:47 crs +lib/common/Version.cpp + +Added missing file. + +---------- +2004/02/28 12:19:49 crs +acinclude.m4 +cmd/launcher/LaunchUtil.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp +cmd/synergyc/CMSWindowsClientTaskBarReceiver.h +cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp +cmd/synergyc/CXWindowsClientTaskBarReceiver.h +cmd/synergyc/Makefile.am +cmd/synergyc/resource.h +cmd/synergyc/synergyc.cpp +cmd/synergyc/synergyc.rc +cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp +cmd/synergys/CMSWindowsServerTaskBarReceiver.h +cmd/synergys/CXWindowsServerTaskBarReceiver.cpp +cmd/synergys/CXWindowsServerTaskBarReceiver.h +cmd/synergys/Makefile.am +cmd/synergys/resource.h +cmd/synergys/synergys.cpp +cmd/synergys/synergys.rc +configure.in +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchConsoleWindows.cpp +lib/arch/CArchDaemonWindows.cpp +lib/arch/CArchDaemonWindows.h +lib/arch/CArchMiscWindows.cpp +lib/arch/CArchMiscWindows.h +lib/arch/CArchMultithreadPosix.cpp +lib/arch/CArchMultithreadPosix.h +lib/arch/CArchMultithreadWindows.cpp +lib/arch/CArchMultithreadWindows.h +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkBSD.h +lib/arch/CArchNetworkWinsock.cpp +lib/arch/CArchNetworkWinsock.h +lib/arch/CArchTaskBarWindows.cpp +lib/arch/CArchTaskBarWindows.h +lib/arch/IArchMultithread.h +lib/arch/IArchNetwork.h +lib/arch/arch.dsp +lib/base/CEventQueue.cpp +lib/base/CPriorityQueue.h +lib/base/CSimpleEventQueueBuffer.cpp +lib/client/CServerProxy.cpp +lib/client/CServerProxy.h +lib/common/Makefile.am +lib/common/Version.h +lib/common/common.dsp +lib/mt/CThread.cpp +lib/mt/CThread.h +lib/net/CSocketMultiplexer.cpp +lib/net/IDataSocket.cpp +lib/net/IDataSocket.h +lib/platform/CMSWindowsDesktop.h +lib/platform/CMSWindowsEventQueueBuffer.cpp +lib/platform/CMSWindowsEventQueueBuffer.h +lib/platform/CMSWindowsKeyMapper.cpp +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/CSynergyHook.cpp +lib/platform/CSynergyHook.h +lib/platform/CXWindowsClipboard.cpp +lib/platform/CXWindowsEventQueueBuffer.cpp +lib/platform/CXWindowsKeyMapper.cpp +lib/platform/CXWindowsScreen.cpp +lib/platform/Makefile.am +lib/platform/platform.dsp +lib/server/CClientListener.cpp +lib/server/CServer.cpp +lib/synergy/CProtocolUtil.cpp +lib/synergy/CProtocolUtil.h +lib/synergy/CScreen.cpp +lib/synergy/IScreen.h + +Merged Win32 updates. Added full warnings on g++. Fixed bug in +client when handling server rejection. + +---------- +2004/02/15 18:12:35 crs +lib/base/CFunctionEventJob.cpp +lib/base/CJobList.cpp +lib/base/CJobList.h +lib/base/Makefile.am +lib/base/base.dsp +lib/io/io.dsp +lib/mt/CTimerThread.cpp +lib/mt/CTimerThread.h +lib/mt/Makefile.am +lib/mt/mt.dsp +lib/net/net.dsp +lib/platform/CMSWindowsEventQueueBuffer.cpp +lib/platform/CMSWindowsEventQueueBuffer.h +lib/platform/Makefile.am +lib/platform/platform.dsp +lib/server/server.dsp +lib/synergy/libsynergy.dsp + +Updated Makefiles and win32 projects and removed dead classes. + +---------- +2004/02/15 17:32:11 crs +cmd/synergyc/CClientTaskBarReceiver.cpp +cmd/synergyc/CClientTaskBarReceiver.h +cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp +cmd/synergyc/CXWindowsClientTaskBarReceiver.h +cmd/synergyc/synergyc.cpp +cmd/synergys/CServerTaskBarReceiver.cpp +cmd/synergys/synergys.cpp +lib/arch/CArchNetworkBSD.cpp +lib/arch/XArch.h +lib/base/CEvent.cpp +lib/base/CEvent.h +lib/client/CClient.cpp +lib/client/CClient.h +lib/client/CServerProxy.cpp +lib/client/CServerProxy.h +lib/io/CStreamFilter.cpp +lib/net/CNetworkAddress.cpp +lib/net/CTCPListenSocket.cpp +lib/net/CTCPSocket.cpp +lib/net/CTCPSocket.h +lib/net/IDataSocket.h +lib/net/XSocket.cpp +lib/net/XSocket.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/server/CClientProxy.h +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CConfig.cpp +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CServer.cpp +lib/synergy/CClipboard.cpp +lib/synergy/CClipboard.h +lib/synergy/CPacketStreamFilter.cpp +lib/synergy/IClient.h +lib/synergy/IClipboard.cpp +lib/synergy/IClipboard.h +lib/synergy/IPlatformScreen.cpp +lib/synergy/IPlatformScreen.h +lib/synergy/Makefile.am +lib/synergy/ProtocolTypes.h + +Checkpoint. Conversion to event driven system complete for Unix. +Still need to convert win32 platform specific files. + +---------- +2004/02/14 16:30:27 crs +cmd/synergys/CServerTaskBarReceiver.cpp +cmd/synergys/synergys.cpp +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h + +Minor cleanup. + +---------- +2004/02/14 14:04:36 crs +cmd/synergys/CServerTaskBarReceiver.cpp +cmd/synergys/CServerTaskBarReceiver.h +cmd/synergys/CXWindowsServerTaskBarReceiver.cpp +cmd/synergys/CXWindowsServerTaskBarReceiver.h +cmd/synergys/Makefile.am +cmd/synergys/synergys.cpp +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchMultithreadPosix.cpp +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkBSD.h +lib/arch/IArchNetwork.h +lib/arch/vsnprintf.cpp +lib/base/CEvent.cpp +lib/base/CEvent.h +lib/base/CEventQueue.cpp +lib/base/CEventQueue.h +lib/base/CSimpleEventQueueBuffer.cpp +lib/base/CSimpleEventQueueBuffer.h +lib/base/CStringUtil.cpp +lib/base/IEventQueue.h +lib/base/IEventQueueBuffer.h +lib/io/IStream.cpp +lib/net/CNetworkAddress.cpp +lib/net/CNetworkAddress.h +lib/net/CSocketMultiplexer.cpp +lib/net/CTCPListenSocket.cpp +lib/net/CTCPSocket.cpp +lib/net/IDataSocket.cpp +lib/net/IListenSocket.cpp +lib/net/ISocket.cpp +lib/platform/CXWindowsEventQueue.cpp +lib/platform/CXWindowsEventQueue.h +lib/platform/CXWindowsEventQueueBuffer.cpp +lib/platform/CXWindowsEventQueueBuffer.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/platform/CXWindowsScreenSaver.cpp +lib/platform/CXWindowsScreenSaver.h +lib/platform/Makefile.am +lib/server/CClientListener.cpp +lib/server/CClientListener.h +lib/server/CClientProxy.cpp +lib/server/CClientProxy.h +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CClientProxy1_1.cpp +lib/server/CClientProxy1_1.h +lib/server/CClientProxyUnknown.cpp +lib/server/CClientProxyUnknown.h +lib/server/CConfig.cpp +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/server/Makefile.am +lib/synergy/CProtocolUtil.cpp +lib/synergy/CScreen.cpp +lib/synergy/CScreen.h +lib/synergy/IClient.h +lib/synergy/IPlatformScreen.cpp +lib/synergy/IPlatformScreen.h +lib/synergy/IPrimaryScreen.h +lib/synergy/IPrimaryScreenReceiver.h +lib/synergy/IScreen.cpp +lib/synergy/IScreen.h +lib/synergy/IScreenFactory.h +lib/synergy/IScreenReceiver.h +lib/synergy/IScreenSaver.h +lib/synergy/IServer.h +lib/synergy/Makefile.am + +Checkpoint. synergys now works. Still need to do lib/client and +synergyc. + +---------- +2004/02/08 17:07:11 crs +lib/base/CEventQueue.cpp +lib/base/CEventQueue.h +lib/base/CSimpleEventQueue.cpp +lib/base/CSimpleEventQueue.h +lib/base/IEventQueue.h +lib/base/Makefile.am + +Refactored event queue. The event queue is now separated from the +buffer that holds the events and generates system events. This +allows us to switch in/out a platform specific event handler as +necessary without losing our timers and handlers. + +---------- +2004/02/08 16:51:45 crs +lib/net/CTCPSocket.cpp + +No longer sending incorrect disconnect events in read() and +removed redundant sending of disconnect event in close(). + +---------- +2004/02/01 21:09:22 crs +cmd/synergys/synergys.cpp +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchMultithreadPosix.cpp +lib/arch/CArchMultithreadPosix.h +lib/arch/CArchNetworkBSD.cpp +lib/arch/IArchMultithread.h +lib/arch/XArch.h +lib/base/CEventQueue.cpp +lib/base/CEventQueue.h +lib/base/IEventQueue.cpp +lib/base/IEventQueue.h +lib/base/Makefile.am +lib/io/CBufferedInputStream.cpp +lib/io/CBufferedInputStream.h +lib/io/CBufferedOutputStream.cpp +lib/io/CBufferedOutputStream.h +lib/io/CInputStreamFilter.cpp +lib/io/CInputStreamFilter.h +lib/io/COutputStreamFilter.cpp +lib/io/COutputStreamFilter.h +lib/io/CStreamFilter.cpp +lib/io/CStreamFilter.h +lib/io/IInputStream.h +lib/io/IOutputStream.h +lib/io/IStream.cpp +lib/io/IStream.h +lib/io/IStreamFilterFactory.h +lib/io/Makefile.am +lib/io/XIO.cpp +lib/io/XIO.h +lib/net/CSocketMultiplexer.cpp +lib/net/CSocketMultiplexer.h +lib/net/CTCPListenSocket.cpp +lib/net/CTCPListenSocket.h +lib/net/CTCPSocket.cpp +lib/net/CTCPSocket.h +lib/net/IDataSocket.cpp +lib/net/IDataSocket.h +lib/net/IListenSocket.h +lib/net/ISocket.h +lib/net/ISocketMultiplexerJob.h +lib/net/Makefile.am +lib/net/TSocketMultiplexerMethodJob.h +lib/server/CClientProxy.cpp +lib/server/CClientProxy.h +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CClientProxy1_1.cpp +lib/server/CClientProxy1_1.h +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/server/CHTTPServer.cpp +lib/server/CHTTPServer.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/server/Makefile.am +lib/synergy/CInputPacketStream.cpp +lib/synergy/CInputPacketStream.h +lib/synergy/COutputPacketStream.cpp +lib/synergy/COutputPacketStream.h +lib/synergy/CPacketStreamFilter.cpp +lib/synergy/CPacketStreamFilter.h +lib/synergy/CProtocolUtil.cpp +lib/synergy/CProtocolUtil.h +lib/synergy/Makefile.am + +Checkpoint. Code does not run. Still converting over to new +event loop model. Streams, stream filters, and sockets are +converted. Client proxies are almost converted. CServer is +in progress. Removed all HTTP code. Haven't converted the +necessary win32 arch stuff. + +---------- +2004/02/01 20:56:52 crs +cmd/launcher/launcher.dsp +cmd/synergys/Makefile.am +cmd/synergys/synergys.dsp +configure.in +lib/Makefile.am +lib/http/CHTTPProtocol.cpp +lib/http/CHTTPProtocol.h +lib/http/Makefile.am +lib/http/XHTTP.cpp +lib/http/XHTTP.h +lib/http/http.dsp +lib/server/server.dsp +synergy.dsw + +Removed most HTTP stuff. It doesn't seem like the appropriate +choice for server control. May later provide some other means +for controlling the synergy server remotely. + +---------- +2004/01/24 16:09:25 crs +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchMultithreadPosix.cpp +lib/arch/CArchMultithreadPosix.h +lib/arch/CArchNetworkBSD.cpp +lib/arch/IArchMultithread.h +lib/base/CEvent.cpp +lib/base/CEvent.h +lib/base/CEventQueue.cpp +lib/base/CEventQueue.h +lib/base/CFunctionEventJob.cpp +lib/base/CFunctionEventJob.h +lib/base/CLog.cpp +lib/base/CPriorityQueue.h +lib/base/CSimpleEventQueue.cpp +lib/base/CSimpleEventQueue.h +lib/base/IEventJob.h +lib/base/IEventQueue.h +lib/base/Makefile.am +lib/base/TMethodEventJob.h +lib/io/CBufferedInputStream.cpp +lib/io/CBufferedInputStream.h +lib/io/CBufferedOutputStream.cpp +lib/io/CBufferedOutputStream.h +lib/mt/CThread.cpp +lib/mt/CThread.h +lib/net/CTCPListenSocket.cpp +lib/net/CTCPListenSocket.h +lib/net/CTCPSocket.cpp +lib/net/CTCPSocket.h +lib/net/IDataSocket.cpp +lib/net/IDataSocket.h +lib/net/IListenSocket.cpp +lib/net/IListenSocket.h +lib/net/ISocket.cpp +lib/net/ISocket.h +lib/net/Makefile.am +lib/platform/CXWindowsEventQueue.cpp +lib/platform/CXWindowsEventQueue.h +lib/platform/Makefile.am + +Checkpointing centralized event queue stuff. Currently have: +an event queue and events, TCP sockets converted to use events, +unix multithreading and network stuff converted, and an X Windows +event queue subclass. + +---------- +2003/07/19 17:22:06 crs +cmd/launcher/launcher.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +cmd/synergyc/synergyc.cpp +configure.in +lib/client/CClient.cpp +lib/client/CClient.h +lib/client/CServerProxy.cpp +lib/client/CServerProxy.h +lib/common/Version.h +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsPrimaryScreen.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h +lib/server/CConfig.cpp +lib/synergy/OptionTypes.h + +Merge synergy 1.1 fixes into 1.0 branch. + +---------- +2003/07/19 17:19:26 crs + +Branched synergy 1.0 from 1.0.11. + +---------- +2003/07/17 21:16:58 crs +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.cpp + +Fixed handling of a dead key followed by space on win32 and X11. +A dead key followed by space should convert the dead key to a +regular character. + +---------- +2003/07/16 22:38:43 crs +lib/platform/CMSWindowsSecondaryScreen.cpp + +Fixed handling of some non-ASCII but directly mapped characters +on win32. The o, a, and u with diaeresis in the german keyboard +mapping are examples. + +---------- +2003/07/16 21:40:57 crs +lib/platform/CMSWindowsPrimaryScreen.cpp + +Fixed handling of shift/ctrl/alt on special keys on win32 server. + +---------- +2003/07/13 20:42:11 crs +lib/platform/CMSWindowsPrimaryScreen.cpp + +Fixed handling of some keystrokes on win32. Pressing a dead key +and then space should convert the dead key to a non-dead key but +previous the key was discarded. Fixed that but VkKeyScan() fails +in this case so added special case to fix that (assuming AltGr is +required). VkKeyScan() can return the wrong result for characters +that have more than one virtual key mapped to them. AltGr+9 (^) +on the French layout has this problem. Now detecting that problem +and using the current keyboard state to decide if AltGr is +required. + +---------- +2003/07/13 17:03:41 crs +cmd/synergyc/synergyc.cpp + +Forgot to remove --camp and --no-camp from brief usage message. + +---------- +2003/07/13 16:57:08 crs +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h + +Changed XSync() to XFlush() in X windows secondary screen. This +doesn't appear to have any negative consequences and may prevent +synergy from freezing when some X client (probably the window +manager) grabs the server. + +---------- +2003/07/12 17:57:31 crs +cmd/synergyc/synergyc.cpp +lib/client/CClient.cpp +lib/client/CClient.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CXWindowsScreen.cpp + +Prevent INFO level log messages when client is repeatedly trying +to connect. This prevents a log from filling up while the client +can't connect for no useful reason. Also removed --camp option +and cleaned up handling of client connection. Users must now use +--restart instead of --camp. + +---------- +2003/07/08 18:40:46 crs +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsPrimaryScreen.h + +Changed windows server to release ctrl and alt keys when it's +sending a key that requires AltGr. That's because AltGr *is* +ctrl and alt but AltGr should be seen on clients as mode +switch without the ctrl and alt. I can't think of a better +way to do this other than to not send modifier keystrokes to +the clients at all. + +---------- +2003/07/05 17:06:18 crs +configure.in +lib/common/Version.h + +Change version to 1.0.11. Skipping version 1.0.10 because there +have been too many major changes since 1.0.8. A new experimental +release will provide a stable starting point for testing. + +---------- +2003/07/05 17:05:12 crs +lib/synergy/CSecondaryScreen.cpp + +Fix to avoid warping mouse until client successfully connects to +the server. + +---------- +2003/07/05 17:04:26 crs +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.h + +Keyboard fixes on win32. + +---------- +2003/07/05 17:04:06 crs +lib/server/CConfig.h + +Fix for new template syntax. + +---------- +2003/07/05 14:49:08 crs +lib/platform/CXWindowsPrimaryScreen.cpp +lib/platform/CXWindowsPrimaryScreen.h +lib/platform/CXWindowsSecondaryScreen.cpp + +Minor X11 keyboard code cleanup. Also now handling KeyPress with +keycode == 0 generated by XFilterEvent() by using the keycode from +the previous KeyPress. + +---------- +2003/07/05 14:47:41 crs +lib/platform/CXWindowsScreen.cpp + +Compress sequential MappingNotify events into one. + +---------- +2003/07/01 19:35:28 crs +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h + +Rewrote key handling on X11 client. This should fix problems +with applying the incorrect shift and mode switch modifiers to +some keycodes, such as getting Pointer_EnableKeys when pressing +shift with NumLock enabled. + +---------- +2003/06/22 21:27:38 crs +lib/platform/CXWindowsPrimaryScreen.cpp +lib/platform/CXWindowsPrimaryScreen.h + +Added support for input methods. Only handling IMs that don't +need a precompose area or status area. This includes IMs that +do simple dead key composition. This only changes the server. +The client still does not decompose a character it cannot +generate directly into the keysyms to compose the character. + +---------- +2003/06/22 16:39:25 crs +lib/client/CServerProxy.cpp + +More fixes for X11 client keyboard handling. + +---------- +2003/06/22 16:39:02 crs +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h + +More fixes for X11 client keyboard handling. + +---------- +2003/06/22 15:01:44 crs +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h + +Checkpoint for improving X11 client key handling. Should prevent +unintentional Pointer_EnableKeys (i.e. generating NumLock press +and release around a shift press). + +---------- +2003/06/08 22:20:01 crs +lib/platform/CXWindowsPrimaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.cpp + +Another ctrl+alt+del checkpoint. + +---------- +2003/06/08 22:12:12 crs +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.h +lib/platform/CXWindowsPrimaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.cpp + +ctrl+alt+del emulation checkpoint. + +---------- +2003/06/08 16:31:52 crs +lib/platform/CXWindowsSecondaryScreen.cpp + +More DEBUG2 level debugging of keyboard handling. + +---------- +2003/06/08 15:42:05 crs +lib/common/Makefile.am + +Added new file to Makefile. + +---------- +2003/06/02 20:07:16 crs +lib/platform/CMSWindowsSecondaryScreen.cpp + +Fixed ctrl and alt keys on win32 clients. Was broken by a recent +fix to character handling. + +---------- +2003/06/02 20:06:20 crs +lib/client/CServerProxy.cpp + +Fixed errors in log strings. + +---------- +2003/06/02 20:06:03 crs +cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp +cmd/synergyc/CMSWindowsClientTaskBarReceiver.h +cmd/synergyc/resource.h +cmd/synergyc/synergyc.cpp +cmd/synergyc/synergyc.rc +cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp +cmd/synergys/CMSWindowsServerTaskBarReceiver.h +cmd/synergys/resource.h +cmd/synergys/synergys.cpp +cmd/synergys/synergys.rc +lib/base/CLog.cpp +lib/base/CLog.h +lib/base/LogOutputters.cpp +lib/base/LogOutputters.h +lib/common/common.dsp +lib/common/stddeque.h + +Added menu item on win32 tray icon to copy the last 1000 lines from +the log to the clipboard. + +---------- +2003/05/26 09:50:35 crs +lib/platform/CXWindowsClipboard.cpp + +Added workaround for broken clipboard owners that report the +type of TARGETS as TARGETS instead of ATOM. + +---------- +2003/05/26 09:49:38 crs +lib/platform/CMSWindowsClipboard.cpp + +No longer installing clibboard format for plain text on windows nt +family because nt automatically converts to and from the unicode +format. This may fix text encoding errors when synergy puts +non-ascii text on the clipboard and other clients prefer CF_TEXT +to CF_UNICODE (which they should not because synergy lists +CF_UNICODE first). + +---------- +2003/05/26 09:46:52 crs +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsScreen.cpp + +Fixed loss of ctrl+alt+del key releases when the Winlogin desktop +is accessible (was already fixed when inaccessible). This change +also ignores press and release of virtual key 0, which should never +happen but does according to one user. + +---------- +2003/05/21 21:22:14 crs +lib/arch/CArchMultithreadWindows.cpp +lib/synergy/CPrimaryScreen.cpp +lib/synergy/CSecondaryScreen.cpp + +Fixed unsigned compare against zero. Changed win32 priority to +maximum. + +---------- +2003/05/21 19:38:11 crs +lib/server/CServer.cpp +lib/server/CServer.h + +Made double tap require moving farther away from the tapped edge +before arming. This should reduce spurious double taps. + +---------- +2003/05/20 19:15:58 crs +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.h + +Attempt to improve key event synthesis. This change adds support +for dead keys and attempts to choose the correct code page for the +thread that will (probably) receive synthesized events. + +---------- +2003/05/20 19:14:40 crs +lib/platform/CSynergyHook.cpp + +Minor thread ID compare fix. + +---------- +2003/05/20 19:14:24 crs +lib/arch/CArchMultithreadWindows.cpp + +Reduced maximum priority in debug build. + +---------- +2003/05/17 20:58:27 crs +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsPrimaryScreen.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.h +lib/platform/CSynergyHook.cpp +lib/platform/IMSWindowsScreenEventHandler.h +lib/synergy/CPrimaryScreen.cpp + +Fixed getting locked to screen after ctrl+alt+del. Also fixed +cursor not being hidden on win32 server when on client screens +(which happened when using low-level hooks). + +---------- +2003/05/17 14:10:11 crs +INSTALL + +Added documentation for xtestIsXineramaUnaware option. + +---------- +2003/05/17 14:03:32 crs +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h + +Fixed previous fix. Was trying to avoid using XWarpPointer() when +warping on screen 0. That just doesn't work if screen 0 is not at +0,0. So now always use XWarpPointer() if there are multiple +xinerama screens and the appropriate option is enabled. + +---------- +2003/05/17 13:44:24 crs +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h +lib/server/CConfig.cpp +lib/synergy/OptionTypes.h + +Added workaround for when XTest is unaware of Xinerama. When that's +true, faking a mouse motion outside screen 0 is clamped onto screen 0. +When the workaround is enabled, we use XWarpPointer() instead of an +XTest fake motion. This isn't perfect but the only real fix requires +patching XTest. + +---------- +2003/05/17 12:48:32 crs +lib/platform/CXWindowsSecondaryScreen.cpp + +Added support for old versions of XF86keysym.h that are missing +some expected #defines. + +---------- +2003/05/10 17:27:05 crs +configure.in +lib/common/Version.h + +Changed version to 1.0.8. + +---------- +2003/05/10 17:26:42 crs +INSTALL +README + +Updated documentation. + +---------- +2003/05/08 21:59:35 crs +lib/server/CServer.cpp + +Fixed jumping to same client screen. It was broken by an earlier +change (probably double tap). Jumping to the same server screen +worked correctly. + +---------- +2003/05/04 21:40:42 crs +configure.in +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsPrimaryScreen.h +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.h +lib/platform/CSynergyHook.cpp +lib/platform/CXWindowsPrimaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.cpp +lib/synergy/KeyTypes.h +lib/synergy/MouseTypes.h + +Added support for 4th and 5th (non-mouse-wheel) buttons and +"Internet" keyboard keys. + +---------- +2003/05/03 15:16:30 crs +cmd/launcher/CGlobalOptions.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h + +Added screen saver synchronization option to win32 launcher dialog. + +---------- +2003/05/03 14:54:03 crs +lib/synergy/CSecondaryScreen.cpp + +Removed accidental debugging code. + +---------- +2003/05/03 14:38:36 crs +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.cpp +lib/server/CConfig.cpp +lib/synergy/CSecondaryScreen.cpp +lib/synergy/CSecondaryScreen.h +lib/synergy/OptionTypes.h + +Added global configuration option to disable screen saver +synchronization. + +---------- +2003/05/03 13:57:52 crs +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h + +Forgot to restore global auto-repeat configuration on exit. + +---------- +2003/05/03 13:50:06 crs +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.h +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h +lib/synergy/CSecondaryScreen.cpp +lib/synergy/CSecondaryScreen.h + +Now warping mouse to center of screen when leaving client screens. +Some users requested this. Also, the hider window is mapped before +warping the mouse so the active window shouldn't change if the focus +policy is point-to-focus. Showing the window first can also reduce +the likelihood of seeing the cursor briefly in its hidden position. + +---------- +2003/05/03 13:28:21 crs +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h + +Now turning off auto-repeat when on an X11 client. This prevents +the server from auto-repeating fake events, which is undesired +since synergy will do the auto-repeating itself. This also +disables auto-repeat on any keys locally configured on X11 to not +auto-repeat. That's mainly to suppress auto-repeat on modifier +keys, which auto-repeat on win32 but not X11. + +---------- +2003/05/03 12:54:22 crs +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsPrimaryScreen.h +lib/platform/CSynergyHook.cpp +lib/platform/CSynergyHook.h + +Fixed a few win32 keyboard/mouse problems. First, the mouse hook +now captures non-client area mouse messages. Previously, these +were ignored (because i forgot about them) and they caused all +kinds of problems because they weren't forwarded. For example, +clicking on a window border would cause the window to start +resizing when the mouse came back to the server screen. Moving +inside a title bar meant that the mouse wouldn't move on the +client screen. + +Second, because non-client messages are now handled, the full +screen transparent window is no longer necessary to capture +input so it's never displayed. (The window is still necessary +for clipboard ownership so it's still created.) No transparent +window means no screen flashing. It also means we don't have to +become the foreground and active window. This plays better with +apps that minimize or restore when they're no longer the +foreground application/active window. + +Third, fixed the low level keyboard hook to forward toggle key +updates, which it was neglecting to do. + +Finally, keyboard and mouse input is always forwarded from the hook +to the primary screen handler which then shadows the current key +and mouse button state. If we're using low level hooks then this +isn't really necessary and GetKeyState() always returns the right +info but without low level hooks it means we can just use the +shadow state. It also means we don't have to show our window in +order to get the system's key state table up to date, fixing the +screen flash when checking for the scroll lock state. + +---------- +2003/05/03 12:37:03 crs +lib/arch/CArchMultithreadWindows.cpp +lib/synergy/CPrimaryScreen.cpp +lib/synergy/CSecondaryScreen.cpp + +Boosted priority of main synergy threads to be very high (highest +realtime priority). After some testing it appears that anything +less than this can starve synergy in some circumstances, preventing +it from forwarding messages to clients. This is a rather risky +change since synergy can now virtually take over a system if it +behaves badly. This change only affects windows systems since +lib/arch of other platforms don't yet attempt to boost priority. + +---------- +2003/04/27 18:05:32 crs +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.h +lib/server/Makefile.am +lib/server/server.dsp + +Fixes to previous checkpoint. Non-ascii keys seem to work correctly. +Still not supporting key composition on X11. + +---------- +2003/04/27 17:01:14 crs +lib/client/CClient.cpp +lib/client/CClient.h +lib/client/CServerProxy.cpp +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.h +lib/platform/CXWindowsPrimaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h +lib/server/CClientProxy.h +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CClientProxy1_1.cpp +lib/server/CClientProxy1_1.h +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/server/Makefile.am +lib/synergy/CSecondaryScreen.h +lib/synergy/IClient.h +lib/synergy/IPrimaryScreenReceiver.h +lib/synergy/KeyTypes.h +lib/synergy/ProtocolTypes.h + +Checkpointing improved key handling. This change adds non-ASCII +key handling to win32 on both client and server. It also changes +the protocol and adds code to ensure every key pressed also gets +released and that that doesn't get confused when the KeyID for +the press is different from the KeyID of the release (or repeat). + +---------- +2003/04/24 20:11:38 crs +lib/platform/CXWindowsPrimaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsUtil.cpp +lib/platform/CXWindowsUtil.h + +Added KeySym <-> Unicode mappings. Changed code to use those +mappings to better support Unicode key events. + +---------- +2003/04/24 20:10:13 crs +cmd/Makefile.am + +Added exec.dsp to EXTRA_DIST. + +---------- +2003/04/16 20:59:25 crs +all.dsp +cmd/exec.dsp +lib/platform/makehook.dsp +lib/platform/synrgyhk.dsp +synergy.dsw + +Win32 project configuration fixes. + +---------- +2003/04/16 20:59:14 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp +lib/platform/CMSWindowsPrimaryScreen.cpp + +Minor win32 fixes. + +---------- +2003/04/16 20:05:00 crs +lib/server/CConfig.cpp + +Now allowing screen names with underscores. + +---------- +2003/04/14 22:16:21 crs +lib/platform/CXWindowsPrimaryScreen.cpp + +Fixed incorrect initialization of an XMotionEvent. + +---------- +2003/04/14 22:15:56 crs +configure.in +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h + +Added workaround for apparent Xinerama bug when warping the pointer. +This should allow synergy to be used on a system using Xinerama to +create a single logical screen from multiple physical screens. + +---------- +2003/04/13 18:14:01 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp + +Fixed problem with type casting void* to int. + +---------- +2003/04/13 17:13:27 crs +lib/platform/CXWindowsScreenSaver.cpp + +Removed periodic call to XForceScreenSaver() to prevent the built-in +screen saver from activating. It was unnecessary since the built-in +screen saver is disabled as appropriate; this call was just to +ensure that the screen saver wouldn't start if an external program +reactivated the screen saver after synergy disabled it. + +It's possible that this was causing screen flicker under gnome, though +i don't know why. It's also possible that periodically sending events +to xscreensaver is causing the flicker but removing that code is more +difficult because xscreensaver can't be disabled, only deactivated or +killed. + +---------- +2003/04/13 14:59:53 crs +lib/platform/CMSWindowsClipboard.cpp +lib/platform/CMSWindowsClipboard.h +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsPrimaryScreen.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/CSynergyHook.cpp + +Fixed several win32 bugs. First, synergy wasn't forwarding mouse +events to other hook functions, which broke some tools like objectbar. +Second, windows key processing was fixed. Previously pressing and +release the key would only send a press event, locking the user onto +the client window; also, the win32 server treated as a Meta modifier +instead of a Super modifier, which broke any use of it as any kind of +modifier key. Third, added hacks to support several key combinations +on windows 95/98/me that are treated specially by windows, including +Alt+Tab, Alt+Shift+Tab, Alt+Esc, Alt+Shift+Esc, Ctrl+Esc, and any +combination using the windows key like Win+E and Win+F but not +Ctrl+Alt+Del. Fourth, scroll lock only locking to the client (which +only happened when using a synergy server on windows) has been fixed; +unfortunately the solution causes a lot of screen redraws for some +reason. Finally, there's been a fix to clipboard handling that may +or may not fix a problem where the clipboard would stop transferring +between systems after a little while. I can't be sure if it fixes +the problem because I can't reproduce the problem. + +---------- +2003/04/13 14:39:17 crs +cmd/launcher/launcher.rc + +Added mention of tray icon to launcher start message box. + +---------- +2003/03/26 21:03:58 crs +configure.in +lib/common/Version.h + +Changed version to 1.0.6. + +---------- +2003/03/25 21:31:39 crs +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CSynergyHook.cpp + +This should fix multimon support on win32. + +---------- +2003/03/22 11:49:23 crs +FAQ +INSTALL +PORTING +README +TODO + +Documentation updates. + +---------- +2003/03/22 11:49:13 crs +cmd/launcher/CGlobalOptions.cpp +cmd/launcher/CGlobalOptions.h +cmd/launcher/launcher.rc +cmd/launcher/resource.h + +Added key modifier and heartbeat options to GUI. + +---------- +2003/03/21 19:34:08 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp + +Oops, included a windows only header in non-windows builds. + +---------- +2003/03/21 19:16:37 crs +lib/platform/CMSWindowsScreenSaver.cpp + +Added check for the screen saver actually being active before +entering the loop waiting for it to deactivate. The failure +to check was causing the screen saver code to kick in when +the screen saver timeout occurred, even if the screen saver +wasn't enabled (because Windows still sends the screen saver +activating message for no good reason when the screen saver +is disabled). + +---------- +2003/03/21 19:14:32 crs +lib/arch/CArchMiscWindows.cpp + +Fixed errors in merge causing infinite loops. + +---------- +2003/03/21 19:14:10 crs +cmd/synergyc/tb_idle.ico +cmd/synergyc/tb_run.ico +cmd/synergyc/tb_wait.ico +cmd/synergys/tb_idle.ico +cmd/synergys/tb_run.ico +cmd/synergys/tb_wait.ico + +Fixed icons. + +---------- +2003/03/21 19:13:15 crs +lib/platform/CXWindowsUtil.cpp + +Fixed getWindowProperty(). It wasn't catching all failure +cases correctly. + +---------- +2003/03/17 22:32:10 crs +cmd/launcher/CAdvancedOptions.cpp +cmd/launcher/CAdvancedOptions.h +cmd/launcher/LaunchUtil.cpp +cmd/launcher/LaunchUtil.h +cmd/launcher/launcher.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +lib/arch/CArchDaemonWindows.cpp +lib/arch/CArchDaemonWindows.h +lib/arch/CArchMiscWindows.cpp +lib/arch/CArchMiscWindows.h + +Added options and advanced options dialogs which should've been +part of an earlier checkin. Also now saving and restoring +options that aren't in the configuration file to/from the +registry. + +---------- +2003/03/17 22:32:01 crs +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CXWindowsPrimaryScreen.cpp +lib/server/CServer.cpp +lib/synergy/CPrimaryScreen.h + +Added a log message why the user is locked to the screen. + +---------- +2003/03/17 22:31:59 crs +lib/platform/CMSWindowsSecondaryScreen.cpp + +Added type casts to avoid warning. + +---------- +2003/03/16 17:40:57 crs +lib/platform/CMSWindowsScreenSaver.cpp +lib/platform/CMSWindowsScreenSaver.h + +Fixed detection of screen saver shutdown on windows nt. + +---------- +2003/03/16 17:40:56 crs +lib/common/Makefile.am +lib/common/common.dsp +lib/common/stdbitset.h +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.h +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h + +Made releaseKeys() only synthesize key releases for those keys +that synergy synthesized a press for, not keys that the user +is physically pressing. + +---------- +2003/03/16 17:40:47 crs +lib/platform/CSynergyHook.cpp + +Minor hook fixes. + +---------- +2003/03/16 17:40:25 crs +cmd/synergyc/synergyc.rc +cmd/synergys/synergys.rc + +Added resources missing from previous checkin. + +---------- +2003/03/13 20:24:45 crs +lib/platform/CXWindowsScreenSaver.cpp + +Moved comment to more relevant location. + +---------- +2003/03/13 19:20:55 crs +lib/platform/CXWindowsScreen.cpp + +Fixed double locking of mutex. + +---------- +2003/03/12 22:34:07 crs +cmd/launcher/CAdvancedOptions.cpp +cmd/launcher/CAdvancedOptions.h +cmd/launcher/CGlobalOptions.cpp +cmd/launcher/CGlobalOptions.h +cmd/launcher/LaunchUtil.cpp +cmd/launcher/LaunchUtil.h +cmd/launcher/Makefile.am +cmd/launcher/launcher.cpp +cmd/launcher/launcher.dsp +cmd/launcher/resource.h +cmd/synergyc/CClientTaskBarReceiver.cpp +cmd/synergyc/CClientTaskBarReceiver.h +cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp +cmd/synergyc/CMSWindowsClientTaskBarReceiver.h +cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp +cmd/synergyc/CXWindowsClientTaskBarReceiver.h +cmd/synergyc/Makefile.am +cmd/synergyc/resource.h +cmd/synergyc/synergyc.cpp +cmd/synergyc/synergyc.dsp +cmd/synergyc/tb_error.ico +cmd/synergyc/tb_idle.ico +cmd/synergyc/tb_run.ico +cmd/synergyc/tb_wait.ico +cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp +cmd/synergys/CMSWindowsServerTaskBarReceiver.h +cmd/synergys/CServerTaskBarReceiver.cpp +cmd/synergys/CServerTaskBarReceiver.h +cmd/synergys/CXWindowsServerTaskBarReceiver.cpp +cmd/synergys/CXWindowsServerTaskBarReceiver.h +cmd/synergys/Makefile.am +cmd/synergys/resource.h +cmd/synergys/synergys.cpp +cmd/synergys/synergys.dsp +cmd/synergys/tb_error.ico +cmd/synergys/tb_idle.ico +cmd/synergys/tb_run.ico +cmd/synergys/tb_wait.ico +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchConsoleWindows.cpp +lib/arch/CArchConsoleWindows.h +lib/arch/CArchImpl.cpp +lib/arch/CArchMultithreadPosix.cpp +lib/arch/CArchMultithreadPosix.h +lib/arch/CArchMultithreadWindows.cpp +lib/arch/CArchMultithreadWindows.h +lib/arch/CArchTaskBarWindows.cpp +lib/arch/CArchTaskBarWindows.h +lib/arch/CArchTaskBarXWindows.cpp +lib/arch/CArchTaskBarXWindows.h +lib/arch/IArchMultithread.h +lib/arch/IArchTaskBar.h +lib/arch/IArchTaskBarReceiver.h +lib/arch/Makefile.am +lib/arch/arch.dsp +lib/base/CJobList.cpp +lib/base/CJobList.h +lib/base/LogOutputters.cpp +lib/base/LogOutputters.h +lib/base/Makefile.am +lib/base/base.dsp +lib/client/CClient.cpp +lib/client/CClient.h +lib/client/CServerProxy.cpp +lib/mt/CThread.cpp +lib/mt/CThread.h +lib/mt/CTimerThread.cpp +lib/mt/CTimerThread.h +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsPrimaryScreen.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.h +lib/platform/CSynergyHook.cpp +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/synergy/CSecondaryScreen.cpp +lib/synergy/CSecondaryScreen.h + +Added switch delay and double-tap options to win32 and added a +tray icon to the client and server that gives status feedback to +the user and allows the user to kill the app. + +---------- +2003/02/23 19:29:08 crs +lib/server/CConfig.cpp +lib/server/CServer.cpp +lib/server/CServer.h +lib/synergy/OptionTypes.h +lib/synergy/ProtocolTypes.h + +Added support for a user option to require hitting the edge of a +screen twice within a specified amount of time in order to switch +screens. This can help prevent unintended switching. + +---------- +2003/02/22 21:53:25 crs +lib/platform/CXWindowsPrimaryScreen.cpp +lib/platform/CXWindowsPrimaryScreen.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h +lib/server/CConfig.cpp +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/synergy/CPrimaryScreen.h +lib/synergy/IPrimaryScreenReceiver.h +lib/synergy/IScreenEventHandler.h +lib/synergy/OptionTypes.h + +Added support on X11 for a global option to delay switching screens +when the mouse reaches a jump zone. + +---------- +2003/02/22 16:41:03 crs +lib/server/CConfig.cpp +lib/server/CConfig.h + +Added global options to CConfig (needed for heartbeat option). + +---------- +2003/02/22 16:20:23 crs +lib/client/CServerProxy.cpp +lib/client/CServerProxy.h +lib/server/CClientProxy.cpp +lib/server/CClientProxy.h +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/server/CServer.cpp +lib/synergy/OptionTypes.h +lib/synergy/ProtocolTypes.h + +Added support for heartbeat global option. + +---------- +2003/02/22 15:04:09 crs +configure.in +lib/common/Version.h + +Changed version to 1.0.5. + +---------- +2003/02/22 15:03:31 crs +lib/client/CServerProxy.cpp +lib/client/CServerProxy.h +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/synergy/KeyTypes.h +lib/synergy/OptionTypes.h + +Changes to support remapping modifier keys on clients. + +---------- +2003/02/17 12:44:37 crs +configure.in +lib/common/Version.h + +Changed version to 1.0.3. + +---------- +2003/02/16 19:55:54 crs +lib/platform/CMSWindowsSecondaryScreen.cpp + +Changed win32 client side cursor warping to be all relative motion +when not on the primary monitor. This should eliminate the flicker +between virtual display 0,0 and the correct position. While this +allows the user to confuse synergy by using the client's mouse, +synergy recovers quickly and easily from any confusion. + +---------- +2003/02/16 19:53:56 crs +lib/platform/CMSWindowsPrimaryScreen.cpp + +Added hack to heuristically detect bogus mouse motion caused by +a race condition where the synergy server updates the mouse +position but the synergy hook later receives a mouse update from +before the position change (i.e. out of order). + +---------- +2003/02/16 19:51:46 crs +lib/platform/CSynergyHook.cpp + +Commented out an unnecessary hook and added a compile time +switch to disable grabbing of keyboard on win32 to facilitate +debugging. + +---------- +2003/02/16 19:50:36 crs +lib/platform/CMSWindowsScreen.cpp + +Changed heap to stack allocation in an oft-called function for +data that's never used outside the function. + +---------- +2003/02/16 19:49:44 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp +lib/arch/CArchMultithreadWindows.cpp +lib/base/CUnicode.cpp + +Fixed memory leaks. + +---------- +2003/02/12 20:59:25 crs +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.cpp + +Fixed incorrect mouse button swapping on client screens. + +---------- +2003/02/12 20:59:08 crs +lib/arch/CArchDaemonWindows.cpp + +Fixed error in debug build on win32. + +---------- +2003/02/12 19:50:22 crs +lib/arch/vsnprintf.cpp + +Added a simple implementation of vsnprintf for unix platforms +without it using /dev/null, vfprintf(), and vsprintf(). + +---------- +2003/02/12 19:38:39 crs +lib/arch/CArchMiscWindows.h +lib/arch/XArch.h +lib/base/CLog.h +lib/base/CString.h +lib/base/ILogOutputter.h +lib/mt/CCondVar.h +lib/mt/CLock.h +lib/mt/CThread.h +lib/mt/CTimerThread.h + +Made sure every file includes common.h directly or indirectly. +Also made sure common.h is included before any system headers. + +---------- +2003/02/01 18:10:43 crs +FAQ +INSTALL +README +TODO + +Added info about using SSH for authentication and encryption. + +---------- +2003/01/29 22:16:40 crs +lib/platform/CXWindowsSecondaryScreen.cpp + +To support keymaps with only upper (or lower) case keysyms we now +use Xlib to convert an unmatched keysym to upper and lower case and +use whichever, if any, is not the same as the original keysym. +This supports case conversion in any language that Xlib supports +it in. + +---------- +2003/01/29 19:32:25 crs +lib/platform/CXWindowsSecondaryScreen.cpp + +Applied patch from grmcdorman at users dot sourceforge dot net to +support keymaps that have only uppercase letters, which is the case +by default on the Sun X server (for US keyboards anyway). + +---------- +2003/01/25 13:39:26 crs +NEWS +configure.in +lib/common/Version.h + +Changed version number to 1.0.2. + +---------- +2003/01/25 13:34:51 crs +cmd/launcher/launcher.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +lib/server/CConfig.cpp +lib/server/CConfig.h + +Added ability to set screen options from the windows launch dialog. + +---------- +2003/01/25 13:34:17 crs +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkWinsock.cpp + +Added missing entry in a socket family table. This was a serious +bug and should've failed on all platforms but just happened to +work on linux and windows. + +---------- +2003/01/22 08:37:32 crs +NEWS +configure.in +lib/common/Version.h + +Changed version number to 1.0.1. + +---------- +2003/01/22 08:36:43 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp +lib/arch/CArchDaemonWindows.cpp +lib/arch/CArchDaemonWindows.h +lib/arch/CArchLogWindows.cpp +lib/arch/CArchMiscWindows.cpp +lib/arch/CArchMiscWindows.h +lib/arch/CArchMultithreadWindows.cpp +lib/arch/CArchMultithreadWindows.h + +Fixed running as a service on Windows NT family. + +---------- +2003/01/18 14:36:19 crs +lib/arch/CArchSleepWindows.cpp +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CSynergyHook.cpp + +Fixed stupid errors introduced by last attempt to fix broken +mouse behavior on multimonitor windows systems. Those errors +broke synergy on all windows systems running as a server. +Also added an attempt to reduce the occasional jump that can +occur when switching screens when windows is the server. + +---------- +2003/01/18 14:31:54 crs +lib/platform/CXWindowsSecondaryScreen.cpp + +Was forcing modifier keys that have no effect on the keysym +lookup to be up when synthesizing key events. Now leaving +those modifiers in their current state. + +---------- +2003/01/18 10:49:13 crs +Makefile.am + +Added a dist-pkg target to put the binary distribution files into +a tar gzip file. This is to ease distribution of the binaries on +systems without a packaging system supported by synergy (which +currently supports only RPM). + +---------- +2003/01/16 21:28:15 crs +lib/server/CServer.cpp + +Fixed lookup of neighbor screens. The first problem was an old +code in a conditional for moving left that blew an assert verifying +that the mouse position was really on the screen if the neighbor +screen wasn't connected. + +After that was fixed there was another problem when one screen +linked to another which then linked (in the same direction) to +itself. If the latter screen wasn't connected then it'd get into +an infinite loop. + +---------- +2003/01/14 19:46:41 crs +lib/server/CServer.cpp + +Moved log message into conditionals so it only appears when the +conditions are true. + +---------- +2003/01/14 19:46:17 crs +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CSynergyHook.cpp + +Another try at fixing broken mouse behavior when a windows system +has multiple monitors with 0,0 of the virtual desktop not at the +upper-left. + +---------- +2003/01/12 16:35:54 crs +cmd/launcher/launcher.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h + +Added test of using the client's own name as the server name +with an appropriate error message. + +---------- +2003/01/12 16:08:45 crs +lib/server/CServer.cpp + +Now catching and ignoring errors when writing to a socket in those +cases where errors were not being caught, typically when responding +to some other socket or protocol error. + +---------- +2003/01/11 21:06:21 crs +acinclude.m4 +configure.in +lib/arch/CArchMultithreadPosix.cpp +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkBSD.h +lib/arch/CArchSleepUnix.cpp +lib/arch/CMultibyteEmu.cpp +lib/common/Makefile.am + +Fixes to support FreeBSD and Darwin. + +---------- +2003/01/11 15:16:41 crs +lib/platform/CXWindowsScreenSaver.cpp +lib/platform/CXWindowsScreenSaver.h + +Synergy no longer tries to suppress the screen saver once it starts. +It was doing that already if started through synergy but not if +started by something outside of synergy. In particular, if you +use `xscreensaver-command --activate' synergy used to send fake +mouse motion events every 5 seconds to deactivate it. That's +unlikely to be what the user wanted, especially if the locking is +enabled since it would force the password dialog to appear. + +As before, it's recommended that client screens not use locking +because xscreensaver will not deactivate without getting a +password even if we make the request through a programmatic +interface. Presumably that's for security reasons but it makes +life harder for synergy. + +---------- +2003/01/11 14:01:44 crs +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.cpp + +Attempt to fix problems with multimon windows. The mouse position +reported by the synergy hook dll is in a space with 0,0 in the +upper-left which is not necessarily the same as the virtual desktop +space. So the windows primary screen now accounts for that. On +the secondary screen, mouse_event() doesn't seem to accept negative +coordinates even on the windows NT family, making monitors with +negative coordinates inaccessible via absolute moves. So if the +move will be to negative coordinates, use the windows 95 family +fallback of absolute moving to 0,0 then relative moving to the +final position. + +---------- +2003/01/08 22:17:44 crs +FAQ +INSTALL + +Added bit about configuring on Solaris, which requires some +options to find the X11 includes and libraries. + +---------- +2003/01/08 21:36:14 crs +lib/arch/CArchMultithreadPosix.cpp +lib/arch/CArchMultithreadPosix.h +lib/arch/CArchNetworkBSD.cpp + +Portability fixes. Now builds on Linux 2.2 and 2.4 and solaris. +Also builds on i386, alpha, G3/G4, and sparc. + +---------- +2003/01/08 21:36:13 crs +lib/client/CClient.cpp +lib/platform/CXWindowsUtil.cpp + +Changed log level of two messages. Now won't spew about reading +window properties and will report connection failure at DEBUG +instead of DEBUG1. + +---------- +2003/01/08 21:36:10 crs +FAQ + +Added a FAQ entry for client being rejected. User probably didn't +start the server or told the client the wrong server host name. + +---------- +2003/01/07 21:47:27 crs +ChangeLog +configure.in +lib/common/Version.h + +Changed version number to 0.9.15. Added 0.9.15 log entries. + +---------- +2003/01/07 21:12:51 crs +lib/platform/CMSWindowsPrimaryScreen.cpp + +Attempts to improve forcing synergy window to foreground. These +changes don't seem to improve the situation but don't seem to +hurt either. + +---------- +2003/01/07 21:11:54 crs +lib/platform/CSynergyHook.cpp + +Added low-level mouse hook to support mouse wheel on NT (>=SP3). +Thanks to karsten for the patch used as a starting point. + +---------- +2003/01/05 21:52:28 crs +lib/base/LogOutputters.cpp +lib/base/LogOutputters.h + +Added missing files. + +---------- +2003/01/05 21:48:54 crs +PORTING +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp +doc/doxygen.cfg.in +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchConsoleUnix.h +lib/arch/CArchConsoleWindows.h +lib/arch/CArchDaemonNone.h +lib/arch/CArchDaemonUnix.h +lib/arch/CArchDaemonWindows.h +lib/arch/CArchFileUnix.h +lib/arch/CArchFileWindows.h +lib/arch/CArchLogUnix.h +lib/arch/CArchLogWindows.h +lib/arch/CArchMiscWindows.h +lib/arch/CArchMultithreadPosix.h +lib/arch/CArchMultithreadWindows.cpp +lib/arch/CArchMultithreadWindows.h +lib/arch/CArchNetworkBSD.h +lib/arch/CArchNetworkWinsock.cpp +lib/arch/CArchNetworkWinsock.h +lib/arch/CArchSleepUnix.h +lib/arch/CArchSleepWindows.h +lib/arch/CArchStringUnix.cpp +lib/arch/CArchStringUnix.h +lib/arch/CArchStringWindows.cpp +lib/arch/CArchStringWindows.h +lib/arch/CArchTimeUnix.h +lib/arch/CArchTimeWindows.h +lib/arch/IArchConsole.h +lib/arch/IArchFile.h +lib/arch/IArchLog.h +lib/arch/IArchMultithread.h +lib/arch/IArchNetwork.h +lib/arch/IArchSleep.h +lib/arch/IArchString.h +lib/arch/IArchTime.h +lib/arch/Makefile.am +lib/arch/XArchImpl.h +lib/arch/arch.dsp +lib/base/CLog.cpp +lib/base/CUnicode.cpp +lib/base/XBase.cpp +lib/base/XBase.h +lib/client/CMSWindowsSecondaryScreen.cpp +lib/client/CMSWindowsSecondaryScreen.h +lib/client/CSecondaryScreen.cpp +lib/client/CSecondaryScreen.h +lib/client/CXWindowsSecondaryScreen.cpp +lib/client/CXWindowsSecondaryScreen.h +lib/client/ISecondaryScreenFactory.h +lib/client/Makefile.am +lib/client/client.dsp +lib/http/XHTTP.h +lib/platform/CMSWindowsPrimaryScreen.cpp +lib/platform/CMSWindowsPrimaryScreen.h +lib/platform/CMSWindowsScreen.h +lib/platform/CMSWindowsSecondaryScreen.cpp +lib/platform/CMSWindowsSecondaryScreen.h +lib/platform/CXWindowsPrimaryScreen.cpp +lib/platform/CXWindowsPrimaryScreen.h +lib/platform/CXWindowsScreen.h +lib/platform/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsSecondaryScreen.h +lib/platform/Makefile.am +lib/platform/platform.dsp +lib/server/CMSWindowsPrimaryScreen.cpp +lib/server/CMSWindowsPrimaryScreen.h +lib/server/CPrimaryScreen.cpp +lib/server/CPrimaryScreen.h +lib/server/CXWindowsPrimaryScreen.cpp +lib/server/CXWindowsPrimaryScreen.h +lib/server/IPrimaryScreenFactory.h +lib/server/Makefile.am +lib/server/server.dsp +lib/synergy/CPrimaryScreen.cpp +lib/synergy/CPrimaryScreen.h +lib/synergy/CSecondaryScreen.cpp +lib/synergy/CSecondaryScreen.h +lib/synergy/IPrimaryScreenFactory.h +lib/synergy/ISecondaryScreenFactory.h +lib/synergy/Makefile.am +lib/synergy/libsynergy.dsp + +Moved CPrimaryScreen and CSecondaryScreen to the lib/synergy +and the platform specific implementations to lib/platform. +Added an lib/arch method to query the platform's native wide +character encoding and changed CUnicode to use it. All +platform dependent code is now in lib/arch, lib/platform, +and the programs under cmd. Also added more documentation. + +---------- +2003/01/04 22:01:32 crs +PORTING +cmd/launcher/CAutoStart.cpp +cmd/launcher/CAutoStart.h +cmd/launcher/LaunchUtil.cpp +cmd/launcher/launcher.cpp +cmd/launcher/launcher.dsp +cmd/launcher/launcher.rc +cmd/synergyc/Makefile.am +cmd/synergyc/synergyc.cpp +cmd/synergyc/synergyc.dsp +cmd/synergys/Makefile.am +cmd/synergys/synergys.cpp +cmd/synergys/synergys.dsp +configure.in +lib/Makefile.am +lib/arch/CArch.cpp +lib/arch/CArch.h +lib/arch/CArchConsoleUnix.cpp +lib/arch/CArchConsoleUnix.h +lib/arch/CArchConsoleWindows.cpp +lib/arch/CArchConsoleWindows.h +lib/arch/CArchDaemonNone.cpp +lib/arch/CArchDaemonNone.h +lib/arch/CArchDaemonUnix.cpp +lib/arch/CArchDaemonUnix.h +lib/arch/CArchDaemonWindows.cpp +lib/arch/CArchDaemonWindows.h +lib/arch/CArchFileUnix.cpp +lib/arch/CArchFileUnix.h +lib/arch/CArchFileWindows.cpp +lib/arch/CArchFileWindows.h +lib/arch/CArchImpl.cpp +lib/arch/CArchLogUnix.cpp +lib/arch/CArchLogUnix.h +lib/arch/CArchLogWindows.cpp +lib/arch/CArchLogWindows.h +lib/arch/CArchMiscWindows.cpp +lib/arch/CArchMiscWindows.h +lib/arch/CArchMultithreadPosix.cpp +lib/arch/CArchMultithreadPosix.h +lib/arch/CArchMultithreadWindows.cpp +lib/arch/CArchMultithreadWindows.h +lib/arch/CArchNetworkBSD.cpp +lib/arch/CArchNetworkBSD.h +lib/arch/CArchNetworkWinsock.cpp +lib/arch/CArchNetworkWinsock.h +lib/arch/CArchSleepUnix.cpp +lib/arch/CArchSleepUnix.h +lib/arch/CArchSleepWindows.cpp +lib/arch/CArchSleepWindows.h +lib/arch/CArchStringUnix.cpp +lib/arch/CArchStringUnix.h +lib/arch/CArchStringWindows.cpp +lib/arch/CArchStringWindows.h +lib/arch/CArchTimeUnix.cpp +lib/arch/CArchTimeUnix.h +lib/arch/CArchTimeWindows.cpp +lib/arch/CArchTimeWindows.h +lib/arch/CMultibyte.cpp +lib/arch/CMultibyteEmu.cpp +lib/arch/CMultibyteOS.cpp +lib/arch/IArchConsole.h +lib/arch/IArchDaemon.h +lib/arch/IArchFile.h +lib/arch/IArchLog.h +lib/arch/IArchMultithread.h +lib/arch/IArchNetwork.h +lib/arch/IArchSleep.h +lib/arch/IArchString.h +lib/arch/IArchTime.h +lib/arch/Makefile.am +lib/arch/XArch.cpp +lib/arch/XArch.h +lib/arch/XArchImpl.h +lib/arch/XArchUnix.cpp +lib/arch/XArchUnix.h +lib/arch/XArchWindows.cpp +lib/arch/XArchWindows.h +lib/arch/arch.dsp +lib/arch/vsnprintf.cpp +lib/base/BasicTypes.h +lib/base/CLog.cpp +lib/base/CLog.h +lib/base/CStopwatch.cpp +lib/base/CString.cpp +lib/base/CString.h +lib/base/CStringUtil.cpp +lib/base/CStringUtil.h +lib/base/CUnicode.cpp +lib/base/CUnicode.h +lib/base/IInterface.h +lib/base/ILogOutputter.h +lib/base/Makefile.am +lib/base/Version.h +lib/base/XBase.cpp +lib/base/XBase.h +lib/base/base.dsp +lib/base/common.h +lib/base/stdfstream.h +lib/base/stdistream.h +lib/base/stdlist.h +lib/base/stdmap.h +lib/base/stdostream.h +lib/base/stdpost.h +lib/base/stdpre.h +lib/base/stdset.h +lib/base/stdsstream.h +lib/base/stdvector.h +lib/client/CClient.cpp +lib/client/CMSWindowsSecondaryScreen.cpp +lib/client/Makefile.am +lib/client/client.dsp +lib/common/BasicTypes.h +lib/common/IInterface.h +lib/common/Makefile.am +lib/common/Version.h +lib/common/common.dsp +lib/common/common.h +lib/common/stdfstream.h +lib/common/stdistream.h +lib/common/stdlist.h +lib/common/stdmap.h +lib/common/stdostream.h +lib/common/stdpost.h +lib/common/stdpre.h +lib/common/stdset.h +lib/common/stdsstream.h +lib/common/stdstring.h +lib/common/stdvector.h +lib/http/CHTTPProtocol.h +lib/http/Makefile.am +lib/http/XHTTP.cpp +lib/http/http.dsp +lib/io/CUnicode.cpp +lib/io/CUnicode.h +lib/io/Makefile.am +lib/io/XIO.cpp +lib/io/XIO.h +lib/io/io.dsp +lib/mt/CCondVar.cpp +lib/mt/CCondVar.h +lib/mt/CMutex.cpp +lib/mt/CMutex.h +lib/mt/CThread.cpp +lib/mt/CThread.h +lib/mt/CThreadRep.cpp +lib/mt/CThreadRep.h +lib/mt/CTimerThread.cpp +lib/mt/Makefile.am +lib/mt/XMT.cpp +lib/mt/XMT.h +lib/mt/XThread.h +lib/mt/mt.dsp +lib/net/CNetwork.cpp +lib/net/CNetwork.h +lib/net/CNetworkAddress.cpp +lib/net/CNetworkAddress.h +lib/net/CTCPListenSocket.cpp +lib/net/CTCPListenSocket.h +lib/net/CTCPSocket.cpp +lib/net/CTCPSocket.h +lib/net/Makefile.am +lib/net/XNetwork.cpp +lib/net/XNetwork.h +lib/net/XSocket.cpp +lib/net/XSocket.h +lib/net/net.dsp +lib/platform/CMSWindowsScreen.cpp +lib/platform/CPlatform.cpp +lib/platform/CPlatform.h +lib/platform/CUnixPlatform.cpp +lib/platform/CUnixPlatform.h +lib/platform/CWin32Platform.cpp +lib/platform/CWin32Platform.h +lib/platform/CXWindowsClipboard.cpp +lib/platform/IPlatform.h +lib/platform/Makefile.am +lib/platform/platform.dsp +lib/platform/synrgyhk.dsp +lib/server/CConfig.h +lib/server/CMSWindowsPrimaryScreen.cpp +lib/server/CServer.cpp +lib/server/CXWindowsPrimaryScreen.cpp +lib/server/Makefile.am +lib/server/server.dsp +lib/synergy/Makefile.am +lib/synergy/XScreen.cpp +lib/synergy/XScreen.h +lib/synergy/XSynergy.cpp +lib/synergy/XSynergy.h +lib/synergy/libsynergy.dsp +synergy.dsw + +Refactored some platform dependent code into a new library, +lib/arch. This should make porting easier. Will probably +continue to refactor a little more, moving platform dependent +event handling stuff into lib/platform. + +---------- +2002/12/26 18:40:22 crs +FAQ + +More FAQs. + +---------- +2002/12/25 23:49:42 crs +BUGS +FAQ +INSTALL +README +TODO + +Documentation update. + +---------- +2002/12/25 22:56:09 crs +lib/server/CMSWindowsPrimaryScreen.cpp + +Made synrgyhk.dll error messages less cryptic. + +---------- +2002/12/25 19:21:17 crs +NEWS +configure.in +lib/base/Version.h + +Changed version number to 0.9.14. Added NEWS item. + +---------- +2002/12/25 18:44:54 crs +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreenSaver.cpp + +Improved handling of screen saver handling when windows 2k is +the client and the screen saver is password protected. It used +to immediately turn off the screen saver (unintentionally) in +that case. + +---------- +2002/12/25 10:35:59 crs +acinclude.m4 +config/config.guess +config/config.sub +configure.in +lib/base/common.h +lib/mt/CThreadRep.cpp +lib/net/CNetwork.cpp +lib/net/CNetwork.h +lib/platform/CUnixPlatform.cpp + +Changes to support building on solaris, irix, and darwin. Also +removed test for working fork (AC_FORK). + +---------- +2002/12/23 14:47:44 crs +lib/client/CServerProxy.cpp +lib/client/CServerProxy.h + +Added code to process set/reset options messages from server. + +---------- +2002/12/23 13:55:21 crs +lib/client/CClient.cpp +lib/client/CClient.h +lib/client/CMSWindowsSecondaryScreen.cpp +lib/client/CMSWindowsSecondaryScreen.h +lib/client/CSecondaryScreen.cpp +lib/client/CSecondaryScreen.h +lib/client/CXWindowsSecondaryScreen.cpp +lib/client/CXWindowsSecondaryScreen.h +lib/server/CClientProxy.h +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/server/CMSWindowsPrimaryScreen.cpp +lib/server/CMSWindowsPrimaryScreen.h +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CPrimaryScreen.cpp +lib/server/CPrimaryScreen.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/server/CXWindowsPrimaryScreen.cpp +lib/server/CXWindowsPrimaryScreen.h +lib/synergy/CProtocolUtil.cpp +lib/synergy/CProtocolUtil.h +lib/synergy/IClient.h +lib/synergy/Makefile.am +lib/synergy/OptionTypes.h +lib/synergy/ProtocolTypes.h + +Added support for per-screen options in the configuration file +and sending those options to the appropriate client screens. +Currently, two options are supported: halfDuplexCapsLock and +halfDuplexNumLock mark the caps lock and num lock keys, +respectively, as being half-duplex. + +---------- +2002/12/22 14:51:41 crs +configure.in +doc/doxygen.cfg.in + +Doxygen config file now sets HAVE_DOT to YES only if dot is found +by configure. + +---------- +2002/12/15 22:39:59 crs +lib/client/CXWindowsSecondaryScreen.cpp +lib/client/CXWindowsSecondaryScreen.h + +Now handling any number of pointer buttons. + +---------- +2002/12/15 22:17:18 crs +lib/server/CXWindowsPrimaryScreen.cpp + +Now ignoring half-duplex keys that are down when deciding if +the mouse is locked to the screen. We can't tell if a half- +duplex key is physically down and logically down just means +it's active so there's no point in letting it lock the mouse +to the screen. + +---------- +2002/12/15 22:14:49 crs +lib/client/CMSWindowsSecondaryScreen.cpp +lib/client/CMSWindowsSecondaryScreen.h +lib/client/CSecondaryScreen.cpp +lib/client/CSecondaryScreen.h +lib/client/CXWindowsSecondaryScreen.cpp +lib/client/CXWindowsSecondaryScreen.h + +Now restoring toggle key states on leaving a client screen to +their state when the screen was entered. Previously when +leaving a client screen the toggle keys kept their state so, +say, caps lock, would remain on. This was inconvenient if +you then used the client's keyboard directly. + +---------- +2002/12/15 20:00:52 crs +lib/server/CMSWindowsPrimaryScreen.cpp + +Fixed loss of ctrl+alt when transmitted to non-windows platforms +from a windows server. Was converting ctrl+alt on windows to +mode switch on the server. No longer doing that; windows clients +will interpret ctrl+alt as AltGr and other clients will just see +ctrl+alt. Also made the right alt key mode switch on windows +servers in case the user wants to force a mode switch, but that +means the right alt key no longer acts as alt on clients. + +---------- +2002/12/15 19:58:41 crs +lib/platform/CMSWindowsScreen.cpp + +Fixed client not reconnecting when server dies bug. + +---------- +2002/12/15 19:57:28 crs +lib/client/CXWindowsSecondaryScreen.cpp + +Cleanup and changed some DEBUG1 messages to DEBUG2. + +---------- +2002/12/15 11:12:39 crs +doc/doxygen.cfg.in + +Enabled using dot and class diagrams. + +---------- +2002/11/05 19:23:05 crs +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreenSaver.cpp +lib/platform/CXWindowsScreenSaver.h + +Fixed bug in detecting screen saver activation. Was using || instead +of && in conditional. + +---------- +2002/11/03 18:09:28 crs +acinclude.m4 +configure.in +lib/io/CUnicode.h +lib/net/CNetwork.h +lib/platform/CUnixPlatform.cpp + +Merged fixes for building on MacOS X. It dies on one file with +an internal compiler error; building that file without +optimization works around the compiler bug. Sadly, synergy can +only interact with X windows, not native MacOS windows. + +---------- +2002/10/30 22:22:16 crs +acinclude.m4 + +Escaped quotes to satisfy older autoheader versions. + +---------- +2002/10/30 22:16:30 crs +lib/net/CNetwork.h +lib/net/CTCPSocket.cpp + +Fixed bugs in error handling in CTCPSocket; previously was not +handling read errors at all and error handling for writes was +never being used. Now the socket disconnects if a read or write +fails on the socket for any reason except EINTR. Also added + to includes in CNetwork.h because it's needed on +some platforms. + +---------- +2002/10/29 22:07:55 crs +all.dsp +lib/base/base.dsp +lib/client/client.dsp +lib/http/http.dsp +lib/io/CUnicode.cpp +lib/io/io.dsp +lib/mt/mt.dsp +lib/net/CNetwork.cpp +lib/net/CNetwork.h +lib/net/net.dsp +lib/platform/makehook.dsp +lib/platform/platform.dsp +lib/platform/synrgyhk.dsp +lib/server/server.dsp +lib/synergy/libsynergy.dsp + +Ported recent changes to win32 and fixed CRLF problems with project +files (most had CRCRCRLF). + +---------- +2002/10/28 22:49:21 crs +acinclude.m4 +configure.in +lib/mt/CThreadRep.cpp + +solaris configure and build fixes. without having solaris i +can only hope that these changes actually work. + +---------- +2002/10/22 22:35:13 crs +configure.in +lib/io/CUnicode.cpp + +Added workarounds for missing reentrant versions of wide char +to/from multi-byte conversion functions. + +---------- +2002/10/22 21:30:48 crs +lib/base/CUnicode.cpp +lib/base/CUnicode.h +lib/base/Makefile.am +lib/io/CUnicode.cpp +lib/io/CUnicode.h +lib/io/Makefile.am +lib/platform/Makefile.am + +Moved CUnicode to lib/io. That's a reasonable place for it +that's after lib/mt. It needs to be after lib/mt in preparation +for supporting platforms without the reentrant wide char and +multi-byte functions. + +---------- +2002/10/20 22:39:54 crs +lib/client/CMSWindowsSecondaryScreen.cpp + +Fixed conditional to test for multimon to do nasty win32 mouse +positioning hack. Was doing hack if *not* a multiple monitor +system but should've been doing it if is *is* a multimon system. + +---------- +2002/10/20 22:36:24 crs +lib/net/CNetwork.cpp +lib/net/CNetwork.h +lib/net/CNetworkAddress.cpp + +Replaced inet_addr() with inet_aton(), which is a better function +anyway but isn't implemented in winsock, removed use of INADDR_NONE +which some platforms don't define except on winsock which does +define it, and changed SOL_TCP to IPPROTO_TCP which should work on +more platforms. + +---------- +2002/10/17 21:37:41 crs +lib/platform/CXWindowsScreen.cpp + +Fixed CXWindowsScreen to force the event loop to wake up when +exitMainLoop() is called. + +---------- +2002/10/17 21:37:37 crs +lib/mt/CThreadRep.cpp + +Fixed CThreadRep to not raise a signal on the thread if it's +already dead. Otherwise the signal could propagate to the +parent thread (at least on linux threads) and cause havoc. + +---------- +2002/10/17 21:37:31 crs +lib/server/CServer.cpp +lib/server/CServer.h + +Changed server to fail with an error if in can't bind() the listen +socket for any reason other than it's in use. + +---------- +2002/10/17 20:56:28 crs +lib/net/CNetwork.cpp +lib/net/CNetwork.h +lib/net/CNetworkAddress.cpp + +Changed non-reentrant network functions to be reentrant and +thread safe. + +---------- +2002/10/16 22:01:50 crs +acinclude.m4 +configure.in +lib/net/CNetwork.cpp +lib/net/CNetwork.h +lib/net/CTCPSocket.cpp +lib/platform/CXWindowsScreen.cpp + +Added support for using select() instead of poll(). + +---------- +2002/10/15 22:17:41 crs +lib/server/CConfig.cpp + +CConfig now accepts and discards \r at the end of a line. This +allows the unix server to read configuration files created on +microsoft windows platforms. + +---------- +2002/10/15 22:08:10 crs +cmd/synergys/synergys.cpp +lib/server/CConfig.cpp + +Fixed use of %s instead of %{1} in format() call. + +---------- +2002/10/15 22:01:41 crs +lib/client/CClient.cpp +lib/mt/CThreadRep.cpp +lib/mt/Makefile.am +lib/mt/XMT.cpp +lib/mt/XMT.h +lib/mt/XThread.h +lib/server/CServer.cpp + +Renamed XThreadUnavailable to XMTThreadUnavailable and derived it +from XBase so it can be caught normally. Changed client and server +to handle unavailable threads (in main loop, anyway). + +---------- +2002/10/15 21:35:12 crs +lib/mt/CThreadRep.cpp + +Workaround for pthread bug on RedHat 7.2 on multiprocessor +systems. + +---------- +2002/10/15 21:29:44 crs +cmd/synergyc/synergyc.cpp +cmd/synergys/synergys.cpp +lib/base/CLog.h +lib/client/CClient.cpp +lib/client/CMSWindowsSecondaryScreen.cpp +lib/client/CSecondaryScreen.cpp +lib/client/CServerProxy.cpp +lib/client/CXWindowsSecondaryScreen.cpp +lib/http/CHTTPProtocol.cpp +lib/mt/CMutex.cpp +lib/mt/CThread.cpp +lib/mt/CThreadRep.cpp +lib/mt/CTimerThread.cpp +lib/net/CNetwork.cpp +lib/platform/CMSWindowsClipboard.cpp +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreenSaver.cpp +lib/platform/CWin32Platform.cpp +lib/platform/CXWindowsClipboard.cpp +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreenSaver.cpp +lib/platform/CXWindowsUtil.cpp +lib/server/CClientProxy1_0.cpp +lib/server/CHTTPServer.cpp +lib/server/CMSWindowsPrimaryScreen.cpp +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryScreen.cpp +lib/server/CServer.cpp +lib/server/CXWindowsPrimaryScreen.cpp +lib/synergy/CProtocolUtil.cpp + +Changed log() and logc() macros to LOG() and LOGC(), respectively. +This avoids a conflict with the standard math library log() +function. + +---------- +2002/09/14 21:31:35 crs +lib/base/XBase.cpp +lib/base/XBase.h + +removed std::exception from base class list of XBase. this +is a workaround for gcc 3.2 until everything necessary has +throw() specifiers. + +---------- +2002/09/14 20:56:50 crs +lib/server/CServer.cpp + +now logging bind failures as warnings. + +---------- +2002/09/14 20:56:28 crs +lib/base/XBase.cpp +lib/base/XBase.h +lib/io/XIO.cpp +lib/io/XIO.h +lib/net/CTCPListenSocket.cpp +lib/net/CTCPSocket.cpp +lib/net/XSocket.cpp +lib/net/XSocket.h + +added better network error message support. + +---------- +2002/09/14 12:07:02 crs +lib/client/CMSWindowsSecondaryScreen.cpp +lib/client/CXWindowsSecondaryScreen.cpp +lib/client/CXWindowsSecondaryScreen.h +lib/server/CMSWindowsPrimaryScreen.cpp +lib/server/CXWindowsPrimaryScreen.cpp +lib/server/CXWindowsPrimaryScreen.h +lib/synergy/KeyTypes.h + +Rewrote handling of key press on X11 client; it should be much +more robust now. Also added handling of Super modifier key and +changed windows keys to map to Super instead of Meta, which is +the default on my keyboard. + +---------- +2002/09/14 12:05:06 crs +cmd/launcher/launcher.cpp +cmd/launcher/launcher.rc +cmd/launcher/resource.h + +Added debug level combo box and version number to title bar of win32 +launcher. + +---------- +2002/09/14 12:03:43 crs +cmd/synergyc/resource.h +cmd/synergyc/synergyc.cpp +cmd/synergyc/synergyc.rc +cmd/synergys/resource.h +cmd/synergys/synergys.cpp +cmd/synergys/synergys.rc + +Fixed backend mode. Now reports log messages and, if any are +serious, shows a message box before exiting. + +---------- +2002/09/04 21:17:01 crs +NEWS +configure.in +lib/base/Version.h + +Changed version number to 0.9.11. Added NEWS item. + +---------- +2002/09/04 21:14:18 crs +lib/client/CMSWindowsSecondaryScreen.cpp + +now looking up SendEvent() using GetProcAddress() so win95 +systems can run the synergy client. + +---------- +2002/09/04 20:17:54 crs +lib/client/CXWindowsSecondaryScreen.cpp + +fixed bug that caused the wrong keycode to be used for most, +possibly all, keysyms. was reading past the end of an array +of keysyms. + +---------- +2002/09/02 17:36:25 crs +configure.in +lib/base/Version.h + +Changed version number to 0.9.10. + +---------- +2002/09/02 17:30:04 crs +BUGS +INSTALL +cmd/launcher/CAutoStart.cpp +cmd/launcher/CAutoStart.h +cmd/launcher/LaunchUtil.cpp +cmd/launcher/LaunchUtil.h +cmd/launcher/launcher.cpp +cmd/launcher/launcher.dsp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +cmd/synergyc/resource.h +cmd/synergyc/synergyc.cpp +cmd/synergyc/synergyc.rc +cmd/synergys/resource.h +cmd/synergys/synergys.cpp +lib/client/CClient.cpp +lib/client/CMSWindowsSecondaryScreen.cpp +lib/platform/CUnixPlatform.cpp +lib/platform/CUnixPlatform.h +lib/platform/CWin32Platform.cpp +lib/platform/CWin32Platform.h +lib/platform/IPlatform.h +lib/server/CMSWindowsPrimaryScreen.cpp + +Fixed win32 config saving, keyboard mapping, and AltGr bugs. +Made extensive changes to the launcher to provide more control +over setting up auto-start and it now saves configuration to +the user's documents directory if auto-starting at login and +saves to the system directory if auto-starting at boot. +Replaced MapVirtualKey() with table lookup to work around that +function's lack of support for extended keyboard scan codes. +Added first cut at support for AltGr. + +---------- +2002/09/01 15:30:00 crs +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/KeyTypes.h + +Added support for mode switch key to X11 screens. + +---------- +2002/09/01 10:31:10 crs +acinclude.m4 +configure.in +lib/base/stdsstream.h + +added more tests to autoconf. also now handling missing sstream +header in gcc 2.95 by including sstream header backported from v3. + +---------- +2002/09/01 09:28:54 crs +lib/platform/CXWindowsUtil.cpp + +lowered severity of some debug messages. + +---------- +2002/08/18 17:45:59 crs +configure.in +lib/base/Version.h + +Changed version number to 0.9.9. + +---------- +2002/08/18 17:40:10 crs +lib/server/CMSWindowsPrimaryScreen.cpp + +fixed win32 deadlock. when a client disconnects the server will +warp the mouse to the primary screen. entering the primary +screen causes the primary screen's window to be hidden. the +deadlock occurs because hiding the window seems to post a +message then wait for it to be handled (or possibly it won't +send a message while a posted message is being handled). +thread A locks the mutex, warps the mouse, the hides the window. +thread B begins processing the mouse warp then tries to lock +the mutex. thread A is waiting on the event loop owned by B +while B is waiting on the mutex owned by A. this fix simply +hides the window asynchronously. however, there may be other +ways to cause a similar deadlock that have not been found. + +---------- +2002/08/18 17:35:10 crs +lib/client/CMSWindowsSecondaryScreen.cpp +lib/client/CMSWindowsSecondaryScreen.h +lib/server/CMSWindowsPrimaryScreen.cpp + +fixed PrintScrn handling; it was being changed to keypad multiply. + +---------- +2002/08/18 17:31:48 crs +lib/client/CXWindowsSecondaryScreen.cpp +lib/client/CXWindowsSecondaryScreen.h + +no longer sending fake events for unmapped logical buttons. + +---------- +2002/08/11 22:43:07 crs +BUGS +INSTALL +NEWS +PORTING +README +cmd/Makefile.am +cmd/launcher/launcher.cpp +cmd/synergy/Makefile.am +cmd/synergy/resource.h +cmd/synergy/synergy.cpp +cmd/synergy/synergy.dsp +cmd/synergy/synergy.ico +cmd/synergy/synergy.rc +cmd/synergyc/Makefile.am +cmd/synergyc/resource.h +cmd/synergyc/synergyc.cpp +cmd/synergyc/synergyc.dsp +cmd/synergyc/synergyc.ico +cmd/synergyc/synergyc.rc +cmd/synergyd/Makefile.am +cmd/synergyd/resource.h +cmd/synergyd/synergy.ico +cmd/synergyd/synergyd.cpp +cmd/synergyd/synergyd.dsp +cmd/synergyd/synergyd.rc +cmd/synergys/Makefile.am +cmd/synergys/resource.h +cmd/synergys/synergys.cpp +cmd/synergys/synergys.dsp +cmd/synergys/synergys.ico +cmd/synergys/synergys.rc +configure.in +dist/rpm/synergy.spec.in +synergy.dsw + +Moved synergy client to cmd/synergyc and renamed it synergyc. +Moved synergy server to cmd/synergys and renamed it synergys. +Updated documentation to reflect that and the win32 launcher. + +---------- +2002/08/11 11:50:49 crs +Makefile.am +TODO + +added TODO file and top-level rule to make zip file of distribution +files. + +---------- +2002/08/03 11:50:07 crs +lib/mt/CCondVar.h + +removed pre-instantiation of templates in header file. + +---------- +2002/08/03 11:49:36 crs +all.dsp +cmd/Makefile.am +cmd/launcher/Makefile.am +cmd/launcher/launcher.cpp +cmd/launcher/launcher.dsp +cmd/launcher/launcher.rc +cmd/launcher/resource.h +cmd/launcher/synergy.ico +cmd/synergy/Makefile.am +cmd/synergy/resource.h +cmd/synergy/synergy.cpp +cmd/synergy/synergy.dsp +cmd/synergy/synergy.ico +cmd/synergy/synergy.rc +cmd/synergyd/Makefile.am +cmd/synergyd/resource.h +cmd/synergyd/synergy.ico +cmd/synergyd/synergyd.cpp +cmd/synergyd/synergyd.dsp +cmd/synergyd/synergyd.rc +configure.in +lib/base/CString.cpp +lib/base/Version.h +lib/base/base.dsp +lib/client/client.dsp +lib/http/http.dsp +lib/io/io.dsp +lib/mt/mt.dsp +lib/net/net.dsp +lib/platform/CMSWindowsScreen.cpp +lib/platform/makehook.dsp +lib/platform/platform.dsp +lib/platform/synrgyhk.dsp +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/server/server.dsp +lib/synergy/libsynergy.dsp +synergy.dsw + +added win32 launcher program. also changed VC++ dsp and dsw +files to binary form so \r\n aren't converted. added icons +to client and server apps on win32. + +---------- +2002/08/02 17:57:54 crs +Makefile.am +configure.in +dist/Makefile.am +dist/rpm/Makefile.am +dist/rpm/synergy.spec.in + +added build rule to create RPMs. + +---------- +2002/08/02 17:53:23 crs +AUTHORS +BUGS +FAQ +HISTORY +INSTALL +NEWS +PORTING +README + +minor documentation updates. added HISTORY and PORTING. + +---------- +2002/08/01 18:07:32 crs +AUTHORS +BUGS +COPYING +ChangeLog +FAQ +INSTALL +NEWS +README + +added files for release. + +---------- +2002/08/01 11:45:21 crs +configure.in +lib/base/CStopwatch.cpp +lib/mt/CCondVar.cpp +lib/platform/CUnixPlatform.cpp + +minor automake fixes. + +---------- +2002/07/31 17:40:36 crs +Makefile.am + +added simple rule to build doxygen. + +---------- +2002/07/31 17:40:21 crs +lib/synergy/XSynergy.h + +fixed comment. + +---------- +2002/07/31 17:35:43 crs +Makefile.am + +removed two programs from files to clean. + +---------- +2002/07/31 17:34:05 crs +Makefile.am +cmd/Makefile.am +cmd/synergy/Makefile.am +cmd/synergyd/Makefile.am +lib/Makefile.am +lib/base/Makefile.am +lib/client/Makefile.am +lib/http/Makefile.am +lib/io/Makefile.am +lib/mt/Makefile.am +lib/net/Makefile.am +lib/platform/Makefile.am +lib/server/Makefile.am +lib/synergy/Makefile.am + +fixes to get vpath builds working (necessary for `make distcheck'). + +---------- +2002/07/31 16:57:26 crs +Makefile.am +cmd/Makefile.am +cmd/synergy/Makefile.am +cmd/synergy/synergy.cpp +cmd/synergyd/Makefile.am +cmd/synergyd/synergyd.cpp +configure.in +lib/Makefile.am +lib/base/Makefile.am +lib/base/Version.h +lib/client/Makefile.am +lib/http/Makefile.am +lib/io/Makefile.am +lib/mt/Makefile.am +lib/net/Makefile.am +lib/platform/Makefile.am +lib/server/Makefile.am +lib/synergy/Makefile.am +lib/synergy/ProtocolTypes.h +lib/synergy/Version.h + +Moved version header to base and it now uses VERSION macro +from config.h if available (which means version is now a +string, not three integers). Changed version to 1.0.0 and +protocol version to 1.0. And added MAINTAINERCLEANFILES +to makefiles to remove generated files. + +---------- +2002/07/31 16:27:06 crs +Makefile.am +cmd/synergy/Makefile.am +cmd/synergyd/Makefile.am +examples/synergy.linux.init +examples/synergyd.linux.init +lib/base/Makefile.am +lib/client/Makefile.am +lib/http/Makefile.am +lib/io/Makefile.am +lib/mt/Makefile.am +lib/net/Makefile.am +lib/platform/Makefile.am +lib/server/Makefile.am +lib/synergy/Makefile.am +nodist/notes +notes + +added EXTRA_* files to get `make dist' doing the right thing. + +---------- +2002/07/31 16:24:45 crs +notes + +checkpoint notes. + +---------- +2002/07/31 13:56:59 crs +lib/base/CLog.cpp + +removed now unnecssary #define. + +---------- +2002/07/31 13:41:58 crs +lib/http/http.dsp +lib/platform/CMSWindowsClipboardAnyTextConverter.cpp +lib/platform/CMSWindowsClipboardAnyTextConverter.h +lib/platform/CMSWindowsClipboardTextConverter.cpp +lib/platform/CMSWindowsClipboardTextConverter.h +lib/platform/CMSWindowsClipboardUTF16Converter.cpp +lib/platform/CMSWindowsClipboardUTF16Converter.h +lib/platform/CMSWindowsScreenSaver.cpp +lib/platform/CMSWindowsScreenSaver.h +lib/platform/IMSWindowsScreenEventHandler.h + +okay, now the files should no longer be executable. + +---------- +2002/07/31 13:34:18 crs +lib/http/http.dsp +lib/platform/CMSWindowsClipboardAnyTextConverter.cpp +lib/platform/CMSWindowsClipboardAnyTextConverter.h +lib/platform/CMSWindowsClipboardTextConverter.cpp +lib/platform/CMSWindowsClipboardTextConverter.h +lib/platform/CMSWindowsClipboardUTF16Converter.cpp +lib/platform/CMSWindowsClipboardUTF16Converter.h +lib/platform/CMSWindowsScreenSaver.cpp +lib/platform/CMSWindowsScreenSaver.h +lib/platform/IMSWindowsScreenEventHandler.h +notes + +removed unintentional executable flag. + +---------- +2002/07/31 13:29:33 crs +notes + +checkpoint notes. + +---------- +2002/07/31 13:18:27 crs +README + +added comment about large motif clipboard items to README. + +---------- +2002/07/31 13:10:15 crs +README + +updated README. + +---------- +2002/07/31 12:40:41 crs +lib/platform/CSynergyHook.cpp +lib/platform/synrgyhk.dsp + +now building hook dll for release without linking in standard +C runtime. need C runtime for debug build for asserts. + +---------- +2002/07/31 12:39:34 crs +cmd/synergy/synergy.cpp +cmd/synergyd/synergyd.cpp +lib/client/CClient.cpp +lib/client/CClient.h +lib/client/CXWindowsSecondaryScreen.cpp +lib/platform/CXWindowsScreen.cpp +lib/server/CClientProxy.h +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CMSWindowsPrimaryScreen.cpp +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/synergy/IClient.h +lib/synergy/IScreen.h +lib/synergy/XScreen.cpp +lib/synergy/XScreen.h + +fixed problem with opening client and server. in some cases it +would fail to open in such a way that it could never succeed +but it'd never stop retrying. now terminating when open fails +such that it'll never succeed. + +---------- +2002/07/30 19:03:40 crs +lib/client/client.dsp +lib/io/io.dsp +lib/net/net.dsp +lib/server/server.dsp +lib/synergy/libsynergy.dsp + +added new files to projects and added two project files that +should've been adding in change 530. + +---------- +2002/07/30 18:49:31 crs +lib/client/CServerProxy.cpp +lib/server/CClientProxy1_0.cpp +lib/synergy/ProtocolTypes.h + +made it so a negative kHeartRate disables heartbeats and set +kHeartRate to -1. + +---------- +2002/07/30 18:31:21 crs +lib/mt/CThreadRep.cpp +lib/mt/XThread.h + +moved exception definition to header file. + +---------- +2002/07/30 18:31:00 crs +cmd/synergy/synergy.cpp +cmd/synergyd/synergyd.cpp +lib/client/CClient.cpp +lib/client/CClient.h +lib/client/ISecondaryScreenFactory.h +lib/client/Makefile.am +lib/io/IStreamFilterFactory.h +lib/io/Makefile.am +lib/net/CTCPSocketFactory.cpp +lib/net/CTCPSocketFactory.h +lib/net/ISocketFactory.h +lib/net/Makefile.am +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/server/IPrimaryScreenFactory.h +lib/server/Makefile.am +lib/synergy/CTCPSocketFactory.cpp +lib/synergy/CTCPSocketFactory.h +lib/synergy/ISocketFactory.h +lib/synergy/Makefile.am +lib/synergy/ProtocolTypes.h + +now using class factories to move some decisions from the libraries +into the application. + +---------- +2002/07/30 16:52:46 crs +Makefile.am +base/BasicTypes.h +base/CFunctionJob.cpp +base/CFunctionJob.h +base/CLog.cpp +base/CLog.h +base/CStopwatch.cpp +base/CStopwatch.h +base/CString.cpp +base/CString.h +base/CUnicode.cpp +base/CUnicode.h +base/IInterface.h +base/IJob.h +base/Makefile.am +base/TMethodJob.h +base/XBase.cpp +base/XBase.h +base/base.dsp +base/common.h +base/stdfstream.h +base/stdistream.h +base/stdlist.h +base/stdmap.h +base/stdostream.h +base/stdpost.h +base/stdpre.h +base/stdset.h +base/stdsstream.h +base/stdvector.h +client/CClient.cpp +client/CClient.h +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CSecondaryScreen.cpp +client/CSecondaryScreen.h +client/CServerProxy.cpp +client/CServerProxy.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +client/Makefile.am +client/client.cpp +client/client.dsp +client/client.rc +client/resource.h +cmd/Makefile.am +cmd/synergy/Makefile.am +cmd/synergy/resource.h +cmd/synergy/synergy.cpp +cmd/synergy/synergy.dsp +cmd/synergy/synergy.rc +cmd/synergyd/Makefile.am +cmd/synergyd/resource.h +cmd/synergyd/synergyd.cpp +cmd/synergyd/synergyd.dsp +cmd/synergyd/synergyd.rc +configure.in +http/CHTTPProtocol.cpp +http/CHTTPProtocol.h +http/Makefile.am +http/XHTTP.cpp +http/XHTTP.h +http/http.dsp +io/CBufferedInputStream.cpp +io/CBufferedInputStream.h +io/CBufferedOutputStream.cpp +io/CBufferedOutputStream.h +io/CInputStreamFilter.cpp +io/CInputStreamFilter.h +io/COutputStreamFilter.cpp +io/COutputStreamFilter.h +io/CStreamBuffer.cpp +io/CStreamBuffer.h +io/IInputStream.h +io/IOutputStream.h +io/Makefile.am +io/XIO.cpp +io/XIO.h +io/io.dsp +lib/Makefile.am +lib/base/BasicTypes.h +lib/base/CFunctionJob.cpp +lib/base/CFunctionJob.h +lib/base/CLog.cpp +lib/base/CLog.h +lib/base/CStopwatch.cpp +lib/base/CStopwatch.h +lib/base/CString.cpp +lib/base/CString.h +lib/base/CUnicode.cpp +lib/base/CUnicode.h +lib/base/IInterface.h +lib/base/IJob.h +lib/base/Makefile.am +lib/base/TMethodJob.h +lib/base/XBase.cpp +lib/base/XBase.h +lib/base/base.dsp +lib/base/common.h +lib/base/stdfstream.h +lib/base/stdistream.h +lib/base/stdlist.h +lib/base/stdmap.h +lib/base/stdostream.h +lib/base/stdpost.h +lib/base/stdpre.h +lib/base/stdset.h +lib/base/stdsstream.h +lib/base/stdvector.h +lib/client/CClient.cpp +lib/client/CClient.h +lib/client/CMSWindowsSecondaryScreen.cpp +lib/client/CMSWindowsSecondaryScreen.h +lib/client/CSecondaryScreen.cpp +lib/client/CSecondaryScreen.h +lib/client/CServerProxy.cpp +lib/client/CServerProxy.h +lib/client/CXWindowsSecondaryScreen.cpp +lib/client/CXWindowsSecondaryScreen.h +lib/client/Makefile.am +lib/http/CHTTPProtocol.cpp +lib/http/CHTTPProtocol.h +lib/http/Makefile.am +lib/http/XHTTP.cpp +lib/http/XHTTP.h +lib/http/http.dsp +lib/io/CBufferedInputStream.cpp +lib/io/CBufferedInputStream.h +lib/io/CBufferedOutputStream.cpp +lib/io/CBufferedOutputStream.h +lib/io/CInputStreamFilter.cpp +lib/io/CInputStreamFilter.h +lib/io/COutputStreamFilter.cpp +lib/io/COutputStreamFilter.h +lib/io/CStreamBuffer.cpp +lib/io/CStreamBuffer.h +lib/io/IInputStream.h +lib/io/IOutputStream.h +lib/io/Makefile.am +lib/io/XIO.cpp +lib/io/XIO.h +lib/io/io.dsp +lib/mt/CCondVar.cpp +lib/mt/CCondVar.h +lib/mt/CLock.cpp +lib/mt/CLock.h +lib/mt/CMutex.cpp +lib/mt/CMutex.h +lib/mt/CThread.cpp +lib/mt/CThread.h +lib/mt/CThreadRep.cpp +lib/mt/CThreadRep.h +lib/mt/CTimerThread.cpp +lib/mt/CTimerThread.h +lib/mt/Makefile.am +lib/mt/XThread.h +lib/mt/mt.dsp +lib/net/CNetwork.cpp +lib/net/CNetwork.h +lib/net/CNetworkAddress.cpp +lib/net/CNetworkAddress.h +lib/net/CTCPListenSocket.cpp +lib/net/CTCPListenSocket.h +lib/net/CTCPSocket.cpp +lib/net/CTCPSocket.h +lib/net/IDataSocket.h +lib/net/IListenSocket.h +lib/net/ISocket.h +lib/net/Makefile.am +lib/net/XNetwork.cpp +lib/net/XNetwork.h +lib/net/XSocket.cpp +lib/net/XSocket.h +lib/net/net.dsp +lib/platform/CMSWindowsClipboard.cpp +lib/platform/CMSWindowsClipboard.h +lib/platform/CMSWindowsClipboardAnyTextConverter.cpp +lib/platform/CMSWindowsClipboardAnyTextConverter.h +lib/platform/CMSWindowsClipboardTextConverter.cpp +lib/platform/CMSWindowsClipboardTextConverter.h +lib/platform/CMSWindowsClipboardUTF16Converter.cpp +lib/platform/CMSWindowsClipboardUTF16Converter.h +lib/platform/CMSWindowsScreen.cpp +lib/platform/CMSWindowsScreen.h +lib/platform/CMSWindowsScreenSaver.cpp +lib/platform/CMSWindowsScreenSaver.h +lib/platform/CPlatform.cpp +lib/platform/CPlatform.h +lib/platform/CSynergyHook.cpp +lib/platform/CSynergyHook.h +lib/platform/CUnixPlatform.cpp +lib/platform/CUnixPlatform.h +lib/platform/CWin32Platform.cpp +lib/platform/CWin32Platform.h +lib/platform/CXWindowsClipboard.cpp +lib/platform/CXWindowsClipboard.h +lib/platform/CXWindowsClipboardTextConverter.cpp +lib/platform/CXWindowsClipboardTextConverter.h +lib/platform/CXWindowsClipboardUCS2Converter.cpp +lib/platform/CXWindowsClipboardUCS2Converter.h +lib/platform/CXWindowsClipboardUTF8Converter.cpp +lib/platform/CXWindowsClipboardUTF8Converter.h +lib/platform/CXWindowsScreen.cpp +lib/platform/CXWindowsScreen.h +lib/platform/CXWindowsScreenSaver.cpp +lib/platform/CXWindowsScreenSaver.h +lib/platform/CXWindowsUtil.cpp +lib/platform/CXWindowsUtil.h +lib/platform/IMSWindowsScreenEventHandler.h +lib/platform/IPlatform.h +lib/platform/Makefile.am +lib/platform/makehook.dsp +lib/platform/platform.dsp +lib/platform/synrgyhk.dsp +lib/server/CClientProxy.cpp +lib/server/CClientProxy.h +lib/server/CClientProxy1_0.cpp +lib/server/CClientProxy1_0.h +lib/server/CConfig.cpp +lib/server/CConfig.h +lib/server/CHTTPServer.cpp +lib/server/CHTTPServer.h +lib/server/CMSWindowsPrimaryScreen.cpp +lib/server/CMSWindowsPrimaryScreen.h +lib/server/CPrimaryClient.cpp +lib/server/CPrimaryClient.h +lib/server/CPrimaryScreen.cpp +lib/server/CPrimaryScreen.h +lib/server/CServer.cpp +lib/server/CServer.h +lib/server/CXWindowsPrimaryScreen.cpp +lib/server/CXWindowsPrimaryScreen.h +lib/server/Makefile.am +lib/synergy/CClipboard.cpp +lib/synergy/CClipboard.h +lib/synergy/CInputPacketStream.cpp +lib/synergy/CInputPacketStream.h +lib/synergy/COutputPacketStream.cpp +lib/synergy/COutputPacketStream.h +lib/synergy/CProtocolUtil.cpp +lib/synergy/CProtocolUtil.h +lib/synergy/CTCPSocketFactory.cpp +lib/synergy/CTCPSocketFactory.h +lib/synergy/ClipboardTypes.h +lib/synergy/IClient.h +lib/synergy/IClipboard.h +lib/synergy/IPrimaryScreenReceiver.h +lib/synergy/IScreen.h +lib/synergy/IScreenEventHandler.h +lib/synergy/IScreenReceiver.h +lib/synergy/IScreenSaver.h +lib/synergy/IServer.h +lib/synergy/ISocketFactory.h +lib/synergy/KeyTypes.h +lib/synergy/Makefile.am +lib/synergy/MouseTypes.h +lib/synergy/ProtocolTypes.h +lib/synergy/Version.h +lib/synergy/XScreen.cpp +lib/synergy/XScreen.h +lib/synergy/XSynergy.cpp +lib/synergy/XSynergy.h +lib/synergy/libsynergy.dsp +mt/CCondVar.cpp +mt/CCondVar.h +mt/CLock.cpp +mt/CLock.h +mt/CMutex.cpp +mt/CMutex.h +mt/CThread.cpp +mt/CThread.h +mt/CThreadRep.cpp +mt/CThreadRep.h +mt/CTimerThread.cpp +mt/CTimerThread.h +mt/Makefile.am +mt/XThread.h +mt/mt.dsp +net/CNetwork.cpp +net/CNetwork.h +net/CNetworkAddress.cpp +net/CNetworkAddress.h +net/CTCPListenSocket.cpp +net/CTCPListenSocket.h +net/CTCPSocket.cpp +net/CTCPSocket.h +net/IDataSocket.h +net/IListenSocket.h +net/ISocket.h +net/Makefile.am +net/XNetwork.cpp +net/XNetwork.h +net/XSocket.cpp +net/XSocket.h +net/net.dsp +platform/CMSWindowsClipboard.cpp +platform/CMSWindowsClipboard.h +platform/CMSWindowsClipboardAnyTextConverter.cpp +platform/CMSWindowsClipboardAnyTextConverter.h +platform/CMSWindowsClipboardTextConverter.cpp +platform/CMSWindowsClipboardTextConverter.h +platform/CMSWindowsClipboardUTF16Converter.cpp +platform/CMSWindowsClipboardUTF16Converter.h +platform/CMSWindowsScreen.cpp +platform/CMSWindowsScreen.h +platform/CMSWindowsScreenSaver.cpp +platform/CMSWindowsScreenSaver.h +platform/CPlatform.cpp +platform/CPlatform.h +platform/CSynergyHook.cpp +platform/CSynergyHook.h +platform/CUnixPlatform.cpp +platform/CUnixPlatform.h +platform/CWin32Platform.cpp +platform/CWin32Platform.h +platform/CXWindowsClipboard.cpp +platform/CXWindowsClipboard.h +platform/CXWindowsClipboardTextConverter.cpp +platform/CXWindowsClipboardTextConverter.h +platform/CXWindowsClipboardUCS2Converter.cpp +platform/CXWindowsClipboardUCS2Converter.h +platform/CXWindowsClipboardUTF8Converter.cpp +platform/CXWindowsClipboardUTF8Converter.h +platform/CXWindowsScreen.cpp +platform/CXWindowsScreen.h +platform/CXWindowsScreenSaver.cpp +platform/CXWindowsScreenSaver.h +platform/CXWindowsUtil.cpp +platform/CXWindowsUtil.h +platform/IMSWindowsScreenEventHandler.h +platform/IPlatform.h +platform/Makefile.am +platform/makehook.dsp +platform/platform.dsp +platform/synrgyhk.dsp +server/CClientProxy.cpp +server/CClientProxy.h +server/CClientProxy1_0.cpp +server/CClientProxy1_0.h +server/CConfig.cpp +server/CConfig.h +server/CHTTPServer.cpp +server/CHTTPServer.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CPrimaryClient.cpp +server/CPrimaryClient.h +server/CPrimaryScreen.cpp +server/CPrimaryScreen.h +server/CServer.cpp +server/CServer.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/Makefile.am +server/resource.h +server/server.cpp +server/server.dsp +server/server.rc +synergy.dsw +synergy/CClipboard.cpp +synergy/CClipboard.h +synergy/CInputPacketStream.cpp +synergy/CInputPacketStream.h +synergy/COutputPacketStream.cpp +synergy/COutputPacketStream.h +synergy/CProtocolUtil.cpp +synergy/CProtocolUtil.h +synergy/CTCPSocketFactory.cpp +synergy/CTCPSocketFactory.h +synergy/ClipboardTypes.h +synergy/IClient.h +synergy/IClipboard.h +synergy/IPrimaryScreenReceiver.h +synergy/IScreen.h +synergy/IScreenEventHandler.h +synergy/IScreenReceiver.h +synergy/IScreenSaver.h +synergy/IServer.h +synergy/ISocketFactory.h +synergy/KeyTypes.h +synergy/Makefile.am +synergy/MouseTypes.h +synergy/ProtocolTypes.h +synergy/Version.h +synergy/XScreen.cpp +synergy/XScreen.h +synergy/XSynergy.cpp +synergy/XSynergy.h +synergy/synergy.dsp + +Reorganized source tree. Moved client.cpp into cmd/synergy as +synergy.cpp and server.cpp into cmd/synergyd as synergyd.cpp. +Moved and renamed related files. Moved remaining source files +into lib/.... Modified and added makefiles as appropriate. +Result is that library files are under lib with each library +in its own directory and program files are under cmd with each +command in its own directory. + +---------- +2002/07/30 15:17:44 crs +client/CClient.cpp +client/CClient.h +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CSecondaryScreen.cpp +client/CSecondaryScreen.h +client/CServerProxy.cpp +client/CServerProxy.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +client/client.cpp +server/CClientProxy.h +server/CClientProxy1_0.cpp +server/CClientProxy1_0.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CPrimaryClient.cpp +server/CPrimaryClient.h +server/CPrimaryScreen.cpp +server/CPrimaryScreen.h +server/CServer.cpp +server/CServer.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/server.cpp +synergy/IClient.h + +Replaced method name `run' with `mainLoop', and `stop' and `quit' +with `exitMainLoop' in most places. + +---------- +2002/07/30 14:59:36 crs +client/CClient.h +client/CMSWindowsSecondaryScreen.h +client/CSecondaryScreen.h +client/CServerProxy.cpp +client/CServerProxy.h +client/CXWindowsSecondaryScreen.h +server/CClientProxy.h +server/CClientProxy1_0.h +server/CConfig.h +server/CHTTPServer.cpp +server/CHTTPServer.h +server/CMSWindowsPrimaryScreen.h +server/CPrimaryClient.h +server/CPrimaryScreen.h +server/CServer.h +server/CXWindowsPrimaryScreen.h +synergy/IClient.h +synergy/IScreen.h +synergy/IScreenReceiver.h +synergy/IServer.h + +Added doxygen comments for all relevant headers in client and server. + +---------- +2002/07/29 17:03:55 crs +platform/CMSWindowsClipboard.h +platform/CMSWindowsClipboardAnyTextConverter.h +platform/CMSWindowsClipboardTextConverter.h +platform/CMSWindowsClipboardUTF16Converter.h +platform/CMSWindowsScreen.h +platform/CMSWindowsScreenSaver.h +platform/CPlatform.h +platform/CSynergyHook.h +platform/CUnixPlatform.h +platform/CWin32Platform.h +platform/CXWindowsClipboard.h +platform/CXWindowsClipboardTextConverter.h +platform/CXWindowsClipboardUCS2Converter.h +platform/CXWindowsClipboardUTF8Converter.h +platform/CXWindowsScreen.h +platform/CXWindowsScreenSaver.h +platform/CXWindowsUtil.h +platform/IMSWindowsScreenEventHandler.h +platform/IPlatform.h + +Added doxygen comments for all relevant headers in platform. + +---------- +2002/07/29 16:07:26 crs +synergy/CClipboard.h +synergy/CInputPacketStream.h +synergy/COutputPacketStream.h +synergy/CProtocolUtil.h +synergy/CTCPSocketFactory.h +synergy/ClipboardTypes.h +synergy/IClient.h +synergy/IClipboard.h +synergy/IPrimaryScreenReceiver.h +synergy/IScreen.h +synergy/IScreenEventHandler.h +synergy/IScreenReceiver.h +synergy/IScreenSaver.h +synergy/IServer.h +synergy/ISocketFactory.h +synergy/KeyTypes.h +synergy/MouseTypes.h +synergy/ProtocolTypes.h +synergy/Version.h +synergy/XScreen.h +synergy/XSynergy.h + +Added doxygen comments for all relevant headers in synergy. + +---------- +2002/07/29 16:06:52 crs +platform/CMSWindowsScreen.cpp +server/CPrimaryClient.cpp + +moved try/catch block from CMSWindowsScreen to CPrimaryClient. +this means CMSWindowsScreen doesn't need to include XSynergy.h. + +---------- +2002/07/29 16:05:59 crs +doc/doxygen.cfg + +changed doxygen configuration. + +---------- +2002/07/28 19:06:52 crs +net/CNetwork.h +net/CNetworkAddress.h +net/CTCPListenSocket.h +net/CTCPSocket.h +net/IDataSocket.h +net/IListenSocket.h +net/ISocket.h +net/XNetwork.h +net/XSocket.cpp +net/XSocket.h + +Added doxygen comments for all relevant headers in net. + +---------- +2002/07/28 17:55:59 crs +http/CHTTPProtocol.h +http/XHTTP.cpp +http/XHTTP.h + +Added doxygen comments for all relevant headers in http. + +---------- +2002/07/28 17:25:13 crs +io/CBufferedInputStream.cpp +io/CBufferedInputStream.h +io/CBufferedOutputStream.cpp +io/CBufferedOutputStream.h +io/CInputStreamFilter.h +io/COutputStreamFilter.h +io/CStreamBuffer.h +io/IInputStream.h +io/IOutputStream.h +io/XIO.h + +Added doxygen comments for all relevant headers in io. + +---------- +2002/07/28 13:34:19 crs +mt/CCondVar.h +mt/CLock.h +mt/CMutex.h +mt/CThread.h +mt/CThreadRep.h +mt/CTimerThread.h +mt/XThread.h + +Added doxygen comments for all relevant headers in mt. + +---------- +2002/07/26 18:28:18 crs +base/CFunctionJob.h +base/CLog.h +base/CStopwatch.h +base/CString.h +base/CUnicode.h +base/IInterface.h +base/IJob.h +base/TMethodJob.h +base/XBase.h +base/common.h + +added doxygen comments for all relevant headers in base. + +---------- +2002/07/26 18:27:31 crs +platform/CXWindowsUtil.cpp + +fixed type mismatch (SInt32 vs int) in definition of +getWindowProperty(). + +---------- +2002/07/26 16:05:59 crs +doc/doxygen.cfg + +added configuration file for building doxygen documentation. +the code is not yet doxygen documented, though. + +---------- +2002/07/26 15:22:25 crs +platform/CXWindowsUtil.cpp + +now deleting property when so requested even if read failed. + +---------- +2002/07/25 18:08:00 crs +notes + +checkpoint. + +---------- +2002/07/25 17:58:01 crs +client/client.cpp +net/XSocket.cpp +server/server.cpp + +improved error messages for bad addresses. + +---------- +2002/07/25 17:52:40 crs +base/XBase.cpp +http/XHTTP.cpp +io/XIO.cpp +net/XNetwork.cpp +net/XSocket.cpp +server/CConfig.cpp +server/server.cpp +synergy/CProtocolUtil.cpp +synergy/XScreen.cpp +synergy/XSynergy.cpp + +made all getWhat() methods on exceptions consistent. they now +all use format() the same way. also changed format() to actually +do formatting. however, it doesn't try looking up formatting +strings by id, it just uses the fallback format string. + +---------- +2002/07/25 17:23:35 crs +base/CLog.cpp +base/CLog.h +base/CString.cpp +base/CString.h + +moved string formatting into CStringUtil from CLog and added +methods for format positional string arguments. + +---------- +2002/07/25 09:55:01 crs +platform/CXWindowsScreen.cpp + +added unix specific implementation of CXWindowsScreen::mainLoop() +that uses poll() to process events more efficiently. it won't +wake up nor sleep any more than necessary, unlike the platform +independent implementation that polls and sleeps. + +---------- +2002/07/25 09:23:24 crs +platform/CXWindowsClipboard.cpp + +finished INCR transfer changes. also made motifGetTime() return +icccmGetTime() because it seems motif does TIMESTAMP like ICCCM. + +---------- +2002/07/25 08:57:46 crs +platform/CXWindowsClipboard.cpp + +checkpoint. working on INCR transfers. + +---------- +2002/07/24 19:26:18 crs +platform/CMSWindowsScreen.cpp +platform/CWin32Platform.cpp +platform/CWin32Platform.h + +fixes for win32 due to changes in how s_restartable is handled. +the main change is that WM_QUIT now causes the thread to be +cancelled instead of mainLoop() just returning. this also +requires runDaemon() to call the run function in a new thread +each time it calls it because it could can cancelled. + +---------- +2002/07/24 19:24:21 crs +platform/CMSWindowsClipboardTextConverter.cpp +platform/CMSWindowsClipboardUTF16Converter.cpp + +fixes for win32 clipboard due to CUnicode nul terminator changes. + +---------- +2002/07/24 19:23:46 crs +base/CUnicode.cpp + +fixed an off-by-one error in UTF8ToText(). + +---------- +2002/07/24 17:39:52 crs +base/CUnicode.cpp + +fixed an off-by-one error in textToUTF8(). + +---------- +2002/07/24 17:30:32 crs +platform/CXWindowsClipboard.cpp +platform/CXWindowsClipboard.h + +fixed type of TARGETS target. + +---------- +2002/07/24 17:22:01 crs +base/CUnicode.cpp +base/CUnicode.h +platform/CMSWindowsClipboardTextConverter.cpp +platform/CMSWindowsClipboardUTF16Converter.cpp + +made handling of nul terminators in CUnicode more sane. + +---------- +2002/07/24 17:07:52 crs +platform/CXWindowsClipboard.cpp +platform/CXWindowsClipboard.h + +some fixes for motif clipboard. still not handling incremental +transfer through root window property because not sure of the +protocol. + +---------- +2002/07/24 13:01:18 crs +client/CClient.cpp +client/client.cpp +net/CTCPSocket.cpp +net/XSocket.h +platform/CUnixPlatform.cpp +platform/CUnixPlatform.h +platform/CWin32Platform.cpp +platform/CWin32Platform.h +platform/CXWindowsScreen.cpp +platform/CXWindowsUtil.cpp +platform/IPlatform.h +server/CPrimaryClient.cpp +server/CServer.cpp +server/server.cpp +synergy/ProtocolTypes.h +synergy/Version.h + +removed restart function from platform. no longer trying to +restart if the X server connection was lost; since synergy +is likely to be started by xdm or the user's xsession, it's +better for synergy to simply terminate when the connection +is lost. synergy will still restart due to other errors. +also fixed numerous other minor bugs and cleaned some stuff +up (like app error codes are now consistent and enumerated +in Version.h, for lack of a better place). and boosted +version and protocol numbers. + +---------- +2002/07/23 19:00:01 crs +notes + +checkpoint. + +---------- +2002/07/23 18:59:44 crs +platform/CMSWindowsClipboard.cpp + +fixed a bug in clipboard conversion (was using wrong converter or +no converter when one was available). + +---------- +2002/07/23 18:59:15 crs +client/CMSWindowsSecondaryScreen.cpp +server/CMSWindowsPrimaryScreen.cpp +synergy/KeyTypes.h + +converted win32 to use unicode based KeyID. + +---------- +2002/07/23 17:04:41 crs +client/CXWindowsSecondaryScreen.cpp +server/CXWindowsPrimaryScreen.cpp +synergy/KeyTypes.h + +checkpoint. converting KeyID to use UTF-32 encoding instead of +X11 keysyms. + +---------- +2002/07/23 15:34:05 crs +synergy/CClipboard.cpp +synergy/IClipboard.h + +no longer attempting to unmarshall clipboard formats that aren't +known to the caller. if the client supports more formats than +the server then the server could get a clipboard format greater +than kNumFormats. with this change the server discards the +extra formats instead of crashing. + +---------- +2002/07/23 15:26:40 crs +base/CUnicode.cpp +base/CUnicode.h +base/base.dsp +platform/CMSWindowsClipboard.cpp +platform/CMSWindowsClipboard.h +platform/CMSWindowsClipboardAnyTextConverter.cpp +platform/CMSWindowsClipboardAnyTextConverter.h +platform/CMSWindowsClipboardTextConverter.cpp +platform/CMSWindowsClipboardTextConverter.h +platform/CMSWindowsClipboardUTF16Converter.cpp +platform/CMSWindowsClipboardUTF16Converter.h +platform/platform.dsp + +unicode clipboard changes for win32 plus some bug fixes. + +---------- +2002/07/23 12:35:36 crs +platform/CXWindowsClipboard.cpp +platform/CXWindowsClipboard.h + +removed unnecessary atoms from X clipboard object. + +---------- +2002/07/23 12:08:30 crs +base/CUnicode.cpp + +checkpoint. more CUnicode fixes. + +---------- +2002/07/23 11:51:13 crs +base/CUnicode.cpp + +checkpoint. fixed the other cases in the same function as the +previous checkin. also prevented the errors flag from getting +reset after the multibyte to wide character conversion. + +---------- +2002/07/23 11:42:54 crs +base/CUnicode.cpp + +checkpoint. fixed cases for mbrtowc (was using 1 and 2 instead +of -1 and -2). + +---------- +2002/07/23 11:36:18 crs +base/CUnicode.cpp +base/CUnicode.h +platform/CXWindowsClipboard.cpp +platform/CXWindowsClipboardTextConverter.cpp + +checkpoint. more UTF8 clipboard stuff. + +---------- +2002/07/23 09:33:50 crs +base/CUnicode.cpp +base/CUnicode.h +platform/CXWindowsClipboard.cpp + +checkpoint. more UTF8 clipboard testing. + +---------- +2002/07/22 18:46:57 crs +base/CUnicode.cpp +platform/CXWindowsClipboard.cpp + +checkpoint. more UTF8 clipboard stuff. + +---------- +2002/07/22 18:17:21 crs +platform/CXWindowsClipboard.cpp + +checkpoint. more UTF8 clipboard stuff. + +---------- +2002/07/22 18:03:44 crs +platform/CXWindowsClipboard.cpp + +checkpoint. working on UTF8 clipboard transfer. + +---------- +2002/07/22 17:32:51 crs +base/CUnicode.cpp +base/CUnicode.h +base/Makefile.am +platform/CXWindowsClipboard.cpp +platform/CXWindowsClipboard.h +platform/CXWindowsClipboardTextConverter.cpp +platform/CXWindowsClipboardTextConverter.h +platform/CXWindowsClipboardUCS2Converter.cpp +platform/CXWindowsClipboardUCS2Converter.h +platform/CXWindowsClipboardUTF8Converter.cpp +platform/CXWindowsClipboardUTF8Converter.h +platform/Makefile.am +synergy/IClipboard.h + +checkpoint. adding support for unicode in clipboard. + +---------- +2002/07/19 21:27:59 crs +README + +changed notes about how to startup configure synergy. it now +discourages using boot scripts, which can't handle X servers +requiring authorization, and suggests modifying xdm's Xsetup. + +---------- +2002/07/19 20:44:57 crs +examples/synergy.linux.init +examples/synergyd.linux.init + +updated init.d scripts to work with SuSE. however, it looks as +if they cannot be used on an X server using authentication +because the daemons they start are not authorized to connect to +the X server. X users should modify Xsetup or Xsession. + +---------- +2002/07/19 18:12:41 crs +server/CXWindowsPrimaryScreen.cpp + +formatting. + +---------- +2002/07/19 17:39:45 crs +server/CPrimaryScreen.cpp + +removed from previous change. + +---------- +2002/07/19 17:38:34 crs +server/CPrimaryScreen.cpp + +reordered operations to reduce cursor flashing when entering +primary screen. + +---------- +2002/07/18 17:03:10 crs +platform/CSynergyHook.cpp +server/CMSWindowsPrimaryScreen.cpp + +fixed handling of calling init() when a previous process did not +call cleanup(). if that process still appears to exist then the +init() fails. otherwise some cleanup is performed and the init() +proceeds. a synergy server started while another is running will +now exit immediately without interfering the original server. + +---------- +2002/07/18 17:00:48 crs +server/CServer.cpp +server/CServer.h + +now cancelling and waiting for the accept client thread before +cancelling any other threads. this prevents a race condition +where we disconnect a client but it reconnects before we +manage to shutdown. that might leave a thread running and +the connection won't be closed down properly. + +---------- +2002/07/18 16:58:08 crs +mt/CThreadRep.cpp +platform/CMSWindowsScreen.cpp + +changed waitForEvent() to handle a peculiar feature of +MsgWaitForMultipleObjects(): it will not return immediately +if an event already in the queue when it's called was already +in the queue during the last call to GetMessage()/PeekMessage(). +also now discarding screen saver events if there are any other +screen saver events in the queue already. this prevents these +events from piling up in the queue, which they'd do because we +sleep for 250ms when handling each one. + +---------- +2002/07/18 08:54:17 crs +synergy.dsw + +fixed incorrect paths to makehook and synrgyhk project files. + +---------- +2002/07/17 17:27:41 crs +platform/CMSWindowsScreen.cpp +platform/CSynergyHook.cpp +platform/CSynergyHook.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CPrimaryScreen.cpp +server/CPrimaryScreen.h + +attempt to fix stuttering when leaving win32 screen. seems to +work but will let testers make the final call. also fixed +desktop synchronization by setting a variable that was +mistakenly left unset. and tried to work around an apparent +bug in MsgWaitForMultipleObjects() that prevented the service +from closing down properly. start/pause/continue/stop +sequence still doesn't shut down correctly. start/pause/stop +and start/stop work fine. + +---------- +2002/07/17 17:24:44 crs +client/CClient.cpp + +removed unnecessary local variable. + +---------- +2002/07/16 19:07:15 crs +base/stdistream.h +io/CStreamBuffer.cpp + +fixes to get it compiling on .NET. + +---------- +2002/07/16 16:52:26 crs +client/CClient.cpp +client/CClient.h +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CSecondaryScreen.cpp +client/CServerProxy.cpp +client/CServerProxy.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +platform/CXWindowsScreen.cpp +platform/IMSWindowsScreenEventHandler.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CPrimaryClient.cpp +server/CPrimaryClient.h +server/CPrimaryScreen.cpp +server/CPrimaryScreen.h +server/CServer.cpp +server/CServer.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/IPrimaryScreenReceiver.h +synergy/IScreen.h +synergy/IScreenEventHandler.h +synergy/IScreenReceiver.h +synergy/IServer.h + +moved onError() method to IScreenReceiver from IPrimaryScreenReceiver. +also implemented onError in CClient which previously did not have +any way to handle display disconnection. + +---------- +2002/07/15 15:03:04 crs +platform/CSynergyHook.cpp +platform/synrgyhk.dsp + +completing previous checkin. + +---------- +2002/07/15 15:01:36 crs +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CSecondaryScreen.cpp +client/CSecondaryScreen.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +client/client.dsp +platform/CMSWindowsScreen.cpp +platform/CMSWindowsScreen.h +platform/CSynergyHook.cpp +platform/CSynergyHook.h +platform/CXWindowsScreen.cpp +platform/CXWindowsScreen.h +platform/IMSWindowsScreenEventHandler.h +platform/makehook.dsp +platform/platform.dsp +platform/synrgyhk.dsp +server/CConfig.cpp +server/CConfig.h +server/CHTTPServer.cpp +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CPrimaryScreen.cpp +server/CPrimaryScreen.h +server/CServer.cpp +server/CServer.h +server/CSynergyHook.cpp +server/CSynergyHook.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/makehook.dsp +server/server.dsp +server/synrgyhk.dsp +synergy/IScreen.h +synergy/IScreenEventHandler.h +synergy/ProtocolTypes.h +synergy/synergy.dsp + +checkpoint. refactored win32 code. had to edit and rename some +files so this is only a checkpoint. + +---------- +2002/07/13 22:00:38 crs +client/CClient.cpp +client/CClient.h +client/CSecondaryScreen.cpp +client/CSecondaryScreen.h +client/CServerProxy.cpp +client/CServerProxy.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +client/ISecondaryScreen.h +client/Makefile.am +client/client.cpp +platform/CXWindowsScreen.cpp +platform/CXWindowsScreen.h +server/CClientProxy.h +server/CClientProxy1_0.cpp +server/CClientProxy1_0.h +server/CPrimaryClient.cpp +server/CPrimaryClient.h +server/CPrimaryScreen.cpp +server/CPrimaryScreen.h +server/CServer.cpp +server/CServer.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/IPrimaryScreen.h +server/Makefile.am +server/server.cpp +synergy/IClient.h +synergy/IPrimaryScreenReceiver.h +synergy/IScreen.h +synergy/IScreenEventHandler.h +synergy/IScreenReceiver.h +synergy/IServer.h +synergy/Makefile.am + +checkpoint. still refactoring. merged common code from primary +screens into CPrimaryScreen and merged common code from secondary +screens into CSecondaryScreen. changed is-a relationship to a +has-a between the primary and secondary screen classes and the +generic platform dependent screen class to avoid multiple +inheritance of implementation. also standardized the interface +for those generic screen classes. adding a platform now involves +implementing simpler interfaces: IScreen for the generic screen, +IScreenEventHandler and some methods of CPrimaryScreen for the +primary screen, and IScreenEventHandler and some methods of +CSecondaryScreen for the secondary screen. did X11 platform +but not win32 platform. + +---------- +2002/07/12 20:41:23 crs +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +platform/CMSWindowsScreen.cpp +platform/CMSWindowsScreen.h +platform/CXWindowsScreen.cpp +platform/CXWindowsScreen.h +platform/CXWindowsScreenSaver.cpp +platform/CXWindowsScreenSaver.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h + +refactoring. refactored stuff in client (with changes to server +as necessary). + +---------- +2002/07/11 18:58:49 crs +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +platform/CMSWindowsScreen.cpp +platform/CMSWindowsScreen.h +platform/CXWindowsScreen.cpp +platform/CXWindowsScreen.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h + +checkpoint. making win32 and X primary screen code more similar +in order to share code later. + +---------- +2002/07/11 13:13:37 crs +client/CClient.cpp +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CServerProxy.cpp +client/client.dsp +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CPrimaryClient.cpp +server/CPrimaryClient.h +server/CServer.cpp +server/CServer.h +server/CSynergyHook.cpp +server/CSynergyHook.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/IPrimaryScreen.h +server/server.dsp +synergy/synergy.dsp + +applied refactoring to win32 code. + +---------- +2002/07/10 21:22:28 crs +client/CClient.cpp +client/CClient.h +client/CServerProxy.cpp +client/CServerProxy.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +client/ISecondaryScreen.h +server/CPrimaryClient.cpp +server/CPrimaryClient.h +server/CServer.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/IPrimaryScreenReceiver.h +synergy/IScreenReceiver.h +synergy/IServer.h +synergy/Makefile.am + +more refactoring. + +---------- +2002/07/10 20:18:32 crs +client/CClient.cpp +client/CClient.h +client/CServerProxy.cpp +client/CServerProxy.h +client/ISecondaryScreen.h +client/Makefile.am +client/client.cpp +server/CClientProxy.h +server/CClientProxy1_0.cpp +server/CClientProxy1_0.h +server/CPrimaryClient.cpp +server/CPrimaryClient.h +server/CServer.cpp +synergy/IClient.h +synergy/ISecondaryScreen.h +synergy/Makefile.am + +refactored client code. it now uses IClient and IServer and +has a CServerProxy, making it's design similar to the server +code. + +---------- +2002/07/10 14:29:50 crs +synergy/IClient.h + +removed some obsolete comments. + +---------- +2002/07/10 14:15:17 crs +server/CClientProxy1_0.cpp +server/CClientProxy1_0.h +server/CPrimaryClient.cpp +server/CPrimaryClient.h +server/CServer.cpp +server/CServer.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/IPrimaryReceiver.h +server/Makefile.am +synergy/IServer.h +synergy/ProtocolTypes.h + +removed IPrimaryReceiver in favor of IServer, which required a few +minor changes to support IPrimaryReciever's functionality. this +does mean that the IPrimaryScreen class will be calling some +methods with dummy arguments. those are documented in +CPrimaryClient. + +---------- +2002/07/09 21:22:31 crs +acinclude.m4 +acsite.m4 +config/depcomp +config/install-sh +config/missing +config/mkinstalldirs +mt/CThread.cpp +mt/CThread.h +notes +platform/CXWindowsClipboard.cpp +server/CClientProxy.cpp +server/CClientProxy.h +server/CClientProxy1_0.cpp +server/CClientProxy1_0.h +server/CPrimaryClient.cpp +server/CPrimaryClient.h +server/CServer.cpp +server/CServer.h +server/CServerProtocol.cpp +server/CServerProtocol.h +server/CServerProtocol1_0.cpp +server/CServerProtocol1_0.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/IPrimaryReceiver.h +server/IPrimaryScreen.h +server/IServerProtocol.h +server/Makefile.am +synergy/IClient.h +synergy/IServer.h +synergy/Makefile.am + +updated to new automake and refactored server stuff. the server +now speaks to the primary screen and secondary screens almost +everywhere the same way through an IClient interface; only +special primary screen calls are accessed through a different +interface, the CPrimaryClient interface. this simplifies the +server since it no longer needs to test whether the active screen +is the primary or a secondary in most cases. + +the server no longer speaks directly to the primary screen; all +that goes through the CPrimaryClient, which often just forwards +the call. the primary screen no longer speaks directly to the +server either, again going through the CPrimaryClient via a +IPrimaryReceiver interface. + +CServerProtocol classes have been replaced by CClientProxy +classes which are very similar. the name makes more sense +though. + +---------- +2002/07/09 17:31:45 crs +server/IPrimaryScreen.h +synergy/IPrimaryScreen.h +synergy/Makefile.am + +checkpoint. moved IPrimaryScreen.h. + +---------- +2002/07/07 15:15:34 crs +server/IServerProtocol.h +synergy/IServerProtocol.h + +moved IServerProtocol to server from synergy directory. + +---------- +2002/07/03 16:25:36 crs +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h + +fixed spurious mouse motions when entering/leaving primary +screen on X11. + +---------- +2002/07/01 15:05:49 crs +server/CMSWindowsPrimaryScreen.cpp + +mistakenly removed mouse button checks when on secondary screens +from isLockedToScreen() in earlier checkin. + +---------- +2002/07/01 14:01:23 crs +README +notes + +checkpoint. + +---------- +2002/07/01 13:03:16 crs +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +synergy/ISecondaryScreen.h + +now synthesizing key release events for each pressed key when +the client screen is closed. this fixes the bug where the +client's keyboard was left with some keys logically pressed +when the client died (e.g. using ctrl+c on the client program +from the server's keyboard would leave the ctrl key logically +pressed). + +---------- +2002/07/01 13:01:16 crs +server/CServerProtocol1_0.cpp + +disabled removing client if no heartbeat is received. we don't +want that while testing because it might hide bugs. + +---------- +2002/07/01 13:00:12 crs +server/CMSWindowsPrimaryScreen.cpp + +fixed locking to screen on win32. was using GetKeyboardState() +to query keys but that doesn't give us up-to-date information. +now using GetAsyncKeyState() if on primary and m_keys if on +secondary. + +---------- +2002/07/01 12:58:52 crs +platform/CMSWindowsScreenSaver.cpp +platform/CMSWindowsScreenSaver.h + +added win32 screen saver class forgotten in previous checkins. + +---------- +2002/06/26 16:31:48 crs +client/CClient.cpp +http/CHTTPProtocol.cpp +io/CBufferedInputStream.cpp +io/CBufferedInputStream.h +io/CInputStreamFilter.h +io/IInputStream.h +server/CMSWindowsPrimaryScreen.cpp +server/CServer.cpp +server/CServerProtocol1_0.cpp +server/CSynergyHook.cpp +synergy/CInputPacketStream.cpp +synergy/CInputPacketStream.h +synergy/CProtocolUtil.cpp +synergy/ProtocolTypes.h + +synergy hook DLL will now restart itself if a client tries to +init() it while it's already running. fixed an uninitialized +pointer bug in CServer and some cleanup-on-error code in +CMSWindowsPrimaryScreen. also added timeout to read() on +IInputStream and a heartbeat sent by clients so the server +can disconnect clients that are dead but never reset the TCP +connection. previously the server would keep these dead +clients around forever and if the user was locked on the +client screen for some reason then the server would have to +be rebooted (or the server would have to be killed via a +remote login). + +---------- +2002/06/26 13:48:08 crs +client/CClient.cpp +client/CClient.h + +client now compresses mouse motion events. this fixes slow +dragging on grace, possibly on win32 too. + +---------- +2002/06/26 13:31:06 crs +synergy/CInputPacketStream.cpp +synergy/CInputPacketStream.h + +fixed getSize() to be non-blocking in CInputPacketStream. + +---------- +2002/06/26 12:44:52 crs +platform/CXWindowsScreen.cpp +platform/CXWindowsScreenSaver.h + +fixed re-entrant calls to X bug. + +---------- +2002/06/23 23:24:22 crs +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CServer.cpp +server/CServer.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/IPrimaryScreen.h + +fixed handling of jumping to primary when screen saver starts +and back to secondary when it stops. also now redirecting +keyboard input to root window when screen saver starts; this +allows the user to type in the lock dialog and also effectively +discards any input used to deactivate the screen saver. + +---------- +2002/06/23 21:54:05 crs +notes + +checkpoint. + +---------- +2002/06/23 21:53:31 crs +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +platform/CMSWindowsScreen.cpp +platform/CMSWindowsScreen.h +platform/platform.dsp +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CSynergyHook.cpp +server/CSynergyHook.h + +win32 screen saver now handled. + +---------- +2002/06/23 21:48:33 crs +platform/CXWindowsScreenSaver.cpp +platform/CXWindowsScreenSaver.h + +now disabling disable job timer when forcing screen saver +activation. previously the timer would deactivate the screen +saver shortly after activation. job timer is restored when +the screen saver is deactivated. + +---------- +2002/06/23 15:43:40 crs +platform/CXWindowsScreen.cpp +platform/CXWindowsScreen.h +platform/CXWindowsScreenSaver.cpp +platform/CXWindowsScreenSaver.h +platform/CXWindowsUtil.cpp +server/CServer.cpp +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/Makefile.am +synergy/IPrimaryScreen.h + +checkpoint screensaver changes. now handling xscreensaver +dying and restarting or starting after synergy does. also +now disabling the screen saver on the client. next step: +win32 support. + +---------- +2002/06/22 20:29:59 crs +platform/CXWindowsScreen.cpp +platform/CXWindowsScreenSaver.cpp +platform/CXWindowsScreenSaver.h + +fixes to get xscreensaver integration working. + +---------- +2002/06/22 19:47:27 crs +platform/CXWindowsClipboard.cpp +platform/CXWindowsScreenSaver.cpp +platform/CXWindowsUtil.cpp +platform/CXWindowsUtil.h +server/CXWindowsPrimaryScreen.cpp + +CXWindowsUtil::CErrorLock wasn't XSync()'ing the display before +installing and uninstalling the new error handler, causing +errors before the lock to be caught and errors during the lock +to not be caught. had to add Display* as argument to c'tor. + +---------- +2002/06/22 19:20:21 crs +client/CClient.cpp +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +platform/CXWindowsScreen.cpp +platform/CXWindowsScreen.h +platform/CXWindowsScreenSaver.cpp +platform/CXWindowsScreenSaver.h +platform/Makefile.am +server/CServer.cpp +server/CServer.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/IPrimaryScreen.h +synergy/IScreenSaver.h +synergy/ISecondaryScreen.h +synergy/Makefile.am + +checkpoint. adding screen saver support. only on X so far +and untested. also some known problems: not detecting an +xscreensaver started after us and not detecting built-in +screen saver activation (not sure if we can without using +ugly extensions). + +---------- +2002/06/22 17:31:24 crs +client/Makefile.am +http/Makefile.am +io/Makefile.am +mt/Makefile.am +net/Makefile.am +platform/Makefile.am +server/Makefile.am +synergy/Makefile.am + +added header files to _SOURCES. + +---------- +2002/06/22 13:55:45 crs +server/CXWindowsPrimaryScreen.cpp + +added comments. + +---------- +2002/06/22 12:09:49 crs +client/CXWindowsSecondaryScreen.cpp + +cleanup. + +---------- +2002/06/21 17:55:47 crs +client/CClient.cpp +client/CClient.h +client/CXWindowsSecondaryScreen.cpp +client/client.cpp +server/CServer.cpp +server/CServer.h +server/server.cpp + +cleaned up some minor bugs. + +---------- +2002/06/21 17:54:22 crs +net/CNetwork.cpp +net/CNetwork.h + +ported network changes to win32. + +---------- +2002/06/21 16:29:35 crs +net/CNetworkAddress.cpp + +now trying to convert hostname as a dot notation address before +trying name lookup. not all platforms will do this for us in +gethostbyname(). + +---------- +2002/06/21 16:19:08 crs +net/CNetwork.cpp +net/CNetwork.h +net/CTCPListenSocket.cpp +net/CTCPSocket.cpp + +fixed CTCPSocket::connect() to allow cancellation. + +---------- +2002/06/21 15:18:01 crs +server/CServer.cpp +server/CServer.h + +some cleanup. also fixed a race condition when adding threads +to the thread list: the child thread would add itself to the +list which means there could be a time interval in the parent +where the child thread exists but isn't on the list. the +parent now does the adding and removing. + +---------- +2002/06/21 15:15:34 crs +platform/CUnixPlatform.cpp + +now blocking SIGINT and SIGTERM in restart function. the child +should handle the signal and terminate. then the restart +function will exit. + +---------- +2002/06/21 15:14:32 crs +mt/CThreadRep.cpp + +signal handler thread now dies when SIGABRT is raised. ignoring +SIGABRT in sigwait() seems to be a bug in the linux pthread +library. + +---------- +2002/06/20 16:27:49 crs +platform/CXWindowsClipboard.cpp +platform/CXWindowsClipboard.h + +fixed bug introduced by previous checkin. calling XCheckIfEvent() +multiple times is *not* the same as calling XIfEvent() because the +former will re-encounter events that it didn't process previously. +to make things simple it now pulls events off the queue and saves +them if not processed for selection transfer and puts them back +afterwards. + +---------- +2002/06/20 14:01:44 crs +platform/CXWindowsClipboard.cpp + +speeded up clipboard transfer by avoiding a selection request +when it wasn't necessary. (in particular, we were getting the +clipboard update time from the owner then emptying the clipboard, +so we didn't need to get the time. worse, most owners don't +support getting the time and we often timed out.) + +also fixed a multithread bug using the X display. we were using +a CThread to send an event after a timeout while we were waiting +in XIfEvent(). this necessarily involved two threads calling +into Xlib at once, which is not allowed. now using polling to +do the timeout because Xlib doesn't have a function to get +events with a timeout. + +---------- +2002/06/20 13:35:28 crs +platform/CXWindowsClipboard.cpp +platform/CXWindowsClipboard.h + +checkpoint. trying to fix a delay when sending clipboards on X. + +---------- +2002/06/20 11:13:37 crs +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h + +added workaround for bug windows 98 (Me?) and multiple displays: +absolute mouse_event() moves don't work except for primary +display. + +---------- +2002/06/20 09:19:55 crs +server/CXWindowsPrimaryScreen.cpp + +work around for bug with mouse driver on lombard powerbook. + +---------- +2002/06/19 20:24:35 crs +client/CMSWindowsSecondaryScreen.cpp +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CSynergyHook.cpp +server/CSynergyHook.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h + +fixed bugs in mouse motion. wasn't taking care to capture all +motion events relative to the previous mouse position. for +example, if two mouse events arrive, the first at x+1,y and +the second at x+2,y, we used to compute deltas of 1,0 and 2,0 +instead of 1,0 and 1,0. that's fixed. also worked around a +bug (probably) in windows that caused a motion event after a +SetCursorPos() to be lost or reported one pixel off from the +correct position. now using mouse_event() which doesn't +have that problem. also fixed calculation of normalized +coordinates for mouse_event() when there are multiple +displays. + +---------- +2002/06/19 17:03:29 crs +client/CClient.cpp +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +platform/CMSWindowsScreen.cpp +platform/CMSWindowsScreen.h +platform/CXWindowsScreen.cpp +platform/CXWindowsScreen.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CServer.cpp +server/CServer.h +server/CServerProtocol1_0.cpp +server/CSynergyHook.cpp +server/CSynergyHook.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/IPrimaryScreen.h +synergy/ISecondaryScreen.h +synergy/ProtocolTypes.h + +checkpoint. initial support for multiple displays on win32. + +---------- +2002/06/19 14:45:22 crs +client/Makefile.am +configure.in +server/Makefile.am + +fixed addition of X11 -L and -l options on link lines. + +---------- +2002/06/19 12:21:26 crs +configure.in +platform/CUnixPlatform.cpp + +checkpoint. automake changes for wait(). + +---------- +2002/06/19 11:58:48 crs +configure.in +http/CHTTPProtocol.cpp +platform/CUnixPlatform.cpp + +checkpoint. automake changes for reentrant functions. + +---------- +2002/06/19 11:23:49 crs +base/BasicTypes.h +base/CLog.cpp +base/CLog.h +base/CStopwatch.cpp +base/XBase.cpp +base/common.h +base/stdistream.h +base/stdostream.h +client/CClient.cpp +client/CXWindowsSecondaryScreen.cpp +client/client.cpp +configure.in +mt/CCondVar.cpp +mt/CCondVar.h +mt/CMutex.cpp +mt/CThread.cpp +mt/CThread.h +mt/CThreadRep.cpp +mt/CThreadRep.h +net/CNetwork.cpp +net/CNetwork.h +platform/CPlatform.cpp +platform/CPlatform.h +platform/CXWindowsClipboard.h +platform/CXWindowsScreen.h +platform/CXWindowsUtil.h +server/CServer.cpp +server/CSynergyHook.h +server/CXWindowsPrimaryScreen.cpp +server/server.cpp +synergy/CProtocolUtil.h + +checkpoint. more conversion to automake. + +---------- +2002/06/19 08:23:08 crs +config/install-sh +config/missing +config/mkinstalldirs +configure.in +install-sh +missing +mkinstalldirs + +moved auxillary automake files into config directory. + +---------- +2002/06/18 19:47:52 crs +install-sh +missing +mkinstalldirs + +added automake required tools. + +---------- +2002/06/18 19:44:34 crs +Make-linux +Make-solaris +Makecommon +Makefile +Makefile.am +acsite.m4 +base/Makefile +base/Makefile.am +client/Makefile +client/Makefile.am +configure.in +http/Makefile +http/Makefile.am +io/Makefile +io/Makefile.am +mt/Makefile +mt/Makefile.am +net/Makefile +net/Makefile.am +platform/Makefile +platform/Makefile.am +server/Makefile +server/Makefile.am +synergy/Makefile +synergy/Makefile.am +tools/depconv + +started to convert to autoconf/automake. + +---------- +2002/06/18 18:34:55 crs +base/CLog.cpp + +now checking vsnprintf result against < 0 instead of == -1 +for portability. + +---------- +2002/06/18 18:33:59 crs +net/XSocket.cpp + +added FIXME to commented out code. + +---------- +2002/06/17 15:44:45 crs +README +client/client.cpp +server/server.cpp + +made command line parsing a little more sane with respect to +windows NT services. + +---------- +2002/06/17 14:10:25 crs +README + +updates + +---------- +2002/06/17 13:31:21 crs +base/CLog.cpp +base/CString.cpp +base/XBase.cpp +client/CClient.cpp +client/CMSWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.cpp +client/client.cpp +http/CHTTPProtocol.cpp +http/XHTTP.cpp +io/CBufferedInputStream.cpp +io/CBufferedOutputStream.cpp +io/CInputStreamFilter.cpp +io/COutputStreamFilter.cpp +io/CStreamBuffer.cpp +mt/CCondVar.cpp +mt/CMutex.cpp +mt/CThread.cpp +mt/CThreadRep.cpp +mt/CTimerThread.cpp +net/CNetwork.cpp +net/CNetworkAddress.cpp +net/CTCPListenSocket.cpp +net/CTCPSocket.cpp +net/XNetwork.cpp +net/XSocket.cpp +platform/CMSWindowsClipboard.cpp +platform/CMSWindowsScreen.cpp +platform/CUnixPlatform.cpp +platform/CWin32Platform.cpp +platform/CXWindowsClipboard.cpp +platform/CXWindowsScreen.cpp +platform/CXWindowsUtil.cpp +server/CConfig.cpp +server/CHTTPServer.cpp +server/CMSWindowsPrimaryScreen.cpp +server/CServer.cpp +server/CServerProtocol.cpp +server/CServerProtocol1_0.cpp +server/CSynergyHook.cpp +server/CXWindowsPrimaryScreen.cpp +server/server.cpp +synergy/CClipboard.cpp +synergy/CInputPacketStream.cpp +synergy/COutputPacketStream.cpp +synergy/CProtocolUtil.cpp +synergy/XSynergy.cpp + +formatting changes. + +---------- +2002/06/17 12:02:26 crs +client/CClient.cpp +net/CTCPListenSocket.cpp +net/CTCPListenSocket.h +net/CTCPSocket.cpp +net/CTCPSocket.h +net/IDataSocket.h +net/IListenSocket.h +net/ISocket.h +server/CHTTPServer.cpp +server/CHTTPServer.h +server/CServer.cpp +synergy/CTCPSocketFactory.cpp +synergy/CTCPSocketFactory.h +synergy/ISocketFactory.h + +refactored ISocket into IDataSocket. the latter and IListenSocket +now derive from ISocket. + +---------- +2002/06/14 18:08:20 crs +base/CFunctionJob.cpp +base/CFunctionJob.h +base/CLog.cpp +base/TMethodJob.h +base/common.h +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/client.cpp +io/CStreamBuffer.cpp +io/CStreamBuffer.h +mt/CThread.cpp +mt/CThread.h +mt/CThreadRep.cpp +mt/CThreadRep.h +platform/CMSWindowsScreen.cpp +platform/CUnixPlatform.cpp +platform/CUnixPlatform.h +platform/CWin32Platform.cpp +platform/CWin32Platform.h +platform/IPlatform.h +server/server.cpp + +performance fixes on win32 plus clean up of some warnings. also +improved error messages when uninstalling service. + +---------- +2002/06/11 20:10:49 crs +README + +added a blurb about synrgyhk.dll and that the service manager +will look for the binary wherever it was when --install was +used. + +---------- +2002/06/11 20:09:59 crs +base/stdpre.h +platform/CMSWindowsScreen.h + +windows fixes needed for formatting changes. + +---------- +2002/06/11 18:33:03 crs +client/CXWindowsSecondaryScreen.cpp + +commented out half-duplex flags that should never have been +uncommented. + +---------- +2002/06/11 18:31:06 crs +server/CServer.cpp + +fixed bug with switching screens on primary when there's no +link in that direction (it would assert). introduced bug +when adding support for wrapping. now ignores attempts to +move in a direction with no link. + +---------- +2002/06/11 18:30:08 crs +platform/CUnixPlatform.cpp + +added missing #include . + +---------- +2002/06/10 22:06:45 crs +base/BasicTypes.h +base/CFunctionJob.cpp +base/CLog.cpp +base/CLog.h +base/CString.cpp +base/CString.h +base/TMethodJob.h +base/XBase.cpp +base/XBase.h +base/common.h +base/stdistream.h +base/stdostream.h +client/CClient.cpp +client/CClient.h +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +client/client.cpp +http/CHTTPProtocol.cpp +http/CHTTPProtocol.h +http/XHTTP.cpp +http/XHTTP.h +io/CBufferedInputStream.cpp +io/CBufferedInputStream.h +io/CBufferedOutputStream.cpp +io/CBufferedOutputStream.h +io/CInputStreamFilter.cpp +io/COutputStreamFilter.cpp +io/CStreamBuffer.cpp +io/IInputStream.h +io/IOutputStream.h +io/XIO.cpp +io/XIO.h +mt/CCondVar.cpp +mt/CCondVar.h +mt/CLock.cpp +mt/CLock.h +mt/CMutex.cpp +mt/CMutex.h +mt/CThread.cpp +mt/CThread.h +mt/CThreadRep.cpp +mt/CTimerThread.cpp +mt/CTimerThread.h +mt/XThread.h +net/CNetwork.cpp +net/CNetworkAddress.cpp +net/CNetworkAddress.h +net/CTCPListenSocket.cpp +net/CTCPSocket.cpp +net/CTCPSocket.h +net/IListenSocket.h +net/ISocket.h +net/Makefile +net/XNetwork.cpp +net/XNetwork.h +net/XSocket.cpp +net/XSocket.h +platform/CMSWindowsClipboard.cpp +platform/CMSWindowsScreen.cpp +platform/CMSWindowsScreen.h +platform/CPlatform.cpp +platform/CUnixPlatform.cpp +platform/CUnixPlatform.h +platform/CWin32Platform.cpp +platform/CWin32Platform.h +platform/CXWindowsClipboard.cpp +platform/CXWindowsClipboard.h +platform/CXWindowsScreen.cpp +platform/CXWindowsScreen.h +platform/CXWindowsUtil.cpp +platform/CXWindowsUtil.h +platform/IPlatform.h +server/CConfig.cpp +server/CConfig.h +server/CHTTPServer.cpp +server/CHTTPServer.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CServer.cpp +server/CServer.h +server/CServerProtocol.cpp +server/CServerProtocol.h +server/CServerProtocol1_0.cpp +server/CServerProtocol1_0.h +server/CSynergyHook.cpp +server/CSynergyHook.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/server.cpp +synergy/CClipboard.cpp +synergy/CClipboard.h +synergy/CInputPacketStream.cpp +synergy/COutputPacketStream.cpp +synergy/CProtocolUtil.cpp +synergy/CProtocolUtil.h +synergy/CTCPSocketFactory.cpp +synergy/IClipboard.h +synergy/IPrimaryScreen.h +synergy/ISecondaryScreen.h +synergy/IServerProtocol.h +synergy/ISocketFactory.h +synergy/XScreen.cpp +synergy/XSynergy.cpp + +indentation and other formatting changes. also cleaned up +#includes. + +---------- +2002/06/10 16:49:46 crs +base/CLog.cpp +base/CStopwatch.cpp +mt/CCondVar.cpp +mt/CMutex.cpp +mt/CThreadRep.h +net/CNetwork.cpp +net/CNetwork.h +platform/CMSWindowsClipboard.h +platform/CMSWindowsScreen.h +platform/CWin32Platform.h +server/CSynergyHook.h +server/synrgyhk.dsp + +win32 changes. now including windows.h with WIN32_LEAN_AND_MEAN +to avoid including some stuff we don't want (like winsock). + +---------- +2002/06/10 11:09:02 crs +README + +fixes. + +---------- +2002/06/10 11:08:02 crs +README + +updates. + +---------- +2002/06/10 11:00:55 crs +README +examples/synergy.conf +examples/synergy.linux.init +examples/synergyd.linux.init + +added example files and a README. + +---------- +2002/06/10 10:08:36 crs +server/CMSWindowsPrimaryScreen.cpp +server/CServer.cpp +server/CXWindowsPrimaryScreen.cpp + +now allowing a screen to be its own neighbor to allow wrapping. +also no longer warping mouse to 0,0 when setting server screen +info. that was causing the mouse to jump if the server screen +had itself as its left or top neighbor (directly or indirectly) +once a screen could be its own neighbor. + +---------- +2002/06/10 09:49:21 crs +server/CConfig.cpp + +fixed stripping of comments from configuration streams. + +---------- +2002/06/10 09:49:03 crs +client/client.cpp +server/server.cpp + +changed "permitted" to "supported" in error messages. + +---------- +2002/06/09 23:08:18 crs +client/CClient.cpp + +no longer camps if the server sends an error message. + +---------- +2002/06/09 22:20:28 crs +client/CClient.cpp +client/CClient.h +client/client.cpp + +added support for camping, i.e. repeatly trying to connect to the +server until we succeed. + +---------- +2002/06/09 22:20:01 crs +mt/CTimerThread.cpp +mt/CTimerThread.h + +CTimerThread now allows zero and negative timeouts. a negative +timeout never times out and CTimerThread is a no-op. + +---------- +2002/06/09 18:03:32 crs +platform/CXWindowsScreen.cpp + +now using ":0.0" as the display if DISPLAY isn't set. + +---------- +2002/06/09 18:00:03 crs +notes + +checkpoint. + +---------- +2002/06/09 17:59:32 crs +client/client.cpp +server/CServer.cpp +server/CServer.h +server/server.cpp + +added command line option to choose the screen name. also now +using the hostname as the default name. this is on both client +and server. + +---------- +2002/06/09 17:35:28 crs +mt/CThreadRep.cpp + +added FIXME comment. + +---------- +2002/06/09 17:21:33 crs +server/CServer.cpp + +fixed problem with setConfig(). if the new config didn't +include a screen that was already connected under an alias +then that screen wouldn't be disconnected and removed from +the screen list until the screen voluntarily disconnected. +at that time removeConnection() would assert because the +screen name would not be found. now using the canonical +name in the protocol object as well as CServer. this +allows setConfig() to always detect removed screens and +disconnect them. + +---------- +2002/06/09 16:53:57 crs +platform/CUnixPlatform.cpp + +now exits instead of restarting if child dies due to an +unexpected signal. + +---------- +2002/06/09 16:53:25 crs +client/client.cpp +net/CNetwork.cpp +net/CNetwork.h +net/CNetworkAddress.cpp +net/CNetworkAddress.h +net/XSocket.cpp +net/XSocket.h +server/CConfig.cpp +server/CConfig.h +server/CHTTPServer.cpp +server/CServer.cpp +server/CServer.h +server/server.cpp +synergy/ProtocolTypes.h + +added command line and configuration file arguments to choose +the address and port to listen on or connect to. changed the +default port and put it in ProtocolTypes.h. the HTTP port is +now no longer opened unless the --http argument is supplied +or the config file includes it. + +---------- +2002/06/08 23:24:40 crs +server/CConfig.cpp +server/CConfig.h +server/CServer.cpp +server/server.cpp + +added aliases to configuration. an alias is another name for +a screen. it's expected that the server will want to accept +a given client under several names (e.g. the hostname and the +FQDN). + +---------- +2002/06/08 21:48:16 crs +notes + +checkpoint. + +---------- +2002/06/08 21:48:00 crs +all.dsp +base/CLog.cpp +base/CLog.h +base/base.dsp +base/common.h +base/stdpre.h +client/CClient.cpp +client/CClient.h +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/client.cpp +client/client.dsp +http/http.dsp +io/io.dsp +mt/CThreadRep.cpp +mt/mt.dsp +net/net.dsp +platform/CMSWindowsScreen.cpp +platform/CMSWindowsScreen.h +platform/CUnixPlatform.cpp +platform/CUnixPlatform.h +platform/CWin32Platform.cpp +platform/CWin32Platform.h +platform/IPlatform.h +platform/platform.dsp +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CServer.cpp +server/CSynergyHook.cpp +server/CSynergyHook.h +server/makehook.dsp +server/server.cpp +server/server.dsp +server/synrgyhk.dsp +synergy/IPrimaryScreen.h +synergy/synergy.dsp + +win32 changes. changed names of binaries. added support for +running as (and installing/installing) a service. added +support for multiple desktops (NT only, 95 doesn't support +multiple desktops). + +---------- +2002/06/04 12:26:23 crs +Makefile +client/Makefile +client/client.cpp +client/client.dsp +platform/CMSWindowsClipboard.cpp +platform/CMSWindowsClipboard.h +platform/CMSWindowsScreen.cpp +platform/CMSWindowsScreen.h +platform/CPlatform.cpp +platform/CPlatform.h +platform/CUnixPlatform.cpp +platform/CUnixPlatform.h +platform/CWin32Platform.cpp +platform/CWin32Platform.h +platform/CXWindowsClipboard.cpp +platform/CXWindowsClipboard.h +platform/CXWindowsScreen.cpp +platform/CXWindowsScreen.h +platform/CXWindowsUtil.cpp +platform/CXWindowsUtil.h +platform/IPlatform.h +platform/Makefile +server/Makefile +server/server.cpp +server/server.dsp +synergy.dsw +synergy/CMSWindowsClipboard.cpp +synergy/CMSWindowsClipboard.h +synergy/CMSWindowsScreen.cpp +synergy/CMSWindowsScreen.h +synergy/CXWindowsClipboard.cpp +synergy/CXWindowsClipboard.h +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h +synergy/CXWindowsUtil.cpp +synergy/CXWindowsUtil.h +synergy/Makefile +synergy/synergy.dsp +test.cpp + +refactored some common platform dependent stuff into a new +library: platform. also removed test.cpp. + +---------- +2002/06/04 11:06:26 crs +client/CClient.cpp +client/client.cpp +server/CServer.cpp +server/CServerProtocol.cpp +server/server.cpp +synergy/ProtocolTypes.h +synergy/Version.h + +added command line parsing, restartability, and daemonizing to +client. broke win32 stuff though. also moved version and +copyright constants into a new file and renamed protocol +version constants. + +---------- +2002/06/04 11:03:34 crs +base/CLog.cpp + +fixed delete bug in printt -- when skipping file and line the +deleted pointer was wrong. + +---------- +2002/06/04 11:02:33 crs +synergy/CXWindowsClipboard.cpp + +fixed timeout when getting selection -- forgot to set flag to +terminate event loop. + +---------- +2002/06/03 18:53:18 crs +base/CLog.cpp +base/CLog.h +server/CConfig.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CServer.cpp +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/server.cpp +synergy/CXWindowsScreen.cpp +synergy/IPrimaryScreen.h + +changes to add command line arguments. also added automatic +restarting and daemonizing on unix. daemon sends log messages +to syslog. unix now reads config file from file named on +command line; if no command line arg then uses effective +user's config file and if that's not there it finally tries +/etc/synergy.conf. if there are no screens configured then +one is added for the primary screen. broke some startup +stuff on win32. + +also now timing out if X primary screen can't grab the mouse +and keyboard. the server will just give up trying to switch +screens. the grabs will fail is some other app has a grab +and won't release it. note that kdm grabs the keyboard for +the duration that the login window is displayed, effectively +disabling synergy. + +---------- +2002/06/03 16:36:45 crs +base/CLog.cpp +base/CLog.h + +added a method to set the filter given a priority string (instead +of a number). fixed a comment related to what those priority +strings are. added a CLOG_PRINT priority which is never filtered +and suppresses the trace info and the priority level message. +it's intended as a way to output a message through the logger +without getting extra output. + +---------- +2002/06/03 16:34:22 crs +base/CString.cpp +base/CString.h +base/Makefile +base/base.dsp +http/CHTTPProtocol.cpp +http/CHTTPProtocol.h + +moved case insensitive comparison utility functions into CString +from CHTTPProtocol. + +---------- +2002/06/03 13:45:30 crs +client/CClient.cpp +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +server/CServer.cpp +server/CServer.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h +synergy/Makefile +synergy/XScreen.cpp +synergy/XScreen.h +synergy/synergy.dsp + +added better handling of X server disconnecting unexpectedly. +the apps still exit but they do it in a mostly controlled +manner. in particular, the server threads except the one +processing primary screen events will terminate gracefully. +this will be important should the server ever allow HTTP +clients to rewrite the configuration file. + +note that X makes it effectively impossible to continue once +the X server disconnects. even if it didn't it would be +difficult for synergy to recover. users will have to add +synergy to the X display manager's startup script if they +expect the server to be restarted. alternatively, we could +add code to fork synergy at startup; the child would do +the normal work while the parent would simply wait for the +child to exit and restart it. + +---------- +2002/06/02 23:07:57 crs +net/CTCPListenSocket.cpp + +shortened poll() timeout. + +---------- +2002/06/02 22:57:50 crs +io/CBufferedOutputStream.cpp +io/CBufferedOutputStream.h +net/CTCPSocket.cpp + +changed buffered output stream to wait() when flush()ing instead +of polling/sleeping. changed CTCPSocket to not use thread +cancellation but to instead use m_connected to exit the thread. +also shortened poll timeout. + +---------- +2002/06/02 21:35:20 crs +synergy/CMSWindowsScreen.cpp +synergy/CXWindowsScreen.cpp + +make sleep shorter in poll/sleep getEvent() loops. + +---------- +2002/06/02 21:03:38 crs +synergy/CXWindowsClipboard.cpp +synergy/CXWindowsClipboard.h +synergy/CXWindowsUtil.cpp + +removed poll/sleep code to improve performance. + +---------- +2002/06/02 19:04:24 crs +client/CXWindowsSecondaryScreen.cpp + +now ignores key if there's no key mapped for a required modifier. +was asserting (on the wrong expression). + +---------- +2002/06/02 18:49:35 crs +client/CClient.cpp +client/client.cpp +mt/CThread.cpp +mt/CThread.h +mt/CThreadRep.cpp +mt/CThreadRep.h +server/CServer.cpp +server/server.cpp + +added SIGINT and SIGTERM handling to unix client and server. +either signal causes the main thread to be cancelled. added +necessary code to make main thread cancellation clean up +nicely. + +---------- +2002/06/02 13:34:35 crs +http/CHTTPProtocol.cpp +http/CHTTPProtocol.h +server/CHTTPServer.cpp +server/CHTTPServer.h + +added a maximum request size to CHTTPProtocol so we can bail +on clients that cause us to use too much memory. also put +methods in CHTTPRequest to get/set headers and changed the +data structure used to store them. fixed a couple of other +miscellaneous bugs in CHTTPProtocol.cpp. + +---------- +2002/06/02 11:49:46 crs +mt/CCondVar.h +server/CServer.cpp +server/CServer.h + +now limiting number of simultaneous HTTP requests being handled +at once. this is to prevent denial of service. + +---------- +2002/06/01 19:26:11 crs +base/CLog.cpp +base/CLog.h +base/CString.h +base/XBase.h +base/base.dsp +base/common.h +base/stdfstream.h +base/stdistream.h +base/stdlist.h +base/stdmap.h +base/stdostream.h +base/stdpost.h +base/stdpre.h +base/stdset.h +base/stdsstream.h +base/stdvector.h +client/CMSWindowsSecondaryScreen.h +client/CXWindowsSecondaryScreen.h +client/client.cpp +client/client.dsp +http/CHTTPProtocol.cpp +http/CHTTPProtocol.h +http/XHTTP.cpp +http/http.dsp +io/CStreamBuffer.h +io/io.dsp +mt/mt.dsp +net/net.dsp +server/CConfig.cpp +server/CConfig.h +server/CHTTPServer.cpp +server/CHTTPServer.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CServer.cpp +server/CServer.h +server/CSynergyHook.cpp +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/makehook.dsp +server/server.cpp +server/server.dsp +server/synrgyhk.dsp +synergy.dsw +synergy/CXWindowsClipboard.h +synergy/IPrimaryScreen.h +synergy/synergy.dsp + +fixes, mainly for windows. first, had to add a notification from +CServer to the primary screen when the configuration changes so it +can make necessary adjustments (the win32 primary screen must tell +the hook dll about the new jump zones). + +changed includes of some std c++ library files to go through +our own include files. these wrap the include with stuff to +keep vc++ quiet when compiling at warning level 4, which is +what it does now. it also works around missing and + on g++2.96. + +added missing std:: where necessary. g++ doesn't really support +namespaces so it lets references without the namespace slip +through. + +added workaround or fix. not sure if istringstream::str(string) +should reset eofbit. it does on g++ but does not on vc++. +added clear() after str() so it works either way. + +added low-level keyboard hook to win32. if available (it's only +available on NT SP3 and up) it allows us to catch and handle +alt+tab, alt+esc, ctrl+esc, and windows key hot keys. i think +that leaves only ctrl+alt+del and accessibility functions +uncaught on those systems. + +---------- +2002/06/01 10:52:02 crs +server/CServer.cpp +server/CServer.h +server/CServerProtocol1_0.cpp + +added mutex to all public methods that didn't already have it. +fixed two blown assertions. first, if user tried to switch to +a client that had connected but hadn't yet sent the first info +message it would assert on the zero size screen. second, if +the primary screen was handling a mouse motion on behalf of a +secondary screen when that secondary screen disconnected then +an assert would blow because the primary screen would call +onMouseMoveSecondary() but m_protocol on the active screen is +NULL because disconnecting the active secondary screen caused +the mouse to jump to the primary screen. + +---------- +2002/05/31 18:35:53 crs +server/CConfig.h + +changed iterator to use iterator_traits directly instead of +std::iterator to support the old STL on grace. + +---------- +2002/05/31 18:18:29 crs +client/CClient.cpp +client/CClient.h +server/CServer.cpp +synergy/ProtocolTypes.h +synergy/XSynergy.cpp +synergy/XSynergy.h + +server now rejects clients that are not in the configuration. +added a protocol message to indicate this. + +---------- +2002/05/31 18:09:43 crs +server/CServer.cpp +server/CServer.h +server/CServerProtocol1_0.cpp + +fixed setConfig() to disconnect secondary screens that aren't +in the new configuration. + +---------- +2002/05/31 18:08:08 crs +server/CConfig.cpp +server/CConfig.h + +made isScreen() a const method. + +---------- +2002/05/31 17:32:26 crs +server/CConfig.cpp +server/CConfig.h +server/server.cpp + +added I/O for configuration files and changed the server to use +an external file for its configuration (was hard coding a config +for testing). + +---------- +2002/05/31 14:44:54 crs +server/CConfig.cpp +server/CScreenMap.cpp +server/Makefile +server/server.dsp + +finished renaming CScreenMap to CConfig. + +---------- +2002/05/31 14:43:23 crs +server/CConfig.h +server/CHTTPServer.cpp +server/CHTTPServer.h +server/CScreenMap.cpp +server/CServer.cpp +server/CServer.h +server/CSynergyHook.cpp +server/server.cpp + +checkpoint. changed CScreenMap to CConfig. must still change +CScreenMap.cpp to CConfig.cpp. + +---------- +2002/05/31 14:34:16 crs +server/CConfig.h +server/CHTTPServer.cpp +server/CScreenMap.cpp +server/CScreenMap.h +server/CServer.h +server/CSynergyHook.cpp +server/server.cpp + +checkpoint. renamed CScreenMap.h to CConfig.h. will be +changing CScreenMap to CConfig everywhere. + +---------- +2002/05/31 14:25:26 crs +base/CLog.cpp +base/CLog.h +client/client.cpp +server/server.cpp + +added methods to CLog for getting the outputter, getting and +setting the priority filter, and added code for thread safety. +added code to apps to enable thread safety in CLog. + +---------- +2002/05/30 16:13:16 crs +Makefile +base/common.h +http/CHTTPProtocol.cpp +http/CHTTPProtocol.h +http/Makefile +http/XHTTP.cpp +http/XHTTP.h +server/CHTTPServer.cpp +server/CHTTPServer.h +server/CScreenMap.cpp +server/CScreenMap.h +server/CServer.cpp +server/CServer.h +server/Makefile + +added basic support for an embedded HTTP server. server +currently supports editing the screen map but changing +the map won't behave correctly if there are connected +screens. + +---------- +2002/05/30 16:11:59 crs +net/CTCPSocket.cpp +net/CTCPSocket.h + +fixed bug in closing down a socket. + +---------- +2002/05/27 18:55:51 crs +synergy/CMSWindowsClipboard.cpp +synergy/CMSWindowsClipboard.h + +updated win32 clipboard to match new model. + +---------- +2002/05/27 18:35:14 crs +notes + +checkpoint + +---------- +2002/05/27 18:30:13 crs +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/CXWindowsClipboard.cpp +synergy/CXWindowsScreen.h +synergy/CXWindowsUtil.cpp +synergy/CXWindowsUtil.h + +removed getEventMask() from primary screen. added a class to +CXWindowsUtil that installs/uninstalls an X error hander. +using that in primary screen, clipboard, and util to ensure +that certain errors don't kill the app. + +---------- +2002/05/27 18:28:06 crs +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h + +removed getEventMask() and fixed some comments. also now using +toggle key states in updateModifiers(). + +---------- +2002/05/27 17:05:34 crs +synergy/CXWindowsClipboard.cpp + +changed lesstif hack to only apply to the CLIPBOARD selection. +apprently the PRIMARY selection must follow the ICCCM protocol +correctly. + +---------- +2002/05/27 16:51:07 crs +synergy/CXWindowsUtil.cpp +synergy/CXWindowsUtil.h + +added missing files from previous submit. + +---------- +2002/05/27 16:22:59 crs +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/CClipboard.cpp +synergy/CClipboard.h +synergy/CXWindowsClipboard.cpp +synergy/CXWindowsClipboard.h +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h +synergy/IClipboard.h +synergy/Makefile + +checkpoint. changed clipboard model. the clipboard can only +be accessed now between open()/close(). ownership of the +clipboard is asserted via the empty() method. this parallels +the win32 model (but the win32 code hasn't been updated yet). + +refactored X11 clipboard code. moved the bulk of it into +CXWindowsClipboard and moved some comment event handling into +CXWindowsScreen. changed how requests are processed into a +hopefully easier to understand model. added support for getting +clipboard from and sending clipboard to motif (or at least +lesstif) clients. sending to lesstif required a hack to work +around an apparent bug in lesstif. + +---------- +2002/05/24 17:54:34 crs +notes + +checkpoint + +---------- +2002/05/24 17:54:28 crs +client/CClient.cpp +client/CClient.h +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +server/CMSWindowsPrimaryScreen.cpp +server/CServer.cpp +server/CServer.h +server/CServerProtocol.h +server/CServerProtocol1_0.cpp +server/CServerProtocol1_0.h +server/CXWindowsPrimaryScreen.cpp +synergy/CMSWindowsScreen.cpp +synergy/CMSWindowsScreen.h +synergy/IPrimaryScreen.h +synergy/ISecondaryScreen.h +synergy/IServerProtocol.h +synergy/ProtocolTypes.h + +added screen locking support to win32. added support for +resolution changing (only semi-supported on X because that +has no means for resizing screen anyway). also fixed some +clipboard problems on win32. + +---------- +2002/05/24 14:37:12 crs +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CServer.cpp +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/IPrimaryScreen.h + +added support for locking to a screen when the sroll lock is +toggled on or when any key or button is pressed. fully +implemented on X but stubbed out for now on win32. + +---------- +2002/05/23 18:35:15 crs +notes + +checkpoint + +---------- +2002/05/23 18:35:08 crs +server/CMSWindowsPrimaryScreen.cpp +server/CSynergyHook.cpp +server/CSynergyHook.h + +added support for mouse wheel on win32. + +---------- +2002/05/23 15:50:38 crs +client/CXWindowsSecondaryScreen.cpp +server/CXWindowsPrimaryScreen.cpp + +added support for mouse wheel on X. + +---------- +2002/05/23 15:00:39 crs +server/server.cpp + +added a third screen to hard coded map for testing purposes. + +---------- +2002/05/23 15:00:13 crs +server/CServer.cpp + +fixed log message. + +---------- +2002/05/23 14:56:03 crs +client/CClient.cpp +client/CClient.h +server/CServer.cpp +server/CServerProtocol.cpp +server/CServerProtocol1_0.cpp +synergy/ProtocolTypes.h +synergy/XSynergy.cpp +synergy/XSynergy.h + +server no longer asserts when a client connects with a name that's +already in use by another client. also added reporting of errors +from the server to clients so clients can report meaningful +messages to users. + +---------- +2002/05/23 14:04:43 crs +notes + +checkpoint + +---------- +2002/05/23 14:04:35 crs +net/CNetwork.h +synergy/CXWindowsScreen.h + +changed structs to classes. there should be no more structs now. + +---------- +2002/05/22 17:09:08 crs +notes + +checkpoint. + +---------- +2002/05/22 17:08:37 crs +synergy/CMSWindowsClipboard.cpp +synergy/CMSWindowsScreen.cpp +synergy/CMSWindowsScreen.h +synergy/CProtocolUtil.cpp + +removed unnecessary call in screen class, added logging calls +in clipboard class, and added another cast in protocol util +to avoid warning on win32. + +---------- +2002/05/22 17:05:26 crs +server/CSynergyHook.cpp + +now letting some key events filter through. this allows the +keyboard lights to track toggle changes. however, it also +seems to let through keyboard events that shouldn't get +through. + +---------- +2002/05/22 17:02:58 crs +server/CScreenMap.cpp + +fixed incorrect for-loop over directions conditional. + +---------- +2002/05/22 17:01:17 crs +base/CLog.cpp +base/CLog.h +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h + +win32 changes. replaced log dialog hack with a windows console +window. now attaching thread input queues as necessary. shifted +code around so toggling toggle keys is immediately reflected by +secondary screen's keyboard. now setting extended key flag for +keys that need it. fixed handling of shift + caps-lock. added +handling of keys that should distinguish between left and right +but don't. fixed get/set of active window on leave/enter of +primary screen. replaced 1x1 primary window with a full screen +window to work around a problem with losing key events. changed +calculation of mouse move deltas. + +---------- +2002/05/22 16:56:06 crs +net/CTCPListenSocket.cpp + +fixed type of socket handle (from int to CNetwork::Socket). + +---------- +2002/05/22 16:55:19 crs +mt/CTimerThread.cpp + +removed blank line. + +---------- +2002/05/22 16:55:05 crs +mt/CThread.cpp +mt/CThread.h +mt/CThreadRep.cpp + +changed un-inlined code to avoid bogus VC++ level 4 warnings. +added support for more win32 thread priorities. + +---------- +2002/05/22 16:51:59 crs +client/client.cpp + +fixed parameter type for socket port. + +---------- +2002/05/22 16:43:14 crs +base/common.h + +changed set of disabled win32 warnings. + +---------- +2002/05/22 16:42:48 crs +client/CClient.cpp + +fixed NULL dereference. + +---------- +2002/05/22 16:41:24 crs +base/common.h + +changed set of disabled win32 warnings. + +---------- +2002/05/22 16:40:51 crs +base/CLog.cpp +base/CLog.h + +replaced logging dialog hack with a windows console window. + +---------- +2002/05/22 16:40:38 crs +client/CClient.cpp + +fixed NULL dereference. + +---------- +2002/05/05 23:37:12 crs +net/CTCPSocket.cpp + +removed setting send buffer to zero size. it just reduced +performance. + +---------- +2002/05/05 19:52:03 crs +client/CXWindowsSecondaryScreen.cpp + +replaced True/False with true/false when assigning to m_repeat. +also should now work if the first element of a modifier +keymapping is 0. that won't normally be the case but xmodmap +was doing weird things on grace. if the first element is 0 +it'll try the second element. if that's also zero then that +modifier will be ignored. + +---------- +2002/05/05 19:38:09 crs +client/CMSWindowsSecondaryScreen.cpp +server/CMSWindowsPrimaryScreen.cpp + +fixes for win32 keyboard. + +---------- +2002/05/04 19:43:20 crs +client/CXWindowsSecondaryScreen.cpp + +fixed caps-lock handling. + +---------- +2002/05/04 18:33:48 crs +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h + +checkpoint. added half duplex for num lock. + +---------- +2002/05/04 18:31:54 crs +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h + +checkpoint. fixing up handling of half-duplex num-lock. + +---------- +2002/05/04 18:09:02 crs +client/CXWindowsSecondaryScreen.cpp + +checkpoint. changed when toggle keys toggle (now always on +release). must see if this works. + +---------- +2002/05/04 18:08:22 crs +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +net/CTCPSocket.cpp +server/CMSWindowsPrimaryScreen.cpp + +Fixes for win32 key handling. + +---------- +2002/05/04 11:23:11 crs +client/CXWindowsSecondaryScreen.cpp + +fixed handling of shift + caps-lock. those two modifiers should +cancel out if the keysym is subject to case conversion, but not +otherwise. also added logging of key lookup code. + +---------- +2002/05/03 12:23:48 crs +client/CXWindowsSecondaryScreen.cpp + +fixed handling of shift+tab on a system that can map ISO_Left_Tab. +now tries to map ISO_Left_Tab without shift first then falls back +to Tab (note that if ISO_Left_Tab can be mapped but requires a +modifier then the modifier will be added). also changed attempt +to map ISO_Left_Tab as a backup to Tab to request the shift +modifier whether or not the primary screen requested it. + +---------- +2002/05/03 12:14:55 crs +client/CXWindowsSecondaryScreen.cpp + +fixed handling of ISO_Left_Tab when that is not mapped to a +keycode by mapping it to tab with shift pressed. + +---------- +2002/05/03 11:49:30 crs +client/CXWindowsSecondaryScreen.cpp + +removed attempt to make release/press of a repeating key use +the same server time. was getting what appears to be deadlock +but not sure why. + +---------- +2002/05/03 11:26:44 crs +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h + +checkpoint. made changes to support key autorepeats on X. + +---------- +2002/05/02 11:44:21 crs +synergy/COutputPacketStream.cpp + +Indentation change. + +---------- +2002/05/02 11:43:52 crs +io/CStreamBuffer.cpp +net/CTCPSocket.cpp + +Fixed bug in stream buffer that could cause data to be +inserted out of order. Also removed unnecessary limit +on writes to the TCP socket. + +---------- +2002/05/02 11:33:34 crs +io/CStreamBuffer.cpp + +checkpoint debugging of stream buffer. + +---------- +2002/05/01 16:30:20 crs +synergy/CXWindowsScreen.cpp + +Was trying to avoid sending clipboard if timestamp wasn't +changed but clipboard owners may not update that timestamp +when the selection is changed. Disabled the timestamp check. + +---------- +2002/05/01 16:17:57 crs +server/CServer.cpp +synergy/CXWindowsScreen.cpp + +Added more checks to avoid sending unchanged clipboard data. +Still takes too long to query the clipboard owner for info +(maybe 1/10th second) but not sure why or if that can be +improved. + +---------- +2002/05/01 15:31:47 crs +Make-linux +net/CNetwork.h +net/CTCPSocket.cpp +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h + +checkpoint. turned off nagle and send buffering. also +added test to skip clipboard conversion if a previous +conversion from that owner failed. + +---------- +2002/05/01 14:36:52 crs +server/CServer.cpp +server/CXWindowsPrimaryScreen.cpp + +Fixed uninitialized variable when computing toggle mask. Also +reduced priority of some mouse motion log messages. + +---------- +2002/05/01 14:35:55 crs +net/CSocketInputStream.cpp +net/CSocketInputStream.h +net/CSocketOutputStream.cpp +net/CSocketOutputStream.h +net/CSocketStreamBuffer.cpp +net/CSocketStreamBuffer.h +net/net.dsp + +removed obsolete files. + +---------- +2002/04/30 18:30:05 crs +client/CXWindowsSecondaryScreen.cpp + +added fallback for missing numpad movement keys (if there's no +mapping for those keys then the non-keypad versions are tried). + +---------- +2002/04/30 17:48:11 crs +client/CClient.cpp +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CServer.cpp +server/CServerProtocol.h +server/CServerProtocol1_0.cpp +server/CServerProtocol1_0.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/IPrimaryScreen.h +synergy/ISecondaryScreen.h +synergy/IServerProtocol.h +synergy/ProtocolTypes.h + +checkpoint. now sending toggle modifier state when entering +a screen. this allows the secondary screen to set it's +modifier state to match the primary screen's state. this is +not strictly necessary since each keystroke should adjust the +modifier state as needed to get the right result. + +---------- +2002/04/30 16:25:29 crs +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h + +Added logging and handling of "half-duplex" caps-lock key. + +---------- +2002/04/30 16:23:30 crs +Make-linux +Make-solaris + +Changed name for auto-generated dependency files from +Makedepend to .depend. + +---------- +2002/04/30 16:23:03 crs +client/CClient.cpp +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CServerProtocol1_0.cpp +server/server.rc +synergy/CClipboard.cpp +synergy/CMSWindowsClipboard.cpp +synergy/CMSWindowsClipboard.h +synergy/synergy.dsp + +Fixes to get win32 client and server up to date. + +---------- +2002/04/29 14:40:01 crs +base/CFunctionJob.h +base/CLog.h +base/CStopwatch.cpp +base/CStopwatch.h +base/CString.h +base/IInterface.h +base/IJob.h +base/TMethodJob.h +base/XBase.h +client/CClient.h +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +io/CBufferedInputStream.h +io/CBufferedOutputStream.h +io/CInputStreamFilter.h +io/COutputStreamFilter.h +io/CStreamBuffer.h +io/IInputStream.h +io/IOutputStream.h +io/XIO.h +mt/CCondVar.cpp +mt/CCondVar.h +mt/CLock.h +mt/CMutex.cpp +mt/CMutex.h +mt/CThread.h +mt/CThreadRep.cpp +mt/CThreadRep.h +mt/CTimerThread.h +mt/XThread.h +net/CNetwork.h +net/CNetworkAddress.cpp +net/CNetworkAddress.h +net/CSocketInputStream.h +net/CSocketOutputStream.h +net/CSocketStreamBuffer.h +net/CTCPListenSocket.h +net/CTCPSocket.h +net/IListenSocket.h +net/ISocket.h +net/XNetwork.h +net/XSocket.h +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CScreenMap.h +server/CServer.cpp +server/CServer.h +server/CServerProtocol.h +server/CServerProtocol1_0.h +server/CSynergyHook.cpp +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/CClipboard.h +synergy/CInputPacketStream.cpp +synergy/CInputPacketStream.h +synergy/CMSWindowsClipboard.cpp +synergy/CMSWindowsClipboard.h +synergy/CMSWindowsScreen.h +synergy/COutputPacketStream.h +synergy/CProtocolUtil.cpp +synergy/CProtocolUtil.h +synergy/CTCPSocketFactory.h +synergy/CXWindowsClipboard.h +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h +synergy/IClipboard.h +synergy/IPrimaryScreen.h +synergy/ISecondaryScreen.h +synergy/IServerProtocol.h +synergy/ISocketFactory.h +synergy/XSynergy.h + +Indentation changes. + +---------- +2002/04/29 14:25:24 crs +client/CClient.cpp +server/CServer.cpp +server/CServerProtocol1_0.cpp + +Added some validation of protocol message parameters. + +---------- +2002/04/29 14:12:48 crs +synergy/CXWindowsScreen.cpp + +Shortened timeout on waiting for clipboard response. + +---------- +2002/04/29 14:08:48 crs +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +server/CServer.cpp +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h + +Made event selection a little more robust. Also fixed failure +to marshall clipboard data when updating primary clipboards. + +---------- +2002/04/29 13:49:56 crs +client/CXWindowsSecondaryScreen.cpp + +Added missing event mask. + +---------- +2002/04/29 13:31:44 crs +client/CClient.cpp +client/CClient.h +server/CServer.cpp +server/CServer.h +server/CServerProtocol.h +server/CServerProtocol1_0.cpp +server/CServerProtocol1_0.h +synergy/CClipboard.cpp +synergy/CClipboard.h +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h +synergy/IClipboard.h +synergy/IPrimaryScreen.h +synergy/IServerProtocol.h +synergy/ProtocolTypes.h + +checkpoint. changed protocol to better handle clipboards. now +sending a sequence number with enter messages. screens use that +sequence number in clipboard grab and data messages. the server +uses the sequence number to order messages across clients. also +changed secondary screens to send clipboard updates on leaving +(or when grab occurs when not active) instead of on a query from +the server. primary effectively does the same. the query +message has been removed. + +---------- +2002/04/29 11:58:17 crs +client/CClient.cpp + +changed logging levels. + +---------- +2002/04/28 00:46:15 crs +client/CXWindowsSecondaryScreen.cpp +server/CXWindowsPrimaryScreen.cpp +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h + +Clipboard improvements. Still not working right. Nedit +doesn't work at all but at least now there's a timeout to +prevent synergy from hanging waiting on a reply. + +---------- +2002/04/27 18:49:03 crs +base/CLog.cpp +base/CLog.h +client/CClient.cpp +mt/CThread.cpp +mt/CThreadRep.cpp +mt/CTimerThread.cpp +net/CNetwork.cpp +server/CServer.cpp +server/CServerProtocol1_0.cpp +server/CXWindowsPrimaryScreen.cpp +synergy/CProtocolUtil.cpp +synergy/CXWindowsScreen.cpp + +Added more debug levels and moved some annoying debug messages +to those levels. Default log level is now DEBUG for debug +builds and INFO for release builds. + +---------- +2002/04/27 18:06:40 crs +client/CClient.cpp +client/CXWindowsSecondaryScreen.cpp +server/CServer.cpp +server/CServerProtocol1_0.cpp +server/CXWindowsPrimaryScreen.cpp +synergy/CProtocolUtil.cpp +synergy/CXWindowsScreen.cpp +synergy/ProtocolTypes.h + +checkpoint. changed CProtocolUtil::readf() to store 1 and 2 +byte integers into pointers to 1 and 2 byte integers. was +always assuming pointers to 4 byte integers. + +---------- +2002/04/27 14:19:53 crs +client/CClient.cpp +client/CClient.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +server/CServer.cpp +server/CServer.h +server/CServerProtocol.h +server/CServerProtocol1_0.cpp +server/CServerProtocol1_0.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h +synergy/ClipboardTypes.h +synergy/IPrimaryScreen.h +synergy/ISecondaryScreen.h +synergy/IServerProtocol.h +synergy/ProtocolTypes.h + +Added support for multiple clipboards. This is mainly to +support both PRIMARY and CLIPBOARD selections on X windows. + +---------- +2002/04/27 14:19:19 crs +Makecommon + +set TARGETS macro to BIN and LIB targets. + +---------- +2002/04/26 20:15:59 crs +notes + +updated + +---------- +2002/04/26 20:14:46 crs +client/CXWindowsSecondaryScreen.cpp + +Fixed caps-lock and num-lock behavior. It seems to work okay +now but did notice one problem: when powerbook is primary and +num-lock is on the keypad works fine until shift is pressed +(and released); after that the keypad only works while the +shift key is down. + +---------- +2002/04/26 20:12:55 crs +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h + +Added hack to handle "half-duplex" caps-lock key on powerbook. +That key only reports press when pressed and released when +caps-lock is activated and only reports release when pressed +and released when caps-lock is deactivated. I don't know of a +way to detect this behavior so it may have to be configured by +the user. The code assumes normal behavior; will have to add +code to set the flag (perhaps from a user configuration). + +---------- +2002/04/26 17:38:01 crs +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +synergy/KeyTypes.h + +changed processing of key events in X. secondary screen now +activates/deactivates modifiers as necessary to get a keycode +interpreted as the expected keysym. still some work and +testing to do on this. + +---------- +2002/04/25 10:44:01 crs +notes + +Added notes on keyboard handling. + +---------- +2002/04/25 10:43:53 crs +client/CXWindowsSecondaryScreen.cpp +server/CXWindowsPrimaryScreen.cpp +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h + +added handling for DestroyNotify of clipboard requestors. + +---------- +2001/11/26 22:36:51 crs +synergy/CXWindowsScreen.cpp + +checkpoint. improvements to clipboard transfer on X windows. +not detecting a change to clipboard when synergy window isn't +the owner (since there's no event for this; we'll have to +check when we leave the screen i guess). large transfers +don't seem to work. + +---------- +2001/11/26 22:09:53 crs +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h + +checkpoint. testing clipboard transfer on X windows. + +---------- +2001/11/25 22:20:41 crs +client/CXWindowsSecondaryScreen.cpp +server/CXWindowsPrimaryScreen.cpp +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h + +checkpoint. implementing clipboard owner in x windows. + +---------- +2001/11/25 18:44:13 crs +synergy/CXWindowsClipboard.cpp +synergy/CXWindowsClipboard.h + +fixed function signature. + +---------- +2001/11/25 18:42:13 crs +Make-linux +Make-solaris +Makecommon +client/Makefile +server/Makefile + +executables are now built into a common area on unix (and they +already were on win32). + +---------- +2001/11/25 18:32:41 crs +client/CClient.cpp +client/CClient.h +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +client/client.cpp +client/client.dsp +net/CNetwork.cpp +notes +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CServer.cpp +server/CServer.h +server/CServerProtocol.h +server/CServerProtocol1_0.cpp +server/CServerProtocol1_0.h +server/CSynergyHook.cpp +server/CSynergyHook.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/makehook.dsp +server/server.cpp +server/server.dsp +server/synrgyhk.dsp +synergy.dsw +synergy/CClipboard.cpp +synergy/CClipboard.h +synergy/CMSWindowsClipboard.cpp +synergy/CMSWindowsClipboard.h +synergy/CMSWindowsScreen.cpp +synergy/CMSWindowsScreen.h +synergy/CProtocolUtil.cpp +synergy/CProtocolUtil.h +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h +synergy/IClipboard.h +synergy/IPrimaryScreen.h +synergy/ISecondaryScreen.h +synergy/IServerProtocol.h +synergy/Makefile +synergy/ProtocolTypes.h +synergy/synergy.dsp + +added platform independent clipboard transfer stuff +clipboard owner support (MS windows done, X windows partial) +added key transfer on ms windows +mutex fixes in CClient (had race conditions) +faster debug output in ms windows +changed temporary screen name to "secondary" +network fixes on ms windows (poll returned wrong result) +fixed transparent cursor on ms windows + +---------- +2001/11/19 00:33:36 crs +Make-linux +all.dsp +base/BasicTypes.h +base/CLog.cpp +base/CLog.h +base/XBase.cpp +base/base.dsp +base/common.h +client/CClient.cpp +client/CClient.h +client/CMSWindowsSecondaryScreen.cpp +client/CMSWindowsSecondaryScreen.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +client/Makefile +client/client.cpp +client/client.dsp +client/client.rc +client/resource.h +io/io.dsp +mt/CCondVar.cpp +mt/CThread.cpp +mt/CThreadRep.cpp +mt/CThreadRep.h +mt/mt.dsp +net/CNetwork.cpp +net/CNetwork.h +net/CNetworkAddress.cpp +net/CNetworkAddress.h +net/CTCPListenSocket.cpp +net/CTCPListenSocket.h +net/CTCPSocket.cpp +net/CTCPSocket.h +net/Makefile +net/XNetwork.cpp +net/XNetwork.h +net/net.dsp +notes +server/CMSWindowsPrimaryScreen.cpp +server/CMSWindowsPrimaryScreen.h +server/CScreenMap.h +server/CServer.cpp +server/CServer.h +server/CSynergyHook.cpp +server/CSynergyHook.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/Makefile +server/makehook.dsp +server/resource.h +server/server.cpp +server/server.dsp +server/server.rc +server/synrgyhk.dsp +synergy.dsw +synergy/CMSWindowsClipboard.cpp +synergy/CMSWindowsClipboard.h +synergy/CMSWindowsScreen.cpp +synergy/CMSWindowsScreen.h +synergy/CXWindowsClipboard.h +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h +synergy/IClipboard.h +synergy/IPrimaryScreen.h +synergy/ISecondaryScreen.h +synergy/synergy.dsp + +checkpoint. merging win32 code. server on X is currently broken +and client probably is. + +---------- +2001/11/18 23:14:28 crs +Makefile +client/CClient.cpp +client/CClient.h +client/CXWindowsSecondaryScreen.cpp +client/CXWindowsSecondaryScreen.h +client/Makefile +client/client.cpp +server/CScreenMap.cpp +server/CScreenMap.h +server/CServer.cpp +server/CServer.h +server/CServerProtocol.cpp +server/CServerProtocol.h +server/CServerProtocol1_0.cpp +server/CServerProtocol1_0.h +server/CXWindowsPrimaryScreen.cpp +server/CXWindowsPrimaryScreen.h +server/Makefile +server/server.cpp +synergy/CClient.cpp +synergy/CClient.h +synergy/CScreenMap.cpp +synergy/CScreenMap.h +synergy/CServer.cpp +synergy/CServer.h +synergy/CServerProtocol.cpp +synergy/CServerProtocol.h +synergy/CServerProtocol1_0.cpp +synergy/CServerProtocol1_0.h +synergy/CXWindowsPrimaryScreen.cpp +synergy/CXWindowsPrimaryScreen.h +synergy/CXWindowsSecondaryScreen.cpp +synergy/CXWindowsSecondaryScreen.h +synergy/Makefile +synergy/client.cpp +synergy/server.cpp + +moved client and server files into their own respective +directories. + +---------- +2001/11/13 23:34:12 crs +synergy/CServer.cpp +synergy/CXWindowsClipboard.cpp +synergy/CXWindowsClipboard.h +synergy/CXWindowsPrimaryScreen.cpp +synergy/CXWindowsPrimaryScreen.h +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h +synergy/IClipboard.h +synergy/IPrimaryScreen.h +synergy/Makefile + +added preliminary support for getting the X selection. + +---------- +2001/11/11 21:27:36 crs +synergy/CServer.cpp + +fixed clamping when mapping to a different screen when beyond +bottom or right of source screen. + +---------- +2001/11/11 21:15:30 crs +synergy/CXWindowsPrimaryScreen.cpp +synergy/CXWindowsPrimaryScreen.h +synergy/CXWindowsScreen.cpp +synergy/CXWindowsScreen.h +synergy/CXWindowsSecondaryScreen.cpp +synergy/CXWindowsSecondaryScreen.h +synergy/Makefile + +factored common X windows screen stuff into a common base class. + +---------- +2001/11/10 22:28:37 crs +notes + +updated notes. + +---------- +2001/11/10 22:28:30 crs +Makefile + +added main app directory to build. + +---------- +2001/10/25 22:17:17 crs +io/CBufferedInputStream.cpp +mt/CCondVar.cpp +mt/CMutex.cpp +mt/CThreadRep.cpp +net/CNetworkAddress.cpp +net/CSocketInputStream.cpp +net/CTCPListenSocket.cpp +net/CTCPSocket.cpp +synergy/CXWindowsPrimaryScreen.cpp +synergy/CXWindowsSecondaryScreen.cpp + +removed unnecessary global scoping operators. + +---------- +2001/10/25 22:09:27 crs +synergy/CXWindowsSecondaryScreen.cpp + +changed hider window to move underneath mouse when leaving the +screen. this makes it so if the mouse is moved locally, it'll +reappear where it was last seen. + +---------- +2001/10/25 21:40:29 crs +synergy/CClient.cpp +synergy/CXWindowsSecondaryScreen.cpp +synergy/CXWindowsSecondaryScreen.h +synergy/ISecondaryScreen.h + +changed some method names and removed warpCursor() from +secondary screen interface. + +---------- +2001/10/24 23:29:29 crs +synergy/CServer.cpp +synergy/CServer.h + +now handling disconnect of secondary screen that has the cursor +by jumping back to the primary screen (without trying to notify +the now disconnected secondary screen). also fixed blown assert +in mapPosition(). + +---------- +2001/10/24 22:33:24 crs +synergy/CXWindowsPrimaryScreen.cpp +synergy/CXWindowsPrimaryScreen.h +synergy/CXWindowsSecondaryScreen.cpp +synergy/CXWindowsSecondaryScreen.h + +made calls to X thread safe. + +---------- +2001/10/23 22:45:59 crs +notes + +more notes. + +---------- +2001/10/23 22:41:46 crs +synergy/CXWindowsPrimaryScreen.cpp +synergy/CXWindowsPrimaryScreen.h +synergy/CXWindowsSecondaryScreen.cpp +synergy/CXWindowsSecondaryScreen.h + +added cursor hiding. + +---------- +2001/10/23 21:23:29 crs +base/CLog.cpp + +can now filter logging by level. + +---------- +2001/10/23 21:13:08 crs +synergy/CServer.cpp + +fixed blown assert trying to find neighbor when there was none. + +---------- +2001/10/21 00:21:21 crs +synergy/CClient.cpp + +fixed handling of stream ownership. + +---------- +2001/10/21 00:21:02 crs +io/CBufferedInputStream.cpp +io/CBufferedOutputStream.cpp +mt/CThreadRep.cpp +net/CTCPSocket.cpp +net/CTCPSocket.h +synergy/CServer.cpp +synergy/server.cpp + +fixed bugs in handling streams. + +---------- +2001/10/20 20:43:31 crs +Make-linux +mt/CThreadRep.cpp + +threading fixes. had sigmask set in wrong place. was setting +m_exit flag potentially after the object had been destroyed. +most importantly, RTTI must be enabled on PPC to avoid SIGILL. + +---------- +2001/10/14 19:16:54 crs +mt/CThread.cpp +mt/CThreadRep.cpp +mt/CTimerThread.cpp + +some debugging code. + +---------- +2001/10/14 18:29:43 crs +base/CLog.h +mt/CMutex.cpp +mt/CThread.cpp +mt/CThread.h +mt/CThreadRep.cpp +mt/CThreadRep.h +mt/CTimerThread.cpp +synergy/CClient.cpp +synergy/CClient.h +synergy/CProtocolUtil.cpp +synergy/CServerProtocol1_0.cpp +synergy/client.cpp +synergy/server.cpp + +fixed timeout bug in CThreadRep::wait() (negative timeout wouldn't +wait forever). also fixed early return from sleep due to signal. +now forcing client to initialize CThread to ensure global mutex +gets initialized before threads are used. + +---------- +2001/10/14 16:58:01 crs +io/CBufferedInputStream.cpp +io/CBufferedInputStream.h +io/CBufferedOutputStream.cpp +io/CBufferedOutputStream.h +io/CInputStreamFilter.cpp +io/CInputStreamFilter.h +io/COutputStreamFilter.cpp +io/COutputStreamFilter.h +io/CStreamBuffer.cpp +io/CStreamBuffer.h +io/IInputStream.h +io/IOutputStream.h +mt/CCondVar.cpp +mt/CCondVar.h +mt/CLock.cpp +mt/CLock.h +mt/CMutex.cpp +mt/CMutex.h +net/CNetworkAddress.cpp +net/CNetworkAddress.h +net/CSocketInputStream.cpp +net/CSocketInputStream.h +net/CSocketOutputStream.cpp +net/CSocketOutputStream.h +net/CSocketStreamBuffer.cpp +net/CSocketStreamBuffer.h +net/CTCPListenSocket.cpp +net/CTCPListenSocket.h +net/CTCPSocket.cpp +net/CTCPSocket.h +net/IListenSocket.h +net/ISocket.h +synergy/CInputPacketStream.cpp +synergy/CInputPacketStream.h +synergy/COutputPacketStream.cpp +synergy/COutputPacketStream.h +synergy/CProtocolUtil.cpp +synergy/CProtocolUtil.h +synergy/CScreenMap.cpp +synergy/CScreenMap.h +synergy/CServer.cpp +synergy/CServer.h +synergy/CServerProtocol.cpp +synergy/CServerProtocol.h +synergy/CServerProtocol1_0.cpp +synergy/CServerProtocol1_0.h +synergy/CTCPSocketFactory.cpp +synergy/CTCPSocketFactory.h +synergy/IServerProtocol.h +synergy/ISocketFactory.h + +removed exception specifications. thread exceptions weren't +being listed and they'd have to be added to every one. just +doesn't seem worth the trouble. + +---------- +2001/10/14 14:56:06 crs +synergy/CProtocolUtil.cpp + +stupid bug fixes. writef() used the wrong variable as the number +of bytes to write. readf() forgot to prepare the va_list. + +---------- +2001/10/14 14:38:45 crs +base/CLog.cpp +base/CLog.h + +forgot to add the logger files. + +---------- +2001/10/14 14:37:41 crs +Make-linux +base/Makefile +synergy/CClient.cpp +synergy/CScreenMap.cpp +synergy/CScreenMap.h +synergy/CServer.cpp +synergy/CServerProtocol1_0.cpp +synergy/CXWindowsPrimaryScreen.cpp + +added logging facility and added a bunch of log messages. + +---------- +2001/10/08 19:24:46 crs +Makefile +notes +synergy/CClient.cpp +synergy/CClient.h +synergy/CServer.cpp +synergy/CXWindowsPrimaryScreen.cpp +synergy/CXWindowsSecondaryScreen.cpp +synergy/CXWindowsSecondaryScreen.h +synergy/ISecondaryScreen.h +synergy/Makefile +synergy/client.cpp +synergy/server.cpp + +checkpoint. first cut of client and server apps. not tested +yet but they compile and *should* work as is. + +---------- +2001/10/06 14:18:01 crs +Make-linux +Makefile + +updated old files to new implementation + +---------- +2001/10/06 14:13:28 crs +BasicTypes.h +CClient.cpp +CClient.h +CEvent.h +CEventQueue.cpp +CEventQueue.h +CMessageSocket.cpp +CMessageSocket.h +CProtocol.h +CScreenProxy.cpp +CScreenProxy.h +CServer.cpp +CServer.h +CSocket.cpp +CSocket.h +CSocketFactory.cpp +CSocketFactory.h +CString.h +CTrace.cpp +CTrace.h +CUnixEventQueue.cpp +CUnixEventQueue.h +CUnixTCPSocket.cpp +CUnixTCPSocket.h +CUnixXScreen.cpp +CUnixXScreen.h +CXScreen.cpp +CXScreen.h +IClient.h +IClipboard.h +IEventQueue.h +IJob.h +IScreen.h +IServer.h +ISocket.h +KeyTypes.h +Make-linux +Make-solaris +Makecommon +Makefile +MouseTypes.h +TMethodJob.h +XBase.cpp +XBase.h +XSocket.h +base/BasicTypes.h +base/CFunctionJob.cpp +base/CFunctionJob.h +base/CStopwatch.cpp +base/CStopwatch.h +base/CString.h +base/IInterface.h +base/IJob.h +base/Makefile +base/TMethodJob.h +base/XBase.cpp +base/XBase.h +base/common.h +io/CBufferedInputStream.cpp +io/CBufferedInputStream.h +io/CBufferedOutputStream.cpp +io/CBufferedOutputStream.h +io/CInputStreamFilter.cpp +io/CInputStreamFilter.h +io/COutputStreamFilter.cpp +io/COutputStreamFilter.h +io/CStreamBuffer.cpp +io/CStreamBuffer.h +io/IInputStream.h +io/IOutputStream.h +io/Makefile +io/XIO.cpp +io/XIO.h +main.cpp +mt/CCondVar.cpp +mt/CCondVar.h +mt/CLock.cpp +mt/CLock.h +mt/CMutex.cpp +mt/CMutex.h +mt/CThread.cpp +mt/CThread.h +mt/CThreadRep.cpp +mt/CThreadRep.h +mt/CTimerThread.cpp +mt/CTimerThread.h +mt/Makefile +mt/XThread.h +net/CNetworkAddress.cpp +net/CNetworkAddress.h +net/CSocketInputStream.cpp +net/CSocketInputStream.h +net/CSocketOutputStream.cpp +net/CSocketOutputStream.h +net/CSocketStreamBuffer.cpp +net/CSocketStreamBuffer.h +net/CTCPListenSocket.cpp +net/CTCPListenSocket.h +net/CTCPSocket.cpp +net/CTCPSocket.h +net/IListenSocket.h +net/ISocket.h +net/Makefile +net/XSocket.cpp +net/XSocket.h +notes +synergy/CClient.cpp +synergy/CClient.h +synergy/CInputPacketStream.cpp +synergy/CInputPacketStream.h +synergy/COutputPacketStream.cpp +synergy/COutputPacketStream.h +synergy/CProtocolUtil.cpp +synergy/CProtocolUtil.h +synergy/CScreenMap.cpp +synergy/CScreenMap.h +synergy/CServer.cpp +synergy/CServer.h +synergy/CServerProtocol.cpp +synergy/CServerProtocol.h +synergy/CServerProtocol1_0.cpp +synergy/CServerProtocol1_0.h +synergy/CTCPSocketFactory.cpp +synergy/CTCPSocketFactory.h +synergy/CXWindowsPrimaryScreen.cpp +synergy/CXWindowsPrimaryScreen.h +synergy/IPrimaryScreen.h +synergy/ISecondaryScreen.h +synergy/IServerProtocol.h +synergy/ISocketFactory.h +synergy/KeyTypes.h +synergy/Makefile +synergy/MouseTypes.h +synergy/ProtocolTypes.h +synergy/XSynergy.cpp +synergy/XSynergy.h +test.cpp + +Started over. + +---------- +2001/05/14 21:14:49 crs +MouseTypes.h + +flipped order of buttons to match default X setup. + +---------- +2001/05/14 21:14:25 crs +CClient.cpp +CEvent.h +CScreenProxy.cpp +CScreenProxy.h +CServer.cpp +CXScreen.cpp +CXScreen.h +IScreen.h +KeyTypes.h + +added other mouse and key event handling to CXScreen. key repeat +isn't implemented and modifier masks are ignored. modifier masks +are new; they indicate the modifier key (shift, ctrl, etc) state +at the time of the key event. + +---------- +2001/05/13 12:43:16 crs +CUnixTCPSocket.cpp +CUnixTCPSocket.h + +more fixes to reduce latency. nagle agorithm doesn't seem to +stay off on a socket on linux because a connection clearly +doesn't send data as often as possible. will have to implement +a UDP socket to reduce overhead and avoid these delays. wanted +to do that anyway. + +---------- +2001/05/13 12:21:11 crs +CUnixTCPSocket.cpp +CXScreen.cpp + +fixes to avoid update delays. + +---------- +2001/05/13 12:07:32 crs +CMessageSocket.cpp + +fixed bug in read() that miscalculated the message length. + +---------- +2001/05/13 11:40:29 crs +BasicTypes.h +CClient.cpp +CClient.h +CEvent.h +CEventQueue.cpp +CEventQueue.h +CMessageSocket.cpp +CMessageSocket.h +CProtocol.h +CScreenProxy.cpp +CScreenProxy.h +CServer.cpp +CServer.h +CSocket.cpp +CSocket.h +CSocketFactory.cpp +CSocketFactory.h +CString.h +CTrace.cpp +CTrace.h +CUnixEventQueue.cpp +CUnixEventQueue.h +CUnixTCPSocket.cpp +CUnixTCPSocket.h +CUnixXScreen.cpp +CUnixXScreen.h +CXScreen.cpp +CXScreen.h +IClient.h +IClipboard.h +IEventQueue.h +IJob.h +IScreen.h +IServer.h +ISocket.h +KeyTypes.h +Make-linux +Makefile +MouseTypes.h +TMethodJob.h +XBase.cpp +XBase.h +XSocket.h +main.cpp +tools/depconv + +initial revision of synergy. currently semi-supports X windows +on unix, but client screens don't simulate events other than +mouse move. also not supporting clipboard at all yet and the +main app is just a temporary framework to test with. must +clean up protocol and communication. + +---------- diff --git a/INSTALL b/INSTALL new file mode 100644 index 00000000..7262d958 --- /dev/null +++ b/INSTALL @@ -0,0 +1 @@ +See doc/compiling.html. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..26a5086f --- /dev/null +++ b/Makefile.am @@ -0,0 +1,97 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +SUBDIRS = \ + lib \ + cmd \ + doc \ + dist \ + $(NULL) + +EXTRA_DIST = \ + Makefile.win \ + examples/synergy.conf \ + win32util/autodep.cpp \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + aclocal.m4 \ + config.h \ + config.h.in \ + config.log \ + config.status \ + configure \ + stamp-h.in \ + stamp-h1 \ + $(NULL) + +PKG_FILES = \ + ChangeLog \ + README \ + cmd/synergyc/synergyc \ + cmd/synergys/synergys \ + examples/synergy.conf \ + $(NULL) +PKG_DOC_FILES = \ + doc/PORTING \ + doc/*.html \ + doc/*.css \ + $(NULL) +PKG_PROG_FILES = \ + synergyc \ + synergys \ + $(NULL) + +# build doxygen documentation +doxygen: + doxygen doc/doxygen.cfg + +# build RPMs +RPMTOPDIR=/var/tmp/@PACKAGE@-@VERSION@ +dist-rpm: dist + rm -rf $(RPMTOPDIR) + mkdir $(RPMTOPDIR) + (cd $(RPMTOPDIR); mkdir BUILD SOURCES SPECS SRPMS RPMS) + cp @PACKAGE@-@VERSION@.tar.gz $(RPMTOPDIR)/SOURCES + rpm --define '_topdir $(RPMTOPDIR)' -ba dist/rpm/synergy.spec && \ + mv -f $(RPMTOPDIR)/SRPMS/*.rpm . && \ + mv -f $(RPMTOPDIR)/RPMS/*/*.rpm . && \ + rm -rf $(RPMTOPDIR) + +# build zip +# FIXME -- have automake generate this rule for us +dist-zip: distdir + zip -r $(distdir).zip $(distdir) + -chmod -R a+w $(distdir) >/dev/null 2>&1; rm -rf $(distdir) + +# build binary package. owner/group of packaged files will be +# owner/group of user running make. +PKGTOPDIR=/var/tmp/@PACKAGE@-@VERSION@ +dist-pkg: all + rm -rf $(PKGTOPDIR) + mkdir $(PKGTOPDIR) + mkdir $(PKGTOPDIR)/@PACKAGE@-@VERSION@ + mkdir $(PKGTOPDIR)/@PACKAGE@-@VERSION@/doc + cp $(PKG_FILES) $(PKGTOPDIR)/@PACKAGE@-@VERSION@ + cp $(PKG_DOC_FILES) $(PKGTOPDIR)/@PACKAGE@-@VERSION@/doc + (cd $(PKGTOPDIR)/@PACKAGE@-@VERSION@; \ + chmod 644 *; \ + chmod 755 doc $(PKG_PROG_FILES); \ + strip $(PKG_PROG_FILES) ) + type=`uname -s -m | tr '[A-Z] ' '[a-z].'`; \ + (cd $(PKGTOPDIR); tar cf - @PACKAGE@-@VERSION@ | \ + gzip - ) > @PACKAGE@-@VERSION@-1.$${type}.tar.gz && \ + rm -rf $(PKGTOPDIR) diff --git a/Makefile.win b/Makefile.win new file mode 100644 index 00000000..d232b58a --- /dev/null +++ b/Makefile.win @@ -0,0 +1,145 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +# Name of this file for recursive make +MAKEFILE = Makefile.win + +# Default build is release is NODEBUG is defined, debug otherwise. +!if !DEFINED(DEBUG) +NODEBUG = 1 +!else +!undef NODEBUG +!endif + +# Build all by default +default: all + +# Redefine implicit rule suffixes +.SUFFIXES: +.SUFFIXES: .cpp .rc .obj + +# Shut up +.SILENT: + +# Include system macros +#APPVER = 5.0 +#TARGETOS = WINNT +!include + +# Be explicit about C++ compiler +cpp = $(cc) +cppdebug = $(cdebug) +cppflags = $(cflags) +cppvarsmt = $(cvarsmt) + +# Library tool options +ildebug = +ilflags = /nologo + +# Handy macro for defining list macros +NULL = + +# System commands +ECHO = echo +MKDIR = mkdir +RM = del /f +RMR = rmdir /q /s + +# Local build utilities +UTIL_DIR = win32util +AUTODEP = "$(UTIL_DIR)\autodep.exe" + +# Destination for intermediate build targets +BUILD_DIR = build +BUILD_DEBUG_DIR = $(BUILD_DIR)\Debug +BUILD_RELEASE_DIR = $(BUILD_DIR)\Release +!if DEFINED(NODEBUG) +BUILD_DST = $(BUILD_RELEASE_DIR) +!else +BUILD_DST = $(BUILD_DEBUG_DIR) +!endif + +# Compiler argument changes +cflags = $(cflags:-W3=-W4) /WX +cflags = $(cflags) -D_CRT_SECURE_NO_DEPRECATE +cflags = $(cflags) /GR +!if !DEFINED(OLDCOMPILER) +cflags = $(cflags) /EHsc +!else +cflags = $(cflags) /GX +!endif +!if !DEFINED(NODEBUG) +!if !DEFINED(OLDCOMPILER) +cdebug = $(cdebug) /RTC1 +!else +cdebug = $(cdebug) /GZ +!endif +!endif + +# Initialize variables for library and program makefiles +C_FILES = +CPP_FILES = +OBJ_FILES = +LIB_FILES = +PROGRAMS = +OPTPROGRAMS = $(AUTODEP) + +# Include subdirectory makefiles +!include lib\common\$(MAKEFILE) +!include lib\arch\$(MAKEFILE) +!include lib\base\$(MAKEFILE) +!include lib\mt\$(MAKEFILE) +!include lib\io\$(MAKEFILE) +!include lib\net\$(MAKEFILE) +!include lib\synergy\$(MAKEFILE) +!include lib\platform\$(MAKEFILE) +!include lib\client\$(MAKEFILE) +!include lib\server\$(MAKEFILE) +!include cmd\synergyc\$(MAKEFILE) +!include cmd\synergys\$(MAKEFILE) +!include cmd\launcher\$(MAKEFILE) +!include dist\nullsoft\$(MAKEFILE) + +# Collect library and program variables +INTERMEDIATES = $(OBJ_FILES) $(AUTODEP:.exe=.obj) +TARGETS = $(LIB_FILES) $(PROGRAMS) +OPTTARGETS = $(OPTPROGRAMS) + +# Build release by reinvoking make with NODEBUG defined +release: + @$(MAKE) /nologo /f $(MAKEFILE) NODEBUG=1 + +# Build debug by reinvoking make with DEBUG defined +debug: + @$(MAKE) /nologo /f $(MAKEFILE) DEBUG=1 + +# Build all targets +all: $(TARGETS) + +# Clean intermediate targets +clean: + -$(RMR) $(BUILD_DEBUG_DIR) + -$(RMR) $(BUILD_RELEASE_DIR) + +# Clean all targets +clobber: clean + -$(RMR) $(BUILD_DIR) + +# Utility command build rules +$(AUTODEP): $(AUTODEP:.exe=.cpp) +!if DEFINED(NODEBUG) + @$(ECHO) Build $(@F) + $(cpp) $(cppdebug) $(cppflags) $(cppvars) /Fo"$(**:.cpp=.obj)" $** + $(link) $(ldebug) $(conflags) -out:$@ $(**:.cpp=.obj) $(conlibs) +!else + @$(MAKE) /nologo /f $(MAKEFILE) NODEBUG=1 $@ +!endif diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..e9aa7916 --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +See doc/news.html. diff --git a/README b/README new file mode 100644 index 00000000..aa6d7ea3 --- /dev/null +++ b/README @@ -0,0 +1,21 @@ +Synergy +======= + +synergy: [noun] a mutually advantageous conjunction of distinct elements + +Synergy lets you easily share a single mouse and keyboard between +multiple computers with different operating systems, each with its +own display, without special hardware. It's intended for users +with multiple computers on their desk since each system uses its +own display. + +Redirecting the mouse and keyboard is as simple as moving the mouse +off the edge of your screen. Synergy also merges the clipboards of +all the systems into one, allowing cut-and-paste between systems. +Furthermore, it synchronizes screen savers so they all start and stop +together and, if screen locking is enabled, only one screen requires +a password to unlock them all. + +Synergy is open source and released under the GNU Public License (GPL). + +Please see doc/index.html for more information. diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 00000000..29163d5a --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,571 @@ +dnl synergy -- mouse and keyboard sharing utility +dnl Copyright (C) 2002 Chris Schoeneman +dnl +dnl This package is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU General Public License +dnl found in the file COPYING that should have accompanied this file. +dnl +dnl This package is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +dnl GNU General Public License for more details. + +AC_DEFUN([ACX_CHECK_SOCKLEN_T], [ + AC_MSG_CHECKING([for socklen_t]) + AC_TRY_COMPILE([ + #include + #include + ], + [socklen_t len;],[acx_socklen_t_ok=yes],[acx_socklen_t_ok=no]) + AC_MSG_RESULT($acx_socklen_t_ok) + if test x"$acx_socklen_t_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_SOCKLEN_T,1,[Define if your compiler defines socklen_t.]),[$1]) + : + else + acx_socklen_t_ok=no + $2 + fi +])dnl ACX_CHECK_SOCKLEN_T + +# HP-UX defines socklen_t but doesn't use it in arg 3 for accept(). +AC_DEFUN([ACX_FUNC_ACCEPT], [ + AC_MSG_CHECKING([for type of arg 3 for accept]) + acx_accept_socklen_t_arg3=int + if test x"$acx_socklen_t_ok" = xyes; then + AC_TRY_COMPILE([ + #include + #include + ], + [struct sockaddr addr; socklen_t len; accept(0, &addr, &len);], + [acx_accept_socklen_t_arg3=socklen_t], + [acx_accept_socklen_t_arg3=int]) + fi + AC_MSG_RESULT($acx_accept_socklen_t_arg3) + AC_DEFINE_UNQUOTED(ACCEPT_TYPE_ARG3,$acx_accept_socklen_t_arg3,[Define to the base type of arg 3 for `accept'.]) +])dnl ACX_FUNC_ACCEPT + +AC_DEFUN([ACX_CHECK_CXX], [ + AC_MSG_CHECKING([if g++ defines correct C++ macro]) + AC_TRY_COMPILE(, [ + #if defined(_LANGUAGE_C) && !defined(_LANGUAGE_C_PLUS_PLUS) + #error wrong macro + #endif],[acx_cxx_macro_ok=yes],[acx_cxx_macro_ok=no]) + AC_MSG_RESULT($acx_cxx_macro_ok) + if test x"$acx_cxx_macro_ok" = xyes; then + SYNERGY_CXXFLAGS="" + else + SYNERGY_CXXFLAGS="-U_LANGUAGE_C -D_LANGUAGE_C_PLUS_PLUS" + fi +])dnl ACX_CHECK_CXX + +AC_DEFUN([ACX_CHECK_CXX_BOOL], [ + AC_MSG_CHECKING([for bool support]) + AC_TRY_COMPILE(, [bool t = true, f = false;], + [acx_cxx_bool_ok=yes],[acx_cxx_bool_ok=no]) + AC_MSG_RESULT($acx_cxx_bool_ok) + if test x"$acx_cxx_bool_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_CXX_BOOL,1,[Define if your compiler has bool support.]),[$1]) + : + else + acx_cxx_bool_ok=no + $2 + fi +])dnl ACX_CHECK_CXX_BOOL + +AC_DEFUN([ACX_CHECK_CXX_EXCEPTIONS], [ + AC_MSG_CHECKING([for exception support]) + AC_TRY_COMPILE(, [try{throw int(4);}catch(int){throw;}catch(...){}], + [acx_cxx_exception_ok=yes],[acx_cxx_exception_ok=no]) + AC_MSG_RESULT($acx_cxx_exception_ok) + if test x"$acx_cxx_exception_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_CXX_EXCEPTIONS,1,[Define if your compiler has exceptions support.]),[$1]) + : + else + acx_cxx_exception_ok=no + $2 + fi +])dnl ACX_CHECK_CXX_EXCEPTIONS + +AC_DEFUN([ACX_CHECK_CXX_CASTS], [ + AC_MSG_CHECKING([for C++ cast support]) + AC_TRY_COMPILE(, [const char* f="a";const_cast(f); + reinterpret_cast(f);static_cast(4.5);], + [acx_cxx_cast_ok=yes],[acx_cxx_cast_ok=no]) + AC_MSG_RESULT($acx_cxx_cast_ok) + if test x"$acx_cxx_cast_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_CXX_CASTS,1,[Define if your compiler has C++ cast support.]),[$1]) + : + else + acx_cxx_cast_ok=no + $2 + fi +])dnl ACX_CHECK_CXX_CASTS + +AC_DEFUN([ACX_CHECK_CXX_MUTABLE], [ + AC_MSG_CHECKING([for mutable support]) + AC_TRY_COMPILE(, [struct A{mutable int b;void f() const {b=0;}}; + A a;a.f();],[acx_cxx_mutable_ok=yes],[acx_cxx_mutable_ok=no]) + AC_MSG_RESULT($acx_cxx_mutable_ok) + if test x"$acx_cxx_mutable_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_CXX_MUTABLE,1,[Define if your compiler has mutable support.]),[$1]) + : + else + acx_cxx_mutable_ok=no + $2 + fi +])dnl ACX_CHECK_CXX_MUTABLE + +AC_DEFUN([ACX_CHECK_CXX_STDLIB], [ + AC_MSG_CHECKING([for C++ standard library]) + AC_TRY_LINK([#include ], [std::set a; a.insert(3);], + [acx_cxx_stdlib_ok=yes],[acx_cxx_stdlib_ok=no]) + AC_MSG_RESULT($acx_cxx_stdlib_ok) + if test x"$acx_cxx_stdlib_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_CXX_STDLIB,1,[Define if your compiler has standard C++ library support.]),[$1]) + : + else + acx_cxx_stdlib_ok=no + $2 + fi +])dnl ACX_CHECK_CXX_STDLIB + +AC_DEFUN([ACX_CHECK_GETPWUID_R], [ + AC_MSG_CHECKING([for working getpwuid_r]) + AC_TRY_LINK([#include ], + [char buffer[4096]; struct passwd pwd, *pwdp; + getpwuid_r(0, &pwd, buffer, sizeof(buffer), &pwdp);], + acx_getpwuid_r_ok=yes, acx_getpwuid_r_ok=no) + AC_MSG_RESULT($acx_getpwuid_r_ok) + if test x"$acx_getpwuid_r_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_GETPWUID_R,1,[Define if you have a working \`getpwuid_r\' function.]),[$1]) + : + else + acx_getpwuid_r_ok=no + $2 + fi +])dnl ACX_CHECK_GETPWUID_R + +AC_DEFUN([ACX_CHECK_POLL], [ + AC_MSG_CHECKING([for poll]) + AC_TRY_LINK([#include ], + [#if defined(_POLL_EMUL_H_) + #error emulated poll + #endif + struct pollfd ufds[] = { 0, POLLIN, 0 }; poll(ufds, 1, 10);], + acx_poll_ok=yes, acx_poll_ok=no) + AC_MSG_RESULT($acx_poll_ok) + if test x"$acx_poll_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_POLL,1,[Define if you have the \`poll\' function.]),[$1]) + : + else + acx_poll_ok=no + $2 + fi +])dnl ACX_CHECK_POLL + +dnl See if we need extra libraries for nanosleep +AC_DEFUN([ACX_CHECK_NANOSLEEP], [ + acx_nanosleep_ok=no + acx_nanosleep_list="" + + dnl check if user has set NANOSLEEP_LIBS + save_user_NANOSLEEP_LIBS="$NANOSLEEP_LIBS" + if test x"$NANOSLEEP_LIBS" != x; then + acx_nanosleep_list=user + fi + + dnl check various libraries (including no extra libraries) for + dnl nanosleep. `none' should appear first. + acx_nanosleep_list="none $acx_nanosleep_list rt" + for flag in $acx_nanosleep_list; do + case $flag in + none) + AC_MSG_CHECKING([for nanosleep]) + NANOSLEEP_LIBS="" + ;; + + user) + AC_MSG_CHECKING([for nanosleep in $save_user_NANOSLEEP_LIBS]) + NANOSLEEP_LIBS="$save_user_NANOSLEEP_LIBS" + ;; + + *) + AC_MSG_CHECKING([for nanosleep in -l$flag]) + NANOSLEEP_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + LIBS="$NANOSLEEP_LIBS $LIBS" + AC_TRY_LINK([#include ], + [struct timespec t = { 1, 1000 }; nanosleep(&t, NULL);], + acx_nanosleep_ok=yes, acx_nanosleep_ok=no) + LIBS="$save_LIBS" + AC_MSG_RESULT($acx_nanosleep_ok) + if test x"$acx_nanosleep_ok" = xyes; then + break; + fi + NANOSLEEP_LIBS="" + done + + AC_SUBST(NANOSLEEP_LIBS) + + # execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: + if test x"$acx_nanosleep_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_NANOSLEEP,1,[Define if you have the \`nanosleep\' function.]),[$1]) + : + else + acx_nanosleep_ok=no + $2 + fi +])dnl ACX_CHECK_NANOSLEEP + +dnl See if we need extra libraries for inet_aton +AC_DEFUN([ACX_CHECK_INET_ATON], [ + acx_inet_aton_ok=no + acx_inet_aton_list="" + + dnl check if user has set INET_ATON_LIBS + save_user_INET_ATON_LIBS="$INET_ATON_LIBS" + if test x"$INET_ATON_LIBS" != x; then + acx_inet_aton_list=user + fi + + dnl check various libraries (including no extra libraries) for + dnl inet_aton. `none' should appear first. + acx_inet_aton_list="none $acx_inet_aton_list resolv" + for flag in $acx_inet_aton_list; do + case $flag in + none) + AC_MSG_CHECKING([for inet_aton]) + INET_ATON_LIBS="" + ;; + + user) + AC_MSG_CHECKING([for inet_aton in $save_user_INET_ATON_LIBS]) + INET_ATON_LIBS="$save_user_INET_ATON_LIBS" + ;; + + *) + AC_MSG_CHECKING([for inet_aton in -l$flag]) + INET_ATON_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + LIBS="$INET_ATON_LIBS $LIBS" + AC_TRY_LINK([#include + #include + #include + #include ], + [struct in_addr addr; inet_aton("foo.bar", &addr);], + acx_inet_aton_ok=yes, acx_inet_aton_ok=no) + LIBS="$save_LIBS" + AC_MSG_RESULT($acx_inet_aton_ok) + if test x"$acx_inet_aton_ok" = xyes; then + AC_DEFINE(HAVE_INET_ATON,1,[Define if you have the \`inet_aton\' function.]) + break; + fi + INET_ATON_LIBS="" + done + + AC_SUBST(INET_ATON_LIBS) +])dnl ACX_CHECK_INET_ATON + +dnl The following macros are from http://www.gnu.org/software/ac-archive/ +dnl which distributes them under the following license: +dnl +dnl Every Autoconf macro presented on this web site is free software; you can +dnl redistribute it and/or modify it under the terms of the GNU General +dnl Public License as published by the Free Software Foundation; either +dnl version 2, or (at your option) any later version. +dnl +dnl They are distributed in the hope that they will be useful, but WITHOUT +dnl ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +dnl FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +dnl more details. (You should have received a copy of the GNU General Public +dnl License along with this program; if not, write to the Free Software +dnl Foundation, Inc., 59 Temple Place -- Suite 330, Boston, MA 02111-1307, +dnl USA.) +dnl +dnl As a special exception, the Free Software Foundation gives unlimited +dnl permission to copy, distribute and modify the configure scripts that are +dnl the output of Autoconf. You need not follow the terms of the GNU General +dnl Public License when using or distributing such scripts, even though +dnl portions of the text of Autoconf appear in them. The GNU General Public +dnl License (GPL) does govern all other use of the material that constitutes +dnl the Autoconf program. +dnl +dnl Certain portions of the Autoconf source text are designed to be copied +dnl (in certain cases, depending on the input) into the output of Autoconf. +dnl We call these the "data" portions. The rest of the Autoconf source text +dnl consists of comments plus executable code that decides which of the data +dnl portions to output in any given case. We call these comments and +dnl executable code the "non-data" portions. Autoconf never copies any of the +dnl non-data portions into its output. +dnl +dnl This special exception to the GPL applies to versions of Autoconf +dnl released by the Free Software Foundation. When you make and distribute a +dnl modified version of Autoconf, you may extend this special exception to +dnl the GPL to apply to your modified version as well, *unless* your modified +dnl version has the potential to copy into its output some of the text that +dnl was the non-data portion of the version that you started with. (In other +dnl words, unless your change moves or copies text from the non-data portions +dnl to the data portions.) If your modification has such potential, you must +dnl delete any notice of this special exception to the GPL from your modified +dnl version + +AC_DEFUN([ACX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_SAVE +AC_LANG_C +acx_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes) + AC_MSG_RESULT($acx_pthread_ok) + if test x"$acx_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case "${host_cpu}-${host_os}" in + *solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthread or + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + acx_pthread_flags="-pthread -pthreads pthread -mt $acx_pthread_flags" + ;; +esac + +if test x"$acx_pthread_ok" = xno; then +for flag in $acx_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no) + if test x"$acx_pthread_config" = xno; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [acx_pthread_ok=yes]) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT($acx_pthread_ok) + if test "x$acx_pthread_ok" = xyes; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$acx_pthread_ok" = xyes; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_MSG_CHECKING([for joinable pthread attribute]) + attr_name=unknown + for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_TRY_LINK([#include ], [int attr=$attr;], + [attr_name=$attr; break]) + done + AC_MSG_RESULT($attr_name) + if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then + AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case "${host_cpu}-${host_os}" in + *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";; + *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";; + esac + AC_MSG_RESULT(${flag}) + if test "x$flag" != xno; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + # Detect POSIX sigwait() + AC_MSG_CHECKING([for POSIX sigwait]) + AC_TRY_LINK([#include + #include ], + [sigset_t sigset; int signal; sigwait(&sigset, &signal);], + ok=yes, ok=unknown) + if test x"$ok" = xunknown; then + save_CFLAGS2="$CFLAGS" + CFLAGS="$CFLAGS -D_POSIX_PTHREAD_SEMANTICS" + AC_TRY_LINK([#include + #include ], + [sigset_t sigset; int signal; sigwait(&sigset, &signal);], + ok=-D_POSIX_PTHREAD_SEMANTICS, ok=no) + CFLAGS="$save_CFLAGS2" + fi + AC_MSG_RESULT(${ok}) + if test x"$ok" != xno; then + AC_DEFINE(HAVE_POSIX_SIGWAIT,1,[Define if you have a POSIX \`sigwait\' function.]) + if test x"$ok" != xyes; then + PTHREAD_CFLAGS="$ok $PTHREAD_CFLAGS" + fi + fi + + # Detect pthread signal functions + AC_MSG_CHECKING([for pthread signal functions]) + AC_TRY_LINK([#include + #include ], + [pthread_kill(pthread_self(), SIGTERM);], + ok=yes, ok=no) + AC_MSG_RESULT(${ok}) + if test x"$ok" = xyes; then + AC_DEFINE(HAVE_PTHREAD_SIGNAL,1,[Define if you have \`pthread_sigmask\' and \`pthread_kill\' functions.]) + fi + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: must compile with cc_r + AC_CHECK_PROG(PTHREAD_CC, cc_r, cc_r, ${CC}) +else + PTHREAD_CC="$CC" +fi + +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_CC) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test x"$acx_pthread_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) + : +else + acx_pthread_ok=no + $2 +fi +AC_LANG_RESTORE +])dnl ACX_PTHREAD + +dnl enable maximum compiler warnings. must ignore unknown pragmas to +dnl build on solaris. +dnl we only know how to do this for g++ +AC_DEFUN([ACX_CXX_WARNINGS], [ + AC_MSG_CHECKING([for C++ compiler warning flags]) + if test "$GXX" = "yes"; then + acx_cxx_warnings="-Wall -Wno-unknown-pragmas" + fi + if test -n "$acx_cxx_warnings"; then + CXXFLAGS="$CXXFLAGS $acx_cxx_warnings" + else + acx_cxx_warnings="unknown" + fi + AC_MSG_RESULT($acx_cxx_warnings) +])dnl ACX_CXX_WARNINGS + +dnl enable compiler warnings are errors +dnl we only know how to do this for g++ +AC_DEFUN([ACX_CXX_WARNINGS_ARE_ERRORS], [ + AC_MSG_CHECKING([for C++ compiler warning are errors flags]) + if test "$GXX" = "yes"; then + acx_cxx_warnings_are_errors="-Werror" + fi + if test -n "$acx_cxx_warnings_are_errors"; then + CXXFLAGS="$CXXFLAGS $acx_cxx_warnings_are_errors" + else + acx_cxx_warnings_are_errors="unknown" + fi + AC_MSG_RESULT($acx_cxx_warnings_are_errors) +])dnl ACX_CXX_WARNINGS_ARE_ERRORS diff --git a/cmd/Makefile.am b/cmd/Makefile.am new file mode 100644 index 00000000..1e43b156 --- /dev/null +++ b/cmd/Makefile.am @@ -0,0 +1,27 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +SUBDIRS = \ + launcher \ + synergyc \ + synergys \ + $(NULL) + +EXTRA_DIST = \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/cmd/launcher/CAddScreen.cpp b/cmd/launcher/CAddScreen.cpp new file mode 100644 index 00000000..5a876b77 --- /dev/null +++ b/cmd/launcher/CAddScreen.cpp @@ -0,0 +1,427 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "KeyTypes.h" +#include "OptionTypes.h" +#include "ProtocolTypes.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CAddScreen.h" +#include "LaunchUtil.h" +#include "resource.h" + +struct CModifierInfo { +public: + int m_ctrlID; + const char* m_name; + KeyModifierID m_modifierID; + OptionID m_optionID; +}; + +static const CModifierInfo s_modifiers[] = { + { IDC_ADD_MOD_SHIFT, "Shift", + kKeyModifierIDShift, kOptionModifierMapForShift }, + { IDC_ADD_MOD_CTRL, "Ctrl", + kKeyModifierIDControl, kOptionModifierMapForControl }, + { IDC_ADD_MOD_ALT, "Alt", + kKeyModifierIDAlt, kOptionModifierMapForAlt }, + { IDC_ADD_MOD_META, "Meta", + kKeyModifierIDMeta, kOptionModifierMapForMeta }, + { IDC_ADD_MOD_SUPER, "Super", + kKeyModifierIDSuper, kOptionModifierMapForSuper } +}; + +static const KeyModifierID baseModifier = kKeyModifierIDShift; + +// +// CAddScreen +// + +CAddScreen* CAddScreen::s_singleton = NULL; + +CAddScreen::CAddScreen(HWND parent, CConfig* config, const CString& name) : + m_parent(parent), + m_config(config), + m_name(name) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CAddScreen::~CAddScreen() +{ + s_singleton = NULL; +} + +bool +CAddScreen::doModal() +{ + // do dialog + return (DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_ADD), + m_parent, (DLGPROC)dlgProc, (LPARAM)this) != 0); +} + +CString +CAddScreen::getName() const +{ + return m_name; +} + +void +CAddScreen::init(HWND hwnd) +{ + // set title + CString title; + if (m_name.empty()) { + title = getString(IDS_ADD_SCREEN); + } + else { + title = CStringUtil::format( + getString(IDS_EDIT_SCREEN).c_str(), + m_name.c_str()); + } + SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)title.c_str()); + + // fill in screen name + HWND child = getItem(hwnd, IDC_ADD_SCREEN_NAME_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)m_name.c_str()); + + // fill in aliases + CString aliases; + for (CConfig::all_const_iterator index = m_config->beginAll(); + index != m_config->endAll(); ++index) { + if (CStringUtil::CaselessCmp::equal(index->second, m_name) && + !CStringUtil::CaselessCmp::equal(index->second, index->first)) { + if (!aliases.empty()) { + aliases += "\r\n"; + } + aliases += index->first; + } + } + child = getItem(hwnd, IDC_ADD_ALIASES_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)aliases.c_str()); + + // set options + CConfig::CScreenOptions options; + getOptions(options); + CConfig::CScreenOptions::const_iterator index; + child = getItem(hwnd, IDC_ADD_HD_CAPS_CHECK); + index = options.find(kOptionHalfDuplexCapsLock); + setItemChecked(child, (index != options.end() && index->second != 0)); + child = getItem(hwnd, IDC_ADD_HD_NUM_CHECK); + index = options.find(kOptionHalfDuplexNumLock); + setItemChecked(child, (index != options.end() && index->second != 0)); + child = getItem(hwnd, IDC_ADD_HD_SCROLL_CHECK); + index = options.find(kOptionHalfDuplexScrollLock); + setItemChecked(child, (index != options.end() && index->second != 0)); + + // modifier options + for (UInt32 i = 0; i < sizeof(s_modifiers) / + sizeof(s_modifiers[0]); ++i) { + child = getItem(hwnd, s_modifiers[i].m_ctrlID); + + // fill in options + for (UInt32 j = 0; j < sizeof(s_modifiers) / + sizeof(s_modifiers[0]); ++j) { + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)s_modifiers[j].m_name); + } + + // choose current value + index = options.find(s_modifiers[i].m_optionID); + KeyModifierID id = s_modifiers[i].m_modifierID; + if (index != options.end()) { + id = index->second; + } + SendMessage(child, CB_SETCURSEL, id - baseModifier, 0); + } + + // dead corners + UInt32 corners = 0; + index = options.find(kOptionScreenSwitchCorners); + if (index != options.end()) { + corners = index->second; + } + child = getItem(hwnd, IDC_ADD_DC_TOP_LEFT); + setItemChecked(child, (corners & kTopLeftMask) != 0); + child = getItem(hwnd, IDC_ADD_DC_TOP_RIGHT); + setItemChecked(child, (corners & kTopRightMask) != 0); + child = getItem(hwnd, IDC_ADD_DC_BOTTOM_LEFT); + setItemChecked(child, (corners & kBottomLeftMask) != 0); + child = getItem(hwnd, IDC_ADD_DC_BOTTOM_RIGHT); + setItemChecked(child, (corners & kBottomRightMask) != 0); + index = options.find(kOptionScreenSwitchCornerSize); + SInt32 size = 0; + if (index != options.end()) { + size = index->second; + } + char buffer[20]; + sprintf(buffer, "%d", size); + child = getItem(hwnd, IDC_ADD_DC_SIZE); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)buffer); +} + +bool +CAddScreen::save(HWND hwnd) +{ + // get the old aliases and options + CStringList oldAliases; + getAliases(oldAliases); + CConfig::CScreenOptions options; + getOptions(options); + + // extract name and aliases + CString newName; + HWND child = getItem(hwnd, IDC_ADD_SCREEN_NAME_EDIT); + newName = getWindowText(child); + CStringList newAliases; + child = getItem(hwnd, IDC_ADD_ALIASES_EDIT); + tokenize(newAliases, getWindowText(child)); + + // name must be valid + if (!m_config->isValidScreenName(newName)) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_SCREEN_NAME).c_str(), + newName.c_str())); + return false; + } + + // aliases must be valid + for (CStringList::const_iterator index = newAliases.begin(); + index != newAliases.end(); ++index) { + if (!m_config->isValidScreenName(*index)) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_SCREEN_NAME).c_str(), + index->c_str())); + return false; + } + } + + // new name may not be in the new alias list + if (isNameInList(newAliases, newName)) { + showError(hwnd, CStringUtil::format( + getString(IDS_SCREEN_NAME_IS_ALIAS).c_str(), + newName.c_str())); + return false; + } + + // name must not exist in config but allow same name. also + // allow name if it exists in the old alias list but not the + // new one. + if (m_config->isScreen(newName) && + !CStringUtil::CaselessCmp::equal(newName, m_name) && + !isNameInList(oldAliases, newName)) { + showError(hwnd, CStringUtil::format( + getString(IDS_DUPLICATE_SCREEN_NAME).c_str(), + newName.c_str())); + return false; + } + + // aliases must not exist in config but allow same aliases and + // allow an alias to be the old name. + for (CStringList::const_iterator index = newAliases.begin(); + index != newAliases.end(); ++index) { + if (m_config->isScreen(*index) && + !CStringUtil::CaselessCmp::equal(*index, m_name) && + !isNameInList(oldAliases, *index)) { + showError(hwnd, CStringUtil::format( + getString(IDS_DUPLICATE_SCREEN_NAME).c_str(), + index->c_str())); + return false; + } + } + + // dead corner size must be non-negative + child = getItem(hwnd, IDC_ADD_DC_SIZE); + CString valueString = getWindowText(child); + int cornerSize = atoi(valueString.c_str()); + if (cornerSize < 0) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_CORNER_SIZE).c_str(), + valueString.c_str())); + SetFocus(child); + return false; + } + + // collect options + child = getItem(hwnd, IDC_ADD_HD_CAPS_CHECK); + if (isItemChecked(child)) { + options[kOptionHalfDuplexCapsLock] = 1; + } + else { + options.erase(kOptionHalfDuplexCapsLock); + } + child = getItem(hwnd, IDC_ADD_HD_NUM_CHECK); + if (isItemChecked(child)) { + options[kOptionHalfDuplexNumLock] = 1; + } + else { + options.erase(kOptionHalfDuplexNumLock); + } + child = getItem(hwnd, IDC_ADD_HD_SCROLL_CHECK); + if (isItemChecked(child)) { + options[kOptionHalfDuplexScrollLock] = 1; + } + else { + options.erase(kOptionHalfDuplexScrollLock); + } + + // save modifier options + for (UInt32 i = 0; i < sizeof(s_modifiers) / + sizeof(s_modifiers[0]); ++i) { + child = getItem(hwnd, s_modifiers[i].m_ctrlID); + KeyModifierID id = static_cast( + SendMessage(child, CB_GETCURSEL, 0, 0) + + baseModifier); + if (id != s_modifiers[i].m_modifierID) { + options[s_modifiers[i].m_optionID] = id; + } + else { + options.erase(s_modifiers[i].m_optionID); + } + } + + // save dead corner options + UInt32 corners = 0; + if (isItemChecked(getItem(hwnd, IDC_ADD_DC_TOP_LEFT))) { + corners |= kTopLeftMask; + } + if (isItemChecked(getItem(hwnd, IDC_ADD_DC_TOP_RIGHT))) { + corners |= kTopRightMask; + } + if (isItemChecked(getItem(hwnd, IDC_ADD_DC_BOTTOM_LEFT))) { + corners |= kBottomLeftMask; + } + if (isItemChecked(getItem(hwnd, IDC_ADD_DC_BOTTOM_RIGHT))) { + corners |= kBottomRightMask; + } + options[kOptionScreenSwitchCorners] = corners; + options[kOptionScreenSwitchCornerSize] = cornerSize; + + // save new data to config + if (m_name.empty()) { + // added screen + m_config->addScreen(newName); + } + else { + // edited screen + m_config->removeAliases(m_name); + m_config->removeOptions(m_name); + m_config->renameScreen(m_name, newName); + } + m_name = newName; + for (CStringList::const_iterator index = newAliases.begin(); + index != newAliases.end(); ++index) { + m_config->addAlias(m_name, *index); + } + for (CConfig::CScreenOptions::const_iterator + index = options.begin(); + index != options.end(); ++index) { + m_config->addOption(m_name, index->first, index->second); + } + + return true; +} + +BOOL +CAddScreen::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + init(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (save(hwnd)) { + EndDialog(hwnd, 1); + } + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CAddScreen::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} + +void +CAddScreen::getAliases(CStringList& aliases) const +{ + for (CConfig::all_const_iterator index = m_config->beginAll(); + index != m_config->endAll(); ++index) { + if (CStringUtil::CaselessCmp::equal(index->second, m_name) && + !CStringUtil::CaselessCmp::equal(index->second, index->first)) { + aliases.push_back(index->first); + } + } +} + +void +CAddScreen::getOptions(CConfig::CScreenOptions& optionsOut) const +{ + const CConfig::CScreenOptions* options = m_config->getOptions(m_name); + if (options == NULL) { + optionsOut = CConfig::CScreenOptions(); + } + else { + optionsOut = *options; + } +} + +void +CAddScreen::tokenize(CStringList& tokens, const CString& src) +{ + // find first non-whitespace + CString::size_type x = src.find_first_not_of(" \t\r\n"); + if (x == CString::npos) { + return; + } + + // find next whitespace + do { + CString::size_type y = src.find_first_of(" \t\r\n", x); + if (y == CString::npos) { + y = src.size(); + } + tokens.push_back(src.substr(x, y - x)); + x = src.find_first_not_of(" \t\r\n", y); + } while (x != CString::npos); +} + +bool +CAddScreen::isNameInList(const CStringList& names, const CString& name) +{ + for (CStringList::const_iterator index = names.begin(); + index != names.end(); ++index) { + if (CStringUtil::CaselessCmp::equal(name, *index)) { + return true; + } + } + return false; +} diff --git a/cmd/launcher/CAddScreen.h b/cmd/launcher/CAddScreen.h new file mode 100644 index 00000000..e926c498 --- /dev/null +++ b/cmd/launcher/CAddScreen.h @@ -0,0 +1,74 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CADDSCREEN_H +#define CADDSCREEN_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +class CConfig; + +//! Add screen dialog for Microsoft Windows launcher +class CAddScreen { +public: + CAddScreen(HWND parent, CConfig*, const CString& name); + ~CAddScreen(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. Return + \c true if the user accepted the changes, false otherwise. + */ + bool doModal(); + + //@} + //! @name accessors + //@{ + + CString getName() const; + + //@} + +private: + typedef std::vector CStringList; + + void getAliases(CStringList&) const; + void getOptions(CConfig::CScreenOptions&) const; + + static void tokenize(CStringList& tokens, const CString& src); + static bool isNameInList(const CStringList& tokens, + const CString& src); + + void init(HWND hwnd); + bool save(HWND hwnd); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CAddScreen* s_singleton; + + HWND m_parent; + CConfig* m_config; + CString m_name; +}; + +#endif diff --git a/cmd/launcher/CAdvancedOptions.cpp b/cmd/launcher/CAdvancedOptions.cpp new file mode 100644 index 00000000..c1ea83ef --- /dev/null +++ b/cmd/launcher/CAdvancedOptions.cpp @@ -0,0 +1,269 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "ProtocolTypes.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CArchMiscWindows.h" +#include "CAdvancedOptions.h" +#include "LaunchUtil.h" +#include "XArch.h" +#include "resource.h" + +// +// CAdvancedOptions +// + +CAdvancedOptions* CAdvancedOptions::s_singleton = NULL; + +CAdvancedOptions::CAdvancedOptions(HWND parent, CConfig* config) : + m_parent(parent), + m_config(config), + m_isClient(false), + m_screenName(ARCH->getHostName()), + m_port(kDefaultPort), + m_interface() +{ + assert(s_singleton == NULL); + s_singleton = this; + init(); +} + +CAdvancedOptions::~CAdvancedOptions() +{ + s_singleton = NULL; +} + +void +CAdvancedOptions::doModal(bool isClient) +{ + // save state + m_isClient = isClient; + + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_ADVANCED_OPTIONS), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); +} + +CString +CAdvancedOptions::getScreenName() const +{ + return m_screenName; +} + +int +CAdvancedOptions::getPort() const +{ + return m_port; +} + +CString +CAdvancedOptions::getInterface() const +{ + return m_interface; +} + +CString +CAdvancedOptions::getCommandLine(bool isClient, const CString& serverName) const +{ + CString cmdLine; + + // screen name + if (!m_screenName.empty()) { + cmdLine += " --name "; + cmdLine += m_screenName; + } + + // port + char portString[20]; + sprintf(portString, "%d", m_port); + if (isClient) { + cmdLine += " "; + cmdLine += serverName; + cmdLine += ":"; + cmdLine += portString; + } + else { + cmdLine += " --address "; + if (!m_interface.empty()) { + cmdLine += m_interface; + } + cmdLine += ":"; + cmdLine += portString; + } + + return cmdLine; +} + +void +CAdvancedOptions::init() +{ + // get values from registry + HKEY key = CArchMiscWindows::openKey(HKEY_CURRENT_USER, getSettingsPath()); + if (key != NULL) { + DWORD newPort = CArchMiscWindows::readValueInt(key, "port"); + CString newName = CArchMiscWindows::readValueString(key, "name"); + CString newInterface = + CArchMiscWindows::readValueString(key, "interface"); + if (newPort != 0) { + m_port = static_cast(newPort); + } + if (!newName.empty()) { + m_screenName = newName; + } + if (!newInterface.empty()) { + m_interface = newInterface; + } + CArchMiscWindows::closeKey(key); + } +} + +void +CAdvancedOptions::doInit(HWND hwnd) +{ + // set values in GUI + HWND child; + char buffer[20]; + sprintf(buffer, "%d", m_port); + child = getItem(hwnd, IDC_ADVANCED_PORT_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)buffer); + + child = getItem(hwnd, IDC_ADVANCED_NAME_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)m_screenName.c_str()); + + child = getItem(hwnd, IDC_ADVANCED_INTERFACE_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)m_interface.c_str()); +} + +bool +CAdvancedOptions::save(HWND hwnd) +{ + SetCursor(LoadCursor(NULL, IDC_WAIT)); + + HWND child = getItem(hwnd, IDC_ADVANCED_NAME_EDIT); + CString name = getWindowText(child); + if (!m_config->isValidScreenName(name)) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_SCREEN_NAME).c_str(), + name.c_str())); + SetFocus(child); + return false; + } + if (!m_isClient && !m_config->isScreen(name)) { + showError(hwnd, CStringUtil::format( + getString(IDS_UNKNOWN_SCREEN_NAME).c_str(), + name.c_str())); + SetFocus(child); + return false; + } + + child = getItem(hwnd, IDC_ADVANCED_INTERFACE_EDIT); + CString iface = getWindowText(child); + if (!m_isClient) { + try { + if (!iface.empty()) { + ARCH->nameToAddr(iface); + } + } + catch (XArchNetworkName& e) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_INTERFACE_NAME).c_str(), + iface.c_str(), e.what().c_str())); + SetFocus(child); + return false; + } + } + + // get and verify port + child = getItem(hwnd, IDC_ADVANCED_PORT_EDIT); + CString portString = getWindowText(child); + int port = atoi(portString.c_str()); + if (port < 1 || port > 65535) { + CString defaultPortString = CStringUtil::print("%d", kDefaultPort); + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_PORT).c_str(), + portString.c_str(), + defaultPortString.c_str())); + SetFocus(child); + return false; + } + + // save state + m_screenName = name; + m_port = port; + m_interface = iface; + + // save values to registry + HKEY key = CArchMiscWindows::addKey(HKEY_CURRENT_USER, getSettingsPath()); + if (key != NULL) { + CArchMiscWindows::setValue(key, "port", m_port); + CArchMiscWindows::setValue(key, "name", m_screenName); + CArchMiscWindows::setValue(key, "interface", m_interface); + CArchMiscWindows::closeKey(key); + } + + return true; +} + +void +CAdvancedOptions::setDefaults(HWND hwnd) +{ + // restore defaults + m_screenName = ARCH->getHostName(); + m_port = kDefaultPort; + m_interface = ""; + + // update GUI + doInit(hwnd); +} + +BOOL +CAdvancedOptions::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + doInit(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (save(hwnd)) { + EndDialog(hwnd, 0); + } + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + + case IDC_ADVANCED_DEFAULTS: + setDefaults(hwnd); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CAdvancedOptions::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} diff --git a/cmd/launcher/CAdvancedOptions.h b/cmd/launcher/CAdvancedOptions.h new file mode 100644 index 00000000..1dd9dc44 --- /dev/null +++ b/cmd/launcher/CAdvancedOptions.h @@ -0,0 +1,80 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CADVANCEDOPTIONS_H +#define CADVANCEDOPTIONS_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +class CConfig; + +//! Advanced options dialog for Microsoft Windows launcher +class CAdvancedOptions { +public: + CAdvancedOptions(HWND parent, CConfig*); + ~CAdvancedOptions(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(bool isClient); + + //@} + //! @name accessors + //@{ + + //! Get the screen name + CString getScreenName() const; + + //! Get the port + int getPort() const; + + //! Get the interface + CString getInterface() const; + + //! Convert options to command line string + CString getCommandLine(bool isClient, + const CString& serverName) const; + + //@} + +private: + void init(); + void doInit(HWND hwnd); + bool save(HWND hwnd); + void setDefaults(HWND hwnd); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CAdvancedOptions* s_singleton; + + HWND m_parent; + CConfig* m_config; + bool m_isClient; + CString m_screenName; + int m_port; + CString m_interface; +}; + +#endif diff --git a/cmd/launcher/CAutoStart.cpp b/cmd/launcher/CAutoStart.cpp new file mode 100644 index 00000000..eb18a6b0 --- /dev/null +++ b/cmd/launcher/CAutoStart.cpp @@ -0,0 +1,361 @@ +/* + * 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 "ILogOutputter.h" +#include "CArch.h" +#include "CStringUtil.h" +#include "XArch.h" +#include "CAutoStart.h" +#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_INFO = "Uses a shared mouse and keyboard."; +static const char* SERVER_DAEMON_INFO = "Shares this system's mouse and keyboard with others."; + +// +// CAutoStartOutputter +// +// This class detects a message above a certain level and saves it +// + +class CAutoStartOutputter : public ILogOutputter { +public: + CAutoStartOutputter(CString* msg) : m_msg(msg) { } + virtual ~CAutoStartOutputter() { } + + // 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 ""; } + +private: + CString* m_msg; +}; + +bool +CAutoStartOutputter::write(ELevel level, const char* message) +{ + if (level <= CLog::kERROR) { + *m_msg = message; + } + return false; +} + + +// +// CAutoStart +// + +CAutoStart* CAutoStart::s_singleton = NULL; + +CAutoStart::CAutoStart(HWND parent, bool isServer, const CString& cmdLine) : + m_parent(parent), + m_isServer(isServer), + m_cmdLine(cmdLine), + m_name(isServer ? SERVER_DAEMON_NAME : CLIENT_DAEMON_NAME) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CAutoStart::~CAutoStart() +{ + s_singleton = NULL; +} + +void +CAutoStart::doModal() +{ + // install our log outputter + CLOG->insert(new CAutoStartOutputter(&m_errorMessage)); + + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_AUTOSTART), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); + + // remove log outputter + CLOG->pop_front(); +} + +void +CAutoStart::reinstallDaemon(bool isClient, const CString& cmdLine) +{ + // get installation state + const char* name = (isClient ? CLIENT_DAEMON_NAME : SERVER_DAEMON_NAME); + bool installedSystem = ARCH->isDaemonInstalled(name, true); + bool installedUser = ARCH->isDaemonInstalled(name, false); + + // reinstall if anything is installed + if (installedSystem || installedUser) { + ARCH->installDaemon(name, + isClient ? CLIENT_DAEMON_INFO : SERVER_DAEMON_INFO, + getAppPath(isClient ? CLIENT_APP : SERVER_APP).c_str(), + cmdLine.c_str(), + NULL, + installedSystem); + } +} + +void +CAutoStart::uninstallDaemons(bool client) +{ + if (client) { + try { + ARCH->uninstallDaemon(CLIENT_DAEMON_NAME, true); + } + catch (...) { + } + try { + ARCH->uninstallDaemon(CLIENT_DAEMON_NAME, false); + } + catch (...) { + } + } + else { + try { + ARCH->uninstallDaemon(SERVER_DAEMON_NAME, true); + } + catch (...) { + } + try { + ARCH->uninstallDaemon(SERVER_DAEMON_NAME, false); + } + catch (...) { + } + } +} + +bool +CAutoStart::startDaemon() +{ + const char* name = NULL; + if (ARCH->isDaemonInstalled(CLIENT_DAEMON_NAME, true)) { + name = CLIENT_DAEMON_NAME; + } + else if (ARCH->isDaemonInstalled(SERVER_DAEMON_NAME, true)) { + name = SERVER_DAEMON_NAME; + } + if (name == NULL) { + return false; + } + + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + return false; + } + + // open the service + SC_HANDLE service = OpenService(mgr, name, SERVICE_START); + if (service == NULL) { + CloseServiceHandle(mgr); + return false; + } + + // start the service + BOOL okay = StartService(service, 0, NULL); + + // clean up + CloseServiceHandle(service); + CloseServiceHandle(mgr); + + return (okay != 0); +} + +bool +CAutoStart::isDaemonInstalled() +{ + return (ARCH->isDaemonInstalled(CLIENT_DAEMON_NAME, false) || + ARCH->isDaemonInstalled(CLIENT_DAEMON_NAME, true) || + ARCH->isDaemonInstalled(SERVER_DAEMON_NAME, false) || + ARCH->isDaemonInstalled(SERVER_DAEMON_NAME, true)); +} + +void +CAutoStart::update() +{ + // get installation state + const bool installedSystem = ARCH->isDaemonInstalled( + m_name.c_str(), true); + const bool installedUser = ARCH->isDaemonInstalled( + m_name.c_str(), false); + + // get user's permissions + const bool canInstallSystem = ARCH->canInstallDaemon( + m_name.c_str(), true); + const bool canInstallUser = ARCH->canInstallDaemon( + m_name.c_str(), false); + + // update messages + CString msg, label; + if (canInstallSystem) { + if (canInstallUser) { + msg = getString(IDS_AUTOSTART_PERMISSION_ALL); + } + else { + msg = getString(IDS_AUTOSTART_PERMISSION_SYSTEM); + } + } + else if (canInstallUser) { + msg = getString(IDS_AUTOSTART_PERMISSION_USER); + } + else { + msg = getString(IDS_AUTOSTART_PERMISSION_NONE); + } + setWindowText(getItem(m_hwnd, IDC_AUTOSTART_PERMISSION_MSG), msg); + if (installedSystem) { + msg = getString(IDS_AUTOSTART_INSTALLED_SYSTEM); + label = getString(IDS_UNINSTALL_LABEL); + } + else if (installedUser) { + msg = getString(IDS_AUTOSTART_INSTALLED_USER); + label = getString(IDS_UNINSTALL_LABEL); + } + else { + msg = getString(IDS_AUTOSTART_INSTALLED_NONE); + label = getString(IDS_INSTALL_LABEL); + } + setWindowText(getItem(m_hwnd, IDC_AUTOSTART_INSTALLED_MSG), msg); + + // update buttons + setWindowText(getItem(m_hwnd, IDC_AUTOSTART_INSTALL_SYSTEM), label); + setWindowText(getItem(m_hwnd, IDC_AUTOSTART_INSTALL_USER), label); + if (installedSystem) { + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_SYSTEM, canInstallSystem); + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_USER, false); + m_install = false; + } + else if (installedUser) { + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_SYSTEM, false); + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_USER, canInstallUser); + m_install = false; + } + else { + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_SYSTEM, canInstallSystem); + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_USER, canInstallUser); + m_install = true; + } +} + +bool +CAutoStart::onInstall(bool allUsers) +{ + if (!m_install) { + return onUninstall(allUsers); + } + + // get the app path + CString appPath = getAppPath(m_isServer ? SERVER_APP : CLIENT_APP); + + // clear error message + m_errorMessage = ""; + + // install + try { + ARCH->installDaemon(m_name.c_str(), + m_isServer ? SERVER_DAEMON_INFO : CLIENT_DAEMON_INFO, + appPath.c_str(), m_cmdLine.c_str(), + NULL, allUsers); + askOkay(m_hwnd, getString(IDS_INSTALL_TITLE), + getString(allUsers ? + IDS_INSTALLED_SYSTEM : + IDS_INSTALLED_USER)); + return true; + } + catch (XArchDaemon& e) { + if (m_errorMessage.empty()) { + m_errorMessage = CStringUtil::format( + getString(IDS_INSTALL_GENERIC_ERROR).c_str(), + e.what().c_str()); + } + showError(m_hwnd, m_errorMessage); + return false; + } +} + +bool +CAutoStart::onUninstall(bool allUsers) +{ + // clear error message + m_errorMessage = ""; + + // uninstall + try { + ARCH->uninstallDaemon(m_name.c_str(), allUsers); + askOkay(m_hwnd, getString(IDS_UNINSTALL_TITLE), + getString(allUsers ? + IDS_UNINSTALLED_SYSTEM : + IDS_UNINSTALLED_USER)); + return true; + } + catch (XArchDaemon& e) { + if (m_errorMessage.empty()) { + m_errorMessage = CStringUtil::format( + getString(IDS_UNINSTALL_GENERIC_ERROR).c_str(), + e.what().c_str()); + } + showError(m_hwnd, m_errorMessage); + return false; + } +} + +BOOL +CAutoStart::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + // save our hwnd + m_hwnd = hwnd; + + // update the controls + update(); + + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_AUTOSTART_INSTALL_SYSTEM: + onInstall(true); + update(); + return TRUE; + + case IDC_AUTOSTART_INSTALL_USER: + onInstall(false); + update(); + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + m_hwnd = NULL; + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CAutoStart::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} diff --git a/cmd/launcher/CAutoStart.h b/cmd/launcher/CAutoStart.h new file mode 100644 index 00000000..46aad100 --- /dev/null +++ b/cmd/launcher/CAutoStart.h @@ -0,0 +1,90 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CAUTOSTART_H +#define CAUTOSTART_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +//! Auto start dialog for Microsoft Windows launcher +class CAutoStart { +public: + CAutoStart(HWND parent, bool isServer, const CString& cmdLine); + ~CAutoStart(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(); + + //! Reinstall daemon + /*! + Reinstalls the currently installed daemon. + */ + static void reinstallDaemon(bool isClient, const CString& cmdLine); + + //! Uninstalls daemon + /*! + Uninstalls all installed client (\p client is \c true) or server daemons. + */ + static void uninstallDaemons(bool client); + + //! Starts an installed daemon + /*! + Returns \c true iff a daemon was started. This will only start daemons + installed for all users. + */ + static bool startDaemon(); + + //@} + //! @name accessors + //@{ + + //! Tests if any daemons are installed + /*! + Returns \c true if any daemons are installed. + */ + static bool isDaemonInstalled(); + + //@} + +private: + void update(); + bool onInstall(bool allUsers); + bool onUninstall(bool allUsers); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CAutoStart* s_singleton; + + HWND m_parent; + bool m_isServer; + CString m_cmdLine; + CString m_name; + HWND m_hwnd; + bool m_install; + CString m_errorMessage; +}; + +#endif diff --git a/cmd/launcher/CGlobalOptions.cpp b/cmd/launcher/CGlobalOptions.cpp new file mode 100644 index 00000000..8237a07f --- /dev/null +++ b/cmd/launcher/CGlobalOptions.cpp @@ -0,0 +1,281 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "ProtocolTypes.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CGlobalOptions.h" +#include "LaunchUtil.h" +#include "resource.h" + +static const int s_defaultDelay = 250; +static const int s_defaultHeartbeat = 5000; + +// +// CGlobalOptions +// + +CGlobalOptions* CGlobalOptions::s_singleton = NULL; + +CGlobalOptions::CGlobalOptions(HWND parent, CConfig* config) : + m_parent(parent), + m_config(config), + m_delayTime(s_defaultDelay), + m_twoTapTime(s_defaultDelay), + m_heartbeatTime(s_defaultHeartbeat) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CGlobalOptions::~CGlobalOptions() +{ + s_singleton = NULL; +} + +void +CGlobalOptions::doModal() +{ + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_GLOBAL_OPTIONS), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); +} + +void +CGlobalOptions::init(HWND hwnd) +{ + HWND child; + char buffer[30]; + + // reset options + sprintf(buffer, "%d", m_delayTime); + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + setItemChecked(child, false); + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + setWindowText(child, buffer); + sprintf(buffer, "%d", m_twoTapTime); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + setItemChecked(child, false); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + setWindowText(child, buffer); + sprintf(buffer, "%d", m_heartbeatTime); + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_CHECK); + setItemChecked(child, false); + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_TIME); + setWindowText(child, buffer); + child = getItem(hwnd, IDC_GLOBAL_SCREENSAVER_SYNC); + setItemChecked(child, true); + child = getItem(hwnd, IDC_GLOBAL_RELATIVE_MOVES); + setItemChecked(child, false); + child = getItem(hwnd, IDC_GLOBAL_LEAVE_FOREGROUND); + setItemChecked(child, false); + + // get the global options + const CConfig::CScreenOptions* options = m_config->getOptions(""); + if (options != NULL) { + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + const OptionID id = index->first; + const OptionValue value = index->second; + if (id == kOptionScreenSwitchDelay) { + if (value > 0) { + sprintf(buffer, "%d", value); + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + setItemChecked(child, true); + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + setWindowText(child, buffer); + } + } + else if (id == kOptionScreenSwitchTwoTap) { + if (value > 0) { + sprintf(buffer, "%d", value); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + setItemChecked(child, true); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + setWindowText(child, buffer); + } + } + else if (id == kOptionHeartbeat) { + if (value > 0) { + sprintf(buffer, "%d", value); + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_CHECK); + setItemChecked(child, true); + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_TIME); + setWindowText(child, buffer); + } + } + else if (id == kOptionScreenSaverSync) { + child = getItem(hwnd, IDC_GLOBAL_SCREENSAVER_SYNC); + setItemChecked(child, (value != 0)); + } + else if (id == kOptionRelativeMouseMoves) { + child = getItem(hwnd, IDC_GLOBAL_RELATIVE_MOVES); + setItemChecked(child, (value != 0)); + } + else if (id == kOptionWin32KeepForeground) { + child = getItem(hwnd, IDC_GLOBAL_LEAVE_FOREGROUND); + setItemChecked(child, (value != 0)); + } + } + } +} + +bool +CGlobalOptions::save(HWND hwnd) +{ + HWND child; + int newDelayTime = 0; + int newTwoTapTime = 0; + int newHeartbeatTime = 0; + + // get requested options + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + if (isItemChecked(child)) { + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + newDelayTime = getTime(hwnd, child, true); + if (newDelayTime == 0) { + return false; + } + } + else { + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + newDelayTime = getTime(hwnd, child, false); + if (newDelayTime == 0) { + newDelayTime = s_defaultDelay; + } + } + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + if (isItemChecked(child)) { + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + newTwoTapTime = getTime(hwnd, child, true); + if (newTwoTapTime == 0) { + return false; + } + } + else { + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + newTwoTapTime = getTime(hwnd, child, false); + if (newTwoTapTime == 0) { + newTwoTapTime = s_defaultDelay; + } + } + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_CHECK); + if (isItemChecked(child)) { + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_TIME); + newHeartbeatTime = getTime(hwnd, child, true); + if (newHeartbeatTime == 0) { + return false; + } + } + else { + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_TIME); + newHeartbeatTime = getTime(hwnd, child, false); + if (newHeartbeatTime == 0) { + newHeartbeatTime = s_defaultHeartbeat; + } + } + + // remove existing config options + m_config->removeOption("", kOptionScreenSwitchDelay); + m_config->removeOption("", kOptionScreenSwitchTwoTap); + m_config->removeOption("", kOptionHeartbeat); + m_config->removeOption("", kOptionScreenSaverSync); + m_config->removeOption("", kOptionRelativeMouseMoves); + m_config->removeOption("", kOptionWin32KeepForeground); + + // add requested options + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + if (isItemChecked(child)) { + m_config->addOption("", kOptionScreenSwitchDelay, newDelayTime); + } + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + if (isItemChecked(child)) { + m_config->addOption("", kOptionScreenSwitchTwoTap, newTwoTapTime); + } + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_CHECK); + if (isItemChecked(child)) { + m_config->addOption("", kOptionHeartbeat, newHeartbeatTime); + } + child = getItem(hwnd, IDC_GLOBAL_SCREENSAVER_SYNC); + if (!isItemChecked(child)) { + m_config->addOption("", kOptionScreenSaverSync, 0); + } + child = getItem(hwnd, IDC_GLOBAL_RELATIVE_MOVES); + if (isItemChecked(child)) { + m_config->addOption("", kOptionRelativeMouseMoves, 1); + } + child = getItem(hwnd, IDC_GLOBAL_LEAVE_FOREGROUND); + if (isItemChecked(child)) { + m_config->addOption("", kOptionWin32KeepForeground, 1); + } + + // save last values + m_delayTime = newDelayTime; + m_twoTapTime = newTwoTapTime; + m_heartbeatTime = newHeartbeatTime; + return true; +} + +int +CGlobalOptions::getTime(HWND hwnd, HWND child, bool reportError) +{ + CString valueString = getWindowText(child); + int value = atoi(valueString.c_str()); + if (value < 1) { + if (reportError) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_TIME).c_str(), + valueString.c_str())); + SetFocus(child); + } + return 0; + } + return value; +} + +BOOL +CGlobalOptions::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + init(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (save(hwnd)) { + EndDialog(hwnd, 0); + } + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CGlobalOptions::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} diff --git a/cmd/launcher/CGlobalOptions.h b/cmd/launcher/CGlobalOptions.h new file mode 100644 index 00000000..f04f1bae --- /dev/null +++ b/cmd/launcher/CGlobalOptions.h @@ -0,0 +1,67 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CGLOBALOPTIONS_H +#define CGLOBALOPTIONS_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +class CConfig; + +//! Global options dialog for Microsoft Windows launcher +class CGlobalOptions { +public: + CGlobalOptions(HWND parent, CConfig*); + ~CGlobalOptions(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(); + + //@} + //! @name accessors + //@{ + + + //@} + +private: + void init(HWND hwnd); + bool save(HWND hwnd); + + int getTime(HWND hwnd, HWND child, bool reportError); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CGlobalOptions* s_singleton; + + HWND m_parent; + CConfig* m_config; + int m_delayTime; + int m_twoTapTime; + int m_heartbeatTime; +}; + +#endif diff --git a/cmd/launcher/CHotkeyOptions.cpp b/cmd/launcher/CHotkeyOptions.cpp new file mode 100644 index 00000000..5aa981e0 --- /dev/null +++ b/cmd/launcher/CHotkeyOptions.cpp @@ -0,0 +1,1938 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 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 "CMSWindowsKeyState.h" +#include "CConfig.h" +#include "CHotkeyOptions.h" +#include "CStringUtil.h" +#include "LaunchUtil.h" +#include "resource.h" + +#if !defined(WM_XBUTTONDOWN) +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif + +// +// CAdvancedOptions +// + +CHotkeyOptions* CHotkeyOptions::s_singleton = NULL; + +CHotkeyOptions::CHotkeyOptions(HWND parent, CConfig* config) : + m_parent(parent), + m_config(config) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CHotkeyOptions::~CHotkeyOptions() +{ + s_singleton = NULL; +} + +void +CHotkeyOptions::doModal() +{ + // do dialog + m_inputFilter = m_config->getInputFilter(); + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_OPTIONS), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); +} + +void +CHotkeyOptions::doInit(HWND hwnd) +{ + m_activeRuleIndex = (UInt32)-1; + fillHotkeys(hwnd); + openRule(hwnd); +} + +void +CHotkeyOptions::fillHotkeys(HWND hwnd, UInt32 select) +{ + HWND rules = getItem(hwnd, IDC_HOTKEY_HOTKEYS); + + SendMessage(rules, LB_RESETCONTENT, 0, 0); + for (UInt32 i = 0, n = m_inputFilter->getNumRules(); i < n; ++i) { + CInputFilter::CRule& rule = m_inputFilter->getRule(i); + SendMessage(rules, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)rule.getCondition()->format().c_str()); + } + + if (select < m_inputFilter->getNumRules()) { + SendMessage(rules, LB_SETCURSEL, select, 0); + } + + updateHotkeysControls(hwnd); +} + +void +CHotkeyOptions::updateHotkeysControls(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_HOTKEYS); + bool selected = (SendMessage(child, LB_GETCURSEL, 0, 0) != LB_ERR); + + enableItem(hwnd, IDC_HOTKEY_ADD_HOTKEY, TRUE); + enableItem(hwnd, IDC_HOTKEY_EDIT_HOTKEY, selected); + enableItem(hwnd, IDC_HOTKEY_REMOVE_HOTKEY, selected); +} + +void +CHotkeyOptions::addHotkey(HWND hwnd) +{ + closeRule(hwnd); + CInputFilter::CCondition* condition = NULL; + if (editCondition(hwnd, condition)) { + m_inputFilter->addFilterRule(CInputFilter::CRule(condition)); + fillHotkeys(hwnd, m_inputFilter->getNumRules() - 1); + } + else { + delete condition; + } + openRule(hwnd); +} + +void +CHotkeyOptions::removeHotkey(HWND hwnd) +{ + UInt32 ruleIndex = m_activeRuleIndex; + closeRule(hwnd); + + m_inputFilter->removeFilterRule(ruleIndex); + UInt32 n = m_inputFilter->getNumRules(); + if (n > 0 && ruleIndex >= n) { + ruleIndex = n - 1; + } + fillHotkeys(hwnd, ruleIndex); + + openRule(hwnd); +} + +void +CHotkeyOptions::editHotkey(HWND hwnd) +{ + // save selected item in action list + HWND actions = getItem(hwnd, IDC_HOTKEY_ACTIONS); + LRESULT aIndex = SendMessage(actions, LB_GETCURSEL, 0, 0); + + UInt32 index = m_activeRuleIndex; + closeRule(hwnd); + + CInputFilter::CRule& rule = m_inputFilter->getRule(index); + CInputFilter::CCondition* condition = rule.getCondition()->clone(); + if (editCondition(hwnd, condition)) { + rule.setCondition(condition); + fillHotkeys(hwnd, index); + } + else { + delete condition; + } + + openRule(hwnd); + + // restore selected item in action list + if (aIndex != LB_ERR) { + SendMessage(actions, LB_SETCURSEL, aIndex, 0); + } +} + +void +CHotkeyOptions::fillActions(HWND hwnd, UInt32 select) +{ + HWND actions = getItem(hwnd, IDC_HOTKEY_ACTIONS); + SendMessage(actions, LB_RESETCONTENT, 0, 0); + if (m_activeRuleIndex != (UInt32)-1) { + UInt32 n = m_activeRule.getNumActions(true); + UInt32 n2 = m_activeRule.getNumActions(false); + for (UInt32 i = 0; i < n; ++i) { + const CInputFilter::CAction& action = + m_activeRule.getAction(true, i); + CString line("A "); + line += action.format(); + SendMessage(actions, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)line.c_str()); + SendMessage(actions, LB_SETITEMDATA, (WPARAM)i, (LPARAM)i); + } + for (UInt32 i = 0; i < n2; ++i) { + const CInputFilter::CAction& action = + m_activeRule.getAction(false, i); + CString line("D "); + line += action.format(); + SendMessage(actions, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)line.c_str()); + SendMessage(actions, LB_SETITEMDATA, (WPARAM)i + n, + (LPARAM)(i | 0x80000000u)); + } + + if (select < n + n2) { + SendMessage(actions, LB_SETCURSEL, select, 0); + } + } + + updateActionsControls(hwnd); +} + +void +CHotkeyOptions::updateActionsControls(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_HOTKEYS); + bool active = (m_activeRuleIndex != (UInt32)-1); + + child = getItem(hwnd, IDC_HOTKEY_ACTIONS); + bool selected = + (active && (SendMessage(child, LB_GETCURSEL, 0, 0) != LB_ERR)); + + enableItem(hwnd, IDC_HOTKEY_ADD_ACTION, active); + enableItem(hwnd, IDC_HOTKEY_EDIT_ACTION, selected); + enableItem(hwnd, IDC_HOTKEY_REMOVE_ACTION, selected); +} + +void +CHotkeyOptions::addAction(HWND hwnd) +{ + CInputFilter::CAction* action = NULL; + bool onActivate = true; + if (editAction(hwnd, action, onActivate)) { + m_activeRule.adoptAction(action, onActivate); + + UInt32 actionIndex = m_activeRule.getNumActions(true) - 1; + if (!onActivate) { + actionIndex += m_activeRule.getNumActions(false); + } + fillActions(hwnd, actionIndex); + } + else { + delete action; + } +} + +void +CHotkeyOptions::removeAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTIONS); + LRESULT index = SendMessage(child, LB_GETCURSEL, 0, 0); + if (index != LB_ERR) { + UInt32 actionIndex = + (UInt32)SendMessage(child, LB_GETITEMDATA, index, 0); + bool onActivate = ((actionIndex & 0x80000000u) == 0); + actionIndex &= ~0x80000000u; + + m_activeRule.removeAction(onActivate, actionIndex); + + actionIndex = static_cast(index); + UInt32 n = m_activeRule.getNumActions(true) + + m_activeRule.getNumActions(false); + if (n > 0 && actionIndex >= n) { + actionIndex = n - 1; + } + fillActions(hwnd, actionIndex); + } +} + +void +CHotkeyOptions::editAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTIONS); + LRESULT index = SendMessage(child, LB_GETCURSEL, 0, 0); + if (index != LB_ERR) { + UInt32 actionIndex = + (UInt32)SendMessage(child, LB_GETITEMDATA, index, 0); + bool onActivate = ((actionIndex & 0x80000000u) == 0); + actionIndex &= ~0x80000000u; + + CInputFilter::CAction* action = + m_activeRule.getAction(onActivate, actionIndex).clone(); + bool newOnActivate = onActivate; + if (editAction(hwnd, action, newOnActivate)) { + if (onActivate == newOnActivate) { + m_activeRule.replaceAction(action, onActivate, actionIndex); + actionIndex = static_cast(index); + } + else { + m_activeRule.removeAction(onActivate, actionIndex); + m_activeRule.adoptAction(action, newOnActivate); + actionIndex = m_activeRule.getNumActions(true) - 1; + if (!newOnActivate) { + actionIndex += m_activeRule.getNumActions(false); + } + } + fillActions(hwnd, actionIndex); + } + else { + delete action; + } + } +} + +bool +CHotkeyOptions::editCondition(HWND hwnd, CInputFilter::CCondition*& condition) +{ + return CConditionDialog::doModal(hwnd, condition); +} + +bool +CHotkeyOptions::editAction(HWND hwnd, CInputFilter::CAction*& action, + bool& onActivate) +{ + return CActionDialog::doModal(hwnd, m_config, action, onActivate); +} + +void +CHotkeyOptions::openRule(HWND hwnd) +{ + // get the active rule and copy it, merging down/up pairs of keystroke + // and mouse button actions into single actions for the convenience of + // of the user. + HWND rules = getItem(hwnd, IDC_HOTKEY_HOTKEYS); + LRESULT index = SendMessage(rules, LB_GETCURSEL, 0, 0); + if (index != LB_ERR) { + // copy the rule as is + m_activeRuleIndex = (SInt32)index; + m_activeRule = m_inputFilter->getRule(m_activeRuleIndex); + + // look for actions to combine + for (UInt32 i = 0, n = m_activeRule.getNumActions(true); i < n; ++i) { + // get next activate action + const CInputFilter::CAction* action = + &m_activeRule.getAction(true, i); + + // check if it's a key or mouse action + const CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(action); + const CInputFilter::CMouseButtonAction* mouseAction = + dynamic_cast(action); + if (keyAction == NULL && mouseAction == NULL) { + continue; + } + + // check for matching deactivate action + UInt32 j = (UInt32)-1; + CInputFilter::CAction* newAction = NULL; + if (keyAction != NULL) { + j = findMatchingAction(keyAction); + if (j != (UInt32)-1) { + // found a match + const IPlatformScreen::CKeyInfo* oldInfo = + keyAction->getInfo(); + IPlatformScreen::CKeyInfo* newInfo = + IKeyState::CKeyInfo::alloc(*oldInfo); + newAction = new CKeystrokeDownUpAction(newInfo); + } + } + else if (mouseAction != NULL) { + j = findMatchingAction(mouseAction); + if (j != (UInt32)-1) { + // found a match + const IPlatformScreen::CButtonInfo* oldInfo = + mouseAction->getInfo(); + IPlatformScreen::CButtonInfo* newInfo = + IPrimaryScreen::CButtonInfo::alloc(*oldInfo); + newAction = new CMouseButtonDownUpAction(newInfo); + } + } + + // perform merge + if (newAction != NULL) { + m_activeRule.replaceAction(newAction, true, i); + m_activeRule.removeAction(false, j); + } + } + } + else { + m_activeRuleIndex = (UInt32)-1; + } + fillActions(hwnd); +} + +void +CHotkeyOptions::closeRule(HWND) +{ + // copy rule back to input filter, expanding merged actions into the + // two component actions. + if (m_activeRuleIndex != (UInt32)-1) { + // expand merged rules + for (UInt32 i = 0, n = m_activeRule.getNumActions(true); i < n; ++i) { + // get action + const CInputFilter::CAction* action = + &m_activeRule.getAction(true, i); + + // check if it's a merged key or mouse action + const CKeystrokeDownUpAction* keyAction = + dynamic_cast(action); + const CMouseButtonDownUpAction* mouseAction = + dynamic_cast(action); + if (keyAction == NULL && mouseAction == NULL) { + continue; + } + + // expand + if (keyAction != NULL) { + const IPlatformScreen::CKeyInfo* oldInfo = + keyAction->getInfo(); + IPlatformScreen::CKeyInfo* newInfo = + IKeyState::CKeyInfo::alloc(*oldInfo); + CInputFilter::CKeystrokeAction* downAction = + new CInputFilter::CKeystrokeAction(newInfo, true); + newInfo = IKeyState::CKeyInfo::alloc(*oldInfo); + CInputFilter::CKeystrokeAction* upAction = + new CInputFilter::CKeystrokeAction(newInfo, false); + m_activeRule.replaceAction(downAction, true, i); + m_activeRule.adoptAction(upAction, false); + } + else if (mouseAction != NULL) { + const IPlatformScreen::CButtonInfo* oldInfo = + mouseAction->getInfo(); + IPlatformScreen::CButtonInfo* newInfo = + IPrimaryScreen::CButtonInfo::alloc(*oldInfo); + CInputFilter::CMouseButtonAction* downAction = + new CInputFilter::CMouseButtonAction(newInfo, true); + newInfo = IPrimaryScreen::CButtonInfo::alloc(*oldInfo); + CInputFilter::CMouseButtonAction* upAction = + new CInputFilter::CMouseButtonAction(newInfo, false); + m_activeRule.replaceAction(downAction, true, i); + m_activeRule.adoptAction(upAction, false); + } + } + + // copy it back + m_inputFilter->getRule(m_activeRuleIndex) = m_activeRule; + } + m_activeRuleIndex = (UInt32)-1; +} + +UInt32 +CHotkeyOptions::findMatchingAction( + const CInputFilter::CKeystrokeAction* src) const +{ + for (UInt32 i = 0, n = m_activeRule.getNumActions(false); i < n; ++i) { + const CInputFilter::CKeystrokeAction* dst = + dynamic_cast( + &m_activeRule.getAction(false, i)); + if (dst != NULL && + IKeyState::CKeyInfo::equal(src->getInfo(), dst->getInfo())) { + return i; + } + } + return (UInt32)-1; +} + +UInt32 +CHotkeyOptions::findMatchingAction( + const CInputFilter::CMouseButtonAction* src) const +{ + for (UInt32 i = 0, n = m_activeRule.getNumActions(false); i < n; ++i) { + const CInputFilter::CMouseButtonAction* dst = + dynamic_cast( + &m_activeRule.getAction(false, i)); + if (dst != NULL && + IPrimaryScreen::CButtonInfo::equal( + src->getInfo(), dst->getInfo())) { + return i; + } + } + return (UInt32)-1; +} + +BOOL +CHotkeyOptions::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + doInit(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + closeRule(hwnd); + EndDialog(hwnd, 0); + return TRUE; + + case IDC_HOTKEY_HOTKEYS: + switch (HIWORD(wParam)) { + case LBN_DBLCLK: + editHotkey(hwnd); + return TRUE; + + case LBN_SELCHANGE: { + HWND rules = getItem(hwnd, IDC_HOTKEY_HOTKEYS); + LRESULT index = SendMessage(rules, LB_GETCURSEL, 0, 0); + if (m_activeRuleIndex != (UInt32)index) { + closeRule(hwnd); + updateHotkeysControls(hwnd); + openRule(hwnd); + } + return TRUE; + } + } + break; + + case IDC_HOTKEY_ADD_HOTKEY: + addHotkey(hwnd); + return TRUE; + + case IDC_HOTKEY_REMOVE_HOTKEY: + removeHotkey(hwnd); + return TRUE; + + case IDC_HOTKEY_EDIT_HOTKEY: + editHotkey(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTIONS: + switch (HIWORD(wParam)) { + case LBN_DBLCLK: + editAction(hwnd); + return TRUE; + + case LBN_SELCHANGE: + updateActionsControls(hwnd); + return TRUE; + } + break; + + case IDC_HOTKEY_ADD_ACTION: + addAction(hwnd); + return TRUE; + + case IDC_HOTKEY_REMOVE_ACTION: + removeAction(hwnd); + return TRUE; + + case IDC_HOTKEY_EDIT_ACTION: + editAction(hwnd); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CHotkeyOptions::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} + + +// +// CHotkeyOptions::CConditionDialog +// + +CInputFilter::CCondition* + CHotkeyOptions::CConditionDialog::s_condition = NULL; +CInputFilter::CCondition* + CHotkeyOptions::CConditionDialog::s_lastGoodCondition = NULL; +WNDPROC CHotkeyOptions::CConditionDialog::s_editWndProc = NULL; + +bool +CHotkeyOptions::CConditionDialog::doModal(HWND parent, + CInputFilter::CCondition*& condition) +{ + s_condition = condition; + if (s_condition != NULL) { + s_lastGoodCondition = s_condition->clone(); + } + else { + s_lastGoodCondition = NULL; + } + int n = DialogBox(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_CONDITION), + parent, dlgProc); + + condition = s_condition; + delete s_lastGoodCondition; + s_condition = NULL; + s_lastGoodCondition = NULL; + + // user effectively cancelled if the condition is NULL + if (condition == NULL) { + n = 0; + } + + return (n == 1); +} + +void +CHotkeyOptions::CConditionDialog::doInit(HWND hwnd) +{ + // subclass edit control + HWND child = getItem(hwnd, IDC_HOTKEY_CONDITION_HOTKEY); + s_editWndProc = (WNDPROC)GetWindowLong(child, GWL_WNDPROC); + SetWindowLong(child, GWL_WNDPROC, (LONG)editProc); + + // fill control + fillHotkey(hwnd); +} + +void +CHotkeyOptions::CConditionDialog::fillHotkey(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_CONDITION_HOTKEY); + if (s_condition != NULL) { + setWindowText(child, s_condition->format().c_str()); + } + else { + setWindowText(child, ""); + } +} + +void +CHotkeyOptions::CConditionDialog::onButton(HWND hwnd, ButtonID button) +{ + delete s_condition; + s_condition = + new CInputFilter::CMouseButtonCondition(button, getModifiers()); + + fillHotkey(GetParent(hwnd)); +} + +void +CHotkeyOptions::CConditionDialog::onKey(HWND hwnd, WPARAM wParam, LPARAM lParam) +{ + // ignore key repeats + if ((lParam & 0xc0000000u) == 0x40000000u) { + return; + } + + // ignore key releases if the condition is complete and for the tab + // key (in case we were just tabbed to) + if ((lParam & 0x80000000u) != 0) { + if (isGoodCondition() || wParam == VK_TAB) { + return; + } + } + + KeyID key = kKeyNone; + KeyModifierMask mask = getModifiers(); + switch (wParam) { + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + case VK_LWIN: + case VK_RWIN: + break; + + case VK_TAB: + // allow tabbing out of control + if ((mask & (KeyModifierControl | + KeyModifierAlt | KeyModifierSuper)) == 0) { + HWND next = hwnd; + if ((mask & KeyModifierShift) == 0) { + do { + next = GetWindow(next, GW_HWNDNEXT); + if (next == NULL) { + next = GetWindow(hwnd, GW_HWNDFIRST); + } + } while (next != hwnd && + (!IsWindowVisible(next) || + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0)); + } + else { + do { + next = GetWindow(next, GW_HWNDPREV); + if (next == NULL) { + next = GetWindow(hwnd, GW_HWNDLAST); + } + } while (next != hwnd && + (!IsWindowVisible(next) || + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0)); + } + SetFocus(next); + return; + } + // fall through + + default: + key = CMSWindowsKeyState::getKeyID(wParam, + static_cast((lParam & 0x1ff0000u) >> 16)); + switch (key) { + case kKeyNone: + // could be a character + key = getChar(wParam, lParam); + if (key == kKeyNone) { + return; + } + break; + + case kKeyShift_L: + case kKeyShift_R: + case kKeyControl_L: + case kKeyControl_R: + case kKeyAlt_L: + case kKeyAlt_R: + case kKeyMeta_L: + case kKeyMeta_R: + case kKeySuper_L: + case kKeySuper_R: + case kKeyCapsLock: + case kKeyNumLock: + case kKeyScrollLock: + // bogus + return; + } + break; + } + + delete s_condition; + s_condition = new CInputFilter::CKeystrokeCondition(key, mask); + + fillHotkey(GetParent(hwnd)); +} + +KeyID +CHotkeyOptions::CConditionDialog::getChar(WPARAM wParam, LPARAM lParam) +{ + BYTE keyState[256]; + UINT virtualKey = (UINT)wParam; + UINT scanCode = (UINT)((lParam & 0x0ff0000u) >> 16); + GetKeyboardState(keyState); + + // reset modifier state + keyState[VK_SHIFT] = 0; + keyState[VK_LSHIFT] = 0; + keyState[VK_RSHIFT] = 0; + keyState[VK_CONTROL] = 0; + keyState[VK_LCONTROL] = 0; + keyState[VK_RCONTROL] = 0; + keyState[VK_MENU] = 0; + keyState[VK_LMENU] = 0; + keyState[VK_RMENU] = 0; + keyState[VK_LWIN] = 0; + keyState[VK_RWIN] = 0; + + // translate virtual key to character + int n; + KeyID id; + if (CArchMiscWindows::isWindows95Family()) { + // XXX -- how do we get characters not in Latin-1? + WORD ascii; + n = ToAscii(virtualKey, scanCode, keyState, &ascii, 0); + id = static_cast(ascii & 0xffu); + } + else { + typedef int (WINAPI *ToUnicode_t)(UINT wVirtKey, + UINT wScanCode, + PBYTE lpKeyState, + LPWSTR pwszBuff, + int cchBuff, + UINT wFlags); + ToUnicode_t s_ToUnicode = NULL; + if (s_ToUnicode == NULL) { + HMODULE userModule = GetModuleHandle("user32.dll"); + s_ToUnicode = + (ToUnicode_t)GetProcAddress(userModule, "ToUnicode"); + } + + WCHAR unicode[2]; + n = s_ToUnicode(virtualKey, scanCode, keyState, + unicode, sizeof(unicode) / sizeof(unicode[0]), + 0); + id = static_cast(unicode[0]); + } + switch (n) { + case -1: + // no hot keys on dead keys + return kKeyNone; + + default: + case 0: + // unmapped + return kKeyNone; + + case 1: + return id; + } +} + +KeyModifierMask +CHotkeyOptions::CConditionDialog::getModifiers() +{ + KeyModifierMask mask = 0; + if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) { + mask |= KeyModifierShift; + } + if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) { + mask |= KeyModifierControl; + } + if ((GetKeyState(VK_MENU) & 0x8000) != 0) { + mask |= KeyModifierAlt; + } + if ((GetKeyState(VK_LWIN) & 0x8000) != 0 || + (GetKeyState(VK_RWIN) & 0x8000) != 0) { + mask |= KeyModifierSuper; + } + return mask; +} + +bool +CHotkeyOptions::CConditionDialog::isGoodCondition() +{ + CInputFilter::CKeystrokeCondition* keyCondition = + dynamic_cast(s_condition); + return (keyCondition == NULL || keyCondition->getKey() != kKeyNone); +} + +BOOL CALLBACK +CHotkeyOptions::CConditionDialog::dlgProc(HWND hwnd, + UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + doInit(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + EndDialog(hwnd, 1); + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +LRESULT CALLBACK +CHotkeyOptions::CConditionDialog::editProc(HWND hwnd, + UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_LBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonLeft); + } + else { + SetFocus(hwnd); + } + return 0; + + case WM_MBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonMiddle); + } + return 0; + + case WM_RBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonRight); + } + return 0; + + case WM_XBUTTONDOWN: + if (GetFocus() == hwnd) { + switch (HIWORD(wParam)) { + case XBUTTON1: + onButton(hwnd, kButtonExtra0 + 0); + break; + + case XBUTTON2: + onButton(hwnd, kButtonExtra0 + 1); + break; + } + } + return 0; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + onKey(hwnd, wParam, lParam); + return 0; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + return 0; + + case WM_SETFOCUS: + if (s_condition != NULL) { + delete s_lastGoodCondition; + s_lastGoodCondition = s_condition->clone(); + } + break; + + case WM_KILLFOCUS: + if (!isGoodCondition()) { + delete s_condition; + if (s_lastGoodCondition != NULL) { + s_condition = s_lastGoodCondition->clone(); + } + else { + s_condition = NULL; + } + } + fillHotkey(GetParent(hwnd)); + break; + + case WM_GETDLGCODE: + return DLGC_WANTALLKEYS; + + default: + break; + } + return CallWindowProc(s_editWndProc, hwnd, message, wParam, lParam); +} + + +// +// CHotkeyOptions::CActionDialog +// + +CConfig* CHotkeyOptions::CActionDialog::s_config = NULL; +bool CHotkeyOptions::CActionDialog::s_onActivate = false; +CInputFilter::CAction* + CHotkeyOptions::CActionDialog::s_action = NULL; +CInputFilter::CAction* + CHotkeyOptions::CActionDialog::s_lastGoodAction = NULL; +std::set + CHotkeyOptions::CActionDialog::s_screens; +WNDPROC CHotkeyOptions::CActionDialog::s_editWndProc = NULL; + +bool +CHotkeyOptions::CActionDialog::doModal(HWND parent, CConfig* config, + CInputFilter::CAction*& action, bool& onActivate) +{ + s_config = config; + s_onActivate = onActivate; + s_action = action; + if (s_action != NULL) { + s_lastGoodAction = s_action->clone(); + } + else { + s_lastGoodAction = NULL; + } + + int n = DialogBox(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_ACTION), + parent, dlgProc); + + onActivate = s_onActivate; + action = s_action; + delete s_lastGoodAction; + s_action = NULL; + s_lastGoodAction = NULL; + + return (n == 1); +} + +void +CHotkeyOptions::CActionDialog::doInit(HWND hwnd) +{ + // subclass edit control + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY); + s_editWndProc = (WNDPROC)GetWindowLong(child, GWL_WNDPROC); + SetWindowLong(child, GWL_WNDPROC, (LONG)editProc); + setWindowText(getItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY), ""); + fillHotkey(hwnd); + + // fill screens + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST); + SendMessage(child, CB_RESETCONTENT, 0, 0); + for (CConfig::const_iterator index = s_config->begin(); + index != s_config->end(); ) { + const CString& name = *index; + ++index; + if (index != s_config->end()) { + SendMessage(child, CB_INSERTSTRING, + (WPARAM)-1, (LPARAM)name.c_str()); + } + else { + SendMessage(child, CB_ADDSTRING, 0, (LPARAM)name.c_str()); + } + } + SendMessage(child, CB_SETCURSEL, 0, 0); + + // fill directions + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_LEFT).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_RIGHT).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_TOP).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_BOTTOM).c_str()); + SendMessage(child, CB_SETCURSEL, 0, 0); + + // fill lock modes + child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_OFF).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_ON).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_TOGGLE).c_str()); + SendMessage(child, CB_SETCURSEL, 0, 0); + + // fill keyboard broadcast modes + child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_OFF).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_ON).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_TOGGLE).c_str()); + SendMessage(child, CB_SETCURSEL, 0, 0); + + // select when + if (s_onActivate) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_ON_ACTIVATE); + } + else { + child = getItem(hwnd, IDC_HOTKEY_ACTION_ON_DEACTIVATE); + } + setItemChecked(child, true); + + // no screens by default + s_screens.clear(); + + // select mode + child = NULL; + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_action); + CInputFilter::CMouseButtonAction* mouseAction = + dynamic_cast(s_action); + CInputFilter::CLockCursorToScreenAction* lockAction = + dynamic_cast(s_action); + CInputFilter::CSwitchToScreenAction* switchToAction = + dynamic_cast(s_action); + CInputFilter::CSwitchInDirectionAction* switchInAction = + dynamic_cast(s_action); + CInputFilter::CKeyboardBroadcastAction* keyboardBroadcastAction= + dynamic_cast(s_action); + if (keyAction != NULL) { + if (dynamic_cast(s_action) != NULL) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP); + } + else if (keyAction->isOnPress()) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWN); + } + else { + child = getItem(hwnd, IDC_HOTKEY_ACTION_UP); + } + } + else if (mouseAction != NULL) { + if (dynamic_cast(s_action) != NULL) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP); + } + else if (keyAction->isOnPress()) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWN); + } + else { + child = getItem(hwnd, IDC_HOTKEY_ACTION_UP); + } + } + else if (lockAction != NULL) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST); + SendMessage(child, CB_SETCURSEL, lockAction->getMode(), 0); + child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK); + } + else if (switchToAction != NULL) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST); + DWORD i = SendMessage(child, CB_FINDSTRINGEXACT, (WPARAM)-1, + (LPARAM)switchToAction->getScreen().c_str()); + if (i == CB_ERR) { + i = 0; + } + SendMessage(child, CB_SETCURSEL, i, 0); + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO); + } + else if (switchInAction != NULL) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST); + SendMessage(child, CB_SETCURSEL, + switchInAction->getDirection() - kLeft, 0); + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN); + } + else if (keyboardBroadcastAction != NULL) { + // Save the screens we're broadcasting to + s_screens = keyboardBroadcastAction->getScreens(); + + child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST); + SendMessage(child, CB_SETCURSEL, keyboardBroadcastAction->getMode(), 0); + child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST); + } + if (child != NULL) { + setItemChecked(child, true); + } + + updateControls(hwnd); +} + +void +CHotkeyOptions::CActionDialog::fillHotkey(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY); + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_action); + CInputFilter::CMouseButtonAction* mouseAction = + dynamic_cast(s_action); + if (keyAction != NULL || mouseAction != NULL) { + setWindowText(child, s_action->format().c_str()); + } + else { + setWindowText(child, ""); + } + + // can only set screens in key actions + enableItem(hwnd, IDC_HOTKEY_ACTION_SCREENS, keyAction != NULL); +} + +void +CHotkeyOptions::CActionDialog::updateControls(HWND hwnd) +{ + // determine which mode we're in + UInt32 mode = 0; + if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP)) || + isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWN)) || + isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_UP))) { + mode = 1; + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO))) { + mode = 2; + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN))) { + mode = 3; + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_LOCK))) { + mode = 4; + } + else if (isItemChecked(getItem(hwnd, + IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST))) { + mode = 5; + } + + // enable/disable all mode specific controls + enableItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY, mode == 1); + enableItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST, mode == 2); + enableItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST, mode == 3); + enableItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST, mode == 4); + enableItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST, mode == 5); + enableItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_SCREENS, mode == 5); + + // can only set screens in key actions + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_action); + enableItem(hwnd, IDC_HOTKEY_ACTION_SCREENS, keyAction != NULL); +} + +void +CHotkeyOptions::CActionDialog::onButton(HWND hwnd, ButtonID button) +{ + IPlatformScreen::CButtonInfo* info = + IPrimaryScreen::CButtonInfo::alloc(button, getModifiers()); + delete s_action; + HWND parent = GetParent(hwnd); + if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWNUP))) { + s_action = new CMouseButtonDownUpAction(info); + } + else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWN))) { + s_action = new CInputFilter::CMouseButtonAction(info, true); + } + else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_UP))) { + s_action = new CInputFilter::CMouseButtonAction(info, false); + } + else { + s_action = NULL; + } + + fillHotkey(parent); +} + +void +CHotkeyOptions::CActionDialog::onKey(HWND hwnd, WPARAM wParam, LPARAM lParam) +{ + // ignore key repeats + if ((lParam & 0xc0000000u) == 0x40000000u) { + return; + } + + // ignore key releases if the action is complete and for the tab + // key (in case we were just tabbed to) + if ((lParam & 0x80000000u) != 0) { + if (isGoodAction() || wParam == VK_TAB) { + return; + } + } + + KeyID key = kKeyNone; + KeyModifierMask mask = getModifiers(); + switch (wParam) { + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + case VK_LWIN: + case VK_RWIN: + break; + + case VK_TAB: + // allow tabbing out of control + if ((mask & (KeyModifierControl | + KeyModifierAlt | KeyModifierSuper)) == 0) { + HWND next = hwnd; + if ((mask & KeyModifierShift) == 0) { + do { + next = GetWindow(next, GW_HWNDNEXT); + if (next == NULL) { + next = GetWindow(hwnd, GW_HWNDFIRST); + } + } while (next != hwnd && + (!IsWindowVisible(next) || + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0)); + } + else { + do { + next = GetWindow(next, GW_HWNDPREV); + if (next == NULL) { + next = GetWindow(hwnd, GW_HWNDLAST); + } + } while (next != hwnd && + (!IsWindowVisible(next) || + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0)); + } + SetFocus(next); + return; + } + // fall through + + default: + key = CMSWindowsKeyState::getKeyID(wParam, + static_cast((lParam & 0x1ff0000u) >> 16)); + switch (key) { + case kKeyNone: + // could be a character + key = getChar(wParam, lParam); + if (key == kKeyNone) { + return; + } + break; + + case kKeyShift_L: + case kKeyShift_R: + case kKeyControl_L: + case kKeyControl_R: + case kKeyAlt_L: + case kKeyAlt_R: + case kKeyMeta_L: + case kKeyMeta_R: + case kKeySuper_L: + case kKeySuper_R: + case kKeyCapsLock: + case kKeyNumLock: + case kKeyScrollLock: + // bogus + return; + } + break; + } + + // get old screen list + std::set screens; + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_action); + if (keyAction == NULL) { + keyAction = + dynamic_cast(s_lastGoodAction); + } + if (keyAction != NULL) { + IKeyState::CKeyInfo::split(keyAction->getInfo()->m_screens, screens); + } + + // create new action + IPlatformScreen::CKeyInfo* info = + IKeyState::CKeyInfo::alloc(key, mask, 0, 0, screens); + delete s_action; + HWND parent = GetParent(hwnd); + if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWNUP))) { + s_action = new CKeystrokeDownUpAction(info); + } + else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWN))) { + s_action = new CInputFilter::CKeystrokeAction(info, true); + } + else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_UP))) { + s_action = new CInputFilter::CKeystrokeAction(info, false); + } + else { + s_action = NULL; + } + + fillHotkey(parent); +} + +void +CHotkeyOptions::CActionDialog::onLockAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST); + LRESULT index = SendMessage(child, CB_GETCURSEL, 0, 0); + if (index != CB_ERR) { + delete s_action; + s_action = new CInputFilter::CLockCursorToScreenAction( + (CInputFilter::CLockCursorToScreenAction::Mode)index); + } +} + +void +CHotkeyOptions::CActionDialog::onSwitchToAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST); + CString screen = getWindowText(child); + delete s_action; + s_action = new CInputFilter::CSwitchToScreenAction(screen); +} + +void +CHotkeyOptions::CActionDialog::onSwitchInAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST); + LRESULT index = SendMessage(child, CB_GETCURSEL, 0, 0); + if (index != CB_ERR) { + delete s_action; + s_action = new CInputFilter::CSwitchInDirectionAction( + (EDirection)(index + kLeft)); + } +} + +void +CHotkeyOptions::CActionDialog::onKeyboardBroadcastAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST); + LRESULT index = SendMessage(child, CB_GETCURSEL, 0, 0); + if (index != CB_ERR) { + delete s_action; + s_action = new CInputFilter::CKeyboardBroadcastAction( + (CInputFilter::CKeyboardBroadcastAction::Mode)index, s_screens); + } +} + +KeyID +CHotkeyOptions::CActionDialog::getChar(WPARAM wParam, LPARAM lParam) +{ + BYTE keyState[256]; + UINT virtualKey = (UINT)wParam; + UINT scanCode = (UINT)((lParam & 0x0ff0000u) >> 16); + GetKeyboardState(keyState); + + // reset modifier state + keyState[VK_SHIFT] = 0; + keyState[VK_LSHIFT] = 0; + keyState[VK_RSHIFT] = 0; + keyState[VK_CONTROL] = 0; + keyState[VK_LCONTROL] = 0; + keyState[VK_RCONTROL] = 0; + keyState[VK_MENU] = 0; + keyState[VK_LMENU] = 0; + keyState[VK_RMENU] = 0; + keyState[VK_LWIN] = 0; + keyState[VK_RWIN] = 0; + + // translate virtual key to character + int n; + KeyID id; + if (CArchMiscWindows::isWindows95Family()) { + // XXX -- how do we get characters not in Latin-1? + WORD ascii; + n = ToAscii(virtualKey, scanCode, keyState, &ascii, 0); + id = static_cast(ascii & 0xffu); + } + else { + typedef int (WINAPI *ToUnicode_t)(UINT wVirtKey, + UINT wScanCode, + PBYTE lpKeyState, + LPWSTR pwszBuff, + int cchBuff, + UINT wFlags); + ToUnicode_t s_ToUnicode = NULL; + if (s_ToUnicode == NULL) { + HMODULE userModule = GetModuleHandle("user32.dll"); + s_ToUnicode = + (ToUnicode_t)GetProcAddress(userModule, "ToUnicode"); + } + + WCHAR unicode[2]; + n = s_ToUnicode(virtualKey, scanCode, keyState, + unicode, sizeof(unicode) / sizeof(unicode[0]), + 0); + id = static_cast(unicode[0]); + } + switch (n) { + case -1: + // no hot keys on dead keys + return kKeyNone; + + default: + case 0: + // unmapped + return kKeyNone; + + case 1: + return id; + } +} + +KeyModifierMask +CHotkeyOptions::CActionDialog::getModifiers() +{ + KeyModifierMask mask = 0; + if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) { + mask |= KeyModifierShift; + } + if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) { + mask |= KeyModifierControl; + } + if ((GetKeyState(VK_MENU) & 0x8000) != 0) { + mask |= KeyModifierAlt; + } + if ((GetKeyState(VK_LWIN) & 0x8000) != 0 || + (GetKeyState(VK_RWIN) & 0x8000) != 0) { + mask |= KeyModifierSuper; + } + return mask; +} + +bool +CHotkeyOptions::CActionDialog::isGoodAction() +{ + CInputFilter::CMouseButtonAction* mouseAction = + dynamic_cast(s_action); + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_action); + return (mouseAction == NULL || keyAction == NULL || + keyAction->getInfo()->m_key != kKeyNone); +} + +void +CHotkeyOptions::CActionDialog::convertAction(HWND hwnd) +{ + if (s_lastGoodAction != NULL) { + CInputFilter::CMouseButtonAction* mouseAction = + dynamic_cast(s_lastGoodAction); + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_lastGoodAction); + if (mouseAction != NULL) { + IPlatformScreen::CButtonInfo* info = + IPrimaryScreen::CButtonInfo::alloc(*mouseAction->getInfo()); + delete s_action; + if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP))) { + s_action = new CMouseButtonDownUpAction(info); + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWN))) { + s_action = new CInputFilter::CMouseButtonAction(info, true); + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_UP))) { + s_action = new CInputFilter::CMouseButtonAction(info, false); + } + else { + free(info); + s_action = NULL; + } + } + else if (keyAction != NULL) { + IPlatformScreen::CKeyInfo* info = + IKeyState::CKeyInfo::alloc(*keyAction->getInfo()); + delete s_action; + if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP))) { + s_action = new CKeystrokeDownUpAction(info); + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWN))) { + s_action = new CInputFilter::CKeystrokeAction(info, true); + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_UP))) { + s_action = new CInputFilter::CKeystrokeAction(info, false); + } + else { + free(info); + s_action = NULL; + } + } + } +} + +bool +CHotkeyOptions::CActionDialog::isDownUpAction() +{ + return (dynamic_cast(s_action) != NULL || + dynamic_cast(s_action) != NULL); +} + +BOOL CALLBACK +CHotkeyOptions::CActionDialog::dlgProc(HWND hwnd, + UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + doInit(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (isDownUpAction()) { + s_onActivate = true; + } + EndDialog(hwnd, 1); + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + + case IDC_HOTKEY_ACTION_ON_ACTIVATE: + s_onActivate = true; + return TRUE; + + case IDC_HOTKEY_ACTION_ON_DEACTIVATE: + s_onActivate = false; + return TRUE; + + case IDC_HOTKEY_ACTION_DOWNUP: + case IDC_HOTKEY_ACTION_DOWN: + case IDC_HOTKEY_ACTION_UP: + convertAction(hwnd); + fillHotkey(hwnd); + updateControls(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_LOCK: + onLockAction(hwnd); + updateControls(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_SWITCH_TO: + onSwitchToAction(hwnd); + updateControls(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_SWITCH_IN: + onSwitchInAction(hwnd); + updateControls(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST: + onKeyboardBroadcastAction(hwnd); + updateControls(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_LOCK_LIST: + switch (HIWORD(wParam)) { + case LBN_SELCHANGE: + onLockAction(hwnd); + return TRUE; + } + break; + + case IDC_HOTKEY_ACTION_SWITCH_TO_LIST: + switch (HIWORD(wParam)) { + case LBN_SELCHANGE: + onSwitchToAction(hwnd); + return TRUE; + } + break; + + case IDC_HOTKEY_ACTION_SWITCH_IN_LIST: + switch (HIWORD(wParam)) { + case LBN_SELCHANGE: + onSwitchInAction(hwnd); + return TRUE; + } + break; + + case IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST: + switch (HIWORD(wParam)) { + case LBN_SELCHANGE: + onKeyboardBroadcastAction(hwnd); + return TRUE; + } + break; + + case IDC_HOTKEY_ACTION_SCREENS: + CScreensDialog::doModal(hwnd, s_config, + dynamic_cast(s_action)); + fillHotkey(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_SCREENS: { + // convert screens to form that CScreenDialog::doModal() wants + IPlatformScreen::CKeyInfo* tmpInfo = + IPlatformScreen::CKeyInfo::alloc(0, 0, 0, 1, s_screens); + CInputFilter::CKeystrokeAction tmpAction(tmpInfo, true); + + // get the screens + CScreensDialog::doModal(hwnd, s_config, &tmpAction); + + // convert screens back + IPlatformScreen::CKeyInfo::split( + tmpAction.getInfo()->m_screens, s_screens); + + // update + onKeyboardBroadcastAction(hwnd); + return TRUE; + } + } + break; + + default: + break; + } + + return FALSE; +} + +LRESULT CALLBACK +CHotkeyOptions::CActionDialog::editProc(HWND hwnd, + UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_LBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonLeft); + } + else { + SetFocus(hwnd); + } + return 0; + + case WM_MBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonMiddle); + } + return 0; + + case WM_RBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonRight); + } + return 0; + + case WM_XBUTTONDOWN: + if (GetFocus() == hwnd) { + switch (HIWORD(wParam)) { + case XBUTTON1: + onButton(hwnd, kButtonExtra0 + 0); + break; + + case XBUTTON2: + onButton(hwnd, kButtonExtra0 + 1); + break; + } + } + return 0; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + onKey(hwnd, wParam, lParam); + return 0; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + return 0; + + case WM_SETFOCUS: + if (s_action != NULL) { + delete s_lastGoodAction; + s_lastGoodAction = s_action->clone(); + } + break; + + case WM_KILLFOCUS: + if (!isGoodAction()) { + delete s_action; + if (s_lastGoodAction != NULL) { + s_action = s_lastGoodAction->clone(); + } + else { + s_action = NULL; + } + } + else if (s_action != NULL) { + delete s_lastGoodAction; + s_lastGoodAction = s_action->clone(); + } + fillHotkey(GetParent(hwnd)); + break; + + case WM_GETDLGCODE: + return DLGC_WANTALLKEYS; + + default: + break; + } + return CallWindowProc(s_editWndProc, hwnd, message, wParam, lParam); +} + + +// +// CHotkeyOptions::CScreensDialog +// + +CConfig* CHotkeyOptions::CScreensDialog::s_config = NULL; +CInputFilter::CKeystrokeAction* + CHotkeyOptions::CScreensDialog::s_action = NULL; +CHotkeyOptions::CScreensDialog::CScreens + CHotkeyOptions::CScreensDialog::s_nonTargets; +CHotkeyOptions::CScreensDialog::CScreens + CHotkeyOptions::CScreensDialog::s_targets; +CString CHotkeyOptions::CScreensDialog::s_allScreens; + +void +CHotkeyOptions::CScreensDialog::doModal(HWND parent, CConfig* config, + CInputFilter::CKeystrokeAction* action) +{ + s_allScreens = getString(IDS_ALL_SCREENS); + s_config = config; + s_action = action; + DialogBox(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_SCREENS), + parent, dlgProc); + s_config = NULL; + s_action = NULL; +} + +void +CHotkeyOptions::CScreensDialog::doInit(HWND hwnd) +{ + s_nonTargets.clear(); + s_targets.clear(); + + // get screens from config + s_nonTargets.insert("*"); + for (CConfig::const_iterator i = s_config->begin(); + i != s_config->end(); ++i) { + s_nonTargets.insert(*i); + } + + // get screens in action + IKeyState::CKeyInfo::split(s_action->getInfo()->m_screens, s_targets); + + // remove screens in action from screens in config + for (CScreens::const_iterator i = s_targets.begin(); + i != s_targets.end(); ++i) { + s_nonTargets.erase(*i); + } + + // fill dialog + fillScreens(hwnd); + updateControls(hwnd); +} + +void +CHotkeyOptions::CScreensDialog::doFini(HWND) +{ + // put screens into action + const IPlatformScreen::CKeyInfo* oldInfo = s_action->getInfo(); + IPlatformScreen::CKeyInfo* newInfo = + IKeyState::CKeyInfo::alloc(oldInfo->m_key, + oldInfo->m_mask, 0, 0, s_targets); + s_action->adoptInfo(newInfo); +} + +void +CHotkeyOptions::CScreensDialog::fillScreens(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_SCREENS_SRC); + SendMessage(child, LB_RESETCONTENT, 0, 0); + for (CScreens::const_iterator i = s_nonTargets.begin(); + i != s_nonTargets.end(); ++i) { + CString name = *i; + if (name == "*") { + name = s_allScreens; + } + SendMessage(child, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)name.c_str()); + } + + child = getItem(hwnd, IDC_HOTKEY_SCREENS_DST); + SendMessage(child, LB_RESETCONTENT, 0, 0); + for (CScreens::const_iterator i = s_targets.begin(); + i != s_targets.end(); ++i) { + CString name = *i; + if (name == "*") { + name = s_allScreens; + } + SendMessage(child, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)name.c_str()); + } + if (s_targets.empty()) { + // if no targets then add a special item so the user knows + // what'll happen + CString activeScreenLabel = getString(IDS_ACTIVE_SCREEN); + SendMessage(child, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)activeScreenLabel.c_str()); + } +} + +void +CHotkeyOptions::CScreensDialog::updateControls(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_SCREENS_SRC); + bool canAdd = (SendMessage(child, LB_GETSELCOUNT, 0, 0) != 0); + child = getItem(hwnd, IDC_HOTKEY_SCREENS_DST); + bool canRemove = (!s_targets.empty() && + (SendMessage(child, LB_GETSELCOUNT, 0, 0) != 0)); + + enableItem(hwnd, IDC_HOTKEY_SCREENS_ADD, canAdd); + enableItem(hwnd, IDC_HOTKEY_SCREENS_REMOVE, canRemove); +} + +void +CHotkeyOptions::CScreensDialog::add(HWND hwnd) +{ + CScreens selected; + getSelected(hwnd, IDC_HOTKEY_SCREENS_SRC, s_nonTargets, selected); + for (CScreens::const_iterator i = selected.begin(); + i != selected.end(); ++i) { + s_targets.insert(*i); + s_nonTargets.erase(*i); + } + fillScreens(hwnd); + updateControls(hwnd); +} + +void +CHotkeyOptions::CScreensDialog::remove(HWND hwnd) +{ + CScreens selected; + getSelected(hwnd, IDC_HOTKEY_SCREENS_DST, s_targets, selected); + for (CScreens::const_iterator i = selected.begin(); + i != selected.end(); ++i) { + s_nonTargets.insert(*i); + s_targets.erase(*i); + } + fillScreens(hwnd); + updateControls(hwnd); +} + +void +CHotkeyOptions::CScreensDialog::getSelected(HWND hwnd, UINT id, + const CScreens& inScreens, CScreens& outScreens) +{ + // get the selected item indices + HWND child = getItem(hwnd, id); + UInt32 n = (UInt32)SendMessage(child, LB_GETSELCOUNT, 0, 0); + int* index = new int[n]; + SendMessage(child, LB_GETSELITEMS, (WPARAM)n, (LPARAM)index); + + // get the items in a vector + std::vector tmpList; + for (CScreens::const_iterator i = inScreens.begin(); + i != inScreens.end(); ++i) { + tmpList.push_back(*i); + } + + // get selected items into the output set + outScreens.clear(); + for (UInt32 i = 0; i < n; ++i) { + outScreens.insert(tmpList[index[i]]); + } + + // clean up + delete[] index; +} + +BOOL CALLBACK +CHotkeyOptions::CScreensDialog::dlgProc(HWND hwnd, + UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_INITDIALOG: + doInit(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + doFini(hwnd); + EndDialog(hwnd, 0); + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + + case IDC_HOTKEY_SCREENS_ADD: + add(hwnd); + return TRUE; + + case IDC_HOTKEY_SCREENS_REMOVE: + remove(hwnd); + return TRUE; + + case IDC_HOTKEY_SCREENS_SRC: + case IDC_HOTKEY_SCREENS_DST: + switch (HIWORD(wParam)) { + case LBN_SELCANCEL: + case LBN_SELCHANGE: + updateControls(hwnd); + return TRUE; + } + break; + } + break; + + case WM_CTLCOLORLISTBOX: + if (s_targets.empty() && + (HWND)lParam == getItem(hwnd, IDC_HOTKEY_SCREENS_DST)) { + // override colors + HDC dc = (HDC)wParam; + SetTextColor(dc, GetSysColor(COLOR_GRAYTEXT)); + return (BOOL)GetSysColorBrush(COLOR_WINDOW); + } + break; + + default: + break; + } + + return FALSE; +} diff --git a/cmd/launcher/CHotkeyOptions.h b/cmd/launcher/CHotkeyOptions.h new file mode 100644 index 00000000..dec5367a --- /dev/null +++ b/cmd/launcher/CHotkeyOptions.h @@ -0,0 +1,227 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CHOTKEYOPTIONS_H +#define CHOTKEYOPTIONS_H + +#include "CString.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "CInputFilter.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +class CConfig; + +//! Hotkey options dialog for Microsoft Windows launcher +class CHotkeyOptions { +public: + CHotkeyOptions(HWND parent, CConfig*); + ~CHotkeyOptions(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(); + + //@} + //! @name accessors + //@{ + + //@} + +private: + void doInit(HWND hwnd); + + void fillHotkeys(HWND hwnd, UInt32 select = (UInt32)-1); + void updateHotkeysControls(HWND hwnd); + + void addHotkey(HWND hwnd); + void removeHotkey(HWND hwnd); + void editHotkey(HWND hwnd); + + void fillActions(HWND hwnd, UInt32 select = (UInt32)-1); + void updateActionsControls(HWND hwnd); + + void addAction(HWND hwnd); + void removeAction(HWND hwnd); + void editAction(HWND hwnd); + + bool editCondition(HWND hwnd, CInputFilter::CCondition*&); + bool editAction(HWND hwnd, CInputFilter::CAction*&, + bool& onActivate); + + void openRule(HWND hwnd); + void closeRule(HWND hwnd); + UInt32 findMatchingAction( + const CInputFilter::CKeystrokeAction*) const; + UInt32 findMatchingAction( + const CInputFilter::CMouseButtonAction*) const; + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + + // special actions we use to combine matching down/up actions into a + // single action for the convenience of the user. + class CKeystrokeDownUpAction : public CInputFilter::CKeystrokeAction { + public: + CKeystrokeDownUpAction(IPlatformScreen::CKeyInfo* adoptedInfo) : + CInputFilter::CKeystrokeAction(adoptedInfo, true) { } + + // CAction overrides + virtual CInputFilter::CAction* clone() const + { + IKeyState::CKeyInfo* info = IKeyState::CKeyInfo::alloc(*getInfo()); + return new CKeystrokeDownUpAction(info); + } + + protected: + // CKeystrokeAction overrides + virtual const char* formatName() const { return "keystroke"; } + }; + class CMouseButtonDownUpAction : public CInputFilter::CMouseButtonAction { + public: + CMouseButtonDownUpAction(IPrimaryScreen::CButtonInfo* adoptedInfo) : + CInputFilter::CMouseButtonAction(adoptedInfo, true) { } + + // CAction overrides + virtual CInputFilter::CAction* clone() const + { + IPlatformScreen::CButtonInfo* info = + IPrimaryScreen::CButtonInfo::alloc(*getInfo()); + return new CMouseButtonDownUpAction(info); + } + + protected: + // CMouseButtonAction overrides + virtual const char* formatName() const { return "mousebutton"; } + }; + + class CConditionDialog { + public: + static bool doModal(HWND parent, CInputFilter::CCondition*&); + + private: + static void doInit(HWND hwnd); + static void fillHotkey(HWND hwnd); + + static void onButton(HWND hwnd, ButtonID button); + static void onKey(HWND hwnd, WPARAM wParam, LPARAM lParam); + static KeyID getChar(WPARAM wParam, LPARAM lParam); + static KeyModifierMask + getModifiers(); + + static bool isGoodCondition(); + + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK editProc(HWND hwnd, UINT, WPARAM, LPARAM); + + private: + static CInputFilter::CCondition* + s_condition; + static CInputFilter::CCondition* + s_lastGoodCondition; + static WNDPROC s_editWndProc; + }; + + class CActionDialog { + public: + static bool doModal(HWND parent, CConfig* config, + CInputFilter::CAction*&, bool& onActivate); + + private: + static void doInit(HWND hwnd); + static void fillHotkey(HWND hwnd); + static void updateControls(HWND hwnd); + + static void onButton(HWND hwnd, ButtonID button); + static void onKey(HWND hwnd, WPARAM wParam, LPARAM lParam); + static void onLockAction(HWND hwnd); + static void onSwitchToAction(HWND hwnd); + static void onSwitchInAction(HWND hwnd); + static void onKeyboardBroadcastAction(HWND hwnd); + + static KeyID getChar(WPARAM wParam, LPARAM lParam); + static KeyModifierMask + getModifiers(); + + static bool isGoodAction(); + static void convertAction(HWND hwnd); + + static bool isDownUpAction(); + + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK editProc(HWND hwnd, UINT, WPARAM, LPARAM); + + private: + static CConfig* s_config; + static bool s_onActivate; + static CInputFilter::CAction* + s_action; + static CInputFilter::CAction* + s_lastGoodAction; + static std::set s_screens; + static WNDPROC s_editWndProc; + }; + +// public to allow CActionDialog to use it +public: + class CScreensDialog { + public: + static void doModal(HWND parent, CConfig* config, + CInputFilter::CKeystrokeAction*); + + // public due to compiler brokenness + typedef std::set CScreens; + + private: + + static void doInit(HWND hwnd); + static void doFini(HWND hwnd); + static void fillScreens(HWND hwnd); + static void updateControls(HWND hwnd); + + static void add(HWND hwnd); + static void remove(HWND hwnd); + + static void getSelected(HWND hwnd, UINT id, + const CScreens& inScreens, CScreens& outScreens); + + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + + private: + static CConfig* s_config; + static CInputFilter::CKeystrokeAction* s_action; + static CScreens s_nonTargets; + static CScreens s_targets; + static CString s_allScreens; + }; + +private: + static CHotkeyOptions* s_singleton; + + HWND m_parent; + CConfig* m_config; + CInputFilter* m_inputFilter; + CInputFilter::CRule m_activeRule; + UInt32 m_activeRuleIndex; +}; + +#endif diff --git a/cmd/launcher/CInfo.cpp b/cmd/launcher/CInfo.cpp new file mode 100644 index 00000000..669da0e2 --- /dev/null +++ b/cmd/launcher/CInfo.cpp @@ -0,0 +1,111 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 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 "ProtocolTypes.h" +#include "CStringUtil.h" +#include "Version.h" +#include "CArch.h" +#include "CInfo.h" +#include "LaunchUtil.h" +#include "resource.h" + +// +// CInfo +// + +CInfo* CInfo::s_singleton = NULL; + +CInfo::CInfo(HWND parent) : + m_parent(parent) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CInfo::~CInfo() +{ + s_singleton = NULL; +} + +void +CInfo::doModal() +{ + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_INFO), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); +} + +void +CInfo::init(HWND hwnd) +{ + // collect info + CString version = + CStringUtil::format(getString(IDS_TITLE).c_str(), VERSION); + CString hostname = ARCH->getHostName(); + CString address = ARCH->addrToString(ARCH->nameToAddr(hostname)); + CString userConfig = ARCH->getUserDirectory(); + if (!userConfig.empty()) { + userConfig = ARCH->concatPath(userConfig, CONFIG_NAME); + } + CString sysConfig = ARCH->getSystemDirectory(); + if (!sysConfig.empty()) { + sysConfig = ARCH->concatPath(sysConfig, CONFIG_NAME); + } + + // set info + HWND child; + child = getItem(hwnd, IDC_INFO_VERSION); + setWindowText(child, version); + child = getItem(hwnd, IDC_INFO_HOSTNAME); + setWindowText(child, hostname); + child = getItem(hwnd, IDC_INFO_IP_ADDRESS); + setWindowText(child, address); + child = getItem(hwnd, IDC_INFO_USER_CONFIG); + setWindowText(child, userConfig); + child = getItem(hwnd, IDC_INFO_SYS_CONFIG); + setWindowText(child, sysConfig); + + // focus on okay button + SetFocus(getItem(hwnd, IDOK)); +} + +BOOL +CInfo::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + init(hwnd); + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CInfo::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} diff --git a/cmd/launcher/CInfo.h b/cmd/launcher/CInfo.h new file mode 100644 index 00000000..2d85ec7d --- /dev/null +++ b/cmd/launcher/CInfo.h @@ -0,0 +1,57 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CINFO_H +#define CINFO_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +//! Info dialog for Microsoft Windows launcher +class CInfo { +public: + CInfo(HWND parent); + ~CInfo(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(); + + //@} + //! @name accessors + //@{ + + //@} + +private: + void init(HWND hwnd); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CInfo* s_singleton; + + HWND m_parent; +}; + +#endif diff --git a/cmd/launcher/CScreensLinks.cpp b/cmd/launcher/CScreensLinks.cpp new file mode 100644 index 00000000..c7e58a04 --- /dev/null +++ b/cmd/launcher/CScreensLinks.cpp @@ -0,0 +1,855 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "ProtocolTypes.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CScreensLinks.h" +#include "CAddScreen.h" +#include "LaunchUtil.h" +#include "resource.h" + +// +// CScreensLinks +// + +CScreensLinks* CScreensLinks::s_singleton = NULL; + +CScreensLinks::CScreensLinks(HWND parent, CConfig* config) : + m_parent(parent), + m_mainConfig(config), + m_config(&m_scratchConfig) +{ + assert(s_singleton == NULL); + s_singleton = this; + + // get formatting strings + m_linkFormat = getString(IDS_LINK_FORMAT); + m_intervalFormat = getString(IDS_LINK_INTERVAL_FORMAT); + m_newLinkLabel = getString(IDS_NEW_LINK); + m_sideLabel[kLeft - kFirstDirection] = getString(IDS_SIDE_LEFT); + m_sideLabel[kRight - kFirstDirection] = getString(IDS_SIDE_RIGHT); + m_sideLabel[kTop - kFirstDirection] = getString(IDS_SIDE_TOP); + m_sideLabel[kBottom - kFirstDirection] = getString(IDS_SIDE_BOTTOM); + + // GDI objects + m_redPen = CreatePen(PS_INSIDEFRAME, 1, RGB(255, 0, 0)); +} + +CScreensLinks::~CScreensLinks() +{ + DeleteObject(m_redPen); + s_singleton = NULL; +} + +void +CScreensLinks::doModal() +{ + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_SCREENS_LINKS), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); +} + +void +CScreensLinks::init(HWND hwnd) +{ + // get initial config + m_scratchConfig = *m_mainConfig; + + // fill side list box (in EDirection order) + HWND child = getItem(hwnd, IDC_SCREENS_SRC_SIDE); + SendMessage(child, CB_ADDSTRING, 0, (LPARAM)TEXT("---")); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_LEFT).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_RIGHT).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_TOP).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_BOTTOM).c_str()); + + // create error boxes + m_srcSideError = createErrorBox(hwnd); + m_srcScreenError = createErrorBox(hwnd); + m_dstScreenError = createErrorBox(hwnd); + resizeErrorBoxes(); + + m_selectedLink = -1; + m_editedLink = CEdgeLink(); + m_edgeLinks.clear(); + updateScreens(hwnd, ""); + updateScreensControls(hwnd); + updateLinks(hwnd); + updateLinksControls(hwnd); +} + +bool +CScreensLinks::save(HWND /*hwnd*/) +{ + *m_mainConfig = m_scratchConfig; + return true; +} + +BOOL +CScreensLinks::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_INITDIALOG: + init(hwnd); + return TRUE; + + case WM_SIZE: + resizeErrorBoxes(); + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + SetFocus(getItem(hwnd, IDOK)); + if (save(hwnd)) { + EndDialog(hwnd, 0); + } + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + + case IDC_SCREENS_SCREENS: + switch (HIWORD(wParam)) { + case LBN_DBLCLK: + editScreen(hwnd); + return TRUE; + + case LBN_SELCHANGE: + updateScreensControls(hwnd); + updateLinkView(hwnd); + return TRUE; + + case LBN_SELCANCEL: + updateScreensControls(hwnd); + updateLinkView(hwnd); + return TRUE; + } + break; + + case IDC_SCREENS_ADD_SCREEN: + addScreen(hwnd); + return TRUE; + + case IDC_SCREENS_REMOVE_SCREEN: + removeScreen(hwnd); + return TRUE; + + case IDC_SCREENS_EDIT_SCREEN: + editScreen(hwnd); + return TRUE; + + case IDC_SCREENS_LINKS: + switch (HIWORD(wParam)) { + case LBN_SELCHANGE: + editLink(hwnd); + return TRUE; + + case LBN_SELCANCEL: + editLink(hwnd); + return TRUE; + } + break; + + case IDC_SCREENS_ADD_LINK: + addLink(hwnd); + return TRUE; + + case IDC_SCREENS_REMOVE_LINK: + removeLink(hwnd); + return TRUE; + + case IDC_SCREENS_SRC_SIDE: + switch (HIWORD(wParam)) { + case CBN_SELCHANGE: + changeSrcSide(hwnd); + break; + } + break; + + case IDC_SCREENS_SRC_SCREEN: + switch (HIWORD(wParam)) { + case CBN_SELCHANGE: + changeSrcScreen(hwnd); + break; + } + break; + + case IDC_SCREENS_DST_SCREEN: + switch (HIWORD(wParam)) { + case CBN_SELCHANGE: + changeDstScreen(hwnd); + break; + } + break; + + case IDC_SCREENS_SRC_START: + switch (HIWORD(wParam)) { + case EN_KILLFOCUS: + changeIntervalStart(hwnd, LOWORD(wParam), + m_editedLink.m_srcInterval); + break; + } + break; + + case IDC_SCREENS_SRC_END: + switch (HIWORD(wParam)) { + case EN_KILLFOCUS: + changeIntervalEnd(hwnd, LOWORD(wParam), + m_editedLink.m_srcInterval); + break; + } + break; + + case IDC_SCREENS_DST_START: + switch (HIWORD(wParam)) { + case EN_KILLFOCUS: + changeIntervalStart(hwnd, LOWORD(wParam), + m_editedLink.m_dstInterval); + break; + } + break; + + case IDC_SCREENS_DST_END: + switch (HIWORD(wParam)) { + case EN_KILLFOCUS: + changeIntervalEnd(hwnd, LOWORD(wParam), + m_editedLink.m_dstInterval); + break; + } + break; + } + + break; + + case WM_CTLCOLORSTATIC: + switch (GetDlgCtrlID((HWND)lParam)) { + case IDC_SCREENS_OVERLAP_ERROR: + SetBkColor((HDC)wParam, GetSysColor(COLOR_3DFACE)); + SetTextColor((HDC)wParam, RGB(255, 0, 0)); + return (BOOL)GetSysColorBrush(COLOR_3DFACE); + } + break; + + // error outlines + case WM_DRAWITEM: { + DRAWITEMSTRUCT* di = (DRAWITEMSTRUCT*)lParam; + if (di->CtlType == ODT_STATIC) { + HGDIOBJ oldPen = SelectObject(di->hDC, m_redPen); + HGDIOBJ oldBrush = SelectObject(di->hDC, + GetStockObject(NULL_BRUSH)); + Rectangle(di->hDC, di->rcItem.left, di->rcItem.top, + di->rcItem.right, di->rcItem.bottom); + SelectObject(di->hDC, oldPen); + SelectObject(di->hDC, oldBrush); + return TRUE; + } + break; + } + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CScreensLinks::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} + +CString +CScreensLinks::getSelectedScreen(HWND hwnd) const +{ + HWND child = getItem(hwnd, IDC_SCREENS_SCREENS); + LRESULT index = SendMessage(child, LB_GETCURSEL, 0, 0); + if (index == LB_ERR) { + return CString(); + } + + LRESULT size = SendMessage(child, LB_GETTEXTLEN, index, 0); + char* buffer = new char[size + 1]; + SendMessage(child, LB_GETTEXT, index, (LPARAM)buffer); + buffer[size] = '\0'; + CString result(buffer); + delete[] buffer; + return result; +} + +void +CScreensLinks::addScreen(HWND hwnd) +{ + CAddScreen dialog(hwnd, m_config, ""); + if (dialog.doModal()) { + updateScreens(hwnd, dialog.getName()); + updateScreensControls(hwnd); + updateLinks(hwnd); + updateLinksControls(hwnd); + } +} + +void +CScreensLinks::editScreen(HWND hwnd) +{ + CString oldName = getSelectedScreen(hwnd); + CAddScreen dialog(hwnd, m_config, oldName); + if (dialog.doModal()) { + CString newName = dialog.getName(); + + // rename screens in the edge list + if (newName != oldName) { + for (size_t i = 0; i < m_edgeLinks.size(); ++i) { + m_edgeLinks[i].rename(oldName, newName); + } + m_editedLink.rename(oldName, newName); + } + + updateScreens(hwnd, newName); + updateScreensControls(hwnd); + updateLinks(hwnd); + updateLinksControls(hwnd); + } +} + +void +CScreensLinks::removeScreen(HWND hwnd) +{ + // remove screen from config (this also removes aliases) + m_config->removeScreen(getSelectedScreen(hwnd)); + + // update dialog + updateScreens(hwnd, ""); + updateScreensControls(hwnd); + updateLinks(hwnd); + updateLinksControls(hwnd); +} + +void +CScreensLinks::addLink(HWND hwnd) +{ + if (m_editedLink.connect(m_config)) { + m_editedLink = CEdgeLink(); + updateLinks(hwnd); + updateLinksControls(hwnd); + } +} + +void +CScreensLinks::editLink(HWND hwnd) +{ + // get selection + HWND child = getItem(hwnd, IDC_SCREENS_LINKS); + DWORD i = SendMessage(child, LB_GETCURSEL, 0, 0); + if (i != LB_ERR && i != (DWORD)m_edgeLinks.size()) { + // existing link + m_selectedLink = (SInt32)SendMessage(child, LB_GETITEMDATA, i, 0); + m_editedLink = m_edgeLinks[m_selectedLink]; + } + else { + // new link + m_selectedLink = -1; + m_editedLink = CEdgeLink(); + } + updateLinksControls(hwnd); +} + +void +CScreensLinks::removeLink(HWND hwnd) +{ + if (m_editedLink.disconnect(m_config)) { + updateLinks(hwnd); + updateLinksControls(hwnd); + } +} + +void +CScreensLinks::updateScreens(HWND hwnd, const CString& selectName) +{ + HWND child; + + // set screen list + child = getItem(hwnd, IDC_SCREENS_SCREENS); + SendMessage(child, LB_RESETCONTENT, 0, 0); + for (CConfig::const_iterator index = m_config->begin(); + index != m_config->end(); ) { + const CString& name = *index; + ++index; + if (index != m_config->end()) { + SendMessage(child, LB_INSERTSTRING, + (WPARAM)-1, (LPARAM)name.c_str()); + } + else { + SendMessage(child, LB_ADDSTRING, 0, (LPARAM)name.c_str()); + } + } + + // find the named screen + if (!selectName.empty()) { + DWORD i = SendMessage(child, LB_FINDSTRINGEXACT, + (UINT)-1, (LPARAM)selectName.c_str()); + if (i != LB_ERR) { + SendMessage(child, LB_SETSEL, TRUE, i); + } + } +} + +void +CScreensLinks::updateScreensControls(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_SCREENS_SCREENS); + bool screenSelected = (SendMessage(child, LB_GETCURSEL, 0, 0) != LB_ERR); + + enableItem(hwnd, IDC_SCREENS_ADD_SCREEN, TRUE); + enableItem(hwnd, IDC_SCREENS_EDIT_SCREEN, screenSelected); + enableItem(hwnd, IDC_SCREENS_REMOVE_SCREEN, screenSelected); +} + +void +CScreensLinks::updateLinks(HWND hwnd) +{ + HWND links = getItem(hwnd, IDC_SCREENS_LINKS); + HWND srcScreens = getItem(hwnd, IDC_SCREENS_SRC_SCREEN); + HWND dstScreens = getItem(hwnd, IDC_SCREENS_DST_SCREEN); + + // get old selection + CEdgeLink oldLink; + if (m_selectedLink != -1) { + oldLink = m_edgeLinks[m_selectedLink]; + } + + // clear links and screens + SendMessage(links, LB_RESETCONTENT, 0, 0); + SendMessage(srcScreens, CB_RESETCONTENT, 0, 0); + SendMessage(dstScreens, CB_RESETCONTENT, 0, 0); + m_edgeLinks.clear(); + + // add "no screen" items + SendMessage(srcScreens, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)TEXT("----")); + SendMessage(dstScreens, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)TEXT("----")); + + // add links and screens + for (CConfig::const_iterator i = m_config->begin(); + i != m_config->end(); ++i) { + const CString& name = *i; + + // add screen + SendMessage(srcScreens, CB_INSERTSTRING, (WPARAM)-1, + (LPARAM)name.c_str()); + SendMessage(dstScreens, CB_INSERTSTRING, (WPARAM)-1, + (LPARAM)name.c_str()); + + // add links for screen + for (CConfig::link_const_iterator j = m_config->beginNeighbor(name), + n = m_config->endNeighbor(name); + j != n; ++j) { + DWORD k = m_edgeLinks.size(); + m_edgeLinks.push_back(CEdgeLink(name, *j)); + SendMessage(links, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)formatLink(m_edgeLinks.back()).c_str()); + SendMessage(links, LB_SETITEMDATA, (WPARAM)k, (LPARAM)k); + } + } + + // add "new link" item to sort + SendMessage(links, LB_ADDSTRING, 0, (LPARAM)m_newLinkLabel.c_str()); + + // remove the "new link" item then insert it on the end + DWORD i = SendMessage(links, LB_FINDSTRINGEXACT, + (UINT)-1, (LPARAM)m_newLinkLabel.c_str()); + if (i != LB_ERR) { + SendMessage(links, LB_DELETESTRING, i, 0); + } + SendMessage(links, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)getString(IDS_NEW_LINK).c_str()); + SendMessage(links, LB_SETITEMDATA, (WPARAM)m_edgeLinks.size(), + (LPARAM)-1); + + // select the same link as before + SendMessage(links, LB_SETCURSEL, (WPARAM)m_edgeLinks.size(), 0); + if (m_selectedLink != -1) { + m_selectedLink = -1; + for (size_t j = 0; j < m_edgeLinks.size(); ++j) { + if (m_edgeLinks[j] == oldLink) { + // found matching link + m_selectedLink = j; + for (size_t k = 0; k < m_edgeLinks.size(); ++k) { + if (SendMessage(links, LB_GETITEMDATA, k, 0) == (int)j) { + SendMessage(links, LB_SETCURSEL, k, 0); + break; + } + } + break; + } + } + + // if we can't find the link anymore then reset edited link + if (m_selectedLink == -1) { + m_editedLink = CEdgeLink(); + } + } +} + +void +CScreensLinks::updateLinksControls(HWND hwnd) +{ + // get selection. select "new link" if nothing is selected. + HWND child = getItem(hwnd, IDC_SCREENS_LINKS); + if (m_selectedLink == -1) { + SendMessage(child, LB_SETCURSEL, m_edgeLinks.size(), 0); + } + + // enable/disable remove button + enableItem(hwnd, IDC_SCREENS_REMOVE_LINK, m_selectedLink != -1); + + // fill link entry controls from m_editedLink + updateLinkEditControls(hwnd, m_editedLink); + updateLinkValid(hwnd, m_editedLink); + updateLinkView(hwnd); +} + +void +CScreensLinks::changeSrcSide(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_SCREENS_SRC_SIDE); + m_editedLink.m_srcSide = (EDirection)SendMessage(child, CB_GETCURSEL, 0, 0); + updateLink(hwnd); +} + +void +CScreensLinks::changeSrcScreen(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_SCREENS_SRC_SCREEN); + m_editedLink.m_srcName = getWindowText(child); + updateLink(hwnd); +} + +void +CScreensLinks::changeDstScreen(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_SCREENS_DST_SCREEN); + m_editedLink.m_dstName = getWindowText(child); + updateLink(hwnd); +} + +void +CScreensLinks::changeIntervalStart(HWND hwnd, int id, CConfig::CInterval& i) +{ + int x = (int)GetDlgItemInt(hwnd, id, NULL, FALSE); + if (x < 0) { + x = 0; + } + else if (x > 99) { + x = 99; + } + + i.first = 0.01f * (float)x; + if (i.first >= i.second) { + i.second = 0.01f * (float)(x + 1); + } + + updateLinkIntervalControls(hwnd, m_editedLink); + updateLink(hwnd); +} + +void +CScreensLinks::changeIntervalEnd(HWND hwnd, int id, CConfig::CInterval& i) +{ + int x = (int)GetDlgItemInt(hwnd, id, NULL, FALSE); + if (x < 1) { + x = 1; + } + else if (x > 100) { + x = 100; + } + + i.second = 0.01f * (float)x; + if (i.first >= i.second) { + i.first = 0.01f * (float)(x - 1); + } + + updateLinkIntervalControls(hwnd, m_editedLink); + updateLink(hwnd); +} + +void +CScreensLinks::selectScreen(HWND hwnd, int id, const CString& name) +{ + HWND child = getItem(hwnd, id); + DWORD i = SendMessage(child, CB_FINDSTRINGEXACT, (WPARAM)-1, + (LPARAM)name.c_str()); + if (i == CB_ERR) { + // no match, select no screen + SendMessage(child, CB_SETCURSEL, 0, 0); + } + else { + SendMessage(child, CB_SETCURSEL, i, 0); + } +} + +void +CScreensLinks::updateLinkEditControls(HWND hwnd, const CEdgeLink& link) +{ + // fill link entry controls from link + HWND child = getItem(hwnd, IDC_SCREENS_SRC_SIDE); + SendMessage(child, CB_SETCURSEL, link.m_srcSide, 0); + selectScreen(hwnd, IDC_SCREENS_SRC_SCREEN, link.m_srcName); + selectScreen(hwnd, IDC_SCREENS_DST_SCREEN, link.m_dstName); + updateLinkIntervalControls(hwnd, link); +} + +void +CScreensLinks::updateLinkIntervalControls(HWND hwnd, const CEdgeLink& link) +{ + HWND child; + + // src interval + child = getItem(hwnd, IDC_SCREENS_SRC_START); + setWindowText(child, formatIntervalValue(link.m_srcInterval.first)); + child = getItem(hwnd, IDC_SCREENS_SRC_END); + setWindowText(child, formatIntervalValue(link.m_srcInterval.second)); + + // dst interval + child = getItem(hwnd, IDC_SCREENS_DST_START); + setWindowText(child, formatIntervalValue(link.m_dstInterval.first)); + child = getItem(hwnd, IDC_SCREENS_DST_END); + setWindowText(child, formatIntervalValue(link.m_dstInterval.second)); +} + +void +CScreensLinks::updateLink(HWND hwnd) +{ + updateLinkValid(hwnd, m_editedLink); + + // update link in config + if (m_selectedLink != -1 && m_editedLinkIsValid) { + // editing an existing link and entry is valid + if (m_edgeLinks[m_selectedLink].disconnect(m_config)) { + // successfully removed old link + if (!m_editedLink.connect(m_config)) { + // couldn't set new link so restore old link + m_edgeLinks[m_selectedLink].connect(m_config); + } + else { + m_edgeLinks[m_selectedLink] = m_editedLink; + updateLinks(hwnd); + updateLinkEditControls(hwnd, m_editedLink); + } + } + } + + updateLinkView(hwnd); +} + +void +CScreensLinks::updateLinkValid(HWND hwnd, const CEdgeLink& link) +{ + m_editedLinkIsValid = true; + + // check source side and screen + if (link.m_srcSide == kNoDirection) { + m_editedLinkIsValid = false; + ShowWindow(m_srcSideError, SW_SHOWNA); + } + else { + ShowWindow(m_srcSideError, SW_HIDE); + } + if (!m_config->isCanonicalName(link.m_srcName)) { + m_editedLinkIsValid = false; + ShowWindow(m_srcScreenError, SW_SHOWNA); + } + else { + ShowWindow(m_srcScreenError, SW_HIDE); + } + + // check for overlap. if editing a link we must remove it, then + // check for overlap and restore the old link. + bool overlap = false; + if (m_editedLinkIsValid) { + if (m_selectedLink == -1) { + if (link.overlaps(m_config)) { + m_editedLinkIsValid = false; + overlap = true; + } + } + else { + if (m_edgeLinks[m_selectedLink].disconnect(m_config)) { + overlap = link.overlaps(m_config); + m_edgeLinks[m_selectedLink].connect(m_config); + if (overlap) { + m_editedLinkIsValid = false; + } + } + } + } + ShowWindow(getItem(hwnd, IDC_SCREENS_OVERLAP_ERROR), + overlap ? SW_SHOWNA : SW_HIDE); + + // check dst screen + if (!m_config->isCanonicalName(link.m_dstName)) { + m_editedLinkIsValid = false; + ShowWindow(m_dstScreenError, SW_SHOWNA); + } + else { + ShowWindow(m_dstScreenError, SW_HIDE); + } + + // update add link button + enableItem(hwnd, IDC_SCREENS_ADD_LINK, + m_selectedLink == -1 && m_editedLinkIsValid); +} + +void +CScreensLinks::updateLinkView(HWND /*hwnd*/) +{ + // XXX -- draw visual of selected screen, highlighting selected link +} + +HWND +CScreensLinks::createErrorBox(HWND parent) +{ + return CreateWindow(TEXT("STATIC"), TEXT(""), + WS_CHILD | SS_OWNERDRAW, + 0, 0, 1, 1, + parent, (HMENU)-1, + s_instance, NULL); +} + +void +CScreensLinks::resizeErrorBoxes() +{ + HWND hwnd = GetParent(m_srcSideError); + resizeErrorBox(m_srcSideError, getItem(hwnd, IDC_SCREENS_SRC_SIDE)); + resizeErrorBox(m_srcScreenError, getItem(hwnd, IDC_SCREENS_SRC_SCREEN)); + resizeErrorBox(m_dstScreenError, getItem(hwnd, IDC_SCREENS_DST_SCREEN)); +} + +void +CScreensLinks::resizeErrorBox(HWND box, HWND assoc) +{ + RECT rect; + GetWindowRect(assoc, &rect); + MapWindowPoints(NULL, GetParent(box), (POINT*)&rect, 2); + SetWindowPos(box, HWND_TOP, rect.left - 1, rect.top - 1, + rect.right - rect.left + 2, + rect.bottom - rect.top + 2, SWP_NOACTIVATE); +} + +CString +CScreensLinks::formatIntervalValue(float x) const +{ + return CStringUtil::print("%d", (int)(x * 100.0f + 0.5f)); +} + +CString +CScreensLinks::formatInterval(const CConfig::CInterval& i) const +{ + if (i.first == 0.0f && i.second == 1.0f) { + return ""; + } + else { + CString start = formatIntervalValue(i.first); + CString end = formatIntervalValue(i.second); + return CStringUtil::format(m_intervalFormat.c_str(), + start.c_str(), end.c_str()); + } +} + +CString +CScreensLinks::formatLink(const CEdgeLink& link) const +{ + CString srcInterval = formatInterval(link.m_srcInterval); + CString dstInterval = formatInterval(link.m_dstInterval); + return CStringUtil::format(m_linkFormat.c_str(), + link.m_srcName.c_str(), srcInterval.c_str(), + m_sideLabel[link.m_srcSide - kFirstDirection].c_str(), + link.m_dstName.c_str(), dstInterval.c_str()); +} + +// +// CScreensLinks::CEdgeLink +// + +CScreensLinks::CEdgeLink::CEdgeLink() : + m_srcName(), + m_srcSide(kNoDirection), + m_srcInterval(0.0f, 1.0f), + m_dstName(), + m_dstInterval(0.0f, 1.0f) +{ + // do nothing +} + +CScreensLinks::CEdgeLink::CEdgeLink(const CString& name, + const CConfigLink& link) : + m_srcName(name), + m_srcSide(link.first.getSide()), + m_srcInterval(link.first.getInterval()), + m_dstName(link.second.getName()), + m_dstInterval(link.second.getInterval()) +{ + // do nothing +} + +bool +CScreensLinks::CEdgeLink::connect(CConfig* config) +{ + return config->connect(m_srcName, m_srcSide, + m_srcInterval.first, m_srcInterval.second, + m_dstName, + m_dstInterval.first, m_dstInterval.second); +} + +bool +CScreensLinks::CEdgeLink::disconnect(CConfig* config) +{ + return config->disconnect(m_srcName, m_srcSide, 0.5f * + (m_srcInterval.first + m_srcInterval.second)); +} + +void +CScreensLinks::CEdgeLink::rename(const CString& oldName, const CString& newName) +{ + if (m_srcName == oldName) { + m_srcName = newName; + } + if (m_dstName == oldName) { + m_dstName = newName; + } +} + +bool +CScreensLinks::CEdgeLink::overlaps(const CConfig* config) const +{ + return config->hasNeighbor(m_srcName, m_srcSide, + m_srcInterval.first, m_srcInterval.second); +} + +bool +CScreensLinks::CEdgeLink::operator==(const CEdgeLink& x) const +{ + return (m_srcName == x.m_srcName && + m_srcSide == x.m_srcSide && + m_srcInterval == x.m_srcInterval && + m_dstName == x.m_dstName && + m_dstInterval == x.m_dstInterval); +} diff --git a/cmd/launcher/CScreensLinks.h b/cmd/launcher/CScreensLinks.h new file mode 100644 index 00000000..8ffd0089 --- /dev/null +++ b/cmd/launcher/CScreensLinks.h @@ -0,0 +1,138 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSCREENSLINKS_H +#define CSCREENSLINKS_H + +#include "CConfig.h" +#include "ProtocolTypes.h" +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +//! Screens and links dialog for Microsoft Windows launcher +class CScreensLinks { +public: + CScreensLinks(HWND parent, CConfig*); + ~CScreensLinks(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(); + + //@} + //! @name accessors + //@{ + + + //@} + +private: + typedef std::pair CConfigLink; + struct CEdgeLink { + public: + CEdgeLink(); + CEdgeLink(const CString& name, const CConfigLink&); + + bool connect(CConfig*); + bool disconnect(CConfig*); + void rename(const CString& oldName, const CString& newName); + + bool overlaps(const CConfig* config) const; + bool operator==(const CEdgeLink&) const; + + public: + CString m_srcName; + EDirection m_srcSide; + CConfig::CInterval m_srcInterval; + CString m_dstName; + CConfig::CInterval m_dstInterval; + }; + typedef std::vector CEdgeLinkList; + + void init(HWND hwnd); + bool save(HWND hwnd); + + CString getSelectedScreen(HWND hwnd) const; + void addScreen(HWND hwnd); + void editScreen(HWND hwnd); + void removeScreen(HWND hwnd); + void addLink(HWND hwnd); + void editLink(HWND hwnd); + void removeLink(HWND hwnd); + + void updateScreens(HWND hwnd, const CString& name); + void updateScreensControls(HWND hwnd); + void updateLinks(HWND hwnd); + void updateLinksControls(HWND hwnd); + + void changeSrcSide(HWND hwnd); + void changeSrcScreen(HWND hwnd); + void changeDstScreen(HWND hwnd); + void changeIntervalStart(HWND hwnd, int id, + CConfig::CInterval&); + void changeIntervalEnd(HWND hwnd, int id, + CConfig::CInterval&); + + void selectScreen(HWND hwnd, int id, const CString& name); + void updateLinkEditControls(HWND hwnd, + const CEdgeLink& link); + void updateLinkIntervalControls(HWND hwnd, + const CEdgeLink& link); + void updateLink(HWND hwnd); + void updateLinkValid(HWND hwnd, const CEdgeLink& link); + + void updateLinkView(HWND hwnd); + + HWND createErrorBox(HWND parent); + void resizeErrorBoxes(); + void resizeErrorBox(HWND box, HWND assoc); + + CString formatIntervalValue(float) const; + CString formatInterval(const CConfig::CInterval&) const; + CString formatLink(const CEdgeLink&) const; + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CScreensLinks* s_singleton; + + HWND m_parent; + CConfig* m_mainConfig; + CConfig m_scratchConfig; + CConfig* m_config; + + CString m_linkFormat; + CString m_intervalFormat; + CString m_newLinkLabel; + CString m_sideLabel[kNumDirections]; + CEdgeLinkList m_edgeLinks; + SInt32 m_selectedLink; + CEdgeLink m_editedLink; + bool m_editedLinkIsValid; + HPEN m_redPen; + HWND m_srcSideError; + HWND m_srcScreenError; + HWND m_dstScreenError; +}; + +#endif diff --git a/cmd/launcher/LaunchUtil.cpp b/cmd/launcher/LaunchUtil.cpp new file mode 100644 index 00000000..05b517e7 --- /dev/null +++ b/cmd/launcher/LaunchUtil.cpp @@ -0,0 +1,261 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "LaunchUtil.h" +#include "CMSWindowsUtil.h" +#include "CArch.h" +#include "resource.h" +#include "stdfstream.h" + +size_t s_showingDialog = 0; + +CString +getString(DWORD id) +{ + return CMSWindowsUtil::getString(s_instance, id); +} + +CString +getErrorString(DWORD error) +{ + return CMSWindowsUtil::getErrorString(s_instance, error, IDS_ERROR); +} + +void +showError(HWND hwnd, const CString& msg) +{ + CString title = getString(IDS_ERROR); + ++s_showingDialog; + MessageBox(hwnd, msg.c_str(), title.c_str(), MB_OK | MB_APPLMODAL); + --s_showingDialog; +} + +void +askOkay(HWND hwnd, const CString& title, const CString& msg) +{ + ++s_showingDialog; + MessageBox(hwnd, msg.c_str(), title.c_str(), MB_OK | MB_APPLMODAL); + --s_showingDialog; +} + +bool +askVerify(HWND hwnd, const CString& msg) +{ + CString title = getString(IDS_VERIFY); + ++s_showingDialog; + int result = MessageBox(hwnd, msg.c_str(), + title.c_str(), MB_OKCANCEL | MB_APPLMODAL); + --s_showingDialog; + return (result == IDOK); +} + +bool +isShowingDialog() +{ + return (s_showingDialog != 0); +} + +void +setWindowText(HWND hwnd, const CString& msg) +{ + SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)msg.c_str()); +} + +CString +getWindowText(HWND hwnd) +{ + LRESULT size = SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0); + char* buffer = new char[size + 1]; + SendMessage(hwnd, WM_GETTEXT, size + 1, (LPARAM)buffer); + buffer[size] = '\0'; + CString result(buffer); + delete[] buffer; + return result; +} + +HWND +getItem(HWND hwnd, int id) +{ + return GetDlgItem(hwnd, id); +} + +void +enableItem(HWND hwnd, int id, bool enabled) +{ + EnableWindow(GetDlgItem(hwnd, id), enabled); +} + +void +setItemChecked(HWND hwnd, bool checked) +{ + SendMessage(hwnd, BM_SETCHECK, checked ? BST_CHECKED : BST_UNCHECKED, 0); +} + +bool +isItemChecked(HWND hwnd) +{ + return (SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED); +} + +CString +getAppPath(const CString& appName) +{ + // prepare path to app + char myPathname[MAX_PATH]; + GetModuleFileName(s_instance, myPathname, MAX_PATH); + const char* myBasename = ARCH->getBasename(myPathname); + CString appPath = CString(myPathname, myBasename - myPathname); + appPath += appName; + return appPath; +} + +static +void +getFileTime(const CString& path, time_t& t) +{ + struct _stat s; + if (_stat(path.c_str(), &s) != -1) { + t = s.st_mtime; + } +} + +bool +isConfigNewer(time_t& oldTime, bool userConfig) +{ + time_t newTime = oldTime; + if (userConfig) { + CString path = ARCH->getUserDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, CONFIG_NAME); + getFileTime(path, newTime); + } + } + else { + CString path = ARCH->getSystemDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, CONFIG_NAME); + getFileTime(path, newTime); + } + } + bool result = (newTime > oldTime); + oldTime = newTime; + return result; +} + +static +bool +loadConfig(const CString& pathname, CConfig& config) +{ + try { + std::ifstream stream(pathname.c_str()); + if (stream) { + stream >> config; + return true; + } + } + catch (...) { + // ignore + } + return false; +} + +bool +loadConfig(CConfig& config, time_t& t, bool& userConfig) +{ + // load configuration + bool configLoaded = false; + CString path = ARCH->getUserDirectory(); + if (!path.empty()) { + // try loading the user's configuration + path = ARCH->concatPath(path, CONFIG_NAME); + if (loadConfig(path, config)) { + configLoaded = true; + userConfig = true; + getFileTime(path, t); + } + else { + // try the system-wide config file + path = ARCH->getSystemDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, CONFIG_NAME); + if (loadConfig(path, config)) { + configLoaded = true; + userConfig = false; + getFileTime(path, t); + } + } + } + } + return configLoaded; +} + +static +bool +saveConfig(const CString& pathname, const CConfig& config) +{ + try { + std::ofstream stream(pathname.c_str()); + if (stream) { + stream << config; + return !!stream; + } + } + catch (...) { + // ignore + } + return false; +} + +bool +saveConfig(const CConfig& config, bool sysOnly, time_t& t) +{ + // try saving the user's configuration + if (!sysOnly) { + CString path = ARCH->getUserDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, CONFIG_NAME); + if (saveConfig(path, config)) { + getFileTime(path, t); + return true; + } + } + } + + // try the system-wide config file + else { + CString path = ARCH->getSystemDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, CONFIG_NAME); + if (saveConfig(path, config)) { + getFileTime(path, t); + return true; + } + } + } + + return false; +} + +const TCHAR* const* +getSettingsPath() +{ + static const TCHAR* s_keyNames[] = { + TEXT("Software"), + TEXT("Synergy"), + TEXT("Synergy"), + NULL + }; + return s_keyNames; +} diff --git a/cmd/launcher/LaunchUtil.h b/cmd/launcher/LaunchUtil.h new file mode 100644 index 00000000..75954046 --- /dev/null +++ b/cmd/launcher/LaunchUtil.h @@ -0,0 +1,61 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef LAUNCHUTIL_H +#define LAUNCHUTIL_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include +#include +#include + +#define CLIENT_APP "synergyc.exe" +#define SERVER_APP "synergys.exe" +#define CONFIG_NAME "synergy.sgc" + +class CConfig; + +// client must define this and set it before calling any function here +extern HINSTANCE s_instance; + +CString getString(DWORD id); +CString getErrorString(DWORD error); + +void showError(HWND hwnd, const CString& msg); +void askOkay(HWND hwnd, const CString& title, + const CString& msg); +bool askVerify(HWND hwnd, const CString& msg); +bool isShowingDialog(); + +void setWindowText(HWND hwnd, const CString& msg); +CString getWindowText(HWND hwnd); + +HWND getItem(HWND hwnd, int id); +void enableItem(HWND hwnd, int id, bool enabled); + +void setItemChecked(HWND, bool); +bool isItemChecked(HWND); + +CString getAppPath(const CString& appName); + +bool isConfigNewer(time_t&, bool userConfig); +bool loadConfig(CConfig& config, time_t&, bool& userConfig); +bool saveConfig(const CConfig& config, + bool sysOnly, time_t&); + +const TCHAR* const* getSettingsPath(); + +#endif diff --git a/cmd/launcher/Makefile.am b/cmd/launcher/Makefile.am new file mode 100644 index 00000000..6fc879e3 --- /dev/null +++ b/cmd/launcher/Makefile.am @@ -0,0 +1,75 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +MSWINDOWS_SOURCE_FILES = \ + CAddScreen.cpp \ + CAdvancedOptions.cpp \ + CAutoStart.cpp \ + CGlobalOptions.cpp \ + CHotkeyOptions.cpp \ + CInfo.cpp \ + CScreensLinks.cpp \ + LaunchUtil.cpp \ + launcher.cpp \ + CAddScreen.h \ + CAdvancedOptions.h \ + CAutoStart.h \ + CGlobalOptions.h \ + CHotkeyOptions.h \ + CInfo.h \ + CScreensLinks.h \ + LaunchUtil.h \ + resource.h \ + launcher.rc \ + $(NULL) + +EXTRA_DIST = \ + Makefile.win \ + synergy.ico \ + $(MSWINDOWS_SOURCE_FILES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +if MSWINDOWS +bin_PROGRAMS = synergy +synergy_SOURCES = \ + $(MSWINDOWS_SOURCE_FILES) \ + $(NULL) +endif +synergy_LDADD = \ + $(top_builddir)/lib/server/libserver.a \ + $(top_builddir)/lib/platform/libplatform.a \ + $(top_builddir)/lib/synergy/libsynergy.a \ + $(top_builddir)/lib/net/libnet.a \ + $(top_builddir)/lib/io/libio.a \ + $(top_builddir)/lib/mt/libmt.a \ + $(top_builddir)/lib/base/libbase.a \ + $(top_builddir)/lib/common/libcommon.a \ + $(top_builddir)/lib/arch/libarch.a \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + -I$(top_srcdir)/lib/synergy \ + -I$(top_srcdir)/lib/platform \ + -I$(top_srcdir)/lib/server \ + $(NULL) diff --git a/cmd/launcher/Makefile.win b/cmd/launcher/Makefile.win new file mode 100644 index 00000000..3d4f277a --- /dev/null +++ b/cmd/launcher/Makefile.win @@ -0,0 +1,101 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +BIN_LAUNCHER_SRC = cmd\launcher +BIN_LAUNCHER_DST = $(BUILD_DST)\$(BIN_LAUNCHER_SRC) +BIN_LAUNCHER_EXE = "$(BUILD_DST)\synergy.exe" +BIN_LAUNCHER_CPP = \ + "CAddScreen.cpp" \ + "CAdvancedOptions.cpp" \ + "CAutoStart.cpp" \ + "CGlobalOptions.cpp" \ + "CHotkeyOptions.cpp" \ + "CInfo.cpp" \ + "CScreensLinks.cpp" \ + "LaunchUtil.cpp" \ + "launcher.cpp" \ + $(NULL) +BIN_LAUNCHER_OBJ = \ + "$(BIN_LAUNCHER_DST)\CAddScreen.obj" \ + "$(BIN_LAUNCHER_DST)\CAdvancedOptions.obj" \ + "$(BIN_LAUNCHER_DST)\CAutoStart.obj" \ + "$(BIN_LAUNCHER_DST)\CGlobalOptions.obj" \ + "$(BIN_LAUNCHER_DST)\CHotkeyOptions.obj" \ + "$(BIN_LAUNCHER_DST)\CInfo.obj" \ + "$(BIN_LAUNCHER_DST)\CScreensLinks.obj" \ + "$(BIN_LAUNCHER_DST)\LaunchUtil.obj" \ + "$(BIN_LAUNCHER_DST)\launcher.obj" \ + $(NULL) +BIN_LAUNCHER_RC = "$(BIN_LAUNCHER_SRC)\launcher.rc" +BIN_LAUNCHER_RES = "$(BIN_LAUNCHER_DST)\launcher.res" +BIN_LAUNCHER_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + /I"lib\platform" \ + /I"lib\server" \ + $(NULL) +BIN_LAUNCHER_LIB = \ + $(LIB_SERVER_LIB) \ + $(LIB_PLATFORM_LIB) \ + $(LIB_SYNERGY_LIB) \ + $(LIB_NET_LIB) \ + $(LIB_IO_LIB) \ + $(LIB_MT_LIB) \ + $(LIB_BASE_LIB) \ + $(LIB_ARCH_LIB) \ + $(LIB_COMMON_LIB) \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(BIN_LAUNCHER_CPP) +OBJ_FILES = $(OBJ_FILES) $(BIN_LAUNCHER_OBJ) +PROGRAMS = $(PROGRAMS) $(BIN_LAUNCHER_EXE) + +# Need shell functions. +guilibs = $(guilibs) shell32.lib + +# Dependency rules +$(BIN_LAUNCHER_OBJ): $(AUTODEP) +!if EXIST($(BIN_LAUNCHER_DST)\deps.mak) +!include $(BIN_LAUNCHER_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(BIN_LAUNCHER_SRC)\}.cpp{$(BIN_LAUNCHER_DST)\}.obj:: +!else +{$(BIN_LAUNCHER_SRC)\}.cpp{$(BIN_LAUNCHER_DST)\}.obj: +!endif + @$(ECHO) Compile in $(BIN_LAUNCHER_SRC) + -@$(MKDIR) $(BIN_LAUNCHER_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(BIN_LAUNCHER_INC) \ + /Fo$(BIN_LAUNCHER_DST)\ \ + /Fd$(BIN_LAUNCHER_DST)\src.pdb \ + $< | $(AUTODEP) $(BIN_LAUNCHER_SRC) $(BIN_LAUNCHER_DST) +$(BIN_LAUNCHER_RES): $(BIN_LAUNCHER_RC) + @$(ECHO) Compile $(**F) + -@$(MKDIR) $(BIN_LAUNCHER_DST) 2>NUL: + $(rc) $(rcflags) $(rcvars) \ + /fo$@ \ + $** +$(BIN_LAUNCHER_EXE): $(BIN_LAUNCHER_OBJ) $(BIN_LAUNCHER_RES) $(BIN_LAUNCHER_LIB) + @$(ECHO) Link $(@F) + $(link) $(ldebug) $(guilflags) $(guilibsmt) \ + /out:$@ \ + $** + $(AUTODEP) $(BIN_LAUNCHER_SRC) $(BIN_LAUNCHER_DST) \ + $(BIN_LAUNCHER_OBJ:.obj=.d) diff --git a/cmd/launcher/launcher.cpp b/cmd/launcher/launcher.cpp new file mode 100644 index 00000000..938822da --- /dev/null +++ b/cmd/launcher/launcher.cpp @@ -0,0 +1,755 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "KeyTypes.h" +#include "OptionTypes.h" +#include "ProtocolTypes.h" +#include "CLog.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CArchMiscWindows.h" +#include "XArch.h" +#include "Version.h" +#include "stdvector.h" +#include "resource.h" + +// these must come after the above because it includes windows.h +#include "LaunchUtil.h" +#include "CAddScreen.h" +#include "CAdvancedOptions.h" +#include "CAutoStart.h" +#include "CGlobalOptions.h" +#include "CHotkeyOptions.h" +#include "CInfo.h" +#include "CScreensLinks.h" + +typedef std::vector CStringList; + +class CChildWaitInfo { +public: + HWND m_dialog; + HANDLE m_child; + DWORD m_childID; + HANDLE m_ready; + HANDLE m_stop; +}; + +static const char* s_debugName[][2] = { + { TEXT("Error"), "ERROR" }, + { TEXT("Warning"), "WARNING" }, + { TEXT("Note"), "NOTE" }, + { TEXT("Info"), "INFO" }, + { TEXT("Debug"), "DEBUG" }, + { TEXT("Debug1"), "DEBUG1" }, + { TEXT("Debug2"), "DEBUG2" } +}; +static const int s_defaultDebug = 1; // WARNING +static const int s_minTestDebug = 3; // INFO + +HINSTANCE s_instance = NULL; + +static CGlobalOptions* s_globalOptions = NULL; +static CAdvancedOptions* s_advancedOptions = NULL; +static CHotkeyOptions* s_hotkeyOptions = NULL; +static CScreensLinks* s_screensLinks = NULL; +static CInfo* s_info = NULL; + +static bool s_userConfig = true; +static time_t s_configTime = 0; +static CConfig s_lastConfig; + +static const TCHAR* s_mainClass = TEXT("GoSynergy"); +static const TCHAR* s_layoutClass = TEXT("SynergyLayout"); + +enum SaveMode { + SAVE_QUITING, + SAVE_NORMAL, + SAVE_QUIET +}; + +// +// program arguments +// + +#define ARG CArgs::s_instance + +class CArgs { +public: + CArgs() { s_instance = this; } + ~CArgs() { s_instance = NULL; } + +public: + static CArgs* s_instance; + CConfig m_config; + CStringList m_screens; +}; + +CArgs* CArgs::s_instance = NULL; + + +static +BOOL CALLBACK +addDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + +static +bool +isClientChecked(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_MAIN_CLIENT_RADIO); + return isItemChecked(child); +} + +static +void +enableMainWindowControls(HWND hwnd) +{ + bool client = isClientChecked(hwnd); + enableItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_LABEL, client); + enableItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT, client); + enableItem(hwnd, IDC_MAIN_SERVER_SCREENS_LABEL, !client); + enableItem(hwnd, IDC_MAIN_SCREENS, !client); + enableItem(hwnd, IDC_MAIN_OPTIONS, !client); + enableItem(hwnd, IDC_MAIN_HOTKEYS, !client); +} + +static +bool +execApp(const char* app, const CString& cmdLine, PROCESS_INFORMATION* procInfo) +{ + // prepare startup info + STARTUPINFO startup; + startup.cb = sizeof(startup); + startup.lpReserved = NULL; + startup.lpDesktop = NULL; + startup.lpTitle = NULL; + startup.dwX = (DWORD)CW_USEDEFAULT; + startup.dwY = (DWORD)CW_USEDEFAULT; + startup.dwXSize = (DWORD)CW_USEDEFAULT; + startup.dwYSize = (DWORD)CW_USEDEFAULT; + startup.dwXCountChars = 0; + startup.dwYCountChars = 0; + startup.dwFillAttribute = 0; + startup.dwFlags = STARTF_FORCEONFEEDBACK; + startup.wShowWindow = SW_SHOWDEFAULT; + startup.cbReserved2 = 0; + startup.lpReserved2 = NULL; + startup.hStdInput = NULL; + startup.hStdOutput = NULL; + startup.hStdError = NULL; + + // prepare path to app + CString appPath = getAppPath(app); + + // put path to app in command line + CString commandLine = "\""; + commandLine += appPath; + commandLine += "\" "; + commandLine += cmdLine; + + // start child + if (CreateProcess(NULL, (char*)commandLine.c_str(), + NULL, + NULL, + FALSE, + CREATE_DEFAULT_ERROR_MODE | + CREATE_NEW_PROCESS_GROUP | + NORMAL_PRIORITY_CLASS, + NULL, + NULL, + &startup, + procInfo) == 0) { + return false; + } + else { + return true; + } +} + +static +CString +getCommandLine(HWND hwnd, bool testing, bool silent) +{ + CString cmdLine; + + // add constant testing args + if (testing) { + cmdLine += " -z --no-restart --no-daemon"; + } + + // can't start as service on NT + else if (!CArchMiscWindows::isWindows95Family()) { + cmdLine += " --no-daemon"; + } + + // get the server name + CString server; + bool isClient = isClientChecked(hwnd); + if (isClient) { + // check server name + HWND child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT); + server = getWindowText(child); + if (!ARG->m_config.isValidScreenName(server)) { + if (!silent) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_SERVER_NAME).c_str(), + server.c_str())); + } + SetFocus(child); + return CString(); + } + + // compare server name to local host. a common error + // is to provide the client's name for the server. we + // don't bother to check the addresses though that'd be + // more accurate. + if (CStringUtil::CaselessCmp::equal(ARCH->getHostName(), server)) { + if (!silent) { + showError(hwnd, CStringUtil::format( + getString(IDS_SERVER_IS_CLIENT).c_str(), + server.c_str())); + } + SetFocus(child); + return CString(); + } + } + + // debug level. always include this. + if (true) { + HWND child = getItem(hwnd, IDC_MAIN_DEBUG); + int debug = (int)SendMessage(child, CB_GETCURSEL, 0, 0); + + // if testing then we force the debug level to be no less than + // s_minTestDebug. what's the point of testing if you can't + // see the debugging info? + if (testing && debug < s_minTestDebug) { + debug = s_minTestDebug; + } + + cmdLine += " --debug "; + cmdLine += s_debugName[debug][1]; + } + + // add advanced options + cmdLine += s_advancedOptions->getCommandLine(isClient, server); + + return cmdLine; +} + +static +bool +launchApp(HWND hwnd, bool testing, HANDLE* thread, DWORD* threadID) +{ + if (thread != NULL) { + *thread = NULL; + } + if (threadID != NULL) { + *threadID = 0; + } + + // start daemon if it's installed and we're not testing + if (!testing && CAutoStart::startDaemon()) { + return true; + } + + // decide if client or server + const bool isClient = isClientChecked(hwnd); + const char* app = isClient ? CLIENT_APP : SERVER_APP; + + // prepare command line + CString cmdLine = getCommandLine(hwnd, testing, false); + if (cmdLine.empty()) { + return false; + } + + // start child + PROCESS_INFORMATION procInfo; + if (!execApp(app, cmdLine, &procInfo)) { + showError(hwnd, CStringUtil::format( + getString(IDS_STARTUP_FAILED).c_str(), + getErrorString(GetLastError()).c_str())); + return false; + } + + // don't need process handle + CloseHandle(procInfo.hProcess); + + // save thread handle and thread ID if desired + if (thread != NULL) { + *thread = procInfo.hThread; + } + if (threadID != NULL) { + *threadID = procInfo.dwThreadId; + } + + return true; +} + +static +BOOL CALLBACK +waitDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + // only one wait dialog at a time! + static CChildWaitInfo* info = NULL; + + switch (message) { + case WM_INITDIALOG: + // save info pointer + info = reinterpret_cast(lParam); + + // save hwnd + info->m_dialog = hwnd; + + // signal ready + SetEvent(info->m_ready); + + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDCANCEL: + case IDOK: + // signal stop + SetEvent(info->m_stop); + + // done + EndDialog(hwnd, 0); + return TRUE; + } + } + + return FALSE; +} + +static +DWORD WINAPI +waitForChildThread(LPVOID vinfo) +{ + CChildWaitInfo* info = reinterpret_cast(vinfo); + + // wait for ready + WaitForSingleObject(info->m_ready, INFINITE); + + // wait for thread to complete or stop event + HANDLE handles[2]; + handles[0] = info->m_child; + handles[1] = info->m_stop; + DWORD n = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + + // if stop was raised then terminate child and wait for it + if (n == WAIT_OBJECT_0 + 1) { + PostThreadMessage(info->m_childID, WM_QUIT, 0, 0); + WaitForSingleObject(info->m_child, INFINITE); + } + + // otherwise post IDOK to dialog box + else { + PostMessage(info->m_dialog, WM_COMMAND, MAKEWPARAM(IDOK, 0), 0); + } + + return 0; +} + +static +void +waitForChild(HWND hwnd, HANDLE thread, DWORD threadID) +{ + // prepare info for child wait dialog and thread + CChildWaitInfo info; + info.m_dialog = NULL; + info.m_child = thread; + info.m_childID = threadID; + info.m_ready = CreateEvent(NULL, TRUE, FALSE, NULL); + info.m_stop = CreateEvent(NULL, TRUE, FALSE, NULL); + + // create a thread to wait on the child thread and event + DWORD id; + HANDLE waiter = CreateThread(NULL, 0, &waitForChildThread, &info,0, &id); + + // do dialog that let's the user terminate the test + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_WAIT), hwnd, + (DLGPROC)waitDlgProc, (LPARAM)&info); + + // force the waiter thread to finish and wait for it + SetEvent(info.m_ready); + SetEvent(info.m_stop); + WaitForSingleObject(waiter, INFINITE); + + // clean up + CloseHandle(waiter); + CloseHandle(info.m_ready); + CloseHandle(info.m_stop); +} + +static +void +initMainWindow(HWND hwnd) +{ + // append version number to title + CString titleFormat = getString(IDS_TITLE); + setWindowText(hwnd, CStringUtil::format(titleFormat.c_str(), VERSION)); + + // load configuration + bool configLoaded = + loadConfig(ARG->m_config, s_configTime, s_userConfig); + if (configLoaded) { + s_lastConfig = ARG->m_config; + } + + // get settings from registry + bool isServer = configLoaded; + int debugLevel = s_defaultDebug; + CString server; + HKEY key = CArchMiscWindows::openKey(HKEY_CURRENT_USER, getSettingsPath()); + if (key != NULL) { + if (isServer && CArchMiscWindows::hasValue(key, "isServer")) { + isServer = (CArchMiscWindows::readValueInt(key, "isServer") != 0); + } + if (CArchMiscWindows::hasValue(key, "debug")) { + debugLevel = static_cast( + CArchMiscWindows::readValueInt(key, "debug")); + if (debugLevel < 0) { + debugLevel = 0; + } + else if (debugLevel > CLog::kDEBUG2) { + debugLevel = CLog::kDEBUG2; + } + } + server = CArchMiscWindows::readValueString(key, "server"); + CArchMiscWindows::closeKey(key); + } + + // choose client/server radio buttons + HWND child; + child = getItem(hwnd, IDC_MAIN_CLIENT_RADIO); + setItemChecked(child, !isServer); + child = getItem(hwnd, IDC_MAIN_SERVER_RADIO); + setItemChecked(child, isServer); + + // set server name + child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT); + setWindowText(child, server); + + // debug level + child = getItem(hwnd, IDC_MAIN_DEBUG); + for (unsigned int i = 0; i < sizeof(s_debugName) / + sizeof(s_debugName[0]); ++i) { + SendMessage(child, CB_ADDSTRING, 0, (LPARAM)s_debugName[i][0]); + } + SendMessage(child, CB_SETCURSEL, debugLevel, 0); + + // update controls + enableMainWindowControls(hwnd); +} + +static +bool +saveMainWindow(HWND hwnd, SaveMode mode, CString* cmdLineOut = NULL) +{ + DWORD errorID = 0; + CString arg; + CString cmdLine; + + // save dialog state + bool isClient = isClientChecked(hwnd); + HKEY key = CArchMiscWindows::addKey(HKEY_CURRENT_USER, getSettingsPath()); + if (key != NULL) { + HWND child; + child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT); + CArchMiscWindows::setValue(key, "server", getWindowText(child)); + child = getItem(hwnd, IDC_MAIN_DEBUG); + CArchMiscWindows::setValue(key, "debug", + SendMessage(child, CB_GETCURSEL, 0, 0)); + CArchMiscWindows::setValue(key, "isServer", isClient ? 0 : 1); + CArchMiscWindows::closeKey(key); + } + + // save user's configuration + if (!s_userConfig || ARG->m_config != s_lastConfig) { + time_t t; + if (!saveConfig(ARG->m_config, false, t)) { + errorID = IDS_SAVE_FAILED; + arg = getErrorString(GetLastError()); + goto failed; + } + if (s_userConfig) { + s_configTime = t; + s_lastConfig = ARG->m_config; + } + } + + // save autostart configuration + if (CAutoStart::isDaemonInstalled()) { + if (s_userConfig || ARG->m_config != s_lastConfig) { + time_t t; + if (!saveConfig(ARG->m_config, true, t)) { + errorID = IDS_AUTOSTART_SAVE_FAILED; + arg = getErrorString(GetLastError()); + goto failed; + } + if (!s_userConfig) { + s_configTime = t; + s_lastConfig = ARG->m_config; + } + } + } + + // get autostart command + cmdLine = getCommandLine(hwnd, false, mode == SAVE_QUITING); + if (cmdLineOut != NULL) { + *cmdLineOut = cmdLine; + } + if (cmdLine.empty()) { + return (mode == SAVE_QUITING); + } + + // save autostart command + if (CAutoStart::isDaemonInstalled()) { + try { + CAutoStart::reinstallDaemon(isClient, cmdLine); + CAutoStart::uninstallDaemons(!isClient); + } + catch (XArchDaemon& e) { + errorID = IDS_INSTALL_GENERIC_ERROR; + arg = e.what(); + goto failed; + } + } + + return true; + +failed: + CString errorMessage = + CStringUtil::format(getString(errorID).c_str(), arg.c_str()); + if (mode == SAVE_QUITING) { + errorMessage += "\n"; + errorMessage += getString(IDS_UNSAVED_DATA_REALLY_QUIT); + if (askVerify(hwnd, errorMessage)) { + return true; + } + } + else if (mode == SAVE_NORMAL) { + showError(hwnd, errorMessage); + } + return false; +} + +static +LRESULT CALLBACK +mainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_ACTIVATE: + if (LOWORD(wParam) != WA_INACTIVE) { + // activated + + // see if the configuration changed + if (isConfigNewer(s_configTime, s_userConfig)) { + CString message = getString(IDS_CONFIG_CHANGED); + if (askVerify(hwnd, message)) { + time_t configTime; + bool userConfig; + CConfig newConfig; + if (loadConfig(newConfig, configTime, userConfig) && + userConfig == s_userConfig) { + ARG->m_config = newConfig; + s_lastConfig = ARG->m_config; + } + else { + message = getString(IDS_LOAD_FAILED); + showError(hwnd, message); + s_lastConfig = CConfig(); + } + } + } + } + else { + // deactivated; write configuration + if (!isShowingDialog()) { + saveMainWindow(hwnd, SAVE_QUIET); + } + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDCANCEL: + // save data + if (saveMainWindow(hwnd, SAVE_QUITING)) { + // quit + PostQuitMessage(0); + } + return 0; + + case IDOK: + case IDC_MAIN_TEST: { + // note if testing + const bool testing = (LOWORD(wParam) == IDC_MAIN_TEST); + + // save data + if (saveMainWindow(hwnd, SAVE_NORMAL)) { + // launch child app + DWORD threadID; + HANDLE thread; + if (!launchApp(hwnd, testing, &thread, &threadID)) { + return 0; + } + + // handle child program + if (testing) { + // wait for process to stop, allowing the user to kill it + waitForChild(hwnd, thread, threadID); + + // clean up + CloseHandle(thread); + } + else { + // don't need thread handle + if (thread != NULL) { + CloseHandle(thread); + } + + // notify of success + askOkay(hwnd, getString(IDS_STARTED_TITLE), + getString(IDS_STARTED)); + + // quit + PostQuitMessage(0); + } + } + return 0; + } + + case IDC_MAIN_AUTOSTART: { + CString cmdLine; + if (saveMainWindow(hwnd, SAVE_NORMAL, &cmdLine)) { + // run dialog + CAutoStart autoStart(hwnd, !isClientChecked(hwnd), cmdLine); + autoStart.doModal(); + } + return 0; + } + + case IDC_MAIN_CLIENT_RADIO: + case IDC_MAIN_SERVER_RADIO: + enableMainWindowControls(hwnd); + return 0; + + case IDC_MAIN_SCREENS: + s_screensLinks->doModal(); + break; + + case IDC_MAIN_OPTIONS: + s_globalOptions->doModal(); + break; + + case IDC_MAIN_ADVANCED: + s_advancedOptions->doModal(isClientChecked(hwnd)); + break; + + case IDC_MAIN_HOTKEYS: + s_hotkeyOptions->doModal(); + break; + + case IDC_MAIN_INFO: + s_info->doModal(); + break; + } + + default: + break; + } + return DefDlgProc(hwnd, message, wParam, lParam); +} + +int WINAPI +WinMain(HINSTANCE instance, HINSTANCE, LPSTR cmdLine, int nCmdShow) +{ + CArch arch(instance); + CLOG; + CArgs args; + + s_instance = instance; + + // if "/uninstall" is on the command line then just stop and + // uninstall the service and quit. this is the only option + // but we ignore any others. + if (CString(cmdLine).find("/uninstall") != CString::npos) { + CAutoStart::uninstallDaemons(false); + CAutoStart::uninstallDaemons(true); + return 0; + } + + // register main window (dialog) class + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_HREDRAW | CS_VREDRAW; + classInfo.lpfnWndProc = &mainWndProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = DLGWINDOWEXTRA; + classInfo.hInstance = instance; + classInfo.hIcon = (HICON)LoadImage(instance, + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 32, 32, LR_SHARED); + classInfo.hCursor = LoadCursor(NULL, IDC_ARROW); + classInfo.hbrBackground = reinterpret_cast(COLOR_3DFACE + 1); + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = s_mainClass; + classInfo.hIconSm = (HICON)LoadImage(instance, + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 16, 16, LR_SHARED); + RegisterClassEx(&classInfo); + + // create main window + HWND mainWindow = CreateDialog(s_instance, + MAKEINTRESOURCE(IDD_MAIN), 0, NULL); + + // prep windows + initMainWindow(mainWindow); + s_globalOptions = new CGlobalOptions(mainWindow, &ARG->m_config); + s_advancedOptions = new CAdvancedOptions(mainWindow, &ARG->m_config); + s_hotkeyOptions = new CHotkeyOptions(mainWindow, &ARG->m_config); + s_screensLinks = new CScreensLinks(mainWindow, &ARG->m_config); + s_info = new CInfo(mainWindow); + + // show window + ShowWindow(mainWindow, nCmdShow); + + // main loop + MSG msg; + bool done = false; + do { + switch (GetMessage(&msg, NULL, 0, 0)) { + case -1: + // error + break; + + case 0: + // quit + done = true; + break; + + default: + if (!IsDialogMessage(mainWindow, &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + break; + } + } while (!done); + + return msg.wParam; +} diff --git a/cmd/launcher/launcher.rc b/cmd/launcher/launcher.rc new file mode 100644 index 00000000..ff72d069 --- /dev/null +++ b/cmd/launcher/launcher.rc @@ -0,0 +1,617 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +#if !defined(IDC_STATIC) +#define IDC_STATIC (-1) +#endif + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include \r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_MAIN DIALOG DISCARDABLE 32768, 0, 300, 199 +STYLE DS_MODALFRAME | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU +CAPTION "Synergy" +CLASS "GoSynergy" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Choose to share or use a shared keyboard and mouse, provide the requested information, then click Test to check your settings or Start to save your settings and start Synergy.", + IDC_STATIC,7,7,286,19 + GROUPBOX "",IDC_STATIC,7,29,286,36 + GROUPBOX "",IDC_STATIC,7,72,286,36 + GROUPBOX "Options",IDC_STATIC,7,115,286,56 + CONTROL "&Use another computer's shared keyboard and mouse (client)", + IDC_MAIN_CLIENT_RADIO,"Button",BS_AUTORADIOBUTTON | + WS_GROUP | WS_TABSTOP,11,29,205,10 + CONTROL "Share this computer's keyboard and mouse (server)", + IDC_MAIN_SERVER_RADIO,"Button",BS_AUTORADIOBUTTON,11,72, + 177,10 + LTEXT "Other Computer's &Host Name:", + IDC_MAIN_CLIENT_SERVER_NAME_LABEL,12,46,94,8 + EDITTEXT IDC_MAIN_CLIENT_SERVER_NAME_EDIT,111,44,106,12, + ES_AUTOHSCROLL + LTEXT "&Screens && Links:",IDC_MAIN_SERVER_SCREENS_LABEL,12,89, + 54,8 + PUSHBUTTON "Configure...",IDC_MAIN_SCREENS,71,86,50,14 + PUSHBUTTON "&Options...",IDC_MAIN_OPTIONS,12,129,50,14 + PUSHBUTTON "Hot &Keys...",IDC_MAIN_HOTKEYS,68,129,50,14 + PUSHBUTTON "Adva&nced...",IDC_MAIN_ADVANCED,124,129,50,14 + PUSHBUTTON "&AutoStart...",IDC_MAIN_AUTOSTART,180,129,50,14 + LTEXT "&Logging Level:",IDC_STATIC,12,154,48,8 + COMBOBOX IDC_MAIN_DEBUG,68,151,61,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "&Info",IDC_MAIN_INFO,7,178,50,14 + DEFPUSHBUTTON "&Test",IDC_MAIN_TEST,131,179,50,14 + PUSHBUTTON "Start",IDOK,187,179,50,14 + PUSHBUTTON "Quit",IDCANCEL,243,179,50,14 +END + +IDD_ADD DIALOG DISCARDABLE 0, 0, 192, 254 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION +CAPTION "Add Screen" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "&Screen Name:",IDC_STATIC,7,9,46,8 + EDITTEXT IDC_ADD_SCREEN_NAME_EDIT,79,7,106,12,ES_AUTOHSCROLL + LTEXT "&Aliases:",IDC_STATIC,7,25,25,8 + EDITTEXT IDC_ADD_ALIASES_EDIT,79,26,106,24,ES_MULTILINE | + ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN + GROUPBOX "Options",IDC_STATIC,7,55,178,54 + LTEXT "If your Caps, Num, or Scroll Lock keys behave strangely on this client screen then try turning the half-duplex options on and reconnect the client.", + IDC_STATIC,13,65,165,25 + CONTROL "&Caps Lock",IDC_ADD_HD_CAPS_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,13,93,51,10 + CONTROL "&Num Lock",IDC_ADD_HD_NUM_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,69,93,51,10 + CONTROL "Sc&roll Lock",IDC_ADD_HD_SCROLL_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,125,93,51,10 + GROUPBOX "Modifiers",IDC_STATIC,7,113,178,65 + LTEXT "Shift",IDC_STATIC,13,129,15,8 + COMBOBOX IDC_ADD_MOD_SHIFT,37,126,48,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "Ctrl",IDC_STATIC,13,144,11,8 + COMBOBOX IDC_ADD_MOD_CTRL,37,142,48,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "Alt",IDC_STATIC,13,160,9,8 + COMBOBOX IDC_ADD_MOD_ALT,37,158,48,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "Meta",IDC_STATIC,101,128,17,8 + COMBOBOX IDC_ADD_MOD_META,125,126,48,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "Super",IDC_STATIC,101,144,20,8 + COMBOBOX IDC_ADD_MOD_SUPER,125,142,48,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + GROUPBOX "Dead Corners",IDC_STATIC,7,183,178,43 + LTEXT "Don't switch in these corners:",IDC_STATIC,14,198,52,18 + CONTROL "",IDC_STATIC,"Static",SS_BLACKFRAME,68,193,47,28 + CONTROL "",IDC_ADD_DC_TOP_LEFT,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,76,197,16,8 + CONTROL "",IDC_ADD_DC_TOP_RIGHT,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,98,197,16,8 + CONTROL "",IDC_ADD_DC_BOTTOM_LEFT,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,76,210,16,8 + CONTROL "",IDC_ADD_DC_BOTTOM_RIGHT,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,98,210,16,8 + LTEXT "Size",IDC_STATIC,120,202,14,8 + EDITTEXT IDC_ADD_DC_SIZE,139,200,40,12,ES_AUTOHSCROLL | ES_NUMBER + DEFPUSHBUTTON "OK",IDOK,79,233,50,14 + PUSHBUTTON "Cancel",IDCANCEL,135,233,50,14 +END + +IDD_WAIT DIALOG DISCARDABLE 0, 0, 186, 54 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION +CAPTION "Running Test..." +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Stop",IDOK,129,33,50,14 + LTEXT "Running synergy. Press Stop to end the test.", + IDC_STATIC,7,7,172,15 +END + +IDD_AUTOSTART DIALOG DISCARDABLE 0, 0, 195, 189 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Auto Start" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Close",IDCANCEL,138,168,50,14 + LTEXT "Synergy can be configured to start automatically when you log in. If you have sufficient access rights, you can instead configure synergy to start automatically when your computer starts.", + IDC_STATIC,7,7,181,33 + LTEXT "You have sufficient access rights to install and uninstall Auto Start for all users or for just yourself.", + IDC_AUTOSTART_PERMISSION_MSG,7,69,181,17 + LTEXT "Synergy is configured to start automatically when the system starts.", + IDC_AUTOSTART_INSTALLED_MSG,7,93,181,17 + GROUPBOX "When &You Log In",IDC_STATIC,7,119,84,40 + PUSHBUTTON "Install",IDC_AUTOSTART_INSTALL_USER,23,133,50,14 + GROUPBOX "When &Computer Starts",IDC_STATIC,104,119,84,40 + PUSHBUTTON "Install",IDC_AUTOSTART_INSTALL_SYSTEM,119,134,50,14 + LTEXT "Synergy can be configured to start automatically when the computer starts or when you log in but not both.", + IDC_STATIC,7,43,181,17 +END + +IDD_GLOBAL_OPTIONS DIALOG DISCARDABLE 0, 0, 207, 290 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Options" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "It's easy to unintentionally switch screens when the pointer is near a screen's edge. Synergy can prevent switching until certain conditions are met to reduce unintentional switching.", + IDC_STATIC,7,7,191,26 + LTEXT "Synergy can wait to switch until the cursor has been at a screen's edge for some amount of time.", + IDC_STATIC,7,37,193,16 + CONTROL "Switch after waiting",IDC_GLOBAL_DELAY_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,7,59,77,10 + EDITTEXT IDC_GLOBAL_DELAY_TIME,112,58,45,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "ms",IDC_STATIC,159,60,10,8 + LTEXT "Synergy can switch only when the cursor hits a screen edge twice within some amount of time.", + IDC_STATIC,7,77,193,16 + CONTROL "Switch on double tap within",IDC_GLOBAL_TWO_TAP_CHECK, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,99,103,10 + EDITTEXT IDC_GLOBAL_TWO_TAP_TIME,112,98,45,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "ms",IDC_STATIC,159,100,10,8 + LTEXT "Synergy can periodically check that clients are still alive and connected. Use this only if synergy doesn't detect when clients disconnect.", + IDC_STATIC,7,122,193,24 + CONTROL "Check clients every",IDC_GLOBAL_HEARTBEAT_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,7,153,78,10 + EDITTEXT IDC_GLOBAL_HEARTBEAT_TIME,112,152,45,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "ms",IDC_STATIC,159,154,10,8 + LTEXT "Synergy can synchronize screen savers across all screens.", + IDC_STATIC,7,176,193,8 + CONTROL "Synchronize screen savers",IDC_GLOBAL_SCREENSAVER_SYNC, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,192,101,10 + LTEXT "Relative mouse moves on secondary screens.",IDC_STATIC, + 7,213,193,8 + CONTROL "Use relative mouse moves",IDC_GLOBAL_RELATIVE_MOVES, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,229,99,10 + CONTROL "Don't take foreground window on Windows servers", + IDC_GLOBAL_LEAVE_FOREGROUND,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,7,250,177,10 + DEFPUSHBUTTON "OK",IDOK,94,269,50,14 + PUSHBUTTON "Cancel",IDCANCEL,150,269,50,14 +END + +IDD_ADVANCED_OPTIONS DIALOG DISCARDABLE 0, 0, 230, 186 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Advanced Options" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Synergy normally uses this computer's name as its screen name. Enter another name here if you want to use a different screen name.", + IDC_STATIC,7,7,216,19 + LTEXT "Screen &Name:",IDC_STATIC,7,34,46,8 + EDITTEXT IDC_ADVANCED_NAME_EDIT,63,32,106,12,ES_AUTOHSCROLL + LTEXT "Synergy normally uses a particular network port number. Enter an alternative port here. (The server and all clients must use the same port number.)", + IDC_STATIC,7,56,216,26 + LTEXT "&Port:",IDC_STATIC,7,90,16,8 + EDITTEXT IDC_ADVANCED_PORT_EDIT,63,88,40,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "The server normally listens for client connections on all network interfaces. Enter the address of a particular interface to listen on just that interface.", + IDC_STATIC,7,110,216,26 + LTEXT "&Interface:",IDC_STATIC,7,144,31,8 + EDITTEXT IDC_ADVANCED_INTERFACE_EDIT,63,142,81,12,ES_AUTOHSCROLL + PUSHBUTTON "&Defaults",IDC_ADVANCED_DEFAULTS,7,165,50,14 + DEFPUSHBUTTON "OK",IDOK,118,165,50,14 + PUSHBUTTON "Cancel",IDCANCEL,173,165,50,14 +END + +IDD_SCREENS_LINKS DIALOG DISCARDABLE 0, 0, 354, 213 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Screens & Links" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "&Screens:",IDC_STATIC,7,7,29,8 + LISTBOX IDC_SCREENS_SCREENS,7,18,100,36,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "+",IDC_SCREENS_ADD_SCREEN,7,57,17,14 + PUSHBUTTON "-",IDC_SCREENS_REMOVE_SCREEN,28,57,17,14 + PUSHBUTTON "Edit",IDC_SCREENS_EDIT_SCREEN,49,57,24,14 + LTEXT "&Links:",IDC_STATIC,7,83,20,8 + LISTBOX IDC_SCREENS_LINKS,7,94,339,59,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + EDITTEXT IDC_SCREENS_SRC_START,7,156,16,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "to",IDC_STATIC,25,158,8,8 + EDITTEXT IDC_SCREENS_SRC_END,33,156,16,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "% of the",IDC_STATIC,52,158,27,8 + COMBOBOX IDC_SCREENS_SRC_SIDE,80,156,48,69,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "of",IDC_STATIC,129,158,8,8 + COMBOBOX IDC_SCREENS_SRC_SCREEN,139,156,59,53,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "goes to",IDC_STATIC,200,158,24,8 + EDITTEXT IDC_SCREENS_DST_START,225,156,16,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "to",IDC_STATIC,243,158,8,8 + EDITTEXT IDC_SCREENS_DST_END,251,156,16,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "% of",IDC_STATIC,270,158,15,8 + COMBOBOX IDC_SCREENS_DST_SCREEN,287,156,59,53,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "+",IDC_SCREENS_ADD_LINK,7,172,17,14 + PUSHBUTTON "-",IDC_SCREENS_REMOVE_LINK,28,172,17,14 + DEFPUSHBUTTON "OK",IDOK,241,192,50,14 + PUSHBUTTON "Cancel",IDCANCEL,297,192,50,14 + LTEXT "Source edge overlaps an existing edge.", + IDC_SCREENS_OVERLAP_ERROR,72,175,126,8,NOT WS_VISIBLE | + NOT WS_GROUP +END + +IDD_INFO DIALOG DISCARDABLE 0, 0, 186, 95 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Info" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Version:",IDC_STATIC,7,7,26,8 + EDITTEXT IDC_INFO_VERSION,52,7,127,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + LTEXT "Hostname:",IDC_STATIC,7,19,35,8 + EDITTEXT IDC_INFO_HOSTNAME,52,19,127,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + LTEXT "IP Address:",IDC_STATIC,7,31,37,8 + EDITTEXT IDC_INFO_IP_ADDRESS,52,31,127,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + LTEXT "User Config:",IDC_STATIC,7,43,40,8 + EDITTEXT IDC_INFO_USER_CONFIG,52,43,127,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + LTEXT "Sys Config:",IDC_STATIC,7,55,36,8 + EDITTEXT IDC_INFO_SYS_CONFIG,52,55,127,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + DEFPUSHBUTTON "OK",IDOK,129,74,50,14 +END + +IDD_HOTKEY_OPTIONS DIALOG DISCARDABLE 0, 0, 360, 151 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Hot Keys" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "&Hot Keys:",IDC_STATIC,7,7,32,8 + LISTBOX IDC_HOTKEY_HOTKEYS,7,18,169,88,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "+",IDC_HOTKEY_ADD_HOTKEY,7,109,17,14 + PUSHBUTTON "-",IDC_HOTKEY_REMOVE_HOTKEY,28,109,17,14 + PUSHBUTTON "Edit",IDC_HOTKEY_EDIT_HOTKEY,49,109,24,14 + LTEXT "&Actions:",IDC_STATIC,183,7,26,8 + LISTBOX IDC_HOTKEY_ACTIONS,183,18,169,88,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "+",IDC_HOTKEY_ADD_ACTION,183,109,17,14 + PUSHBUTTON "-",IDC_HOTKEY_REMOVE_ACTION,204,109,17,14 + PUSHBUTTON "Edit",IDC_HOTKEY_EDIT_ACTION,225,109,24,14 + DEFPUSHBUTTON "OK",IDOK,302,130,50,14 +END + +IDD_HOTKEY_CONDITION DIALOG DISCARDABLE 0, 0, 183, 58 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Hot Key" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Enter &new hot key or mouse button:",IDC_STATIC,7,7,113, + 8 + EDITTEXT IDC_HOTKEY_CONDITION_HOTKEY,7,17,169,12,ES_WANTRETURN + DEFPUSHBUTTON "OK",IDOK,70,37,50,14 + PUSHBUTTON "Cancel",IDCANCEL,126,37,50,14 +END + +IDD_HOTKEY_ACTION DIALOG DISCARDABLE 0, 0, 183, 218 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Action" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "&Action:",IDC_STATIC,7,7,23,8 + CONTROL "Press:",IDC_HOTKEY_ACTION_DOWN,"Button", + BS_AUTORADIOBUTTON | WS_TABSTOP,7,19,35,10 + CONTROL "Release:",IDC_HOTKEY_ACTION_UP,"Button", + BS_AUTORADIOBUTTON,7,31,44,10 + CONTROL "Press && Release:",IDC_HOTKEY_ACTION_DOWNUP,"Button", + BS_AUTORADIOBUTTON,7,43,69,10 + CONTROL "Switch To Screen:",IDC_HOTKEY_ACTION_SWITCH_TO,"Button", + BS_AUTORADIOBUTTON,7,85,75,10 + CONTROL "Switch In Direction:",IDC_HOTKEY_ACTION_SWITCH_IN, + "Button",BS_AUTORADIOBUTTON,7,101,77,10 + CONTROL "Lock Cursor to Screen:",IDC_HOTKEY_ACTION_LOCK,"Button", + BS_AUTORADIOBUTTON,7,117,89,10 + CONTROL "Keyboard broadcasting:", + IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST,"Button", + BS_AUTORADIOBUTTON,7,133,89,10 + LTEXT "&Hot key or mouse button:",IDC_STATIC,7,55,80,8 + EDITTEXT IDC_HOTKEY_ACTION_HOTKEY,7,67,152,12,ES_WANTRETURN + PUSHBUTTON "...",IDC_HOTKEY_ACTION_SCREENS,162,67,14,12 + COMBOBOX IDC_HOTKEY_ACTION_SWITCH_TO_LIST,87,83,89,62, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_HOTKEY_ACTION_SWITCH_IN_LIST,106,99,70,66, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_HOTKEY_ACTION_LOCK_LIST,106,115,70,58, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST,106,131,53,58, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "...",IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_SCREENS, + 162,131,14,12 + LTEXT "Action takes place &when:",IDC_STATIC,7,153,81,8 + CONTROL "Hot key is pressed",IDC_HOTKEY_ACTION_ON_ACTIVATE, + "Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,165,74,10 + CONTROL "Hot key is released",IDC_HOTKEY_ACTION_ON_DEACTIVATE, + "Button",BS_AUTORADIOBUTTON,7,177,76,10 + DEFPUSHBUTTON "OK",IDOK,70,197,50,14 + PUSHBUTTON "Cancel",IDCANCEL,126,197,50,14 +END + +IDD_HOTKEY_SCREENS DIALOG DISCARDABLE 0, 0, 237, 79 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Target Screens" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "&Available screens:",IDC_STATIC,7,7,58,8 + LISTBOX IDC_HOTKEY_SCREENS_SRC,7,17,100,36,LBS_NOINTEGRALHEIGHT | + LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP + LISTBOX IDC_HOTKEY_SCREENS_DST,130,17,100,36, + LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | + WS_TABSTOP + PUSHBUTTON "-->",IDC_HOTKEY_SCREENS_ADD,109,21,17,14 + PUSHBUTTON "<--",IDC_HOTKEY_SCREENS_REMOVE,109,38,17,14 + DEFPUSHBUTTON "OK",IDOK,124,58,50,14 + PUSHBUTTON "Cancel",IDCANCEL,180,58,50,14 + LTEXT "&Send action to screens:",IDC_STATIC,130,7,76,8 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_MAIN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 293 + TOPMARGIN, 7 + BOTTOMMARGIN, 192 + END + + IDD_ADD, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 185 + TOPMARGIN, 7 + BOTTOMMARGIN, 247 + END + + IDD_WAIT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 47 + END + + IDD_AUTOSTART, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 188 + TOPMARGIN, 7 + BOTTOMMARGIN, 182 + END + + IDD_GLOBAL_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 200 + TOPMARGIN, 7 + BOTTOMMARGIN, 283 + END + + IDD_ADVANCED_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 223 + TOPMARGIN, 7 + BOTTOMMARGIN, 179 + END + + IDD_SCREENS_LINKS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 347 + TOPMARGIN, 7 + BOTTOMMARGIN, 206 + END + + IDD_INFO, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 88 + END + + IDD_HOTKEY_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 353 + TOPMARGIN, 7 + BOTTOMMARGIN, 144 + END + + IDD_HOTKEY_CONDITION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 176 + TOPMARGIN, 7 + BOTTOMMARGIN, 51 + END + + IDD_HOTKEY_ACTION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 176 + TOPMARGIN, 7 + BOTTOMMARGIN, 195 + END + + IDD_HOTKEY_SCREENS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 230 + TOPMARGIN, 7 + BOTTOMMARGIN, 72 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_SYNERGY ICON DISCARDABLE "synergy.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_ERROR "Error" + IDS_INVALID_SCREEN_NAME "Screen name `%{1}' is invalid." + IDS_DUPLICATE_SCREEN_NAME "The screen name `%{1}' is already being used." + IDS_SCREEN_NAME_IS_ALIAS "A name may not be an alias of itself." + IDS_VERIFY "Confirm" + IDS_UNSAVED_DATA_REALLY_QUIT "You have unsaved changes. Really quit?" + IDS_UNKNOWN_SCREEN_NAME "The screen name `%{1}' is not in the layout." + IDS_INVALID_PORT "The port `%{1}' is invalid. It must be between 1 and 65535 inclusive. %{2} is the standard port." + IDS_SAVE_FAILED "Failed to save configuration: %{1}" + IDS_STARTUP_FAILED "Failed to start synergy: %{1}" + IDS_STARTED_TITLE "Started" + IDS_STARTED "Synergy was successfully started. Use the task manager or tray icon to terminate it." + IDS_UNINSTALL_TITLE "Removed Auto-Start" +END + +STRINGTABLE DISCARDABLE +BEGIN + IDS_AUTOSTART_PERMISSION_SYSTEM + "You have sufficient access rights to install and uninstall Auto Start for all users." + IDS_AUTOSTART_PERMISSION_USER + "You have sufficient access rights to install and uninstall Auto Start for just yourself." + IDS_AUTOSTART_PERMISSION_NONE + "You do not have sufficient access rights to install or uninstall Auto Start." + IDS_AUTOSTART_INSTALLED_SYSTEM + "Synergy is configured to start automatically when the system starts." + IDS_AUTOSTART_INSTALLED_USER + "Synergy is configured to start automatically when you log in." + IDS_AUTOSTART_INSTALLED_NONE + "Synergy is not configured to start automatically." + IDS_INSTALL_LABEL "Install" + IDS_UNINSTALL_LABEL "Uninstall" + IDS_INSTALL_GENERIC_ERROR "Install failed: %{1}" + IDS_UNINSTALL_GENERIC_ERROR "Uninstall failed: %{1}" + IDS_INSTALL_TITLE "Installed Auto-Start" + IDS_INSTALLED_SYSTEM "Installed auto-start. Synergy will automatically start each time you start your computer." + IDS_INSTALLED_USER "Installed auto-start. Synergy will automatically start each time you log in." +END + +STRINGTABLE DISCARDABLE +BEGIN + IDS_UNINSTALLED_SYSTEM "Removed auto-start. Synergy will not automatically start each time you start or reboot your computer." + IDS_UNINSTALLED_USER "Removed auto-start. Synergy will not automatically start each time you log in." + IDS_INVALID_SERVER_NAME "Server name `%{1}' is invalid." + IDS_TITLE "Synergy - Version %{1}" + IDS_SERVER_IS_CLIENT "Please enter the name of the computer sharing a\nkeyboard and mouse, not the name of this computer,\nin the Other Computer's Host Name field." + IDS_ADD_SCREEN "Add Screen" + IDS_EDIT_SCREEN "Edit Screen %{1}" + IDS_ERROR_CODE "Error code: %{1}" + IDS_AUTOSTART_PERMISSION_ALL + "You have sufficient access rights to install and uninstall Auto Start for all users or for just yourself." + IDS_INVALID_INTERFACE_NAME "The interface '%{1}' is invalid: %{2}" + IDS_INVALID_CORNER_SIZE "The dead corner size %{1} is invalid; it must be 0 or higher." + IDS_NEW_LINK "[New Link]" + IDS_SIDE_LEFT "left of" + IDS_SIDE_RIGHT "right of" + IDS_SIDE_TOP "above" + IDS_SIDE_BOTTOM "below" +END + +STRINGTABLE DISCARDABLE +BEGIN + IDS_LINK_FORMAT "%{4}%{5} is %{3} %{1}%{2}" + IDS_LINK_INTERVAL_FORMAT "(%{1},%{2})" + IDS_EDGE_LEFT "left" + IDS_EDGE_RIGHT "right" + IDS_EDGE_TOP "top" + IDS_EDGE_BOTTOM "bottom" + IDS_AUTOSTART_SAVE_FAILED "Failed to save autostart configuration: %{1}" + IDS_LOAD_FAILED "Failed to load configuration." + IDS_CONFIG_CHANGED "Configuration changed on disk. Reload?" + IDS_MODE_OFF "off" + IDS_MODE_ON "on" + IDS_MODE_TOGGLE "toggle" + IDS_ALL_SCREENS "All Screens" + IDS_ACTIVE_SCREEN "Active Screen" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/cmd/launcher/resource.h b/cmd/launcher/resource.h new file mode 100644 index 00000000..f284fd62 --- /dev/null +++ b/cmd/launcher/resource.h @@ -0,0 +1,186 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by launcher.rc +// +#define IDS_ERROR 1 +#define IDS_INVALID_SCREEN_NAME 2 +#define IDS_DUPLICATE_SCREEN_NAME 3 +#define IDS_SCREEN_NAME_IS_ALIAS 4 +#define IDS_VERIFY 5 +#define IDS_UNSAVED_DATA_REALLY_QUIT 6 +#define IDS_UNKNOWN_SCREEN_NAME 7 +#define IDS_INVALID_PORT 8 +#define IDS_SAVE_FAILED 9 +#define IDS_STARTUP_FAILED 10 +#define IDS_STARTED_TITLE 11 +#define IDS_STARTED 12 +#define IDS_INSTALL_FAILED 13 +#define IDS_UNINSTALL_TITLE 14 +#define IDS_UNINSTALLED 15 +#define IDS_UNINSTALL_FAILED 16 +#define IDS_CLIENT 17 +#define IDS_SERVER 18 +#define IDS_AUTOSTART_PERMISSION_SYSTEM 19 +#define IDS_AUTOSTART_PERMISSION_USER 20 +#define IDS_AUTOSTART_PERMISSION_NONE 21 +#define IDS_AUTOSTART_INSTALLED_SYSTEM 22 +#define IDS_AUTOSTART_INSTALLED_USER 23 +#define IDS_AUTOSTART_INSTALLED_NONE 24 +#define IDS_INSTALL_LABEL 25 +#define IDS_UNINSTALL_LABEL 26 +#define IDS_INSTALL_GENERIC_ERROR 27 +#define IDS_UNINSTALL_GENERIC_ERROR 28 +#define IDS_INSTALL_TITLE 29 +#define IDS_INSTALLED_SYSTEM 30 +#define IDS_INSTALLED_USER 31 +#define IDS_UNINSTALLED_SYSTEM 32 +#define IDS_UNINSTALLED_USER 33 +#define IDS_INVALID_SERVER_NAME 34 +#define IDS_TITLE 35 +#define IDS_SERVER_IS_CLIENT 36 +#define IDS_ADD_SCREEN 37 +#define IDS_EDIT_SCREEN 38 +#define IDS_INVALID_TIME 39 +#define IDS_ERROR_CODE 39 +#define IDS_AUTOSTART_PERMISSION_ALL 40 +#define IDS_INVALID_INTERFACE_NAME 41 +#define IDS_INVALID_CORNER_SIZE 42 +#define IDS_NEW_LINK 43 +#define IDS_SIDE_LEFT 44 +#define IDS_SIDE_RIGHT 45 +#define IDS_SIDE_TOP 46 +#define IDS_SIDE_BOTTOM 47 +#define IDS_LINK_FORMAT 48 +#define IDS_LINK_INTERVAL_FORMAT 49 +#define IDS_EDGE_LEFT 50 +#define IDS_EDGE_RIGHT 51 +#define IDS_EDGE_TOP 52 +#define IDS_EDGE_BOTTOM 53 +#define IDS_AUTOSTART_SAVE_FAILED 54 +#define IDS_LOAD_FAILED 55 +#define IDS_CONFIG_CHANGED 56 +#define IDS_MODE_OFF 57 +#define IDS_MODE_ON 58 +#define IDS_MODE_TOGGLE 59 +#define IDS_ALL_SCREENS 60 +#define IDS_ACTIVE_SCREEN 61 +#define IDD_MAIN 101 +#define IDD_ADD 102 +#define IDD_WAIT 103 +#define IDI_SYNERGY 104 +#define IDD_AUTOSTART 105 +#define IDD_ADVANCED_OPTIONS 106 +#define IDD_GLOBAL_OPTIONS 107 +#define IDD_SCREENS_LINKS 110 +#define IDD_INFO 111 +#define IDD_HOTKEY_OPTIONS 112 +#define IDD_HOTKEY_CONDITION 113 +#define IDD_HOTKEY_ACTION 114 +#define IDD_HOTKEY_SCREENS 115 +#define IDC_MAIN_CLIENT_RADIO 1000 +#define IDC_MAIN_SERVER_RADIO 1001 +#define IDC_MAIN_CLIENT_SERVER_NAME_LABEL 1002 +#define IDC_MAIN_CLIENT_SERVER_NAME_EDIT 1003 +#define IDC_MAIN_SERVER_SCREENS_LABEL 1004 +#define IDC_MAIN_SCREENS 1005 +#define IDC_MAIN_OPTIONS 1006 +#define IDC_MAIN_ADVANCED 1007 +#define IDC_MAIN_AUTOSTART 1008 +#define IDC_MAIN_TEST 1009 +#define IDC_MAIN_SAVE 1010 +#define IDC_MAIN_HOTKEYS 1010 +#define IDC_MAIN_DEBUG 1011 +#define IDC_ADD_SCREEN_NAME_EDIT 1020 +#define IDC_ADD_ALIASES_EDIT 1021 +#define IDC_AUTOSTART_INSTALLED_MSG 1031 +#define IDC_AUTOSTART_PERMISSION_MSG 1032 +#define IDC_AUTOSTART_INSTALL_USER 1033 +#define IDC_AUTOSTART_INSTALL_SYSTEM 1034 +#define IDC_ADD_HD_CAPS_CHECK 1037 +#define IDC_ADD_HD_NUM_CHECK 1038 +#define IDC_ADVANCED_NAME_EDIT 1038 +#define IDC_ADVANCED_PORT_EDIT 1039 +#define IDC_ADD_HD_SCROLL_CHECK 1039 +#define IDC_ADVANCED_INTERFACE_EDIT 1040 +#define IDC_GLOBAL_DELAY_CHECK 1041 +#define IDC_GLOBAL_DELAY_TIME 1042 +#define IDC_GLOBAL_TWO_TAP_CHECK 1043 +#define IDC_ADD_MOD_SHIFT 1043 +#define IDC_GLOBAL_TWO_TAP_TIME 1044 +#define IDC_ADD_MOD_CTRL 1044 +#define IDC_ADD_MOD_ALT 1045 +#define IDC_GLOBAL_HEARTBEAT_CHECK 1045 +#define IDC_ADD_MOD_META 1046 +#define IDC_GLOBAL_HEARTBEAT_TIME 1046 +#define IDC_ADD_MOD_SUPER 1047 +#define IDC_GLOBAL_SCREENSAVER_SYNC 1047 +#define IDC_GLOBAL_RELATIVE_MOVES 1048 +#define IDC_ADVANCED_DEFAULTS 1049 +#define IDC_GLOBAL_LEAVE_FOREGROUND 1049 +#define IDC_ADD_DC_SIZE 1052 +#define IDC_ADD_DC_TOP_LEFT 1053 +#define IDC_ADD_DC_TOP_RIGHT 1054 +#define IDC_ADD_DC_BOTTOM_LEFT 1055 +#define IDC_ADD_DC_BOTTOM_RIGHT 1056 +#define IDC_SCREENS_SRC_SIDE 1057 +#define IDC_SCREENS_SRC_START 1058 +#define IDC_SCREENS_SRC_END 1059 +#define IDC_SCREENS_SRC_SCREEN 1060 +#define IDC_SCREENS_SCREENS 1061 +#define IDC_SCREENS_ADD_SCREEN 1062 +#define IDC_SCREENS_LINKS 1063 +#define IDC_SCREENS_DST_START 1064 +#define IDC_SCREENS_DST_END 1065 +#define IDC_SCREENS_DST_SCREEN 1066 +#define IDC_SCREENS_REMOVE_SCREEN 1067 +#define IDC_SCREENS_EDIT_SCREEN 1068 +#define IDC_SCREENS_ADD_LINK 1069 +#define IDC_SCREENS_REMOVE_LINK 1070 +#define IDC_SCREENS_OVERLAP_ERROR 1071 +#define IDC_INFO_VERSION 1073 +#define IDC_MAIN_INFO 1074 +#define IDC_INFO_HOSTNAME 1076 +#define IDC_HOTKEY_HOTKEYS 1076 +#define IDC_INFO_IP_ADDRESS 1077 +#define IDC_HOTKEY_ADD_HOTKEY 1077 +#define IDC_INFO_USER_CONFIG 1078 +#define IDC_HOTKEY_REMOVE_HOTKEY 1078 +#define IDC_INFO_SYS_CONFIG 1079 +#define IDC_HOTKEY_EDIT_HOTKEY 1079 +#define IDC_HOTKEY_ACTIONS 1080 +#define IDC_HOTKEY_CONDITION_HOTKEY 1080 +#define IDC_HOTKEY_ACTION_DOWNUP 1081 +#define IDC_HOTKEY_ADD_ACTION 1082 +#define IDC_HOTKEY_ACTION_DOWN 1082 +#define IDC_HOTKEY_REMOVE_ACTION 1083 +#define IDC_HOTKEY_ACTION_UP 1083 +#define IDC_HOTKEY_EDIT_ACTION 1084 +#define IDC_HOTKEY_ACTION_HOTKEY 1085 +#define IDC_HOTKEY_ACTION_SWITCH_TO_LIST 1086 +#define IDC_HOTKEY_ACTION_SWITCH_TO 1087 +#define IDC_HOTKEY_ACTION_SWITCH_IN 1088 +#define IDC_HOTKEY_ACTION_LOCK 1089 +#define IDC_HOTKEY_ACTION_SWITCH_IN_LIST 1090 +#define IDC_HOTKEY_ACTION_LOCK_LIST 1091 +#define IDC_HOTKEY_ACTION_ON_ACTIVATE 1092 +#define IDC_HOTKEY_ACTION_ON_DEACTIVATE 1093 +#define IDC_HOTKEY_ACTION_SCREENS 1094 +#define IDC_HOTKEY_SCREENS_SRC 1095 +#define IDC_HOTKEY_SCREENS_DST 1096 +#define IDC_HOTKEY_SCREENS_ADD 1097 +#define IDC_HOTKEY_SCREENS_REMOVE 1098 +#define IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST 1099 +#define IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST 1100 +#define IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_SCREENS 1101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 116 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1102 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/cmd/launcher/synergy.ico b/cmd/launcher/synergy.ico new file mode 100644 index 0000000000000000000000000000000000000000..89f965f4432c5f58054b9a1b0eae5bf6cbfd245d GIT binary patch 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= literal 0 HcmV?d00001 diff --git a/cmd/synergyc/CClientTaskBarReceiver.cpp b/cmd/synergyc/CClientTaskBarReceiver.cpp new file mode 100644 index 00000000..025b43f6 --- /dev/null +++ b/cmd/synergyc/CClientTaskBarReceiver.cpp @@ -0,0 +1,136 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClientTaskBarReceiver.h" +#include "CClient.h" +#include "CLock.h" +#include "CStringUtil.h" +#include "IEventQueue.h" +#include "CArch.h" +#include "Version.h" + +// +// CClientTaskBarReceiver +// + +CClientTaskBarReceiver::CClientTaskBarReceiver() : + m_state(kNotRunning) +{ + // do nothing +} + +CClientTaskBarReceiver::~CClientTaskBarReceiver() +{ + // do nothing +} + +void +CClientTaskBarReceiver::updateStatus(CClient* client, const CString& errorMsg) +{ + { + // update our status + m_errorMessage = errorMsg; + if (client == NULL) { + if (m_errorMessage.empty()) { + m_state = kNotRunning; + } + else { + m_state = kNotWorking; + } + } + else { + m_server = client->getServerAddress().getHostname(); + + if (client->isConnected()) { + m_state = kConnected; + } + else if (client->isConnecting()) { + m_state = kConnecting; + } + else { + m_state = kNotConnected; + } + } + + // let subclasses have a go + onStatusChanged(client); + } + + // tell task bar + ARCH->updateReceiver(this); +} + +CClientTaskBarReceiver::EState +CClientTaskBarReceiver::getStatus() const +{ + return m_state; +} + +const CString& +CClientTaskBarReceiver::getErrorMessage() const +{ + return m_errorMessage; +} + +void +CClientTaskBarReceiver::quit() +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + +void +CClientTaskBarReceiver::onStatusChanged(CClient*) +{ + // do nothing +} + +void +CClientTaskBarReceiver::lock() const +{ + // do nothing +} + +void +CClientTaskBarReceiver::unlock() const +{ + // do nothing +} + +std::string +CClientTaskBarReceiver::getToolTip() const +{ + switch (m_state) { + case kNotRunning: + return CStringUtil::print("%s: Not running", kAppVersion); + + case kNotWorking: + return CStringUtil::print("%s: %s", + kAppVersion, m_errorMessage.c_str()); + + case kNotConnected: + return CStringUtil::print("%s: Not connected: %s", + kAppVersion, m_errorMessage.c_str()); + + case kConnecting: + return CStringUtil::print("%s: Connecting to %s...", + kAppVersion, m_server.c_str()); + + case kConnected: + return CStringUtil::print("%s: Connected to %s", + kAppVersion, m_server.c_str()); + + default: + return ""; + } +} diff --git a/cmd/synergyc/CClientTaskBarReceiver.h b/cmd/synergyc/CClientTaskBarReceiver.h new file mode 100644 index 00000000..0e3440e1 --- /dev/null +++ b/cmd/synergyc/CClientTaskBarReceiver.h @@ -0,0 +1,83 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTTASKBARRECEIVER_H +#define CCLIENTTASKBARRECEIVER_H + +#include "CString.h" +#include "IArchTaskBarReceiver.h" + +class CClient; + +//! Implementation of IArchTaskBarReceiver for the synergy server +class CClientTaskBarReceiver : public IArchTaskBarReceiver { +public: + CClientTaskBarReceiver(); + virtual ~CClientTaskBarReceiver(); + + //! @name manipulators + //@{ + + //! Update status + /*! + Determine the status and query required information from the client. + */ + void updateStatus(CClient*, const CString& errorMsg); + + //@} + + // IArchTaskBarReceiver overrides + virtual void showStatus() = 0; + virtual void runMenu(int x, int y) = 0; + virtual void primaryAction() = 0; + virtual void lock() const; + virtual void unlock() const; + virtual const Icon getIcon() const = 0; + virtual std::string getToolTip() const; + +protected: + enum EState { + kNotRunning, + kNotWorking, + kNotConnected, + kConnecting, + kConnected, + kMaxState + }; + + //! Get status + EState getStatus() const; + + //! Get error message + const CString& getErrorMessage() const; + + //! Quit app + /*! + Causes the application to quit gracefully + */ + void quit(); + + //! Status change notification + /*! + Called when status changes. The default implementation does nothing. + */ + virtual void onStatusChanged(CClient* client); + +private: + EState m_state; + CString m_errorMessage; + CString m_server; +}; + +#endif diff --git a/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp new file mode 100644 index 00000000..c002b9f1 --- /dev/null +++ b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp @@ -0,0 +1,346 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsClientTaskBarReceiver.h" +#include "CClient.h" +#include "CMSWindowsClipboard.h" +#include "LogOutputters.h" +#include "BasicTypes.h" +#include "CArch.h" +#include "CArchTaskBarWindows.h" +#include "resource.h" + +// +// CMSWindowsClientTaskBarReceiver +// + +const UINT CMSWindowsClientTaskBarReceiver::s_stateToIconID[kMaxState] = +{ + IDI_TASKBAR_NOT_RUNNING, + IDI_TASKBAR_NOT_WORKING, + IDI_TASKBAR_NOT_CONNECTED, + IDI_TASKBAR_NOT_CONNECTED, + IDI_TASKBAR_CONNECTED +}; + +CMSWindowsClientTaskBarReceiver::CMSWindowsClientTaskBarReceiver( + HINSTANCE appInstance, const CBufferedLogOutputter* logBuffer) : + CClientTaskBarReceiver(), + m_appInstance(appInstance), + m_window(NULL), + m_logBuffer(logBuffer) +{ + for (UInt32 i = 0; i < kMaxState; ++i) { + m_icon[i] = loadIcon(s_stateToIconID[i]); + } + m_menu = LoadMenu(m_appInstance, MAKEINTRESOURCE(IDR_TASKBAR)); + + // don't create the window yet. we'll create it on demand. this + // has the side benefit of being created in the thread used for + // the task bar. that's good because it means the existence of + // the window won't prevent changing the main thread's desktop. + + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CMSWindowsClientTaskBarReceiver::~CMSWindowsClientTaskBarReceiver() +{ + ARCH->removeReceiver(this); + for (UInt32 i = 0; i < kMaxState; ++i) { + deleteIcon(m_icon[i]); + } + DestroyMenu(m_menu); + destroyWindow(); +} + +void +CMSWindowsClientTaskBarReceiver::showStatus() +{ + // create the window + createWindow(); + + // lock self while getting status + lock(); + + // get the current status + std::string status = getToolTip(); + + // done getting status + unlock(); + + // update dialog + HWND child = GetDlgItem(m_window, IDC_TASKBAR_STATUS_STATUS); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)status.c_str()); + + if (!IsWindowVisible(m_window)) { + // position it by the mouse + POINT cursorPos; + GetCursorPos(&cursorPos); + RECT windowRect; + GetWindowRect(m_window, &windowRect); + int x = cursorPos.x; + int y = cursorPos.y; + int fw = GetSystemMetrics(SM_CXDLGFRAME); + int fh = GetSystemMetrics(SM_CYDLGFRAME); + int ww = windowRect.right - windowRect.left; + int wh = windowRect.bottom - windowRect.top; + int sw = GetSystemMetrics(SM_CXFULLSCREEN); + int sh = GetSystemMetrics(SM_CYFULLSCREEN); + if (fw < 1) { + fw = 1; + } + if (fh < 1) { + fh = 1; + } + if (x + ww - fw > sw) { + x -= ww - fw; + } + else { + x -= fw; + } + if (x < 0) { + x = 0; + } + if (y + wh - fh > sh) { + y -= wh - fh; + } + else { + y -= fh; + } + if (y < 0) { + y = 0; + } + SetWindowPos(m_window, HWND_TOPMOST, x, y, ww, wh, + SWP_SHOWWINDOW); + } +} + +void +CMSWindowsClientTaskBarReceiver::runMenu(int x, int y) +{ + // do popup menu. we need a window to pass to TrackPopupMenu(). + // the SetForegroundWindow() and SendMessage() calls around + // TrackPopupMenu() are to get the menu to be dismissed when + // another window gets activated and are just one of those + // win32 weirdnesses. + createWindow(); + SetForegroundWindow(m_window); + HMENU menu = GetSubMenu(m_menu, 0); + SetMenuDefaultItem(menu, IDC_TASKBAR_STATUS, FALSE); + HMENU logLevelMenu = GetSubMenu(menu, 3); + CheckMenuRadioItem(logLevelMenu, 0, 6, + CLOG->getFilter() - CLog::kERROR, MF_BYPOSITION); + int n = TrackPopupMenu(menu, + TPM_NONOTIFY | + TPM_RETURNCMD | + TPM_LEFTBUTTON | + TPM_RIGHTBUTTON, + x, y, 0, m_window, NULL); + SendMessage(m_window, WM_NULL, 0, 0); + + // perform the requested operation + switch (n) { + case IDC_TASKBAR_STATUS: + showStatus(); + break; + + case IDC_TASKBAR_LOG: + copyLog(); + break; + + case IDC_TASKBAR_SHOW_LOG: + ARCH->showConsole(true); + break; + + case IDC_TASKBAR_LOG_LEVEL_ERROR: + CLOG->setFilter(CLog::kERROR); + break; + + case IDC_TASKBAR_LOG_LEVEL_WARNING: + CLOG->setFilter(CLog::kWARNING); + break; + + case IDC_TASKBAR_LOG_LEVEL_NOTE: + CLOG->setFilter(CLog::kNOTE); + break; + + case IDC_TASKBAR_LOG_LEVEL_INFO: + CLOG->setFilter(CLog::kINFO); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG: + CLOG->setFilter(CLog::kDEBUG); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG1: + CLOG->setFilter(CLog::kDEBUG1); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG2: + CLOG->setFilter(CLog::kDEBUG2); + break; + + case IDC_TASKBAR_QUIT: + quit(); + break; + } +} + +void +CMSWindowsClientTaskBarReceiver::primaryAction() +{ + showStatus(); +} + +const IArchTaskBarReceiver::Icon +CMSWindowsClientTaskBarReceiver::getIcon() const +{ + return reinterpret_cast(m_icon[getStatus()]); +} + +void +CMSWindowsClientTaskBarReceiver::copyLog() const +{ + if (m_logBuffer != NULL) { + // collect log buffer + CString data; + for (CBufferedLogOutputter::const_iterator index = m_logBuffer->begin(); + index != m_logBuffer->end(); ++index) { + data += *index; + data += "\n"; + } + + // copy log to clipboard + if (!data.empty()) { + CMSWindowsClipboard clipboard(m_window); + clipboard.open(0); + clipboard.emptyUnowned(); + clipboard.add(IClipboard::kText, data); + clipboard.close(); + } + } +} + +void +CMSWindowsClientTaskBarReceiver::onStatusChanged() +{ + if (IsWindowVisible(m_window)) { + showStatus(); + } +} + +HICON +CMSWindowsClientTaskBarReceiver::loadIcon(UINT id) +{ + HANDLE icon = LoadImage(m_appInstance, + MAKEINTRESOURCE(id), + IMAGE_ICON, + 0, 0, + LR_DEFAULTCOLOR); + return reinterpret_cast(icon); +} + +void +CMSWindowsClientTaskBarReceiver::deleteIcon(HICON icon) +{ + if (icon != NULL) { + DestroyIcon(icon); + } +} + +void +CMSWindowsClientTaskBarReceiver::createWindow() +{ + // ignore if already created + if (m_window != NULL) { + return; + } + + // get the status dialog + m_window = CreateDialogParam(m_appInstance, + MAKEINTRESOURCE(IDD_TASKBAR_STATUS), + NULL, + (DLGPROC)&CMSWindowsClientTaskBarReceiver::staticDlgProc, + reinterpret_cast( + reinterpret_cast(this))); + + // window should appear on top of everything, including (especially) + // the task bar. + LONG_PTR style = GetWindowLongPtr(m_window, GWL_EXSTYLE); + style |= WS_EX_TOOLWINDOW | WS_EX_TOPMOST; + SetWindowLongPtr(m_window, GWL_EXSTYLE, style); + + // tell the task bar about this dialog + CArchTaskBarWindows::addDialog(m_window); +} + +void +CMSWindowsClientTaskBarReceiver::destroyWindow() +{ + if (m_window != NULL) { + CArchTaskBarWindows::removeDialog(m_window); + DestroyWindow(m_window); + m_window = NULL; + } +} + +BOOL +CMSWindowsClientTaskBarReceiver::dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM) +{ + switch (msg) { + case WM_INITDIALOG: + // use default focus + return TRUE; + + case WM_ACTIVATE: + // hide when another window is activated + if (LOWORD(wParam) == WA_INACTIVE) { + ShowWindow(hwnd, SW_HIDE); + } + break; + } + return FALSE; +} + +BOOL CALLBACK +CMSWindowsClientTaskBarReceiver::staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + // if msg is WM_INITDIALOG, extract the CMSWindowsClientTaskBarReceiver* + // and put it in the extra window data then forward the call. + CMSWindowsClientTaskBarReceiver* self = NULL; + if (msg == WM_INITDIALOG) { + self = reinterpret_cast( + reinterpret_cast(lParam)); + SetWindowLong(hwnd, GWL_USERDATA, lParam); + } + else { + // get the extra window data and forward the call + LONG data = GetWindowLong(hwnd, GWL_USERDATA); + if (data != 0) { + self = reinterpret_cast( + reinterpret_cast(data)); + } + } + + // forward the message + if (self != NULL) { + return self->dlgProc(hwnd, msg, wParam, lParam); + } + else { + return (msg == WM_INITDIALOG) ? TRUE : FALSE; + } +} diff --git a/cmd/synergyc/CMSWindowsClientTaskBarReceiver.h b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.h new file mode 100644 index 00000000..72d33be5 --- /dev/null +++ b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.h @@ -0,0 +1,64 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIENTTASKBARRECEIVER_H +#define CMSWINDOWSCLIENTTASKBARRECEIVER_H + +#define WIN32_LEAN_AND_MEAN + +#include "CClientTaskBarReceiver.h" +#include + +class CBufferedLogOutputter; + +//! Implementation of CClientTaskBarReceiver for Microsoft Windows +class CMSWindowsClientTaskBarReceiver : public CClientTaskBarReceiver { +public: + CMSWindowsClientTaskBarReceiver(HINSTANCE, const CBufferedLogOutputter*); + virtual ~CMSWindowsClientTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; + +protected: + void copyLog() const; + + // CClientTaskBarReceiver overrides + virtual void onStatusChanged(); + +private: + HICON loadIcon(UINT); + void deleteIcon(HICON); + void createWindow(); + void destroyWindow(); + + BOOL dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + static BOOL CALLBACK + staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + +private: + HINSTANCE m_appInstance; + HWND m_window; + HMENU m_menu; + HICON m_icon[kMaxState]; + const CBufferedLogOutputter* m_logBuffer; + static const UINT s_stateToIconID[]; +}; + +#endif diff --git a/cmd/synergyc/COSXClientTaskBarReceiver.cpp b/cmd/synergyc/COSXClientTaskBarReceiver.cpp new file mode 100644 index 00000000..c380ac4d --- /dev/null +++ b/cmd/synergyc/COSXClientTaskBarReceiver.cpp @@ -0,0 +1,56 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "COSXClientTaskBarReceiver.h" +#include "CArch.h" + +// +// COSXClientTaskBarReceiver +// + +COSXClientTaskBarReceiver::COSXClientTaskBarReceiver( + const CBufferedLogOutputter*) +{ + // add ourself to the task bar + ARCH->addReceiver(this); +} + +COSXClientTaskBarReceiver::~COSXClientTaskBarReceiver() +{ + ARCH->removeReceiver(this); +} + +void +COSXClientTaskBarReceiver::showStatus() +{ + // do nothing +} + +void +COSXClientTaskBarReceiver::runMenu(int, int) +{ + // do nothing +} + +void +COSXClientTaskBarReceiver::primaryAction() +{ + // do nothing +} + +const IArchTaskBarReceiver::Icon +COSXClientTaskBarReceiver::getIcon() const +{ + return NULL; +} diff --git a/cmd/synergyc/COSXClientTaskBarReceiver.h b/cmd/synergyc/COSXClientTaskBarReceiver.h new file mode 100644 index 00000000..59bca97c --- /dev/null +++ b/cmd/synergyc/COSXClientTaskBarReceiver.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXCLIENTTASKBARRECEIVER_H +#define COSXCLIENTTASKBARRECEIVER_H + +#include "CClientTaskBarReceiver.h" + +class CBufferedLogOutputter; + +//! Implementation of CClientTaskBarReceiver for OS X +class COSXClientTaskBarReceiver : public CClientTaskBarReceiver { +public: + COSXClientTaskBarReceiver(const CBufferedLogOutputter*); + virtual ~COSXClientTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; +}; + +#endif diff --git a/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp b/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp new file mode 100644 index 00000000..681f9be5 --- /dev/null +++ b/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp @@ -0,0 +1,56 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsClientTaskBarReceiver.h" +#include "CArch.h" + +// +// CXWindowsClientTaskBarReceiver +// + +CXWindowsClientTaskBarReceiver::CXWindowsClientTaskBarReceiver( + const CBufferedLogOutputter*) +{ + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CXWindowsClientTaskBarReceiver::~CXWindowsClientTaskBarReceiver() +{ + ARCH->removeReceiver(this); +} + +void +CXWindowsClientTaskBarReceiver::showStatus() +{ + // do nothing +} + +void +CXWindowsClientTaskBarReceiver::runMenu(int, int) +{ + // do nothing +} + +void +CXWindowsClientTaskBarReceiver::primaryAction() +{ + // do nothing +} + +const IArchTaskBarReceiver::Icon +CXWindowsClientTaskBarReceiver::getIcon() const +{ + return NULL; +} diff --git a/cmd/synergyc/CXWindowsClientTaskBarReceiver.h b/cmd/synergyc/CXWindowsClientTaskBarReceiver.h new file mode 100644 index 00000000..fa9da471 --- /dev/null +++ b/cmd/synergyc/CXWindowsClientTaskBarReceiver.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIENTTASKBARRECEIVER_H +#define CXWINDOWSCLIENTTASKBARRECEIVER_H + +#include "CClientTaskBarReceiver.h" + +class CBufferedLogOutputter; + +//! Implementation of CClientTaskBarReceiver for X Windows +class CXWindowsClientTaskBarReceiver : public CClientTaskBarReceiver { +public: + CXWindowsClientTaskBarReceiver(const CBufferedLogOutputter*); + virtual ~CXWindowsClientTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; +}; + +#endif diff --git a/cmd/synergyc/Makefile.am b/cmd/synergyc/Makefile.am new file mode 100644 index 00000000..b6e8c83d --- /dev/null +++ b/cmd/synergyc/Makefile.am @@ -0,0 +1,98 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +COMMON_SOURCE_FILES = \ + CClientTaskBarReceiver.cpp \ + CClientTaskBarReceiver.h \ + synergyc.cpp \ + $(NULL) +XWINDOWS_SOURCE_FILES = \ + CXWindowsClientTaskBarReceiver.cpp \ + CXWindowsClientTaskBarReceiver.h \ + $(NULL) +MSWINDOWS_SOURCE_FILES = \ + CMSWindowsClientTaskBarReceiver.cpp \ + CMSWindowsClientTaskBarReceiver.h \ + resource.h \ + synergyc.rc \ + $(NULL) +CARBON_SOURCE_FILES = \ + COSXClientTaskBarReceiver.cpp \ + COSXClientTaskBarReceiver.h \ + $(NULL) + +EXTRA_DIST = \ + Makefile.win \ + synergyc.ico \ + tb_error.ico \ + tb_idle.ico \ + tb_run.ico \ + tb_wait.ico \ + $(XWINDOWS_SOURCE_FILES) \ + $(MSWINDOWS_SOURCE_FILES) \ + $(CARBON_SOURCE_FILES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +bin_PROGRAMS = synergyc +if XWINDOWS +synergyc_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(XWINDOWS_SOURCE_FILES) \ + $(NULL) +endif +if MSWINDOWS +synergyc_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(MSWINDOWS_SOURCE_FILES) \ + $(NULL) +endif +if CARBON +synergyc_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(CARBON_SOURCE_FILES) \ + $(NULL) +synergyc_LDFLAGS = \ + -framework ScreenSaver \ + -framework IOKit \ + -framework ApplicationServices \ + -framework Foundation \ + $(NULL) +endif +synergyc_LDADD = \ + $(top_builddir)/lib/client/libclient.a \ + $(top_builddir)/lib/platform/libplatform.a \ + $(top_builddir)/lib/synergy/libsynergy.a \ + $(top_builddir)/lib/net/libnet.a \ + $(top_builddir)/lib/io/libio.a \ + $(top_builddir)/lib/mt/libmt.a \ + $(top_builddir)/lib/base/libbase.a \ + $(top_builddir)/lib/common/libcommon.a \ + $(top_builddir)/lib/arch/libarch.a \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + -I$(top_srcdir)/lib/synergy \ + -I$(top_srcdir)/lib/platform \ + -I$(top_srcdir)/lib/client \ + $(NULL) diff --git a/cmd/synergyc/Makefile.win b/cmd/synergyc/Makefile.win new file mode 100644 index 00000000..29f2e516 --- /dev/null +++ b/cmd/synergyc/Makefile.win @@ -0,0 +1,89 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +BIN_SYNERGYC_SRC = cmd\synergyc +BIN_SYNERGYC_DST = $(BUILD_DST)\$(BIN_SYNERGYC_SRC) +BIN_SYNERGYC_EXE = "$(BUILD_DST)\synergyc.exe" +BIN_SYNERGYC_CPP = \ + "CClientTaskBarReceiver.cpp" \ + "CMSWindowsClientTaskBarReceiver.cpp" \ + "synergyc.cpp" \ + $(NULL) +BIN_SYNERGYC_OBJ = \ + "$(BIN_SYNERGYC_DST)\CClientTaskBarReceiver.obj" \ + "$(BIN_SYNERGYC_DST)\CMSWindowsClientTaskBarReceiver.obj" \ + "$(BIN_SYNERGYC_DST)\synergyc.obj" \ + $(NULL) +BIN_SYNERGYC_RC = "$(BIN_SYNERGYC_SRC)\synergyc.rc" +BIN_SYNERGYC_RES = "$(BIN_SYNERGYC_DST)\synergyc.res" +BIN_SYNERGYC_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + /I"lib\platform" \ + /I"lib\client" \ + $(NULL) +BIN_SYNERGYC_LIB = \ + $(LIB_CLIENT_LIB) \ + $(LIB_PLATFORM_LIB) \ + $(LIB_SYNERGY_LIB) \ + $(LIB_NET_LIB) \ + $(LIB_IO_LIB) \ + $(LIB_MT_LIB) \ + $(LIB_BASE_LIB) \ + $(LIB_ARCH_LIB) \ + $(LIB_COMMON_LIB) \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(BIN_SYNERGYC_CPP) +OBJ_FILES = $(OBJ_FILES) $(BIN_SYNERGYC_OBJ) +PROGRAMS = $(PROGRAMS) $(BIN_SYNERGYC_EXE) + +# Need shell functions. +guilibs = $(guilibs) shell32.lib + +# Dependency rules +$(BIN_SYNERGYC_OBJ): $(AUTODEP) +!if EXIST($(BIN_SYNERGYC_DST)\deps.mak) +!include $(BIN_SYNERGYC_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(BIN_SYNERGYC_SRC)\}.cpp{$(BIN_SYNERGYC_DST)\}.obj:: +!else +{$(BIN_SYNERGYC_SRC)\}.cpp{$(BIN_SYNERGYC_DST)\}.obj: +!endif + @$(ECHO) Compile in $(BIN_SYNERGYC_SRC) + -@$(MKDIR) $(BIN_SYNERGYC_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(BIN_SYNERGYC_INC) \ + /Fo$(BIN_SYNERGYC_DST)\ \ + /Fd$(BIN_SYNERGYC_DST)\src.pdb \ + $< | $(AUTODEP) $(BIN_SYNERGYC_SRC) $(BIN_SYNERGYC_DST) +$(BIN_SYNERGYC_RES): $(BIN_SYNERGYC_RC) + @$(ECHO) Compile $(**F) + -@$(MKDIR) $(BIN_SYNERGYC_DST) 2>NUL: + $(rc) $(rcflags) $(rcvars) \ + /fo$@ \ + $** +$(BIN_SYNERGYC_EXE): $(BIN_SYNERGYC_OBJ) $(BIN_SYNERGYC_RES) $(BIN_SYNERGYC_LIB) + @$(ECHO) Link $(@F) + $(link) $(ldebug) $(guilflags) $(guilibsmt) \ + /out:$@ \ + $** + $(AUTODEP) $(BIN_SYNERGYC_SRC) $(BIN_SYNERGYC_DST) \ + $(BIN_SYNERGYC_OBJ:.obj=.d) diff --git a/cmd/synergyc/resource.h b/cmd/synergyc/resource.h new file mode 100644 index 00000000..eeee6e1e --- /dev/null +++ b/cmd/synergyc/resource.h @@ -0,0 +1,37 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by synergyc.rc +// +#define IDS_FAILED 1 +#define IDS_INIT_FAILED 2 +#define IDS_UNCAUGHT_EXCEPTION 3 +#define IDI_SYNERGY 101 +#define IDI_TASKBAR_NOT_RUNNING 102 +#define IDI_TASKBAR_NOT_WORKING 103 +#define IDI_TASKBAR_NOT_CONNECTED 104 +#define IDI_TASKBAR_CONNECTED 105 +#define IDR_TASKBAR 107 +#define IDD_TASKBAR_STATUS 108 +#define IDC_TASKBAR_STATUS_STATUS 1000 +#define IDC_TASKBAR_QUIT 40001 +#define IDC_TASKBAR_STATUS 40002 +#define IDC_TASKBAR_LOG 40003 +#define IDC_TASKBAR_SHOW_LOG 40004 +#define IDC_TASKBAR_LOG_LEVEL_ERROR 40009 +#define IDC_TASKBAR_LOG_LEVEL_WARNING 40010 +#define IDC_TASKBAR_LOG_LEVEL_NOTE 40011 +#define IDC_TASKBAR_LOG_LEVEL_INFO 40012 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG 40013 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG1 40014 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG2 40015 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 109 +#define _APS_NEXT_COMMAND_VALUE 40016 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/cmd/synergyc/synergyc.cpp b/cmd/synergyc/synergyc.cpp new file mode 100644 index 00000000..1a230f0d --- /dev/null +++ b/cmd/synergyc/synergyc.cpp @@ -0,0 +1,910 @@ +/* + * 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 "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 + +#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_logFilter(NULL), + m_display(NULL), + m_serverAddress(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; + const char* m_logFilter; + const char* m_display; + CString m_name; + CNetworkAddress* m_serverAddress; +}; + +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)); +#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; + +static +void +updateStatus() +{ + s_taskBarReceiver->updateStatus(s_client, ""); +} + +static +void +updateStatus(const CString& msg) +{ + s_taskBarReceiver->updateStatus(s_client, msg); +} + +static +void +resetRestartTimeout() +{ + s_retryTime = 0.0; +} + +static +double +nextRestartTimeout() +{ + // 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 if (s_retryTime < 5.0) { + s_retryTime = 5.0; + } + else if (s_retryTime < 15.0) { + s_retryTime = 15.0; + } + else if (s_retryTime < 30.0) { + s_retryTime = 30.0; + } + else { + s_retryTime = 60.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() +{ + // 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")); + + return kExitSuccess; +} + +static +int +daemonMainLoop(int, const char**) +{ +#if SYSAPI_WIN32 + CSystemLogger sysLogger(DAEMON_NAME, false); +#else + CSystemLogger sysLogger(DAEMON_NAME, true); +#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 ]" +" [--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" +" -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" +" -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); + + // 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, "-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 "Synergy client %s on %s", kVersion, ARCH->getOSName().c_str())); +} + + +// +// 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) +{ + 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; + } +} + +#else + +#error no main() for platform + +#endif diff --git a/cmd/synergyc/synergyc.ico b/cmd/synergyc/synergyc.ico new file mode 100644 index 0000000000000000000000000000000000000000..89f965f4432c5f58054b9a1b0eae5bf6cbfd245d GIT binary patch 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= literal 0 HcmV?d00001 diff --git a/cmd/synergyc/synergyc.rc b/cmd/synergyc/synergyc.rc new file mode 100644 index 00000000..7f2a5dc1 --- /dev/null +++ b/cmd/synergyc/synergyc.rc @@ -0,0 +1,141 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +#if !defined(IDC_STATIC) +#define IDC_STATIC (-1) +#endif + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include \r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_SYNERGY ICON DISCARDABLE "synergyc.ico" +IDI_TASKBAR_NOT_RUNNING ICON DISCARDABLE "tb_idle.ico" +IDI_TASKBAR_NOT_WORKING ICON DISCARDABLE "tb_error.ico" +IDI_TASKBAR_NOT_CONNECTED ICON DISCARDABLE "tb_wait.ico" +IDI_TASKBAR_CONNECTED ICON DISCARDABLE "tb_run.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_TASKBAR_STATUS DIALOG DISCARDABLE 0, 0, 145, 18 +STYLE DS_MODALFRAME | WS_POPUP +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_TASKBAR_STATUS_STATUS,3,3,139,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_TASKBAR MENU DISCARDABLE +BEGIN + POPUP "Synergy" + BEGIN + MENUITEM "Show Status", IDC_TASKBAR_STATUS + MENUITEM "Show Log", IDC_TASKBAR_SHOW_LOG + MENUITEM "Copy Log To Clipboard", IDC_TASKBAR_LOG + POPUP "Set Log Level" + BEGIN + MENUITEM "Error", IDC_TASKBAR_LOG_LEVEL_ERROR + + MENUITEM "Warning", IDC_TASKBAR_LOG_LEVEL_WARNING + + MENUITEM "Note", IDC_TASKBAR_LOG_LEVEL_NOTE + + MENUITEM "Info", IDC_TASKBAR_LOG_LEVEL_INFO + + MENUITEM "Debug", IDC_TASKBAR_LOG_LEVEL_DEBUG + + MENUITEM "Debug1", IDC_TASKBAR_LOG_LEVEL_DEBUG1 + + MENUITEM "Debug2", IDC_TASKBAR_LOG_LEVEL_DEBUG2 + + END + MENUITEM SEPARATOR + MENUITEM "Quit", IDC_TASKBAR_QUIT + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_FAILED "Synergy is about to quit with errors or warnings. Please check the log then click OK." + IDS_INIT_FAILED "Synergy failed to initialize: %{1}" + IDS_UNCAUGHT_EXCEPTION "Uncaught exception: %{1}" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/cmd/synergyc/tb_error.ico b/cmd/synergyc/tb_error.ico new file mode 100644 index 0000000000000000000000000000000000000000..746a87c9ec8ae70f24b4125afe9ca68defb2f6d1 GIT binary patch literal 318 zcmZusyA6Xd5PgscB3VLIX+veDOwBY@u9?8{1k4aIf%|Jb3Y{4t9eD?$OZQF)q4|7v^Hl|=e!@IsjRK@B2u}b(FJQe=z?=V5liH zWoPHjzBjX*nT1SN6a`+-89X}5txV(@w?b&ncnuP0lhP#!b);z;MJKxRrt5r?%Pbk_ z?N%_Tg0`uZoK7he#O%9wJeiXYR@<+l75<*=!?qP*0HwJ2|O6{7v9^DvFs zu!_Y~)D!l8cMcLvA>Yqua2!LMtJfQ~uh~C-l}Rwt@WUxQyu;vMf6!iXu5qpJ`0fd8 C{!x(t literal 0 HcmV?d00001 diff --git a/cmd/synergyc/tb_run.ico b/cmd/synergyc/tb_run.ico new file mode 100644 index 0000000000000000000000000000000000000000..88e160cbfcd029978599f49605ba1a3c7695027e GIT binary patch literal 318 zcmZvXJ#K_B5QRT>QHT}^QKbzPO1ZU9vz2R3fRNJr5IzCD8;(L}A7MN84cny1jNhAi z^COL+lJ|X&*-r&u76q#eLPafx?d1Px0X>%G9mGo6woTC*$N4x8%LKWVjJU*LBRA(t z*&)UlLNF;^_DhTq!s6VW^|KVov_j`difNtFX&-H(O$k3SDILcq=N9iD-8@T;1372! my*B6BBsAGS;Q0-Eqg$^!Uw{7 literal 0 HcmV?d00001 diff --git a/cmd/synergyc/tb_wait.ico b/cmd/synergyc/tb_wait.ico new file mode 100644 index 0000000000000000000000000000000000000000..257be0a1d1bc613eec8cc1838a1dfd25d4644844 GIT binary patch literal 318 zcmZvXF%H5o3`Ji7QN#e9SYe77nRA*>nK?mJi9LtNNy<&SB_ktS@h=k+vHidOZA%U` zW?k2zcWvM#wvckMXxJFSxZpn+z?@1UcuF zl1i)Vw8|M$8oa;3u2z*2ycgFRqu9Ap#396Zhplt1gb?~ejL|uFp_CFr025R~TS5=- dGfb`By0-J}?~kW-COE!+Lz;S;(X4i~`vJXUO(y^V literal 0 HcmV?d00001 diff --git a/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp b/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp new file mode 100644 index 00000000..e332ccea --- /dev/null +++ b/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp @@ -0,0 +1,374 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsServerTaskBarReceiver.h" +#include "CServer.h" +#include "CMSWindowsClipboard.h" +#include "IEventQueue.h" +#include "LogOutputters.h" +#include "BasicTypes.h" +#include "CArch.h" +#include "CArchTaskBarWindows.h" +#include "resource.h" + +extern CEvent::Type getReloadConfigEvent(); +extern CEvent::Type getForceReconnectEvent(); + +// +// CMSWindowsServerTaskBarReceiver +// + +const UINT CMSWindowsServerTaskBarReceiver::s_stateToIconID[kMaxState] = +{ + IDI_TASKBAR_NOT_RUNNING, + IDI_TASKBAR_NOT_WORKING, + IDI_TASKBAR_NOT_CONNECTED, + IDI_TASKBAR_CONNECTED +}; + +CMSWindowsServerTaskBarReceiver::CMSWindowsServerTaskBarReceiver( + HINSTANCE appInstance, const CBufferedLogOutputter* logBuffer) : + CServerTaskBarReceiver(), + m_appInstance(appInstance), + m_window(NULL), + m_logBuffer(logBuffer) +{ + for (UInt32 i = 0; i < kMaxState; ++i) { + m_icon[i] = loadIcon(s_stateToIconID[i]); + } + m_menu = LoadMenu(m_appInstance, MAKEINTRESOURCE(IDR_TASKBAR)); + + // don't create the window yet. we'll create it on demand. this + // has the side benefit of being created in the thread used for + // the task bar. that's good because it means the existence of + // the window won't prevent changing the main thread's desktop. + + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CMSWindowsServerTaskBarReceiver::~CMSWindowsServerTaskBarReceiver() +{ + ARCH->removeReceiver(this); + for (UInt32 i = 0; i < kMaxState; ++i) { + deleteIcon(m_icon[i]); + } + DestroyMenu(m_menu); + destroyWindow(); +} + +void +CMSWindowsServerTaskBarReceiver::showStatus() +{ + // create the window + createWindow(); + + // lock self while getting status + lock(); + + // get the current status + std::string status = getToolTip(); + + // get the connect clients, if any + const CClients& clients = getClients(); + + // done getting status + unlock(); + + // update dialog + HWND child = GetDlgItem(m_window, IDC_TASKBAR_STATUS_STATUS); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)status.c_str()); + child = GetDlgItem(m_window, IDC_TASKBAR_STATUS_CLIENTS); + SendMessage(child, LB_RESETCONTENT, 0, 0); + for (CClients::const_iterator index = clients.begin(); + index != clients.end(); ) { + const char* client = index->c_str(); + if (++index == clients.end()) { + SendMessage(child, LB_ADDSTRING, 0, (LPARAM)client); + } + else { + SendMessage(child, LB_INSERTSTRING, (WPARAM)-1, (LPARAM)client); + } + } + + if (!IsWindowVisible(m_window)) { + // position it by the mouse + POINT cursorPos; + GetCursorPos(&cursorPos); + RECT windowRect; + GetWindowRect(m_window, &windowRect); + int x = cursorPos.x; + int y = cursorPos.y; + int fw = GetSystemMetrics(SM_CXDLGFRAME); + int fh = GetSystemMetrics(SM_CYDLGFRAME); + int ww = windowRect.right - windowRect.left; + int wh = windowRect.bottom - windowRect.top; + int sw = GetSystemMetrics(SM_CXFULLSCREEN); + int sh = GetSystemMetrics(SM_CYFULLSCREEN); + if (fw < 1) { + fw = 1; + } + if (fh < 1) { + fh = 1; + } + if (x + ww - fw > sw) { + x -= ww - fw; + } + else { + x -= fw; + } + if (x < 0) { + x = 0; + } + if (y + wh - fh > sh) { + y -= wh - fh; + } + else { + y -= fh; + } + if (y < 0) { + y = 0; + } + SetWindowPos(m_window, HWND_TOPMOST, x, y, ww, wh, + SWP_SHOWWINDOW); + } +} + +void +CMSWindowsServerTaskBarReceiver::runMenu(int x, int y) +{ + // do popup menu. we need a window to pass to TrackPopupMenu(). + // the SetForegroundWindow() and SendMessage() calls around + // TrackPopupMenu() are to get the menu to be dismissed when + // another window gets activated and are just one of those + // win32 weirdnesses. + createWindow(); + SetForegroundWindow(m_window); + HMENU menu = GetSubMenu(m_menu, 0); + SetMenuDefaultItem(menu, IDC_TASKBAR_STATUS, FALSE); + HMENU logLevelMenu = GetSubMenu(menu, 3); + CheckMenuRadioItem(logLevelMenu, 0, 6, + CLOG->getFilter() - CLog::kERROR, MF_BYPOSITION); + int n = TrackPopupMenu(menu, + TPM_NONOTIFY | + TPM_RETURNCMD | + TPM_LEFTBUTTON | + TPM_RIGHTBUTTON, + x, y, 0, m_window, NULL); + SendMessage(m_window, WM_NULL, 0, 0); + + // perform the requested operation + switch (n) { + case IDC_TASKBAR_STATUS: + showStatus(); + break; + + case IDC_TASKBAR_LOG: + copyLog(); + break; + + case IDC_TASKBAR_SHOW_LOG: + ARCH->showConsole(true); + break; + + case IDC_RELOAD_CONFIG: + EVENTQUEUE->addEvent(CEvent(getReloadConfigEvent(), + IEventQueue::getSystemTarget())); + break; + + case IDC_FORCE_RECONNECT: + EVENTQUEUE->addEvent(CEvent(getForceReconnectEvent(), + IEventQueue::getSystemTarget())); + break; + + case IDC_TASKBAR_LOG_LEVEL_ERROR: + CLOG->setFilter(CLog::kERROR); + break; + + case IDC_TASKBAR_LOG_LEVEL_WARNING: + CLOG->setFilter(CLog::kWARNING); + break; + + case IDC_TASKBAR_LOG_LEVEL_NOTE: + CLOG->setFilter(CLog::kNOTE); + break; + + case IDC_TASKBAR_LOG_LEVEL_INFO: + CLOG->setFilter(CLog::kINFO); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG: + CLOG->setFilter(CLog::kDEBUG); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG1: + CLOG->setFilter(CLog::kDEBUG1); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG2: + CLOG->setFilter(CLog::kDEBUG2); + break; + + case IDC_TASKBAR_QUIT: + quit(); + break; + } +} + +void +CMSWindowsServerTaskBarReceiver::primaryAction() +{ + showStatus(); +} + +const IArchTaskBarReceiver::Icon +CMSWindowsServerTaskBarReceiver::getIcon() const +{ + return reinterpret_cast(m_icon[getStatus()]); +} + +void +CMSWindowsServerTaskBarReceiver::copyLog() const +{ + if (m_logBuffer != NULL) { + // collect log buffer + CString data; + for (CBufferedLogOutputter::const_iterator index = m_logBuffer->begin(); + index != m_logBuffer->end(); ++index) { + data += *index; + data += "\n"; + } + + // copy log to clipboard + if (!data.empty()) { + CMSWindowsClipboard clipboard(m_window); + clipboard.open(0); + clipboard.emptyUnowned(); + clipboard.add(IClipboard::kText, data); + clipboard.close(); + } + } +} + +void +CMSWindowsServerTaskBarReceiver::onStatusChanged() +{ + if (IsWindowVisible(m_window)) { + showStatus(); + } +} + +HICON +CMSWindowsServerTaskBarReceiver::loadIcon(UINT id) +{ + HANDLE icon = LoadImage(m_appInstance, + MAKEINTRESOURCE(id), + IMAGE_ICON, + 0, 0, + LR_DEFAULTCOLOR); + return reinterpret_cast(icon); +} + +void +CMSWindowsServerTaskBarReceiver::deleteIcon(HICON icon) +{ + if (icon != NULL) { + DestroyIcon(icon); + } +} + +void +CMSWindowsServerTaskBarReceiver::createWindow() +{ + // ignore if already created + if (m_window != NULL) { + return; + } + + // get the status dialog + m_window = CreateDialogParam(m_appInstance, + MAKEINTRESOURCE(IDD_TASKBAR_STATUS), + NULL, + (DLGPROC)&CMSWindowsServerTaskBarReceiver::staticDlgProc, + reinterpret_cast( + reinterpret_cast(this))); + + // window should appear on top of everything, including (especially) + // the task bar. + LONG_PTR style = GetWindowLongPtr(m_window, GWL_EXSTYLE); + style |= WS_EX_TOOLWINDOW | WS_EX_TOPMOST; + SetWindowLongPtr(m_window, GWL_EXSTYLE, style); + + // tell the task bar about this dialog + CArchTaskBarWindows::addDialog(m_window); +} + +void +CMSWindowsServerTaskBarReceiver::destroyWindow() +{ + if (m_window != NULL) { + CArchTaskBarWindows::removeDialog(m_window); + DestroyWindow(m_window); + m_window = NULL; + } +} + +BOOL +CMSWindowsServerTaskBarReceiver::dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM) +{ + switch (msg) { + case WM_INITDIALOG: + // use default focus + return TRUE; + + case WM_ACTIVATE: + // hide when another window is activated + if (LOWORD(wParam) == WA_INACTIVE) { + ShowWindow(hwnd, SW_HIDE); + } + break; + } + return FALSE; +} + +BOOL CALLBACK +CMSWindowsServerTaskBarReceiver::staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + // if msg is WM_INITDIALOG, extract the CMSWindowsServerTaskBarReceiver* + // and put it in the extra window data then forward the call. + CMSWindowsServerTaskBarReceiver* self = NULL; + if (msg == WM_INITDIALOG) { + self = reinterpret_cast( + reinterpret_cast(lParam)); + SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam); + } + else { + // get the extra window data and forward the call + LONG data = GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (data != 0) { + self = reinterpret_cast( + reinterpret_cast(data)); + } + } + + // forward the message + if (self != NULL) { + return self->dlgProc(hwnd, msg, wParam, lParam); + } + else { + return (msg == WM_INITDIALOG) ? TRUE : FALSE; + } +} diff --git a/cmd/synergys/CMSWindowsServerTaskBarReceiver.h b/cmd/synergys/CMSWindowsServerTaskBarReceiver.h new file mode 100644 index 00000000..ab679077 --- /dev/null +++ b/cmd/synergys/CMSWindowsServerTaskBarReceiver.h @@ -0,0 +1,64 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSSERVERTASKBARRECEIVER_H +#define CMSWINDOWSSERVERTASKBARRECEIVER_H + +#define WIN32_LEAN_AND_MEAN + +#include "CServerTaskBarReceiver.h" +#include + +class CBufferedLogOutputter; + +//! Implementation of CServerTaskBarReceiver for Microsoft Windows +class CMSWindowsServerTaskBarReceiver : public CServerTaskBarReceiver { +public: + CMSWindowsServerTaskBarReceiver(HINSTANCE, const CBufferedLogOutputter*); + virtual ~CMSWindowsServerTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; + +protected: + void copyLog() const; + + // CServerTaskBarReceiver overrides + virtual void onStatusChanged(); + +private: + HICON loadIcon(UINT); + void deleteIcon(HICON); + void createWindow(); + void destroyWindow(); + + BOOL dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + static BOOL CALLBACK + staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + +private: + HINSTANCE m_appInstance; + HWND m_window; + HMENU m_menu; + HICON m_icon[kMaxState]; + const CBufferedLogOutputter* m_logBuffer; + static const UINT s_stateToIconID[]; +}; + +#endif diff --git a/cmd/synergys/COSXServerTaskBarReceiver.cpp b/cmd/synergys/COSXServerTaskBarReceiver.cpp new file mode 100644 index 00000000..8195b84f --- /dev/null +++ b/cmd/synergys/COSXServerTaskBarReceiver.cpp @@ -0,0 +1,56 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "COSXServerTaskBarReceiver.h" +#include "CArch.h" + +// +// COSXServerTaskBarReceiver +// + +COSXServerTaskBarReceiver::COSXServerTaskBarReceiver( + const CBufferedLogOutputter*) +{ + // add ourself to the task bar + ARCH->addReceiver(this); +} + +COSXServerTaskBarReceiver::~COSXServerTaskBarReceiver() +{ + ARCH->removeReceiver(this); +} + +void +COSXServerTaskBarReceiver::showStatus() +{ + // do nothing +} + +void +COSXServerTaskBarReceiver::runMenu(int, int) +{ + // do nothing +} + +void +COSXServerTaskBarReceiver::primaryAction() +{ + // do nothing +} + +const IArchTaskBarReceiver::Icon +COSXServerTaskBarReceiver::getIcon() const +{ + return NULL; +} diff --git a/cmd/synergys/COSXServerTaskBarReceiver.h b/cmd/synergys/COSXServerTaskBarReceiver.h new file mode 100644 index 00000000..7f6dc298 --- /dev/null +++ b/cmd/synergys/COSXServerTaskBarReceiver.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXSERVERTASKBARRECEIVER_H +#define COSXSERVERTASKBARRECEIVER_H + +#include "CServerTaskBarReceiver.h" + +class CBufferedLogOutputter; + +//! Implementation of CServerTaskBarReceiver for OS X +class COSXServerTaskBarReceiver : public CServerTaskBarReceiver { +public: + COSXServerTaskBarReceiver(const CBufferedLogOutputter*); + virtual ~COSXServerTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; +}; + +#endif diff --git a/cmd/synergys/CServerTaskBarReceiver.cpp b/cmd/synergys/CServerTaskBarReceiver.cpp new file mode 100644 index 00000000..6555b214 --- /dev/null +++ b/cmd/synergys/CServerTaskBarReceiver.cpp @@ -0,0 +1,133 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CServerTaskBarReceiver.h" +#include "CServer.h" +#include "CLock.h" +#include "CStringUtil.h" +#include "IEventQueue.h" +#include "CArch.h" +#include "Version.h" + +// +// CServerTaskBarReceiver +// + +CServerTaskBarReceiver::CServerTaskBarReceiver() : + m_state(kNotRunning) +{ + // do nothing +} + +CServerTaskBarReceiver::~CServerTaskBarReceiver() +{ + // do nothing +} + +void +CServerTaskBarReceiver::updateStatus(CServer* server, const CString& errorMsg) +{ + { + // update our status + m_errorMessage = errorMsg; + if (server == NULL) { + if (m_errorMessage.empty()) { + m_state = kNotRunning; + } + else { + m_state = kNotWorking; + } + } + else { + m_clients.clear(); + server->getClients(m_clients); + if (m_clients.size() <= 1) { + m_state = kNotConnected; + } + else { + m_state = kConnected; + } + } + + // let subclasses have a go + onStatusChanged(server); + } + + // tell task bar + ARCH->updateReceiver(this); +} + +CServerTaskBarReceiver::EState +CServerTaskBarReceiver::getStatus() const +{ + return m_state; +} + +const CString& +CServerTaskBarReceiver::getErrorMessage() const +{ + return m_errorMessage; +} + +const CServerTaskBarReceiver::CClients& +CServerTaskBarReceiver::getClients() const +{ + return m_clients; +} + +void +CServerTaskBarReceiver::quit() +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + +void +CServerTaskBarReceiver::onStatusChanged(CServer*) +{ + // do nothing +} + +void +CServerTaskBarReceiver::lock() const +{ + // do nothing +} + +void +CServerTaskBarReceiver::unlock() const +{ + // do nothing +} + +std::string +CServerTaskBarReceiver::getToolTip() const +{ + switch (m_state) { + case kNotRunning: + return CStringUtil::print("%s: Not running", kAppVersion); + + case kNotWorking: + return CStringUtil::print("%s: %s", + kAppVersion, m_errorMessage.c_str()); + + case kNotConnected: + return CStringUtil::print("%s: Waiting for clients", kAppVersion); + + case kConnected: + return CStringUtil::print("%s: Connected", kAppVersion); + + default: + return ""; + } +} diff --git a/cmd/synergys/CServerTaskBarReceiver.h b/cmd/synergys/CServerTaskBarReceiver.h new file mode 100644 index 00000000..d6ec8571 --- /dev/null +++ b/cmd/synergys/CServerTaskBarReceiver.h @@ -0,0 +1,88 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSERVERTASKBARRECEIVER_H +#define CSERVERTASKBARRECEIVER_H + +#include "CString.h" +#include "IArchTaskBarReceiver.h" +#include "stdvector.h" + +class CServer; + +//! Implementation of IArchTaskBarReceiver for the synergy server +class CServerTaskBarReceiver : public IArchTaskBarReceiver { +public: + CServerTaskBarReceiver(); + virtual ~CServerTaskBarReceiver(); + + //! @name manipulators + //@{ + + //! Update status + /*! + Determine the status and query required information from the server. + */ + void updateStatus(CServer*, const CString& errorMsg); + + //@} + + // IArchTaskBarReceiver overrides + virtual void showStatus() = 0; + virtual void runMenu(int x, int y) = 0; + virtual void primaryAction() = 0; + virtual void lock() const; + virtual void unlock() const; + virtual const Icon getIcon() const = 0; + virtual std::string getToolTip() const; + +protected: + typedef std::vector CClients; + enum EState { + kNotRunning, + kNotWorking, + kNotConnected, + kConnected, + kMaxState + }; + + //! Get status + EState getStatus() const; + + //! Get error message + const CString& getErrorMessage() const; + + //! Get connected clients + const CClients& getClients() const; + + //! Quit app + /*! + Causes the application to quit gracefully + */ + void quit(); + + //! Status change notification + /*! + Called when status changes. The default implementation does + nothing. + */ + virtual void onStatusChanged(CServer* server); + +private: + EState m_state; + CString m_errorMessage; + CClients m_clients; +}; + +#endif diff --git a/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp b/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp new file mode 100644 index 00000000..861d2f8c --- /dev/null +++ b/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp @@ -0,0 +1,56 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsServerTaskBarReceiver.h" +#include "CArch.h" + +// +// CXWindowsServerTaskBarReceiver +// + +CXWindowsServerTaskBarReceiver::CXWindowsServerTaskBarReceiver( + const CBufferedLogOutputter*) +{ + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CXWindowsServerTaskBarReceiver::~CXWindowsServerTaskBarReceiver() +{ + ARCH->removeReceiver(this); +} + +void +CXWindowsServerTaskBarReceiver::showStatus() +{ + // do nothing +} + +void +CXWindowsServerTaskBarReceiver::runMenu(int, int) +{ + // do nothing +} + +void +CXWindowsServerTaskBarReceiver::primaryAction() +{ + // do nothing +} + +const IArchTaskBarReceiver::Icon +CXWindowsServerTaskBarReceiver::getIcon() const +{ + return NULL; +} diff --git a/cmd/synergys/CXWindowsServerTaskBarReceiver.h b/cmd/synergys/CXWindowsServerTaskBarReceiver.h new file mode 100644 index 00000000..73234123 --- /dev/null +++ b/cmd/synergys/CXWindowsServerTaskBarReceiver.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSSERVERTASKBARRECEIVER_H +#define CXWINDOWSSERVERTASKBARRECEIVER_H + +#include "CServerTaskBarReceiver.h" + +class CBufferedLogOutputter; + +//! Implementation of CServerTaskBarReceiver for X Windows +class CXWindowsServerTaskBarReceiver : public CServerTaskBarReceiver { +public: + CXWindowsServerTaskBarReceiver(const CBufferedLogOutputter*); + virtual ~CXWindowsServerTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; +}; + +#endif diff --git a/cmd/synergys/Makefile.am b/cmd/synergys/Makefile.am new file mode 100644 index 00000000..4ad48ce5 --- /dev/null +++ b/cmd/synergys/Makefile.am @@ -0,0 +1,98 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +COMMON_SOURCE_FILES = \ + CServerTaskBarReceiver.cpp \ + CServerTaskBarReceiver.h \ + synergys.cpp \ + $(NULL) +XWINDOWS_SOURCE_FILES = \ + CXWindowsServerTaskBarReceiver.cpp \ + CXWindowsServerTaskBarReceiver.h \ + $(NULL) +MSWINDOWS_SOURCE_FILES = \ + CMSWindowsServerTaskBarReceiver.cpp \ + CMSWindowsServerTaskBarReceiver.h \ + resource.h \ + synergys.rc \ + $(NULL) +CARBON_SOURCE_FILES = \ + COSXServerTaskBarReceiver.cpp \ + COSXServerTaskBarReceiver.h \ + $(NULL) + +EXTRA_DIST = \ + Makefile.win \ + synergys.ico \ + tb_error.ico \ + tb_idle.ico \ + tb_run.ico \ + tb_wait.ico \ + $(XWINDOWS_SOURCE_FILES) \ + $(MSWINDOWS_SOURCE_FILES) \ + $(CARBON_SOURCE_FILES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +bin_PROGRAMS = synergys +if XWINDOWS +synergys_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(XWINDOWS_SOURCE_FILES) \ + $(NULL) +endif +if MSWINDOWS +synergys_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(MSWINDOWS_SOURCE_FILES) \ + $(NULL) +endif +if CARBON +synergys_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(CARBON_SOURCE_FILES) \ + $(NULL) +synergys_LDFLAGS = \ + -framework ScreenSaver \ + -framework IOKit \ + -framework ApplicationServices \ + -framework Foundation \ + $(NULL) +endif +synergys_LDADD = \ + $(top_builddir)/lib/server/libserver.a \ + $(top_builddir)/lib/platform/libplatform.a \ + $(top_builddir)/lib/synergy/libsynergy.a \ + $(top_builddir)/lib/net/libnet.a \ + $(top_builddir)/lib/io/libio.a \ + $(top_builddir)/lib/mt/libmt.a \ + $(top_builddir)/lib/base/libbase.a \ + $(top_builddir)/lib/common/libcommon.a \ + $(top_builddir)/lib/arch/libarch.a \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + -I$(top_srcdir)/lib/synergy \ + -I$(top_srcdir)/lib/platform \ + -I$(top_srcdir)/lib/server \ + $(NULL) diff --git a/cmd/synergys/Makefile.win b/cmd/synergys/Makefile.win new file mode 100644 index 00000000..09d39958 --- /dev/null +++ b/cmd/synergys/Makefile.win @@ -0,0 +1,89 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +BIN_SYNERGYS_SRC = cmd\synergys +BIN_SYNERGYS_DST = $(BUILD_DST)\$(BIN_SYNERGYS_SRC) +BIN_SYNERGYS_EXE = "$(BUILD_DST)\synergys.exe" +BIN_SYNERGYS_CPP = \ + "CServerTaskBarReceiver.cpp" \ + "CMSWindowsServerTaskBarReceiver.cpp" \ + "synergys.cpp" \ + $(NULL) +BIN_SYNERGYS_OBJ = \ + "$(BIN_SYNERGYS_DST)\CServerTaskBarReceiver.obj" \ + "$(BIN_SYNERGYS_DST)\CMSWindowsServerTaskBarReceiver.obj" \ + "$(BIN_SYNERGYS_DST)\synergys.obj" \ + $(NULL) +BIN_SYNERGYS_RC = "$(BIN_SYNERGYS_SRC)\synergys.rc" +BIN_SYNERGYS_RES = "$(BIN_SYNERGYS_DST)\synergys.res" +BIN_SYNERGYS_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + /I"lib\platform" \ + /I"lib\server" \ + $(NULL) +BIN_SYNERGYS_LIB = \ + $(LIB_SERVER_LIB) \ + $(LIB_PLATFORM_LIB) \ + $(LIB_SYNERGY_LIB) \ + $(LIB_NET_LIB) \ + $(LIB_IO_LIB) \ + $(LIB_MT_LIB) \ + $(LIB_BASE_LIB) \ + $(LIB_ARCH_LIB) \ + $(LIB_COMMON_LIB) \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(BIN_SYNERGYS_CPP) +OBJ_FILES = $(OBJ_FILES) $(BIN_SYNERGYS_OBJ) +PROGRAMS = $(PROGRAMS) $(BIN_SYNERGYS_EXE) + +# Need shell functions. +guilibs = $(guilibs) shell32.lib + +# Dependency rules +$(BIN_SYNERGYS_OBJ): $(AUTODEP) +!if EXIST($(BIN_SYNERGYS_DST)\deps.mak) +!include $(BIN_SYNERGYS_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(BIN_SYNERGYS_SRC)\}.cpp{$(BIN_SYNERGYS_DST)\}.obj:: +!else +{$(BIN_SYNERGYS_SRC)\}.cpp{$(BIN_SYNERGYS_DST)\}.obj: +!endif + @$(ECHO) Compile in $(BIN_SYNERGYS_SRC) + -@$(MKDIR) $(BIN_SYNERGYS_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(BIN_SYNERGYS_INC) \ + /Fo$(BIN_SYNERGYS_DST)\ \ + /Fd$(BIN_SYNERGYS_DST)\src.pdb \ + $< | $(AUTODEP) $(BIN_SYNERGYS_SRC) $(BIN_SYNERGYS_DST) +$(BIN_SYNERGYS_RES): $(BIN_SYNERGYS_RC) + @$(ECHO) Compile $(**F) + -@$(MKDIR) $(BIN_SYNERGYS_DST) 2>NUL: + $(rc) $(rcflags) $(rcvars) \ + /fo$@ \ + $** +$(BIN_SYNERGYS_EXE): $(BIN_SYNERGYS_OBJ) $(BIN_SYNERGYS_RES) $(BIN_SYNERGYS_LIB) + @$(ECHO) Link $(@F) + $(link) $(ldebug) $(guilflags) $(guilibsmt) \ + /out:$@ \ + $** + $(AUTODEP) $(BIN_SYNERGYS_SRC) $(BIN_SYNERGYS_DST) \ + $(BIN_SYNERGYS_OBJ:.obj=.d) diff --git a/cmd/synergys/resource.h b/cmd/synergys/resource.h new file mode 100644 index 00000000..0ad5868a --- /dev/null +++ b/cmd/synergys/resource.h @@ -0,0 +1,40 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by synergys.rc +// +#define IDS_FAILED 1 +#define IDS_INIT_FAILED 2 +#define IDS_UNCAUGHT_EXCEPTION 3 +#define IDI_SYNERGY 101 +#define IDI_TASKBAR_NOT_RUNNING 102 +#define IDI_TASKBAR_NOT_WORKING 103 +#define IDI_TASKBAR_NOT_CONNECTED 104 +#define IDI_TASKBAR_CONNECTED 105 +#define IDR_TASKBAR 107 +#define IDD_TASKBAR_STATUS 108 +#define IDC_TASKBAR_STATUS_STATUS 1000 +#define IDC_TASKBAR_STATUS_CLIENTS 1001 +#define IDC_TASKBAR_QUIT 40003 +#define IDC_TASKBAR_STATUS 40004 +#define IDC_TASKBAR_LOG 40005 +#define IDC_RELOAD_CONFIG 40006 +#define IDC_FORCE_RECONNECT 40007 +#define IDC_TASKBAR_SHOW_LOG 40008 +#define IDC_TASKBAR_LOG_LEVEL_ERROR 40009 +#define IDC_TASKBAR_LOG_LEVEL_WARNING 40010 +#define IDC_TASKBAR_LOG_LEVEL_NOTE 40011 +#define IDC_TASKBAR_LOG_LEVEL_INFO 40012 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG 40013 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG1 40014 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG2 40015 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 109 +#define _APS_NEXT_COMMAND_VALUE 40016 +#define _APS_NEXT_CONTROL_VALUE 1003 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/cmd/synergys/synergys.cpp b/cmd/synergys/synergys.cpp new file mode 100644 index 00000000..4319af1e --- /dev/null +++ b/cmd/synergys/synergys.cpp @@ -0,0 +1,1312 @@ +/* + * 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 "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 + +#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_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_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 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"); +} + +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(); + } +} + +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; + + // 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)); + + // 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")); + + return kExitSuccess; +} + +static +int +daemonMainLoop(int, const char**) +{ +#if SYSAPI_WIN32 + CSystemLogger sysLogger(DAEMON_NAME, false); +#else + CSystemLogger sysLogger(DAEMON_NAME, true); +#endif + return mainLoop(); +} + +static +int +standardStartup(int argc, char** argv) +{ + if (!ARG->m_daemon) { + ARCH->showConsole(false); + } + + // 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" +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, "-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 "Synergy server %s on %s", kVersion, ARCH->getOSName().c_str())); +} + +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) +{ + ARCH->showConsole(false); + + // 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) +{ + 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; + } +} + +#else + +#error no main() for platform + +#endif diff --git a/cmd/synergys/synergys.ico b/cmd/synergys/synergys.ico new file mode 100644 index 0000000000000000000000000000000000000000..89f965f4432c5f58054b9a1b0eae5bf6cbfd245d GIT binary patch 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= literal 0 HcmV?d00001 diff --git a/cmd/synergys/synergys.rc b/cmd/synergys/synergys.rc new file mode 100644 index 00000000..d56a4313 --- /dev/null +++ b/cmd/synergys/synergys.rc @@ -0,0 +1,146 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +#if !defined(IDC_STATIC) +#define IDC_STATIC (-1) +#endif + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include \r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_SYNERGY ICON DISCARDABLE "synergys.ico" +IDI_TASKBAR_NOT_RUNNING ICON DISCARDABLE "tb_idle.ico" +IDI_TASKBAR_NOT_WORKING ICON DISCARDABLE "tb_error.ico" +IDI_TASKBAR_NOT_CONNECTED ICON DISCARDABLE "tb_wait.ico" +IDI_TASKBAR_CONNECTED ICON DISCARDABLE "tb_run.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_TASKBAR MENU DISCARDABLE +BEGIN + POPUP "Synergy" + BEGIN + MENUITEM "Show Status", IDC_TASKBAR_STATUS + MENUITEM "Show Log", IDC_TASKBAR_SHOW_LOG + MENUITEM "Copy Log To Clipboard", IDC_TASKBAR_LOG + POPUP "Set Log Level" + BEGIN + MENUITEM "Error", IDC_TASKBAR_LOG_LEVEL_ERROR + + MENUITEM "Warning", IDC_TASKBAR_LOG_LEVEL_WARNING + + MENUITEM "Note", IDC_TASKBAR_LOG_LEVEL_NOTE + + MENUITEM "Info", IDC_TASKBAR_LOG_LEVEL_INFO + + MENUITEM "Debug", IDC_TASKBAR_LOG_LEVEL_DEBUG + + MENUITEM "Debug1", IDC_TASKBAR_LOG_LEVEL_DEBUG1 + + MENUITEM "Debug2", IDC_TASKBAR_LOG_LEVEL_DEBUG2 + + END + MENUITEM "Reload Configuration", IDC_RELOAD_CONFIG + MENUITEM "Force Reconnect", IDC_FORCE_RECONNECT + MENUITEM SEPARATOR + MENUITEM "Quit", IDC_TASKBAR_QUIT + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_TASKBAR_STATUS DIALOG DISCARDABLE 0, 0, 145, 60 +STYLE DS_MODALFRAME | WS_POPUP +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_TASKBAR_STATUS_STATUS,3,3,139,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + LISTBOX IDC_TASKBAR_STATUS_CLIENTS,3,17,139,40,NOT LBS_NOTIFY | + LBS_SORT | LBS_NOINTEGRALHEIGHT | LBS_NOSEL | WS_VSCROLL | + WS_TABSTOP +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_FAILED "Synergy is about to quit with errors or warnings. Please check the log then click OK." + IDS_INIT_FAILED "Synergy failed to initialize: %{1}" + IDS_UNCAUGHT_EXCEPTION "Uncaught exception: %{1}" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/cmd/synergys/tb_error.ico b/cmd/synergys/tb_error.ico new file mode 100644 index 0000000000000000000000000000000000000000..746a87c9ec8ae70f24b4125afe9ca68defb2f6d1 GIT binary patch literal 318 zcmZusyA6Xd5PgscB3VLIX+veDOwBY@u9?8{1k4aIf%|Jb3Y{4t9eD?$OZQF)q4|7v^Hl|=e!@IsjRK@B2u}b(FJQe=z?=V5liH zWoPHjzBjX*nT1SN6a`+-89X}5txV(@w?b&ncnuP0lhP#!b);z;MJKxRrt5r?%Pbk_ z?N%_Tg0`uZoK7he#O%9wJeiXYR@<+l75<*=!?qP*0HwJ2|O6{7v9^DvFs zu!_Y~)D!l8cMcLvA>Yqua2!LMtJfQ~uh~C-l}Rwt@WUxQyu;vMf6!iXu5qpJ`0fd8 C{!x(t literal 0 HcmV?d00001 diff --git a/cmd/synergys/tb_run.ico b/cmd/synergys/tb_run.ico new file mode 100644 index 0000000000000000000000000000000000000000..88e160cbfcd029978599f49605ba1a3c7695027e GIT binary patch literal 318 zcmZvXJ#K_B5QRT>QHT}^QKbzPO1ZU9vz2R3fRNJr5IzCD8;(L}A7MN84cny1jNhAi z^COL+lJ|X&*-r&u76q#eLPafx?d1Px0X>%G9mGo6woTC*$N4x8%LKWVjJU*LBRA(t z*&)UlLNF;^_DhTq!s6VW^|KVov_j`difNtFX&-H(O$k3SDILcq=N9iD-8@T;1372! my*B6BBsAGS;Q0-Eqg$^!Uw{7 literal 0 HcmV?d00001 diff --git a/cmd/synergys/tb_wait.ico b/cmd/synergys/tb_wait.ico new file mode 100644 index 0000000000000000000000000000000000000000..257be0a1d1bc613eec8cc1838a1dfd25d4644844 GIT binary patch literal 318 zcmZvXF%H5o3`Ji7QN#e9SYe77nRA*>nK?mJi9LtNNy<&SB_ktS@h=k+vHidOZA%U` zW?k2zcWvM#wvckMXxJFSxZpn+z?@1UcuF zl1i)Vw8|M$8oa;3u2z*2ycgFRqu9Ap#396Zhplt1gb?~ejL|uFp_CFr025R~TS5=- dGfb`By0-J}?~kW-COE!+Lz;S;(X4i~`vJXUO(y^V literal 0 HcmV?d00001 diff --git a/config/config.guess b/config/config.guess new file mode 100644 index 00000000..6ead80a0 --- /dev/null +++ b/config/config.guess @@ -0,0 +1,1327 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +# Free Software Foundation, Inc. + +timestamp='2001-08-21' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Written by Per Bothner . +# Please send patches to . +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit build system type. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + + +dummy=dummy-$$ +trap 'rm -f $dummy.c $dummy.o $dummy.rel $dummy; exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +set_cc_for_build='case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int dummy(){}" > $dummy.c ; + for c in cc gcc c89 ; do + ($c $dummy.c -c -o $dummy.o) >/dev/null 2>&1 ; + if test $? = 0 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + rm -f $dummy.c $dummy.o $dummy.rel ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case "${UNAME_MACHINE}" in + i?86) + test -z "$VENDOR" && VENDOR=pc + ;; + *) + test -z "$VENDOR" && VENDOR=unknown + ;; +esac +test -f /etc/SuSE-release && VENDOR=suse + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # Netbsd (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # Determine the machine/vendor (is the vendor relevant). + case "${UNAME_MACHINE}" in + amiga) machine=m68k-unknown ;; + arm32) machine=arm-unknown ;; + atari*) machine=m68k-atari ;; + sun3*) machine=m68k-sun ;; + mac68k) machine=m68k-apple ;; + macppc) machine=powerpc-apple ;; + hp3[0-9][05]) machine=m68k-hp ;; + ibmrt|romp-ibm) machine=romp-ibm ;; + *) machine=${UNAME_MACHINE}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE}" in + i386|sparc|amiga|arm*|hp300|mvme68k|vax|atari|luna68k|mac68k|news68k|next68k|pc532|sun3*|x68k) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep __ELF__ >/dev/null + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit 0 ;; + alpha:OSF1:*:*) + if test $UNAME_RELEASE = "V4.0"; then + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + fi + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + cat <$dummy.s + .data +\$Lformat: + .byte 37,100,45,37,120,10,0 # "%d-%x\n" + + .text + .globl main + .align 4 + .ent main +main: + .frame \$30,16,\$26,0 + ldgp \$29,0(\$27) + .prologue 1 + .long 0x47e03d80 # implver \$0 + lda \$2,-1 + .long 0x47e20c21 # amask \$2,\$1 + lda \$16,\$Lformat + mov \$0,\$17 + not \$1,\$18 + jsr \$26,printf + ldgp \$29,0(\$26) + mov 0,\$16 + jsr \$26,exit + .end main +EOF + eval $set_cc_for_build + $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null + if test "$?" = 0 ; then + case `./$dummy` in + 0-0) + UNAME_MACHINE="alpha" + ;; + 1-0) + UNAME_MACHINE="alphaev5" + ;; + 1-1) + UNAME_MACHINE="alphaev56" + ;; + 1-101) + UNAME_MACHINE="alphapca56" + ;; + 2-303) + UNAME_MACHINE="alphaev6" + ;; + 2-307) + UNAME_MACHINE="alphaev67" + ;; + 2-1307) + UNAME_MACHINE="alphaev68" + ;; + esac + fi + rm -f $dummy.s $dummy + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit 0 ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit 0 ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit 0 ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit 0;; + amiga:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit 0 ;; + arc64:OpenBSD:*:*) + echo mips64el-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + hkmips:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + pmax:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sgi:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + wgrisc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit 0 ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit 0;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit 0;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit 0 ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit 0 ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit 0 ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit 0 ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(head -1 /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit 0 ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit 0 ;; + sparc*:NetBSD:*) + echo `uname -p`-unknown-netbsd${UNAME_RELEASE} + exit 0 ;; + atari*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit 0 ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit 0 ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit 0 ;; + sun3*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme88k:OpenBSD:*:*) + echo m88k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit 0 ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit 0 ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit 0 ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + eval $set_cc_for_build + $CC_FOR_BUILD $dummy.c -o $dummy \ + && ./$dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ + && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo mips-mips-riscos${UNAME_RELEASE} + exit 0 ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit 0 ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit 0 ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit 0 ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit 0 ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit 0 ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit 0 ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit 0 ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit 0 ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit 0 ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit 0 ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit 0 ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit 0 ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + eval $set_cc_for_build + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo rs6000-ibm-aix3.2.5 + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit 0 ;; + *:AIX:*:[45]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | head -1 | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit 0 ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit 0 ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit 0 ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit 0 ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit 0 ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit 0 ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit 0 ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + case "${HPUX_REV}" in + 11.[0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + esac ;; + esac + fi ;; + esac + if [ "${HP_ARCH}" = "" ]; then + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + eval $set_cc_for_build + (CCOPTS= $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null ) && HP_ARCH=`./$dummy` + if test -z "$HP_ARCH"; then HP_ARCH=hppa; fi + rm -f $dummy.c $dummy + fi ;; + esac + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit 0 ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit 0 ;; + 3050*:HI-UX:*:*) + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + eval $set_cc_for_build + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo unknown-hitachi-hiuxwe2 + exit 0 ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit 0 ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit 0 ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit 0 ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit 0 ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit 0 ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit 0 ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit 0 ;; + hppa*:OpenBSD:*:*) + echo hppa-unknown-openbsd + exit 0 ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit 0 ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit 0 ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit 0 ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit 0 ;; + CRAY*X-MP:*:*:*) + echo xmp-cray-unicos + exit 0 ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*T3D:*:*:*) + echo alpha-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY-2:*:*:*) + echo cray2-cray-unicos + exit 0 ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + hp300:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit 0 ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:FreeBSD:*:*) + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit 0 ;; + *:OpenBSD:*:*) + echo ${UNAME_MACHINE}-unknown-openbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit 0 ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit 0 ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit 0 ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i386-pc-interix + exit 0 ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit 0 ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit 0 ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + *:GNU:*:*) + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit 0 ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit 0 ;; + arm*:Linux:*:*) + echo ${UNAME_MACHINE}-${VENDOR}-linux + exit 0 ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-${VENDOR}-linux + exit 0 ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-${VENDOR}-linux + exit 0 ;; + mips:Linux:*:*) + case `sed -n '/^byte/s/^.*: \(.*\) endian/\1/p' < /proc/cpuinfo` in + big) echo mips-${VENDOR}-linux && exit 0 ;; + little) echo mipsel-${VENDOR}-linux && exit 0 ;; + esac + case `sed -n '/^system type/s/^.*: \([^ ]*\).*/\1/p' < /proc/cpuinfo` in + SGI|sgi) echo mips-${VENDOR}-linux-gnu && exit 0 ;; + esac + ;; + ppc:Linux:*:*|ppc64:Linux:*:*) + echo powerpc-${VENDOR}-linux + exit 0 ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-gnu + exit 0 ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null + if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi + echo ${UNAME_MACHINE}-${VENDOR}-linux${LIBC} + exit 0 ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-${VENDOR}-linux ;; + PA8*) echo hppa2.0-${VENDOR}-linux ;; + *) echo hppa-${VENDOR}-linux ;; + esac + exit 0 ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-${VENDOR}-linux + exit 0 ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit 0 ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-${VENDOR}-linux + exit 0 ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-${VENDOR}-linux + exit 0 ;; + x86_64:Linux:*:*) + echo x86_64-${VENDOR}-linux + exit 0 ;; + i*86:Linux:*:*) + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + ld_supported_targets=`cd /; ld --help 2>&1 \ + | sed -ne '/supported targets:/!d + s/[ ][ ]*/ /g + s/.*supported targets: *// + s/ .*// + p'` + case "$ld_supported_targets" in + elf32-i386) + TENTATIVE="${UNAME_MACHINE}-${VENDOR}-linux" + ;; + a.out-i386-linux) + echo "${UNAME_MACHINE}-${VENDOR}-linuxaout" + exit 0 ;; + coff-i386) + echo "${UNAME_MACHINE}-${VENDOR}-linuxcoff" + exit 0 ;; + "") + # Either a pre-BFD a.out linker (linuxoldld) or + # one that does not give us useful --help. + echo "${UNAME_MACHINE}-${VENDOR}-linuxoldld" + exit 0 ;; + esac + # Determine whether the default compiler is a.out or elf + cat >$dummy.c < +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif +#ifdef __ELF__ +# ifdef __GLIBC__ +# if __GLIBC__ >= 2 + printf ("%s-${VENDOR}-linux\n", argv[1]); +# else + printf ("%s-${VENDOR}-linuxlibc1\n", argv[1]); +# endif +# else + printf ("%s-${VENDOR}-linuxlibc1\n", argv[1]); +# endif +#else + printf ("%s-${VENDOR}-linuxaout\n", argv[1]); +#endif + return 0; +} +EOF + eval $set_cc_for_build + $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy "${UNAME_MACHINE}" && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit 0 ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit 0 ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit 0 ;; + i*86:*:5:[78]*) + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit 0 ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|egrep Release|sed -e 's/.*= //')` + (/bin/uname -X|egrep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|egrep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|egrep '^Machine.*Pent ?II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|egrep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit 0 ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit 0 ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit 0 ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit 0 ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit 0 ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit 0 ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit 0 ;; + M68*:*:R3V[567]*:*) + test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; + 3[34]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 4850:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4.3${OS_REL} && exit 0 + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4 && exit 0 ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit 0 ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit 0 ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit 0 ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit 0 ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit 0 ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit 0 ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit 0 ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit 0 ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit 0 ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit 0 ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit 0 ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit 0 ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit 0 ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit 0 ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Darwin:*:*) + echo `uname -p`-apple-darwin${UNAME_RELEASE} + exit 0 ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + if test "${UNAME_MACHINE}" = "x86pc"; then + UNAME_MACHINE=pc + fi + echo `uname -p`-${UNAME_MACHINE}-nto-qnx + exit 0 ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit 0 ;; + NSR-[KW]:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit 0 ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit 0 ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit 0 ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit 0 ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit 0 ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit 0 ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit 0 ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit 0 ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit 0 ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit 0 ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit 0 ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit 0 ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit 0 ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +eval $set_cc_for_build +$CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy && rm -f $dummy.c $dummy && exit 0 +rm -f $dummy.c $dummy + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit 0 ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + c34*) + echo c34-convex-bsd + exit 0 ;; + c38*) + echo c38-convex-bsd + exit 0 ;; + c4*) + echo c4-convex-bsd + exit 0 ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/config/config.sub b/config/config.sub new file mode 100644 index 00000000..83f4b015 --- /dev/null +++ b/config/config.sub @@ -0,0 +1,1410 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +# Free Software Foundation, Inc. + +timestamp='2001-08-13' + +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Please send patches to . +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit 0;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | storm-chaos* | os2-emx* | windows32-*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \ + | c4x | clipper \ + | d10v | d30v | dsp16xx \ + | fr30 \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | i370 | i860 | i960 | ia64 \ + | m32r | m68000 | m68k | m88k | mcore \ + | mips16 | mips64 | mips64el | mips64orion | mips64orionel \ + | mips64vr4100 | mips64vr4100el | mips64vr4300 \ + | mips64vr4300el | mips64vr5000 | mips64vr5000el \ + | mipsbe | mipsel | mipsle | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | ns16k | ns32k \ + | openrisc \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ + | pyramid \ + | s390 | s390x \ + | sh | sh[34] | sh[34]eb | shbe | shle \ + | sparc | sparc64 | sparclet | sparclite | sparcv9 | sparcv9b \ + | strongarm \ + | tahoe | thumb | tic80 | tron \ + | v850 \ + | we32k \ + | x86 | xscale \ + | z8k) + basic_machine=$basic_machine-unknown + ;; + m6811 | m68hc11 | m6812 | m68hc12) + # Motorola 68HC11/12. + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alphapca5[67]-* | arc-* \ + | arm-* | armbe-* | armle-* | armv*-* \ + | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c54x-* \ + | clipper-* | cray2-* | cydra-* \ + | d10v-* | d30v-* \ + | elxsi-* \ + | f30[01]-* | f700-* | fr30-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | m32r-* \ + | m68000-* | m680[01234]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | mcore-* \ + | mips-* | mips16-* | mips64-* | mips64el-* | mips64orion-* \ + | mips64orionel-* | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* | mipsbe-* | mipsel-* \ + | mipsle-* | mipstx39-* | mipstx39el-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ + | pyramid-* \ + | romp-* | rs6000-* \ + | s390-* | s390x-* \ + | sh-* | sh[34]-* | sh[34]eb-* | shbe-* | shle-* \ + | sparc-* | sparc64-* | sparc86x-* | sparclite-* \ + | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* \ + | t3e-* | tahoe-* | thumb-* | tic30-* | tic54x-* | tic80-* | tron-* \ + | v850-* | vax-* \ + | we32k-* \ + | x86-* | x86_64-* | xmp-* | xps100-* | xscale-* \ + | ymp-* \ + | z8k-*) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | ymp) + basic_machine=ymp-cray + os=-unicos + ;; + cray2) + basic_machine=cray2-cray + os=-unicos + ;; + [cjt]90) + basic_machine=${basic_machine}-cray + os=-unicos + ;; + crds | unos) + basic_machine=m68k-crds + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + mingw32) + basic_machine=i386-pc + os=-mingw32 + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mipsel*-linux*) + basic_machine=mipsel-unknown + ;; + mips*-linux*) + basic_machine=mips-unknown + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + mmix*) + basic_machine=mmix-knuth + os=-mmixware + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | k6 | nexgen) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon) + basic_machine=i686-pc + ;; + pentiumii | pentium2) + basic_machine=i686-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc64) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sparclite-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=t3e-cray + os=-unicos + ;; + tic54x | c54x*) + basic_machine=tic54x-unknown + os=-coff + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + windows32) + basic_machine=i386-pc + os=-windows32-msvcrt + ;; + xmp) + basic_machine=xmp-cray + os=-unicos + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + mips) + case $os in + linux*) + basic_machine=mips-unknown + ;; + *) + basic_machine=mips-mips + ;; + esac + ;; + romp) + basic_machine=romp-ibm + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh3 | sh4 | sh3eb | sh4eb) + basic_machine=sh-unknown + ;; + sparc | sparcv9 | sparcv9b) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + c4x*) + basic_machine=c4x-none + os=-coff + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -netbsd* | -openbsd* | -freebsd* | -riscix* \ + | -lynxos* | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux* | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto*) + os=-nto-qnx + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -vxsim* | -vxworks*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit 0 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/config/depcomp b/config/depcomp new file mode 100644 index 00000000..65899658 --- /dev/null +++ b/config/depcomp @@ -0,0 +1,411 @@ +#! /bin/sh + +# depcomp - compile a program generating dependencies as side-effects +# Copyright 1999, 2000 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Alexandre Oliva . + +if test -z "$depmode" || test -z "$source" || test -z "$object"; then + echo "depcomp: Variables source, object and depmode must be set" 1>&2 + exit 1 +fi +# `libtool' can also be set to `yes' or `no'. + +depfile=${depfile-`echo "$object" | sed 's,\([^/]*\)$,.deps/\1,;s/\.\([^.]*\)$/.P\1/'`} +tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} + +rm -f "$tmpdepfile" + +# Some modes work just like other modes, but use different flags. We +# parameterize here, but still list the modes in the big case below, +# to make depend.m4 easier to write. Note that we *cannot* use a case +# here, because this file can only contain one case statement. +if test "$depmode" = hp; then + # HP compiler uses -M and no extra arg. + gccflag=-M + depmode=gcc +fi + +if test "$depmode" = dashXmstdout; then + # This is just like dashmstdout with a different argument. + dashmflag=-xM + depmode=dashmstdout +fi + +case "$depmode" in +gcc3) +## gcc 3 implements dependency tracking that does exactly what +## we want. Yay! Note: for some reason libtool 1.4 doesn't like +## it if -MD -MP comes after the -MF stuff. Hmm. + "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + mv "$tmpdepfile" "$depfile" + ;; + +gcc) +## There are various ways to get dependency output from gcc. Here's +## why we pick this rather obscure method: +## - Don't want to use -MD because we'd like the dependencies to end +## up in a subdir. Having to rename by hand is ugly. +## (We might end up doing this anyway to support other compilers.) +## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like +## -MM, not -M (despite what the docs say). +## - Using -M directly means running the compiler twice (even worse +## than renaming). + if test -z "$gccflag"; then + gccflag=-MD, + fi + "$@" -Wp,"$gccflag$tmpdepfile" + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +## The second -e expression handles DOS-style file names with drive letters. + sed -e 's/^[^:]*: / /' \ + -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" +## This next piece of magic avoids the `deleted header file' problem. +## The problem is that when a header file which appears in a .P file +## is deleted, the dependency causes make to die (because there is +## typically no way to rebuild the header). We avoid this by adding +## dummy dependencies for each header file. Too bad gcc doesn't do +## this for us directly. + tr ' ' ' +' < "$tmpdepfile" | +## Some versions of gcc put a space before the `:'. On the theory +## that the space means something, we add a space to the output as +## well. +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +sgi) + if test "$libtool" = yes; then + "$@" "-Wp,-MDupdate,$tmpdepfile" + else + "$@" -MDupdate "$tmpdepfile" + fi + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + + if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files + echo "$object : \\" > "$depfile" + + # Clip off the initial element (the dependent). Don't try to be + # clever and replace this with sed code, as IRIX sed won't handle + # lines with more than a fixed number of characters (4096 in + # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; + # the IRIX cc adds comments like `#:fec' to the end of the + # dependency line. + tr ' ' ' +' < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \ + tr ' +' ' ' >> $depfile + echo >> $depfile + + # The second pass generates a dummy entry for each header file. + tr ' ' ' +' < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ + >> $depfile + else + # The sourcefile does not contain any dependencies, so just + # store a dummy comment line, to avoid errors with the Makefile + # "include basename.Plo" scheme. + echo "#dummy" > "$depfile" + fi + rm -f "$tmpdepfile" + ;; + +aix) + # The C for AIX Compiler uses -M and outputs the dependencies + # in a .u file. This file always lives in the current directory. + # Also, the AIX compiler puts `$object:' at the start of each line; + # $object doesn't have directory information. + stripped=`echo "$object" | sed -e 's,^.*/,,' -e 's/\(.*\)\..*$/\1/'` + tmpdepfile="$stripped.u" + outname="$stripped.o" + if test "$libtool" = yes; then + "$@" -Wc,-M + else + "$@" -M + fi + + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + + if test -f "$tmpdepfile"; then + # Each line is of the form `foo.o: dependent.h'. + # Do two passes, one to just change these to + # `$object: dependent.h' and one to simply `dependent.h:'. + sed -e "s,^$outname:,$object :," < "$tmpdepfile" > "$depfile" + sed -e "s,^$outname: \(.*\)$,\1:," < "$tmpdepfile" >> "$depfile" + else + # The sourcefile does not contain any dependencies, so just + # store a dummy comment line, to avoid errors with the Makefile + # "include basename.Plo" scheme. + echo "#dummy" > "$depfile" + fi + rm -f "$tmpdepfile" + ;; + +tru64) + # The Tru64 AIX compiler uses -MD to generate dependencies as a side + # effect. `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'. + # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put + # dependencies in `foo.d' instead, so we check for that too. + # Subdirectories are respected. + + tmpdepfile1="$object.d" + tmpdepfile2=`echo "$object" | sed -e 's/.o$/.d/'` + if test "$libtool" = yes; then + "$@" -Wc,-MD + else + "$@" -MD + fi + + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile1" "$tmpdepfile2" + exit $stat + fi + + if test -f "$tmpdepfile1"; then + tmpdepfile="$tmpdepfile1" + else + tmpdepfile="$tmpdepfile2" + fi + if test -f "$tmpdepfile"; then + sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile" + # That's a space and a tab in the []. + sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile" + else + echo "#dummy" > "$depfile" + fi + rm -f "$tmpdepfile" + ;; + +#nosideeffect) + # This comment above is used by automake to tell side-effect + # dependency tracking mechanisms from slower ones. + +dashmstdout) + # Important note: in order to support this mode, a compiler *must* + # always write the proprocessed file to stdout, regardless of -o, + # because we must use -o when running libtool. + test -z "$dashmflag" && dashmflag=-M + ( IFS=" " + case " $* " in + *" --mode=compile "*) # this is libtool, let us make it quiet + for arg + do # cycle over the arguments + case "$arg" in + "--mode=compile") + # insert --quiet before "--mode=compile" + set fnord "$@" --quiet + shift # fnord + ;; + esac + set fnord "$@" "$arg" + shift # fnord + shift # "$arg" + done + ;; + esac + "$@" $dashmflag | sed 's:^[^:]*\:[ ]*:'"$object"'\: :' > "$tmpdepfile" + ) & + proc=$! + "$@" + stat=$? + wait "$proc" + if test "$stat" != 0; then exit $stat; fi + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + tr ' ' ' +' < "$tmpdepfile" | \ +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +dashXmstdout) + # This case only exists to satisfy depend.m4. It is never actually + # run, as this mode is specially recognized in the preamble. + exit 1 + ;; + +makedepend) + # X makedepend + ( + shift + cleared=no + for arg in "$@"; do + case $cleared in no) + set ""; shift + cleared=yes + esac + case "$arg" in + -D*|-I*) + set fnord "$@" "$arg"; shift;; + -*) + ;; + *) + set fnord "$@" "$arg"; shift;; + esac + done + obj_suffix="`echo $object | sed 's/^.*\././'`" + touch "$tmpdepfile" + ${MAKEDEPEND-makedepend} 2>/dev/null -o"$obj_suffix" -f"$tmpdepfile" "$@" + ) & + proc=$! + "$@" + stat=$? + wait "$proc" + if test "$stat" != 0; then exit $stat; fi + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + tail +3 "$tmpdepfile" | tr ' ' ' +' | \ +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" "$tmpdepfile".bak + ;; + +cpp) + # Important note: in order to support this mode, a compiler *must* + # always write the proprocessed file to stdout, regardless of -o, + # because we must use -o when running libtool. + ( IFS=" " + case " $* " in + *" --mode=compile "*) + for arg + do # cycle over the arguments + case $arg in + "--mode=compile") + # insert --quiet before "--mode=compile" + set fnord "$@" --quiet + shift # fnord + ;; + esac + set fnord "$@" "$arg" + shift # fnord + shift # "$arg" + done + ;; + esac + "$@" -E | + sed -n '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' | + sed '$ s: \\$::' > "$tmpdepfile" + ) & + proc=$! + "$@" + stat=$? + wait "$proc" + if test "$stat" != 0; then exit $stat; fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + cat < "$tmpdepfile" >> "$depfile" + sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvisualcpp) + # Important note: in order to support this mode, a compiler *must* + # always write the proprocessed file to stdout, regardless of -o, + # because we must use -o when running libtool. + ( IFS=" " + case " $* " in + *" --mode=compile "*) + for arg + do # cycle over the arguments + case $arg in + "--mode=compile") + # insert --quiet before "--mode=compile" + set fnord "$@" --quiet + shift # fnord + ;; + esac + set fnord "$@" "$arg" + shift # fnord + shift # "$arg" + done + ;; + esac + "$@" -E | + sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::echo "`cygpath -u \\"\1\\"`":p' | sort | uniq > "$tmpdepfile" + ) & + proc=$! + "$@" + stat=$? + wait "$proc" + if test "$stat" != 0; then exit $stat; fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + . "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s:: \1 \\:p' >> "$depfile" + echo " " >> "$depfile" + . "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s::\1\::p' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +none) + exec "$@" + ;; + +*) + echo "Unknown depmode $depmode" 1>&2 + exit 1 + ;; +esac + +exit 0 diff --git a/config/install-sh b/config/install-sh new file mode 100644 index 00000000..e9de2384 --- /dev/null +++ b/config/install-sh @@ -0,0 +1,251 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + chmodcmd="" + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/config/missing b/config/missing new file mode 100644 index 00000000..0a7fb5a2 --- /dev/null +++ b/config/missing @@ -0,0 +1,283 @@ +#! /bin/sh +# Common stub for a few missing GNU programs while installing. +# Copyright 1996, 1997, 1999, 2000 Free Software Foundation, Inc. +# Originally by Fran,cois Pinard , 1996. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +if test $# -eq 0; then + echo 1>&2 "Try \`$0 --help' for more information" + exit 1 +fi + +run=: + +# In the cases where this matters, `missing' is being run in the +# srcdir already. +if test -f configure.ac; then + configure_ac=configure.ac +else + configure_ac=configure.in +fi + +case "$1" in +--run) + # Try to run requested program, and just exit if it succeeds. + run= + shift + "$@" && exit 0 + ;; +esac + +# If it does not exist, or fails to run (possibly an outdated version), +# try to emulate it. +case "$1" in + + -h|--h|--he|--hel|--help) + echo "\ +$0 [OPTION]... PROGRAM [ARGUMENT]... + +Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an +error status if there is no known handling for PROGRAM. + +Options: + -h, --help display this help and exit + -v, --version output version information and exit + --run try to run the given command, and emulate it if it fails + +Supported PROGRAM values: + aclocal touch file \`aclocal.m4' + autoconf touch file \`configure' + autoheader touch file \`config.h.in' + automake touch all \`Makefile.in' files + bison create \`y.tab.[ch]', if possible, from existing .[ch] + flex create \`lex.yy.c', if possible, from existing .c + help2man touch the output file + lex create \`lex.yy.c', if possible, from existing .c + makeinfo touch the output file + tar try tar, gnutar, gtar, then tar without non-portable flags + yacc create \`y.tab.[ch]', if possible, from existing .[ch]" + ;; + + -v|--v|--ve|--ver|--vers|--versi|--versio|--version) + echo "missing 0.3 - GNU automake" + ;; + + -*) + echo 1>&2 "$0: Unknown \`$1' option" + echo 1>&2 "Try \`$0 --help' for more information" + exit 1 + ;; + + aclocal) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified \`acinclude.m4' or \`${configure_ac}'. You might want + to install the \`Automake' and \`Perl' packages. Grab them from + any GNU archive site." + touch aclocal.m4 + ;; + + autoconf) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified \`${configure_ac}'. You might want to install the + \`Autoconf' and \`GNU m4' packages. Grab them from any GNU + archive site." + touch configure + ;; + + autoheader) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified \`acconfig.h' or \`${configure_ac}'. You might want + to install the \`Autoconf' and \`GNU m4' packages. Grab them + from any GNU archive site." + files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}` + test -z "$files" && files="config.h" + touch_files= + for f in $files; do + case "$f" in + *:*) touch_files="$touch_files "`echo "$f" | + sed -e 's/^[^:]*://' -e 's/:.*//'`;; + *) touch_files="$touch_files $f.in";; + esac + done + touch $touch_files + ;; + + automake) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'. + You might want to install the \`Automake' and \`Perl' packages. + Grab them from any GNU archive site." + find . -type f -name Makefile.am -print | + sed 's/\.am$/.in/' | + while read f; do touch "$f"; done + ;; + + bison|yacc) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified a \`.y' file. You may need the \`Bison' package + in order for those modifications to take effect. You can get + \`Bison' from any GNU archive site." + rm -f y.tab.c y.tab.h + if [ $# -ne 1 ]; then + eval LASTARG="\${$#}" + case "$LASTARG" in + *.y) + SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'` + if [ -f "$SRCFILE" ]; then + cp "$SRCFILE" y.tab.c + fi + SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'` + if [ -f "$SRCFILE" ]; then + cp "$SRCFILE" y.tab.h + fi + ;; + esac + fi + if [ ! -f y.tab.h ]; then + echo >y.tab.h + fi + if [ ! -f y.tab.c ]; then + echo 'main() { return 0; }' >y.tab.c + fi + ;; + + lex|flex) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified a \`.l' file. You may need the \`Flex' package + in order for those modifications to take effect. You can get + \`Flex' from any GNU archive site." + rm -f lex.yy.c + if [ $# -ne 1 ]; then + eval LASTARG="\${$#}" + case "$LASTARG" in + *.l) + SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'` + if [ -f "$SRCFILE" ]; then + cp "$SRCFILE" lex.yy.c + fi + ;; + esac + fi + if [ ! -f lex.yy.c ]; then + echo 'main() { return 0; }' >lex.yy.c + fi + ;; + + help2man) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified a dependency of a manual page. You may need the + \`Help2man' package in order for those modifications to take + effect. You can get \`Help2man' from any GNU archive site." + + file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` + if test -z "$file"; then + file=`echo "$*" | sed -n 's/.*--output=\([^ ]*\).*/\1/p'` + fi + if [ -f "$file" ]; then + touch $file + else + test -z "$file" || exec >$file + echo ".ab help2man is required to generate this page" + exit 1 + fi + ;; + + makeinfo) + if test -z "$run" && (makeinfo --version) > /dev/null 2>&1; then + # We have makeinfo, but it failed. + exit 1 + fi + + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified a \`.texi' or \`.texinfo' file, or any other file + indirectly affecting the aspect of the manual. The spurious + call might also be the consequence of using a buggy \`make' (AIX, + DU, IRIX). You might want to install the \`Texinfo' package or + the \`GNU make' package. Grab either from any GNU archive site." + file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` + if test -z "$file"; then + file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'` + file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file` + fi + touch $file + ;; + + tar) + shift + if test -n "$run"; then + echo 1>&2 "ERROR: \`tar' requires --run" + exit 1 + fi + + # We have already tried tar in the generic part. + # Look for gnutar/gtar before invocation to avoid ugly error + # messages. + if (gnutar --version > /dev/null 2>&1); then + gnutar ${1+"$@"} && exit 0 + fi + if (gtar --version > /dev/null 2>&1); then + gtar ${1+"$@"} && exit 0 + fi + firstarg="$1" + if shift; then + case "$firstarg" in + *o*) + firstarg=`echo "$firstarg" | sed s/o//` + tar "$firstarg" ${1+"$@"} && exit 0 + ;; + esac + case "$firstarg" in + *h*) + firstarg=`echo "$firstarg" | sed s/h//` + tar "$firstarg" ${1+"$@"} && exit 0 + ;; + esac + fi + + echo 1>&2 "\ +WARNING: I can't seem to be able to run \`tar' with the given arguments. + You may want to install GNU tar or Free paxutils, or check the + command line arguments." + exit 1 + ;; + + *) + echo 1>&2 "\ +WARNING: \`$1' is needed, and you do not seem to have it handy on your + system. You might have modified some files without having the + proper tools for further handling them. Check the \`README' file, + it often tells you about the needed prerequirements for installing + this package. You may also peek at any GNU archive site, in case + some other package would contain this missing \`$1' program." + exit 1 + ;; +esac + +exit 0 diff --git a/config/mkinstalldirs b/config/mkinstalldirs new file mode 100644 index 00000000..6b3b5fc5 --- /dev/null +++ b/config/mkinstalldirs @@ -0,0 +1,40 @@ +#! /bin/sh +# mkinstalldirs --- make directory hierarchy +# Author: Noah Friedman +# Created: 1993-05-16 +# Public domain + +# $Id$ + +errstatus=0 + +for file +do + set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` + shift + + pathcomp= + for d + do + pathcomp="$pathcomp$d" + case "$pathcomp" in + -* ) pathcomp=./$pathcomp ;; + esac + + if test ! -d "$pathcomp"; then + echo "mkdir $pathcomp" + + mkdir "$pathcomp" || lasterr=$? + + if test ! -d "$pathcomp"; then + errstatus=$lasterr + fi + fi + + pathcomp="$pathcomp/" + done +done + +exit $errstatus + +# mkinstalldirs ends here diff --git a/configure.in b/configure.in new file mode 100644 index 00000000..14367546 --- /dev/null +++ b/configure.in @@ -0,0 +1,289 @@ +dnl synergy -- mouse and keyboard sharing utility +dnl Copyright (C) 2002 Chris Schoeneman +dnl +dnl This package is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU General Public License +dnl found in the file COPYING that should have accompanied this file. +dnl +dnl This package is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +dnl GNU General Public License for more details. + +dnl Process this file with autoconf to produce a configure script. + +dnl initialize +AC_INIT(lib/common/common.h) +AC_CONFIG_AUX_DIR(config) + +dnl current version, extracted from $srcdir/lib/common/Version.h +MAJOR_VERSION=`grep '#.*define VERSION "' $srcdir/lib/common/Version.h | sed -e 's/.*"\([0-9]*\)\.[0-9]*\.[0-9]*".*/\1/'` +MINOR_VERSION=`grep '#.*define VERSION "' $srcdir/lib/common/Version.h | sed -e 's/.*"[0-9]*\.\([0-9]*\)\.[0-9]*".*/\1/'` +RELEASE_VERSION=`grep '#.*define VERSION "' $srcdir/lib/common/Version.h | sed -e 's/.*"[0-9]*\.[0-9]*\.\([0-9]*\)".*/\1/'` + +dnl initialize automake +AM_INIT_AUTOMAKE(synergy, $MAJOR_VERSION.$MINOR_VERSION.$RELEASE_VERSION) +AM_CONFIG_HEADER(config.h) + +dnl information on the package + +dnl decide on platform +ARCH_LIBS="" +ARCH_CFLAGS="" +AC_CANONICAL_HOST +case $host in +*-*-mingw32* | *-*-windows*) + acx_host_arch="WIN32" + acx_host_winapi="MSWINDOWS" + ;; +*-*-darwin*) + acx_host_arch="UNIX" + acx_host_winapi="CARBON" + ;; +*) + acx_host_arch="UNIX" + acx_host_winapi="XWINDOWS" + ;; +esac +ARCH_CFLAGS="$ARCH_CFLAGS -DSYSAPI_$acx_host_arch=1 -DWINAPI_$acx_host_winapi=1" +AM_CONDITIONAL(WIN32, test x$acx_host_arch = xWIN32) +AM_CONDITIONAL(UNIX, test x$acx_host_arch = xUNIX) +AM_CONDITIONAL(MSWINDOWS, test x$acx_host_winapi = xMSWINDOWS) +AM_CONDITIONAL(CARBON, test x$acx_host_winapi = xCARBON) +AM_CONDITIONAL(XWINDOWS, test x$acx_host_winapi = xXWINDOWS) + +dnl checks for programs +AC_PROG_CC +AC_PROG_CXX +AC_PROG_RANLIB +AC_CHECK_PROG(HAVE_DOT, dot, YES, NO) + +dnl AC_PROG_OBJC doesn't exist. Borrow some ideas from KDE. +dnl AC_MSG_CHECKING(for an Objective-C compiler) +OBJC="${CC}" +OBJCFLAGS="${CFLAGS}" +AC_SUBST(OBJC) +AC_SUBST(OBJCFLAGS) +_AM_DEPENDENCIES(OBJC) + +dnl do checks using C++ +AC_LANG_CPLUSPLUS + +dnl our files end in .cpp not .C so tests should also end in .cpp +ac_ext=cpp + +dnl enable debugging or disable asserts +AC_ARG_ENABLE([debug], [ --enable-debug enable debugging]) +if test "x$enable_debug" != xno; then + CXXFLAGS="$CXXFLAGS -g" +else + CXXFLAGS="$CXXFLAGS -DNDEBUG" +fi + +dnl check compiler +ACX_CHECK_CXX + +dnl checks for libraries +if test x"$acx_host_arch" = xUNIX; then + ACX_PTHREAD(,AC_MSG_ERROR(You must have pthreads to compile synergy)) + ARCH_LIBS="$PTHREAD_LIBS $ARCH_LIBS" + ARCH_CFLAGS="$ARCH_CFLAGS $PTHREAD_CFLAGS" +fi +if test x"$acx_host_winapi" = xCARBON; then + ARCH_LIBS="-framework Carbon $ARCH_LIBS" +fi +ACX_CHECK_NANOSLEEP +ACX_CHECK_INET_ATON + +dnl checks for header files +AC_HEADER_STDC +AC_CHECK_HEADERS([unistd.h sys/time.h sys/types.h locale.h wchar.h]) +AC_CHECK_HEADERS([sys/socket.h sys/select.h]) +AC_CHECK_HEADERS([sys/utsname.h]) +AC_CHECK_HEADERS([istream ostream sstream]) +AC_HEADER_TIME +if test x"$acx_host_winapi" = xXWINDOWS; then + AC_PATH_X + AC_PATH_XTRA + save_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$X_CFLAGS $CPPFLAGS" + XEXT_LDADD= + + AC_CHECK_LIB(Xtst, + XTestQueryExtension, + [XEXT_LDADD="$XEXT_LDADD -lXtst"], + AC_MSG_ERROR(You must have the XTest library to build synergy), + [$X_LIBS -lXext -lX11 $X_EXTRA_LIBS]) + AC_CHECK_HEADERS([X11/extensions/XTest.h], + , + AC_MSG_ERROR(You must have the XTest headers to compile synergy)) + + acx_have_xkb=no + AC_CHECK_LIB(X11, + XkbQueryExtension, + [acx_have_xkb=yes], + [acx_have_xkb=no], + [$X_LIBS $X_EXTRA_LIBS]) + if test x"$acx_have_xkb" = xyes; then + AC_CHECK_HEADERS([X11/XKBlib.h X11/extensions/XKBstr.h], + [acx_have_xkb=yes], + [acx_have_xkb=no], + [#include ]) + if test x"$acx_have_xkb" = xyes; then + AC_TRY_COMPILE([ + #include + #include + ],[ + XkbQueryExtension(0, 0, 0, 0, 0, 0); + ], + [acx_have_xkb=yes], + [acx_have_xkb=no]) + fi + fi + if test x"$acx_have_xkb" = xyes; then + AC_DEFINE(HAVE_XKB_EXTENSION, 1, + [Define this if the XKB extension is available.]) + fi + + acx_have_xinerama=yes + AC_CHECK_LIB(Xinerama, + XineramaQueryExtension, + [acx_have_xinerama=yes], + [acx_have_xinerama=no], + [$X_LIBS -lXext -lX11 $X_EXTRA_LIBS]) + if test x"$acx_have_xinerama" = xyes; then + AC_CHECK_HEADERS([X11/extensions/Xinerama.h], + [acx_have_xinerama=yes], + [acx_have_xinerama=no], + [#include ]) + fi + if test x"$acx_have_xinerama" = xyes; then + XEXT_LDADD="$XEXT_LDADD -lXinerama" + fi + + X_DPMS_LDADD= + acx_have_dpms=no + AC_CHECK_LIB(Xext, + DPMSQueryExtension, + [acx_have_dpms=yes], + [acx_have_dpms=no], + [$X_LIBS -lX11 $X_EXTRA_LIBS]) + if test x"$acx_have_dpms" != xyes; then + AC_CHECK_LIB(Xdpms, + DPMSQueryExtension, + [acx_have_dpms=yes; XDPMS_LDADD=-lXdpms], + [acx_have_dpms=no], + [$X_LIBS -lX11 $X_EXTRA_LIBS]) + fi + if test x"$acx_have_dpms" = xyes; then + AC_CHECK_HEADERS([X11/extensions/dpms.h], + [acx_have_dpms_h=yes], + [acx_have_dpms_h=no], + [#include ]) + if test x"$acx_have_dpms_h" = xyes; then + XEXT_LDADD="$XEXT_LDADD $XDPMS_LDADD" + AC_MSG_CHECKING(for prototypes in X11/extensions/dpms.h) + acx_have_dpms_protos=no + AC_TRY_COMPILE([ + #include + extern "C" { + #include + } + ],[ + int s = DPMSModeOn; + DPMSQueryExtension(0, 0, 0); + ], + [acx_have_dpms_protos=yes]) + AC_MSG_RESULT($acx_have_dpms_protos) + if test x"$acx_have_dpms_protos" = xyes; then + AC_DEFINE(HAVE_DPMS_PROTOTYPES,1,[Define if the header file declares function prototypes.]) + fi + fi + fi + + CPPFLAGS="$save_CPPFLAGS" + ARCH_LIBS="$X_LIBS $X_PRE_LIBS $XEXT_LDADD -lXext -lX11 $X_EXTRA_LIBS $ARCH_LIBS" + ARCH_CFLAGS="$ARCH_CFLAGS $X_CFLAGS" +fi + +dnl checks for types +AC_TYPE_SIZE_T +ACX_CHECK_SOCKLEN_T + +dnl checks for structures +AC_STRUCT_TM + +dnl checks for compiler characteristics +AC_CHECK_SIZEOF(char, 1) +AC_CHECK_SIZEOF(short, 2) +AC_CHECK_SIZEOF(int, 2) +AC_CHECK_SIZEOF(long, 4) +ACX_CHECK_CXX_BOOL(,AC_MSG_ERROR(Your compiler must support bool to compile synergy)) +ACX_CHECK_CXX_EXCEPTIONS(,AC_MSG_ERROR(Your compiler must support exceptions to compile synergy)) +ACX_CHECK_CXX_CASTS(,AC_MSG_ERROR(Your compiler must support C++ casts to compile synergy)) +ACX_CHECK_CXX_MUTABLE(,AC_MSG_ERROR(Your compiler must support mutable to compile synergy)) +ACX_CHECK_CXX_STDLIB(,AC_MSG_ERROR(Your compiler must support the C++ standard library to compile synergy)) + +dnl checks for library functions +dnl AC_TYPE_SIGNAL +AC_FUNC_MEMCMP +AC_FUNC_STRFTIME +AC_CHECK_FUNCS(gmtime_r) +ACX_CHECK_GETPWUID_R +AC_CHECK_FUNCS(vsnprintf) +AC_FUNC_SELECT_ARGTYPES +ACX_CHECK_POLL +ACX_FUNC_ACCEPT +dnl use AC_REPLACE_FUNCS() for stuff in string.h + +dnl checks for system services + +dnl enable maximum compiler warnings and warnings are errors. +ACX_CXX_WARNINGS +ACX_CXX_WARNINGS_ARE_ERRORS + +dnl adjust compiler and linker variables +CXXFLAGS="$CXXFLAGS $SYNERGY_CXXFLAGS $ARCH_CFLAGS" +OBJCXXFLAGS="$OBJCXXFLAGS $CXXFLAGS $ARCH_CFLAGS" +LIBS="$NANOSLEEP_LIBS $INET_ATON_LIBS $ARCH_LIBS $LIBS" + +dnl we need to have an environment variable set when building on OS X. +dnl i'm not sure of the right way to do that. writing 'export ...' to +dnl the makefiles isn't portable. here we'll hijack XXXDEPMODE (where +dnl XXX depends on the language) to insert setting the environment +dnl variable when running the compiler. we'd like to put that in CC, +dnl CXX and OBJC but that breaks depcomp. let's hope this works. +if test x"$acx_host_winapi" = xCARBON; then + MACOSX_DEPLOYMENT_TARGET="10.2" + CCDEPMODE="MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET $CCDEPMODE" + CXXDEPMODE="MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET $CXXDEPMODE" + OBJCDEPMODE="MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET $OBJCDEPMODE" +else + MACOSX_DEPLOYMENT_TARGET="5" + CXXDEPMODE="FOO=$MACOSX_DEPLOYMENT_TARGET $CXXDEPMODE" +fi + +AC_OUTPUT([ +Makefile +cmd/Makefile +cmd/launcher/Makefile +cmd/synergyc/Makefile +cmd/synergys/Makefile +dist/Makefile +dist/nullsoft/Makefile +dist/rpm/Makefile +dist/rpm/synergy.spec +doc/Makefile +doc/doxygen.cfg +lib/Makefile +lib/arch/Makefile +lib/base/Makefile +lib/client/Makefile +lib/common/Makefile +lib/io/Makefile +lib/mt/Makefile +lib/net/Makefile +lib/platform/Makefile +lib/server/Makefile +lib/synergy/Makefile +]) diff --git a/dist/Makefile.am b/dist/Makefile.am new file mode 100644 index 00000000..1af99c18 --- /dev/null +++ b/dist/Makefile.am @@ -0,0 +1,26 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +SUBDIRS = \ + rpm \ + nullsoft \ + $(NULL) + +EXTRA_DIST = \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/dist/nullsoft/Makefile.am b/dist/nullsoft/Makefile.am new file mode 100644 index 00000000..120cd016 --- /dev/null +++ b/dist/nullsoft/Makefile.am @@ -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. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + synergy.nsi \ + dosify.c \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/dist/nullsoft/Makefile.win b/dist/nullsoft/Makefile.win new file mode 100644 index 00000000..91aa68bb --- /dev/null +++ b/dist/nullsoft/Makefile.win @@ -0,0 +1,63 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +NSIS = "$(PROGRAMFILES)\NSIS\makensis.exe" +NSIS_FLAGS = /NOCD /V1 + +BIN_INSTALLER_SRC = dist\nullsoft +BIN_INSTALLER_DST = $(BUILD_DST)\$(BIN_INSTALLER_SRC) +BIN_DOSIFY_EXE = "$(BIN_INSTALLER_DST)\dosify.exe" +BIN_DOSIFY_C = \ + "$(BIN_INSTALLER_SRC)\dosify.c" \ + $(NULL) +BIN_DOSIFY_OBJ = \ + "$(BIN_INSTALLER_DST)\dosify.obj" \ + $(NULL) + +BIN_INSTALLER_NSI = "$(BIN_INSTALLER_SRC)\synergy.nsi" +BIN_INSTALLER_EXE = "$(BUILD_DST)\SynergyInstaller.exe" +BIN_INSTALLER_DOCS = \ + COPYING \ + ChangeLog \ + $(NULL) +BIN_INSTALLER_DOS_DOCS = \ + $(BUILD_DST)\COPYING.txt \ + $(BUILD_DST)\ChangeLog.txt \ + $(NULL) + +C_FILES = $(C_FILES) $(BIN_DOSIFY_C) +OBJ_FILES = $(OBJ_FILES) $(BIN_DOSIFY_OBJ) +OPTPROGRAMS = $(OPTPROGRAMS) $(BIN_DOSIFY_EXE) + +# Build rules. +$(BIN_DOSIFY_OBJ): $(BIN_DOSIFY_C) + @$(ECHO) Compile $(BIN_DOSIFY_C) + -@$(MKDIR) $(BIN_INSTALLER_DST) 2>NUL: + $(cc) $(cdebug) $(cflags) $(cvars) /Fo$@ /Fd$(@:.obj=.pdb) $** +$(BIN_DOSIFY_EXE): $(BIN_DOSIFY_OBJ) + @$(ECHO) Link $(@F) + $(link) $(ldebug) $(conlflags) $(conlibsmt) /out:$@ $** + +# Convert text files from Unix to DOS format. +$(BIN_INSTALLER_DOS_DOCS): $(BIN_DOSIFY_EXE) $(BIN_INSTALLER_DOCS) + @$(ECHO) Convert text files to DOS format + $(BIN_DOSIFY_EXE) "." "$(BUILD_DST)" $(BIN_INSTALLER_DOCS) + +# Allow installers for both debug and release. +$(BIN_INSTALLER_EXE): $(BIN_INSTALLER_NSI) all $(BIN_INSTALLER_DOS_DOCS) + @$(ECHO) Build $(@F) + $(NSIS) $(NSIS_FLAGS) /DOUTPUTDIR=$(@D) /DOUTPUTFILE=$@ $(BIN_INSTALLER_NSI) +installer: $(BIN_INSTALLER_EXE) +debug-installer: + @$(MAKE) /nologo /f $(MAKEFILE) DEBUG=1 installer +release-installer: + @$(MAKE) /nologo /f $(MAKEFILE) NODEBUG=1 installer diff --git a/dist/nullsoft/dosify.c b/dist/nullsoft/dosify.c new file mode 100644 index 00000000..95d0caee --- /dev/null +++ b/dist/nullsoft/dosify.c @@ -0,0 +1,99 @@ +#include +#include +#include + +static +char* +concatPath(const char* dir, const char* name, const char* ext) +{ + size_t nDir = (dir != NULL) ? strlen(dir) : 0; + size_t nPath = nDir + 1 + strlen(name) + strlen(ext?ext:"") + 1; + char* path = malloc(nPath); + + /* directory */ + if (nDir > 0 && strcmp(dir, ".") != 0) { + strcpy(path, dir); + if (path[nDir - 1] != '\\' && path[nDir - 1] != '/') { + strcat(path, "\\"); + } + } + else { + strcpy(path, ""); + } + + + /* name */ + strcat(path, name); + + /* extension */ + if (ext != NULL && strrchr(name, '.') == NULL) { + strcat(path, ext); + } + + return path; +} + +static +int +dosify(const char* srcdir, const char* dstdir, const char* name) +{ + FILE* dFile, *sFile; + char* dName, *sName; + + sName = concatPath(srcdir, name, NULL); + dName = concatPath(dstdir, name, ".txt"); + + sFile = fopen(sName, "rb"); + if (sFile == NULL) { + fprintf(stderr, "Can't open \"%s\" for reading\n", sName); + return 0; + } + else { + dFile = fopen(dName, "w"); + if (dFile == NULL) { + fclose(sFile); + fprintf(stderr, "Can't open \"%s\" for writing\n", dName); + return 0; + } + else { + char buffer[1024]; + while (!ferror(dFile) && + fgets(buffer, sizeof(buffer), sFile) != NULL) { + fprintf(dFile, "%s", buffer); + } + if (ferror(sFile) || ferror(dFile)) { + fprintf(stderr, + "Error copying \"%s\" to \"%s\"\n", sName, dName); + fclose(dFile); + fclose(sFile); + _unlink(dName); + return 0; + } + } + } + + fclose(dFile); + fclose(sFile); + free(dName); + free(sName); + return 1; +} + +#include +int +main(int argc, char** argv) +{ + int i; + + if (argc < 3) { + fprintf(stderr, "usage: %s [files]\n", argv[0]); + return 1; + } + + for (i = 3; i < argc; ++i) { + if (!dosify(argv[1], argv[2], argv[i])) + return 1; + } + + return 0; +} diff --git a/dist/nullsoft/synergy.nsi b/dist/nullsoft/synergy.nsi new file mode 100644 index 00000000..3370d03a --- /dev/null +++ b/dist/nullsoft/synergy.nsi @@ -0,0 +1,179 @@ +; Synergy.nsi +; +; This script is based on example1.nsi, but it remember the directory, +; has uninstall support and (optionally) installs start menu shortcuts. +; +; It will install makensisw.exe into a directory that the user selects, + +;-------------------------------- + +!ifndef OUTPUTDIR +!define OUTPUTDIR "build\Release" +!endif + +; The name of the installer +Name "Synergy" + +; The file to write +OutFile "${OUTPUTFILE}" + +; The default installation directory +InstallDir $PROGRAMFILES\Synergy + +; Registry key to check for directory (so if you install again, it will +; overwrite the old one automatically) +InstallDirRegKey HKLM "Software\Synergy" "Install_Dir" + +;-------------------------------- + +; Pages + +Page components +Page license +Page directory +Page instfiles + +UninstPage uninstConfirm +UninstPage instfiles + +;-------------------------------- + +; Text +ComponentText "This will install Synergy on your computer. Select the optional components you want to install." +DirText "Choose a directory to install Synergy to:" +UninstallText "This will uninstall Synergy from your computer." +LicenseText "Synergy is distributed under the GNU GPL:" +LicenseData ${OUTPUTDIR}\COPYING.txt + +;-------------------------------- + +; The stuff to install +Section "Synergy (required)" + + SectionIn RO + + ; Set output path to the installation directory. + SetOutPath $INSTDIR + + ; Put files there + File "${OUTPUTDIR}\synergy.exe" + File "${OUTPUTDIR}\synergyc.exe" + File "${OUTPUTDIR}\synergys.exe" + File "${OUTPUTDIR}\*.dll" + File "${OUTPUTDIR}\COPYING.txt" + File "${OUTPUTDIR}\ChangeLog.txt" + File doc\PORTING + File doc\about.html + File doc\authors.html + File doc\autostart.html + File doc\banner.html + File doc\compiling.html + File doc\configuration.html + File doc\contact.html + File doc\developer.html + File doc\faq.html + File doc\history.html + File doc\home.html + File doc\index.html + File doc\license.html + File doc\news.html + File doc\roadmap.html + File doc\running.html + File doc\security.html + File doc\synergy.css + File doc\tips.html + File doc\toc.html + File doc\trouble.html + + SetOutPath $INSTDIR\images + File doc\images\logo.gif + File doc\images\warp.gif + + ; Write the installation path into the registry + WriteRegStr HKLM SOFTWARE\Synergy "Install_Dir" "$INSTDIR" + + ; Write the uninstall keys for Windows + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synergy" "DisplayName" "Synergy" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synergy" "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synergy" "NoModify" 1 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synergy" "NoRepair" 1 + WriteUninstaller "uninstall.exe" + +SectionEnd + +; Optional section (can be disabled by the user) +Section "Start Menu Shortcuts" + + CreateDirectory "$SMPROGRAMS\Synergy" + CreateShortCut "$SMPROGRAMS\Synergy\Synergy.lnk" "$INSTDIR\synergy.exe" "" "$INSTDIR\synergy.exe" 0 + CreateShortCut "$SMPROGRAMS\Synergy\README.lnk" "$INSTDIR\index.html" + CreateShortCut "$SMPROGRAMS\Synergy\Synergy Folder.lnk" "$INSTDIR" + CreateShortCut "$SMPROGRAMS\Synergy\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 + +SectionEnd + +; Optional section (can be disabled by the user) +Section "Desktop Icon" + + CreateShortCut "$DESKTOP\Synergy.lnk" "$INSTDIR\synergy.exe" "" "$INSTDIR\synergy.exe" 0 + +SectionEnd + +;-------------------------------- + +; Uninstaller + +Section "Uninstall" + ; Stop and uninstall the daemons + ExecWait '"$INSTDIR\synergy.exe" /uninstall' + + ; Remove autorun registry keys for synergy + DeleteRegKey HKLM "SYSTEM\CurrentControlSet\Services\Synergy Server" + DeleteRegKey HKLM "SYSTEM\CurrentControlSet\Services\Synergy Client" + DeleteRegValue HKLM "Software\Microsoft\Windows\CurrentVersion\RunServices" "Synergy Server" + DeleteRegValue HKLM "Software\Microsoft\Windows\CurrentVersion\RunServices" "Synergy Client" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "Synergy Server" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "Synergy Client" + + ; not all keys will have existed, so errors WILL have happened + ClearErrors + + ; Remove registry keys + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synergy" + DeleteRegKey HKLM SOFTWARE\Synergy + + ClearErrors + + ; First try to remove files that might be locked (if synergy is running) + Delete /REBOOTOK $INSTDIR\synergy.exe + Delete /REBOOTOK $INSTDIR\synergyc.exe + Delete /REBOOTOK $INSTDIR\synergys.exe + Delete /REBOOTOK $INSTDIR\synrgyhk.dll + + ; Remove files and directory + Delete $INSTDIR\*.* + RMDir $INSTDIR + + ; Remove shortcuts, if any + Delete "$SMPROGRAMS\Synergy\*.*" + Delete "$DESKTOP\Synergy.lnk" + + ; Remove directories used + RMDir "$SMPROGRAMS\Synergy" + RMDir "$INSTDIR" + + IfRebootFlag 0 EndOfAll + MessageBox MB_OKCANCEL "Uninstaller needs to reboot to finish cleaning up. reboot now?" IDCANCEL NoReboot + ClearErrors + Reboot + IfErrors 0 EndOfAll + MessageBox MB_OK "Uninstaller could not reboot. Please reboot manually. Thank you." + Abort "Uninstaller could not reboot. Please reboot manually. Thank you." + NoReboot: + DetailPrint "" + DetailPrint "Uninstaller could not reboot. Please reboot manually. Thank you." + DetailPrint "" + SetDetailsView show + EndOfAll: + +SectionEnd diff --git a/dist/rpm/Makefile.am b/dist/rpm/Makefile.am new file mode 100644 index 00000000..0e86d9ba --- /dev/null +++ b/dist/rpm/Makefile.am @@ -0,0 +1,22 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + synergy.spec.in \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/dist/rpm/synergy.spec.in b/dist/rpm/synergy.spec.in new file mode 100644 index 00000000..0d2b6f48 --- /dev/null +++ b/dist/rpm/synergy.spec.in @@ -0,0 +1,66 @@ +Summary: Mouse and keyboard sharing utility +Name: @PACKAGE@ +Version: @VERSION@ +Release: 1 +License: GPL +Packager: Chris Schoeneman +Group: System Environment/Daemons +Prefixes: /usr/bin +Source: @PACKAGE@-@VERSION@.tar.gz +Buildroot: /var/tmp/@PACKAGE@-@VERSION@-root + +%description +Synergy lets you easily share a single mouse and keyboard between +multiple computers with different operating systems, each with its +own display, without special hardware. It's intended for users +with multiple computers on their desk since each system uses its +own display. + +%prep +%setup +CFLAGS="$RPM_OPT_FLAGS" ./configure --prefix=/usr + +%build +make + +%install +make install DESTDIR=$RPM_BUILD_ROOT +strip $RPM_BUILD_ROOT/usr/bin/synergyc +strip $RPM_BUILD_ROOT/usr/bin/synergys + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-, root, root) +/usr/bin/synergyc +/usr/bin/synergys +%doc AUTHORS +%doc COPYING +%doc ChangeLog +%doc INSTALL +%doc NEWS +%doc README +%doc doc/about.html +%doc doc/authors.html +%doc doc/autostart.html +%doc doc/banner.html +%doc doc/border.html +%doc doc/compiling.html +%doc doc/configuration.html +%doc doc/contact.html +%doc doc/developer.html +%doc doc/faq.html +%doc doc/history.html +%doc doc/home.html +%doc doc/index.html +%doc doc/license.html +%doc doc/news.html +%doc doc/roadmap.html +%doc doc/running.html +%doc doc/security.html +%doc doc/tips.html +%doc doc/toc.html +%doc doc/trouble.html +%doc doc/synergy.css +%doc examples/synergy.conf diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 00000000..2efec24c --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,49 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + PORTING \ + doxygen.cfg.in \ + synergy.css \ + about.html \ + authors.html \ + autostart.html \ + banner.html \ + border.html \ + compiling.html \ + configuration.html \ + contact.html \ + developer.html \ + faq.html \ + history.html \ + home.html \ + index.html \ + license.html \ + news.html \ + roadmap.html \ + running.html \ + security.html \ + tips.html \ + toc.html \ + trouble.html \ + images/logo.gif \ + images/warp.gif \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + doc/doxygen.cfg \ + doc/doxygen/html/* \ + $(NULL) diff --git a/doc/PORTING b/doc/PORTING new file mode 100644 index 00000000..4e2744df --- /dev/null +++ b/doc/PORTING @@ -0,0 +1,419 @@ +Synergy Developer and Porting Guide +=================================== + +This document is under development. + +Code Organization +----------------- + +The synergy source code organization is: + +. -- root makefiles, some standard documentation +cmd -- program source code + launcher -- synergy launcher for Windows + synergyc -- synergy client + synergys -- synergy server +config -- stuff for autoconf/automake +dist -- files for creating distributions + nullsoft -- files for creating Nullsoft NSIS installer (Windows) + rpm -- files for creating RPMs +doc -- placeholder for documentation +examples -- example files +lib -- library source code + arch -- platform dependent utility library + base -- simple utilities + client -- synergy client library + common -- commonly needed header files + io -- I/O + mt -- multithreading + net -- networking + platform -- platform dependent display/window/event stuff + server -- synergy server library + synergy -- synergy shared client/server code library + +Note how the utility code required by the programs is placed into +separate library directories. This makes the makefiles a little +more awkward but makes for a cleaner organization. The top level +directory has only the standard documentation files and the files +necessary to configure and build the rest of the project. + + +Coding Style Guide +------------------ + +Synergy uses many coding conventions. Contributed code should +following these guidelines. + +- Symbol Naming + Names always begin with a letter (never an underscore). The first + letter of interior names are always capitalized. Acronyms should + be all uppercase. For example: myTextAsASCII. + + Names come it two flavors: leading capital and leading lowercase. + The former have the first character capitalized and the latter + don't. In the following table, leading capital names are indicated + by `Name' and leading lowercase names by `name'. + + The naming convention for various things are: + + * Exceptions -- X + Name XMyException + * Interfaces -- I + Name IMyInterface + * Template Classes -- T + Name TMyTemplate<> + * Other Classes -- C + Name CMyClass + * Enumerations -- E + Name EMyEnumeration + * Constants -- k + Name kMyConstant + * Data Members -- m_ + name m_myDataMember + * Methods -- name myMethod + * Functions -- name myFunction + * Variables -- name myVariable + + Exceptions are types that get thrown and are generally derived + (possibly indirectly) from XBase. Interfaces are derived (possibly + indirectly) from IInterface and have only pure virtual functions. + Other classes are classes that aren't exceptions or interfaces. + Constants include global constants and enumerants. + + Method names should usually have the form `verbObject'. For example: + * isGameOn() + * getBeer() + * pressPowerButton() + * setChannel() + In general, use `get' and `set' to read and write state but use `is' + to read boolean state. Note that classes that contain only `is', + `get', and `set' are probably plain old data; you might want to + consider using public data members only or, better, refactor your + design to have classes that actually do something more than just + hold data. + +- File Naming + Each class should have one source and one header file. If the + class is named `CMyClass' then the source file should be named + `CMyClass.cpp' and the header file `CMyClass.h'. + + Headers files not containing a class should have some meaningful + name with a leading capital (e.g. `Version.h'). + + Source files without a header file have a leading lowercase name. + Only files containing the entry point for an application should + lack a header file. + +- Dependencies + * No circular library dependencies + Library dependencies form an acyclic graph. Conceptually + libraries can be arranged in layers where each library only + references libraries in layers below it, not in the same layer + or layers above it. The makefiles build the lowest layer + libraries first and work upwards. + + * Avoid circular uses-a relationships + When possible, design classes with one-way uses-a relationships + and avoid cycles. This makes it easier to understand the code. + However, sometimes it's not always practical so it is permitted. + + * Included files in headers + Headers should #include only the necessary headers. In + particular, if a class is referenced in a header file only as a + pointer or a reference then use `class COtherClass;' instead of + `#include "COtherClass.h".' + + * #include syntax + Non-synergy header files must be included using angle brackets + while synergy header files must be included using double quotes. + #include "CSynergyHeader.h" + #include + The file name in a #include must not be a relative path unless + it's a system header file and it's customary to use a relative + path, e.g. `#include '. Use compiler options to + add necessary directories to the include search path. + + * Included file ordering + Files should be included in the following order: + * Header for source file + The first include for CMyClass.cpp must be CMyClass.h. + * Other headers in directory, sorted alphabetically + * Headers for each library, sorted alphabetically per library + Include headers from the library closest in the dependency graph + first, then the next farthest, etc. Sort alphabetically within + each library. + * System headers + +- C++ + * C++ features + Synergy uses the following more recent C++ features: + * bool + * templates + * exceptions + * mutable + * new scoping rules + * the standard C++ library + + Do not use the following C++ features: + * dynamic_cast + * run time type information + * namespaces and using (use std:: where necessary) + + The new scoping rules say that the scope of a variable declared + in a for statement is limited to the for loop. For example: + + for (int i = 0; i < 10; ++i) { + // i is in scope here + } + // i is not in scope here + + for (int i = -10; i < 0; ++i) { + // an entirely new i is in scope here + } + // i is not in scope here + + This is used routinely in synergy, but only in for loops. There + is a macro for `for' in lib/base/common.h when building under + Microsoft Visual C++ that works around the fact that that compiler + doesn't follow the new scoping rules. Use the macro if your + compiler uses the old scoping rules. + + * Standard C++ library + The standard C++ library containers should always be used in favor + of custom containers wherever reasonable. std::string is used + throughout synergy but only as the CString typedef; always use + CString, never std::string except in the arch library. Synergy + avoids using auto_ptr due to some portability problems. Synergy + makes limited use of standard algorithms and streams but they can + be freely used in new code. + + * Limited multiple inheritance + Classes should inherit implementation from at most one superclass. + Inheriting implementation from multiple classes can have unpleasant + consequences in C++ due to it's limited capabilities. Classes can + inherit from any number of interface classes. An interface class + provides only pure virtual methods. Synergy breaks this rule in + IInterface which implements the virtual destructor for convenience. + + * No globals + Avoid global variables. All global variables must be static, making + it visible only with its source file. Most uses of global variables + are better served by static data members of a class. Global + constants are permitted in some circumstances. + + Also avoid global functions. Use public static member functions in + a class instead. + + These rules are violated by the main source file for each program + (except that the globals are still static). They could easily be + rewritten to put all the variables and functions into a class but + there's little to be gained by that. + + * Private data only + If a class is plain-old-data (i.e. it has no methods) all of its + data members should be public. Otherwise all of its data members + should be private, not public or protected. This makes it much + easier to track the use of a member when reading code. Protected + data is not allowed because `protected' is a synonym for `public + to my subclasses' and public data is a Bad Thing. While it might + seem okay in this limited situation, the situation is not at all + limited since an arbitrary number of classes can be derived, + directly or indirectly, from the class and any of those classes + have full access to the protected data. + + * Plain old data + A class that merely contains data and doesn't perform operations + on that data (other than reads and writes) is plain old data (POD). + POD should have only public data members and non-copy constructors. + It must not have any methods other than constructors, not even a + destructor or assignment operators, nor protected or private data. + Note that this definition of POD is not the definition used in the + C++ standard, which limits the contained data types to types that + have no constructors, destructors, or methods. + + * Avoid using friend + Avoid declaring friend functions or classes. They're sometimes + necessary for operator overloading. If you find it necessary to + add friends to some class C, consider creating a utility class U. + A utility class is declared as the only friend of C and provides + only static methods. Each method forwards to a private method on + an object of C type (passed as a parameter to the U's method). + This makes maintenance easier since only U has friend access to C + and finding any call to U is trivial (they're prefixed by U::). + + * Don't test for NULL when using `delete' or `delete[]' + It's unnecessary since delete does it anyway. + +- Makefiles + Automake's makefiles (named Makefile.am) have a few requirements: + * Define the following macro at the top of the file: + NULL = + * Lists should have one item per line and end in $(NULL). For + example: + EXTRA_DIST = \ + kiwi.txt \ + mango.cpp \ + papaya.h \ + $(NULL) + Indentation must use tabs in a makefile. Line continuations + (backslashes) should be aligned using tabs. + * Lists of files should be sorted alphabetically in groups (e..g + source files, header files, then other files). Lists of + subdirectories must be in the desired build order. + +- Source Formatting + Every project has its own formatting style and no style satisfies + everyone. New code should be consistent with existing code: + + * All files should include the copyright and license notice + * Use tabs to indent + * Tabs are 4 columns + * Lines should not extend past the 80th column + * Open braces ({) go on same line as introducing statement + `for (i = 0; i < 10; ++i) {' not + for (i = 0; i < 10; ++i) + { + * Close braces line up with introducing statement + * Open brace for function is on a line by itself in first column + * Close brace for function lines up with open brace + * Always use braces on: if, else, for, while, do, switch + * `else {' goes on its own line + * Always explicitly test pointers against NULL + e.g. `if (ptr == NULL)' not `if (ptr)' + * Always explicitly test integral values against 0 + e.g. `if (i == 0)' not `if (i)' + * Put spaces around binary operators and after statements + e.g. `if (a == b) {' not `if(a==b){' + * C'tor initializers are one per line, indented one tab stop + * Other indentation should follow existing practice + * Use Qt style comments for extraction by doxygen (i.e. //! and /*!) + * Mark incomplete or buggy code with `FIXME' + +- Other + * calls to LOG() should always be all on one line (even past column 80) + + +Class Relationships +------------------- + +The doxygen documentation can help in understanding the relationships +between objects. Use `make doxygen' in the top level directory to +create the doxygen documentation into doc/doxygen/html. You must have +doxygen installed, of course. + +FIXME -- high level overview of class relationships + + +Portability +----------- + +Synergy is mostly platform independent code but necessarily has +platform dependent parts. The mundane platform dependent parts +come from the usual suspects: networking, multithreading, file +system, high resolution clocks, system logging, etc. Porting +these parts is relatively straightforward. + +Synergy also has more esoteric platform dependent code. The +functions for low-level event interception and insertion, +warping the cursor position, character to keyboard event +translation, clipboard manipulation, and screen saver control +are often obscure and poorly documented. Unfortunately, these +are exactly the functions synergy requires to do its magic. + +Porting synergy to a new platform requires the following steps: + +- Setting up the build +- Adjusting lib/common/common.h +- Implementing lib/arch +- Implementing lib/platform +- Tweaks + +Setting up the build: + +The first phase is simply to create the files necessary to build the +other files. On Unix, synergy uses autoconf/automake which produces +a `configure' script that generates makefiles. On Windows, synergy +uses Visual C++ workspace and project files. If you're porting to +another Unix variant, you may need to adjust `configure.in', +`acinclude.m4', and Unix flavor dependent code in lib/arch. Note +especially the SYSAPI_* and WINAPI_* macro definitions in +ARCH_CFLAGS. Exactly one of each must be defined. It should also +add AM_CONDITIONALs if a new SYSAPI_* or WINAPI_* was added. + +Adjusting lib/common/common.h: + +The lib/common/common.h header file is included directly or indirectly +by every other file. Its primary job is to include config.h, which +defines macros depending on what the 'configure' script discovered +about the system. If the platform does not use the 'configure' script +it must define the appropriate SYSAPI_* and WINAPI_* macro. It may +also do other platform specific setup. + +Adjusting lib/common/BasicTypes.h: + +No changes should be necessary in BasicTypes.h. However, if the +platform's system header files define SInt8, et al. you may need +to adjust the typedefs to match the system's definitions. + +Implementing lib/arch: + +Much platform dependent code lives in lib/arch. There are several +interface classes there and they must all be implemented for each +platform. See the interface header files for more information. + +Platforms requiring special functions should create a class named +CArchMiscXXX where XXX is the platform name. The class should have +only static methods. Clients can include the appropriate header +file and make calls directly, surrounded by a suitable #ifdef/#endif. + +If using automake, the Makefile.am should list the system specific +files in a XXX_SOURCE_FILES macro where XXX matches the appropriate +AM_CONDITIONAL symbol. XXX_SOURCE_FILES must be added to EXTRA_DIST +and the following added above the INCLUDES macro: + + if XXX + libarch_a_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(XXX_SOURCE_FILES) \ + $(NULL) + endif + +Implementing lib/platform: + +Most of the remaining platform dependent code lives in lib/platform. +The code there implements platform dependent window, clipboard, keyboard +and screen saver handling. If a platform is named XXX then the following +classes should be derived and implemented: + + * CXXXClipboard : IClipboard + Provides clipboard operations. Typically, this class will + have helper classes for converting between various clipboard + data formats. + + * CXXXEventQueueBuffer : IEventQueueBuffer + Provides operations for waiting for, posting and retrieving events. + Also provides operations for creating and deleting timers. + + * CXXXKeyState : CKeyState + Provides operations for synthesizing key events and for mapping a + key ID to a sequence of events to generate that key. + + * CXXXScreen : IScreen, IPrimaryScreen, ISecondaryScreen, IPlatformScreen + Provides screen operations. + + * CXXXScreenSaver : IScreenSaver + Provides screen saver operations. + +If using automake, the Makefile.am should list the window system +specific files in a XXX_SOURCE_FILES macro where XXX matches the +appropriate AM_CONDITIONAL symbol. XXX_SOURCE_FILES must be added +to EXTRA_DIST and the following added above the INCLUDES macro: + + if XXX + libplatform_a_SOURCES = $(XXX_SOURCE_FILES) + endif + +Tweaks: + +Finally, each platform typically requires various adjustments here +and there. In particular, synergyc.cpp and synergys.cpp usually +require platform dependent code for the main entry point, parsing +arguments, and reporting errors. Also, some platforms may benefit +from a graphical user interface front end. These are generally +not portable and synergy doesn't provide any infrastructure for +the code common to any platform, though it may do so someday. +There is, however, an implementation of a GUI front end for Windows +that serves as an example. diff --git a/doc/about.html b/doc/about.html new file mode 100644 index 00000000..aadd5764 --- /dev/null +++ b/doc/about.html @@ -0,0 +1,55 @@ + + + + + + + + About Synergy + + +

+With synergy, all the computers on your desktop form a single virtual +screen. You use the mouse and keyboard of only one of the computers +while you use all of the monitors on all of the computers. +You tell synergy how many screens you have and their positions relative +to one another. Synergy then detects when the mouse moves off +the edge of a screen and jumps it instantly to the neighboring screen. +The keyboard works normally on each screen; input goes to whichever +screen has the cursor. +

+In this example, the user is moving the mouse from left to right. +When the cursor reaches the right edge of the left screen it jumps +instantly to the left edge of the right screen. +

+

+

+You can arrange screens side-by-side, above and below one another, +or any combination. You can even have a screen jump to the opposite +edge of itself. Synergy also understands multiple screens attached +to the same computer. +

+Running a game and don't want synergy to jump screens? No problem. +Just toggle Scroll Lock. Synergy keeps the cursor on the same screen +when Scroll Lock is on. (This can be configured to another hot key.) +

+Do you wish you could cut and paste between computers? Now you can! +Just copy text, HTML, or an image as you normally would on one screen +then switch to another screen and paste it. It's as if all your +computers shared a single clipboard (and separate primary selection for +you X11 users). It even converts newlines to each computer's native +form so cut and paste between different operating systems works +seamlessly. And it does it all in Unicode so any text can be copied. +

+

+Do you use a screen saver? With synergy all your screen savers act in +concert. When one starts they all start. When one stops they all +stop. And, if you require a password to unlock the screen, you'll +only have to enter a password on one screen. +

+If you regularly use multiple computers on one desk, give synergy a +try. You'll wonder how you ever lived without it. +

+ + + diff --git a/doc/authors.html b/doc/authors.html new file mode 100644 index 00000000..3fe7b4f1 --- /dev/null +++ b/doc/authors.html @@ -0,0 +1,72 @@ + + + + + + + + Synergy Authors + + +

+

Synergy Authors

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Chris Schoeneman crs23@users.sourceforge.no_spam.net Creator, owner, primary developer
Ryan Breen ryan@ryanbreen.no_spam.com Initial Mac OS X port
Guido Poschta moolder@gmx.no_spam.net Windows installer
Bertrand Landry Hetu bertrand@landryhetu.no_spam.com Mac OS X port
Tom Chadwick vttom@users.sourceforge.no_spam.net PageUp/PageDown on X servers without mouse wheel support
Brent Priddy toopriddy@users.sourceforge.no_spam.net Re-resolving server hostname on each connection
Marc-Antoine Ruel maruel@users.sourceforge.no_spam.net Visual Studio 2005 port
+

+To avoid spam bots, the above email addresses have ".no_spam" +hidden near the end. If you copy and paste the text be sure to +remove it. +

+ + + diff --git a/doc/autostart.html b/doc/autostart.html new file mode 100644 index 00000000..0343048a --- /dev/null +++ b/doc/autostart.html @@ -0,0 +1,428 @@ + + + + + + + + Synergy Autostart Guide + + +

+

Starting synergy automatically

+

+You can configure synergy to start automatically when the computer +starts or when you log in. The steps to do that are different on +each platform. Note that changing these configurations doesn't +actually start or stop synergy. The changes take effect the next +time you start your computer or log in. +

+

Windows

+

+Start synergy and click the Configure... button +by the text Automatic Startup. The +Auto Start dialog will pop up. +If an error occurs then correct the problem and click +Configure again. +

+On the Auto Start dialog you'll configure +synergy to start or not start automatically when the computer starts +or when you log in. You need Administrator access rights to start +synergy automatically when the computer starts. The dialog will let +you know if you have sufficient permission. +

+If synergy is already configured to automatically start then there +will be two Uninstall buttons, at most one +of which is enabled. Click the enabled button, if any, to tell +synergy to not start automatically. +

+If synergy is not configured to start automatically then there will +be two Install buttons. If you have +sufficient permission to have synergy start automatically when the +computer does then the Install button in the +When Computer Starts box will be enabled. +Click it to have synergy start for all users when the computer starts. +In this case, synergy will be available during the login screen. +Otherwise, click the Install button in the +When You Log In box to have synergy +automatically start when you log in. +

+

Unix

+

+Synergy requires an X server. That means a server must be +running and synergy must be authorized to connect to that server. +It's best to have the display manager start synergy. You'll need +the necessary (probably root) permission to modify the display +manager configuration files. If you don't have that permission +you can start synergy after logging in via the +.xsession file. +

+Typically, you need to edit three script files. The first file +will start synergy before a user logs in, the second will kill +that copy of synergy, and the third will start it again after +the user logs in. +

+The contents of the scripts varies greatly between systems so +there's no one definite place where you should insert your edits. +However, these scripts often exit before reaching the bottom so +put the edits near the top of the script. +

+The location and names of these files depend on the operating +system and display manager you're using. A good guess for the +location is /etc/X11. If you use kdm +then try looking in /etc/kde3 or +/usr/kde/version/share/config. +Typical file names are: +

+ + + + + + +
      xdm    kdm    gdm
1 xdm/Xsetup kdm/Xsetup gdm/Init/Default (*)
2 xdm/Xstartup kdm/Xstartup gdm/PostLogin/Default (*)
3 xdm/Xsession kdm/Xsession gdm/Sessions/Default (*, **)
+
+

+*) The Default file is used if no other +suitable file is found. gdm will try +displayname (e.g. :0) +and hostname (e.g. somehost), +in that order, before and instead of Default. +
+**) gdm may use gdm/Xsession, +xdm/Xsession or +dm/Xsession if +gdm/Sessions/Default doesn't exist. +

+For a synergy client, add the following to the first file: + + /usr/bin/killall synergyc + sleep 1 + /usr/bin/synergyc [<options>] synergy-server-hostname + +Of course, the path to synergyc depends on where you installed it +so adjust as necessary. +

+Add to the second file: + + /usr/bin/killall synergyc + sleep 1 + +

+And to the third file: + + /usr/bin/killall synergyc + sleep 1 + /usr/bin/synergyc [<options>] synergy-server-hostname + +Note that <options> +must not include +-f or --no-daemon or +the script will never exit and you won't be able to log in. +

+The changes are the same for the synergy server except replace +synergyc with synergys +and use the appropriate synergys command +line options. Note that the +first script is run as root so synergys will look for the configuration +file in root's home directory then in /etc. +Make sure it exists in one of those places or use the +--config config-pathname +option to specify its location. +

+Note that some display managers (xdm and kdm, but not gdm) grab +the keyboard and do not release it until the user logs in for +security reasons. This prevents a synergy server from sharing +the mouse and keyboard until the user logs in. It doesn't +prevent a synergy client from synthesizing mouse and keyboard +input, though. +

+If you're configuring synergy to start only after you log in then edit +your .xsession file. Add just what you +would add to the third file above. +

+

Mac OS X

+

+[By Tor Slettnes] +

+There are three different ways to automatically start Synergy +(client or server) on Mac OS X: +

+

    +
  1. + The first method involves creating a StartupItem + at the system level, which is executed when the machine starts up + or shuts down. This script will run in the background, and + relaunch synergy as needed. +

    +

    +
    Pros:
    +
    + Synergy is persistent, so this allows for a multi-user + setup and interactive logins. +
    +
    Cons:
    +
    + The synergy process does not have access to the clipboard + of the logged-in user. +
    +
    +
  2. +

    +

  3. + The second method will launch Synergy from the + LoginWindow application, once a particular + user has logged in. +

    +

    +
    Pros:
    +
    + The synergy process inherits the + $SECURITYSESSIONID environment variable, + and therefore copy/paste works. +
    +
    Cons:
    +
    + Once the user logs out, synergy dies, and no remote + control is possible. +
    +
    +
  4. +

    +

  5. + The third method is to launch a startup script from the + "Startup Items" tab under System Preferences -> Accounts. +

    +

    +
    Pros:
    +
    + Does not require root (Administrator) access +
    +
    Cons:
    +
    + Once the user logs out, synergy dies, and no remote + control is possible. +
    +
    +
  6. +
+

+The text below describes how to implement a Synergy client using +the first two methods simultaneously. This way, Synergy is +always running, and the clipboard is available when someone is +logged in. A Mac OS X Synergy server setup will be quite similar. +

+1. Create a System Level Startup Item +

+

    +
  • + Open a Terminal window, and become root: + + $ sudo su - + +
  • +
  • + Create a folder for this item: + + # mkdir -p /Library/StartupItems/Synergy + +
  • +
  • + In this folder, create a new script file by the same name as + the directory itself, Synergy. This script + should contain the following text: +

    + +#!/bin/sh +. /etc/rc.common +  +run=(/usr/local/bin/synergyc -n $(hostname -s) -1 -f synergy-server) +  +KeepAlive () +{ + proc=${1##*/} +  + while [ -x "$1" ] + do + if ! ps axco command | grep -q "^${proc}\$" + then + "$@" + fi +  + sleep 3 + done +} +  +StartService () +{ + ConsoleMessage "Starting Synergy" + KeepAlive "${run[@]}" & +} +  +StopService () +{ + return 0 +} +  +RestartService () +{ + return 0 +} +  +RunService "$1" + +

    + However, replace synergy-server with the actual + name or IP address of your Synergy server. +

    + Note that this scripts takes care not to start + Synergy if another instance is currently running. This + allows it to run in the background even when synergy is also + started independently, e.g. from the LoginWindow + application as described below. +

  • +
  • + Make this script executable: + + # chmod 755 /Library/StartupItems/Synergy/Synergy + +
  • +
  • + In the same folder, create a file named + StartupParameters.plist containing: +

    + +{ + Description = "Synergy Client"; + Provides = ("Synergy"); + Requires = ("Network"); + OrderPreference = "None"; +} + +

  • +
+

+That's it! If you want to test this setup, you can run the +startup script as follows: +

+ + # /Library/StartupItems/Synergy/Synergy start + +

+Any errors, as well as output from Synergy, will be shown in +your terminal window. +

+Next time you reboot, Synergy should start automatically. +

+2. Run Synergy When a User Logs In +

+Each time a user successfully logs in via the console, the +LoginWindow application creates a unique session +cookie and stores it in the environment variable +$SECURITYSESSIONID. For copy and paste operations +to work, Synergy needs access to this environment variable. In +other words, Synergy needs to be launched (directly or +indirectly) via the LoginWindow application. +

+However, in order to kill any synergy processes started at the +system level (as described above), we need root access. Thus, +launching Synergy within the User's environment (e.g. via the +Startup Items tab in System Preferences -> Accounts) is not an +option that work in conjunction with the method above. +

+Fortunately, the LoginWindow application provides +a "hook" for running a custom program (as root, with the username provided as +the first and only argument) once a user has authenticated, but +before the user is logged in. +

+Unfortunately, only one such hook is available. If you have +already installed a Login Hook, you may need to add the text +from below to your existing script, rather than creating a new +one. +

+

    +
  • + Launch a Terminal window, and become root: + + $ sudo su - + +
  • +

    +

  • + Find out if a LoginHook already exists: + + # defaults read com.apple.loginwindow LoginHook + + This will either show the full path to a script or + executable file, or the text: + + The domain/default pair of (com.apple.loginwindow, LoginHook) does not exist + + In the former case, you need to modify your existing script, + and/or create a "superscript" which in turn calls your + existing script plus the one we will create here. +

    + The rest of this text assumes that this item did not already + exist, and that we will create a new script. +

  • +
  • + Create a folder in which we will store our custom startup + script: + + # mkdir -p /Library/LoginWindow + +
  • +
  • + In this folder, create a new script file (let's name it + LoginHook.sh), containing the following text: +

    + +#!/bin/sh +prog=(/usr/local/bin/synergyc -n $(hostname -s) ip-address-of-server) +  +### Stop any currently running Synergy client +killall ${prog[0]##*/} +  +### Start the new client +exec "${prog[@]}" + +

  • +
  • + Make this script executable: + + # chmod 755 /Library/LoginWindow/LoginHook.sh + +
  • +
  • + Create a login hook to call the script you just created: + + # defaults write com.apple.loginwindow LoginHook /Library/LoginWindow/LoginHook.sh + +
  • +
+

+More information on setting up login hooks can be found at +Apple. +

+When running the Synergy client, you may need to use the IP +address of the Synergy server rather than its host name. +Specifically, unless you have listed the server in your +local /etc/hosts file or in your local NetInfo +database, name services (i.e. DNS) may not yet be available by the +time you log in after power-up. synergyc will +quit if it cannot resolve the server name. +

+(This is not an issue with the previous method, because the +StartupParameters.plist file specifies that this +script should not be run until "network" is available). +

+3. Good Luck! +

+Remember to look in your system log on both your server and your +client(s) for clues to any problems you may have +(/var/log/system.log on your OS X box, typically +/var/log/syslog on Linux boxes). +

+ + + diff --git a/doc/banner.html b/doc/banner.html new file mode 100644 index 00000000..eed66918 --- /dev/null +++ b/doc/banner.html @@ -0,0 +1,16 @@ + + + + + + + + Synergy Header + + + + + +
Synergy
+ + diff --git a/doc/border.html b/doc/border.html new file mode 100644 index 00000000..ce45847a --- /dev/null +++ b/doc/border.html @@ -0,0 +1,14 @@ + + + + + + + + Synergy + + + +
+ + diff --git a/doc/compiling.html b/doc/compiling.html new file mode 100644 index 00000000..69321913 --- /dev/null +++ b/doc/compiling.html @@ -0,0 +1,112 @@ + + + + + + + + Building and Installing Synergy + + +

+

Prerequisites for building

+

+To build synergy from the sources you'll need the following: +

    +
  • Windows +
      +
    • Microsoft Windows SDK for Vista; or +
    • VC++ 6.0 or up should work +
    +

    +

  • Unix +
      +
    • gcc 2.95 or up +
    • X11R4 or up headers and libraries +
    +

    +

  • Mac OS X +
      +
    • gcc 2.95 or up +
    • Carbon development headers and libraries +
    +
+

+

Configuring the build

+

+This step is not necessary on Windows. +

+To configure the build for your platform use the configure script: +

+  ./configure
+
+For a list of options to configure use: +
+  ./configure --help
+
+On Solaris you may need to use: +
+  ./configure --x-includes=/usr/openwin/include --x-libraries=/usr/openwin/lib
+
+so synergy can find the X11 includes and libraries. +

+

Building

+

    +
  • Windows +

    + Open a command prompt window (cmd.exe or command.exe). If necessary + run vcvars.bat, created when VC++ or Visual Studio was installed. (Use + search to find it.) It's necessary to run the file if you didn't have + the installer set up environment variables for you. Then enter: +

    +  nmake /nologo /f Makefile.win
    +  
    + This will build the programs into build\Release. +

    +
  • Unix or Mac OS X +

    + Simply enter: +

    +  make
    +  
    + This will build the client and server and leave them in their + respective source directories. +

    +
+

+

Installing

+

    +
  • Windows +

    + You'll need NSIS, + the Nullsoft Scriptable Install System. As in the building on Windows + description above, enter: +

    +  nmake /nologo /f Makefile.win installer
    +  
    + to build build\Release\SynergyInstaller.exe. Run + this to install synergy. +

    + Alternatively, you can simply copy the following files from the + build\Release + directory to a directory you choose (perhaps under the + Program Files directory): +

      +
    • synergy.exe +
    • synergyc.exe +
    • synergys.exe +
    • synrgyhk.dll +
    +

    +
  • Unix or Mac OS X +

    +

    +  make install
    +  
    + will install the client and server into + /usr/local/bin unless you + specified a different directory when you ran configure. +

    + + + diff --git a/doc/configuration.html b/doc/configuration.html new file mode 100644 index 00000000..6c1c8baa --- /dev/null +++ b/doc/configuration.html @@ -0,0 +1,686 @@ + + + + + + + + Synergy Configuration Guide + + +

    +

    Synergy Configuration File Format

    +

    +The synergy server requires configuration. It will try certain +pathnames to load the configuration file if you don't specify a +path using the --config command line +option. synergys --help reports those +pathnames. +

    +The configuration file is a plain text file. Use any text editor +to create the configuration file. The file is broken into sections +and each section has the form: + + section: name + args + end + +Comments are introduced by # and continue to +the end of the line. name must be one of the +following: +

      +
    • screens +
    • aliases +
    • links +
    • options +
    +See below for further explanation of each section type. The +configuration file is case-sensitive so Section, +SECTION, and section +are all different and only the last is valid. Screen names are the +exception; screen names are case-insensitive. +

    +The file is parsed top to bottom and names cannot be used before +they've been defined in the screens or +aliases sections. So the +links and aliases +must appear after the screens and links +cannot refer to aliases unless the aliases +appear before the links. +

    +

    screens

    +

    +args is a list of screen names, one name per +line, each followed by a colon. Names are arbitrary strings but they +must be unique. The hostname of each computer is recommended. (This +is the computer's network name on win32 and the name reported by the +program hostname on Unix and OS X. Note +that OS X may append .local to the name you +gave your computer; e.g. somehost.local.) +There must be a screen name for the server and each client. Each +screen can specify a number of options. Options have the form +name = +value and are listed one per line +after the screen name. +

    +Example: + + section: screens + moe: + larry: + halfDuplexCapsLock = true + halfDuplexNumLock = true + curly: + meta = alt + end + +This declares three screens named moe, +larry, and curly. +Screen larry has half-duplex Caps Lock and +Num Lock keys (see below) and screen curly +converts the meta modifier key to the alt modifier key. +

    +A screen can have the following options: +

      +
    • halfDuplexCapsLock = {true|false} +

      + This computer has a Caps Lock key that doesn't report a + press and a release event when the user presses it but + instead reports a press event when it's turned on and a + release event when it's turned off. If Caps Lock acts + strangely on all screens then you may need to set this + option to true + on the server screen. If it acts strangely on one + screen then that screen may need the option set to + true. +

      +

    • halfDuplexNumLock = {true|false} +

      + This is identical to halfDuplexCapsLock + except it applies to the Num Lock key. +

      +

    • halfDuplexScrollLock = {true|false} +

      + This is identical to halfDuplexCapsLock + except it applies to the Scroll Lock key. Note that, by default, + synergy uses Scroll Lock to keep the cursor on the current screen. That + is, when Scroll Lock is toggled on, the cursor is locked to the screen + that it's currently on. You can use that to prevent accidental switching. + You can also configure other hot keys to do that; see + lockCursorToScreen. +

      +

    • switchCorners = <corners> +

      + See switchCorners below. +

      +

    • switchCornerSize = N +

      + See switchCornerSize below. +

      +

    • xtestIsXineramaUnaware = {true|false} +

      + This option works around a bug in the XTest extension + when used in combination with Xinerama. It affects + X11 clients only. Not all versions of the XTest + extension are aware of the Xinerama extension. As a + result, they do not move the mouse correctly when + using multiple Xinerama screens. This option is + currently true by default. If + you know your XTest extension is Xinerama aware then set + this option to false. +

      +

    • shift = {shift|ctrl|alt|meta|super|none}
      + ctrl = {shift|ctrl|alt|meta|super|none}
      + alt = {shift|ctrl|alt|meta|super|none}
      + meta = {shift|ctrl|alt|meta|super|none}
      + super = {shift|ctrl|alt|meta|super|none}
      +

      + Map a modifier key pressed on the server's keyboard to + a different modifier on this client. This option only + has an effect on a client screen; it's accepted and + ignored on the server screen. +

      + You can map, say, the shift key to shift (the default), + ctrl, alt, meta, super or nothing. Normally, you + wouldn't remap shift or ctrl. You might, however, have + an X11 server with meta bound to the Alt keys. To use + this server effectively with a windows client, which + doesn't use meta but uses alt extensively, you'll want + the windows client to map meta to alt (using + meta = alt). +

      +

    +

    +

    aliases

    +

    + args is a list of screen names just like + in the screens section except each screen + is followed by a list of aliases, one per line, not followed + by a colon. An alias is a screen name and must be unique. During + screen name lookup each alias is equivalent to the screen name it + aliases. So a client can connect using its canonical screen name + or any of its aliases. +

    + Example: + + section: aliases + larry: + larry.stooges.com + curly: + shemp + end + + Screen larry is also known as + larry.stooges.com and can connect as + either name. Screen curly is also + known as shemp (hey, it's just an example). +

    +

    links

    +

    + args is a list of screen names just like + in the screens section except each screen + is followed by a list of links, one per line. Each link has the + form {left|right|up|down}[<range>] = + name[<range>]. A link indicates which + screen is adjacent in the given direction. +

    + Each side of a link can specify a range which defines a portion + of an edge. A range on the direction is the portion of edge you can + leave from while a range on the screen is the portion of edge you'll + enter into. Ranges are optional and default to the entire edge. All + ranges on a particular direction of a particular screen must not + overlap. +

    + A <range> is written as (<start>,<end>). + Both start and end + are percentages in the range 0 to 100, inclusive. The start must be + less than the end. 0 is the left or top of an edge and 100 is the + right or bottom. +

    + Example: + + section: links + moe: + right = larry + up(50,100) = curly(0,50) + larry: + left = moe + up(0,50) = curly(50,100) + curly: + down(0,50) = moe + down(50,100) = larry(0,50) + end + + This indicates that screen larry is to + the right of screen moe (so moving the + cursor off the right edge of moe would + make it appear at the left edge of larry), + the left half of + curly is above the right half of + moe, + moe is to the left of + larry (edges are not necessarily symmetric + so you have to provide both directions), the right half of + curly is above the left half of + larry, all of moe + is below the left half of curly, and the + left half of larry is below the right half of + curly. +

    + Note that links do not have to be + symmetrical; for instance, here the edge between + moe and curly + maps to different ranges depending on if you're going up or down. + In fact links don't have to be bidirectional. You can configure + the right of moe to go to + larry without a link from the left of + larry to moe. + It's possible to configure a screen with no outgoing links; the + cursor will get stuck on that screen unless you have a hot key + configured to switch off of that screen. +

    +

    options

    +

    + args is a list of lines of the form + name = value. These set the global + options. +

    + Example: + + section: options + heartbeat = 5000 + switchDelay = 500 + end + +

    + You can use the following options: +

      +
    • heartbeat = N +

      + The server will expect each client to send a message no + less than every N milliseconds. + If no message arrives from a client within + 3N seconds the server forces that + client to disconnect. +

      + If synergy fails to detect clients disconnecting while + the server is sleeping or vice versa, try using this + option. +

      +

    • switchCorners = <corners> +

      + Synergy won't switch screens when the mouse reaches the edge of + the screen if it's in a listed corner. The size of all corners + is given by the switchCornerSize + option. +

      + Corners are specified by a list using the following names: +

        +
      • none -- no corners +
      • top-left -- the top left corner +
      • top-right -- the top right corner +
      • bottom-left -- the bottom left corner +
      • bottom-right -- the bottom right corner +
      • left -- top and bottom left corners +
      • right -- top and bottom right corners +
      • top -- left and right top corners +
      • bottom -- left and right bottom corners +
      • all -- all corners +
      +

      + The first name in the list is one of the above names and defines + the initial set of corners. Subsequent names are prefixed with + + or - to add the corner to or remove the corner from the set, + respectively. For example: +

      + + all -left +top-left + +

      + starts will all corners, removes the left corners (top and bottom) + then adds the top-left back in, resulting in the top-left, + bottom-left and bottom-right corners. +

      +

    • switchCornerSize = N +

      + Sets the size of all corners in pixels. The cursor must be within + N pixels of the corner to be considered + to be in the corner. +

      +

    • switchDelay = N +

      + Synergy won't switch screens when the mouse reaches the + edge of a screen unless it stays on the edge for + N + milliseconds. This helps prevent unintentional + switching when working near the edge of a screen. +

      +

    • switchDoubleTap = N +

      + Synergy won't switch screens when the mouse reaches the + edge of a screen unless it's moved away from the edge + and then back to the edge within N + milliseconds. With + the option you have to quickly tap the edge twice to + switch. This helps prevent unintentional switching + when working near the edge of a screen. +

      +

    • screenSaverSync = {true|false} +

      + If set to false then synergy + won't synchronize screen savers. Client screen savers + will start according to their individual configurations. + The server screen saver won't start if there is input, + even if that input is directed toward a client screen. +

      +

    • relativeMouseMoves = {true|false} +

      + If set to true then secondary + screens move the mouse using relative rather than absolute + mouse moves when and only when the cursor is locked to the + screen (by Scroll Lock or a configured + hot key). + This is intended to make synergy work better with certain + games. If set to false or not + set then all mouse moves are absolute. +

      +

    • keystroke(key) = actions +

      + Binds the key combination key to the + given actions. key + is an optional list of modifiers (shift, + control, alt, + meta or super) + optionally followed by a character or a key name, all separated by + + (plus signs). You must have either + modifiers or a character/key name or both. See below for + valid key names. +

      + Actions are described below. +

      + Keyboard hot keys are handled while the cursor is on the primary + screen and secondary screens. Separate actions can be assigned + to press and release. +

      +

    • mousebutton(button) = actions +

      + Binds the modifier and mouse button combination + button to the given + actions. button + is an optional list of modifiers (shift, + control, alt, + meta or super) + followed by a button number. The primary button (the + left button for right handed users) is button 1, the middle button + is 2, etc. +

      + Actions are described below. +

      + Mouse button actions are not handled while the cursor is on the + primary screen. You cannot use these to perform an action while + on the primary screen. Separate actions can be assigned to press + and release. +

      +

    + You can use both the switchDelay and + switchDoubleTap options at the same + time. Synergy will switch when either requirement is satisfied. +

    +Actions are two lists of individual actions separated +by commas. The two lists are separated by a semicolon. Either list can be +empty and if the second list is empty then the semicolon is optional. The +first list lists actions to take when the condition becomes true (e.g. the +hot key or mouse button is pressed) and the second lists actions to take +when the condition becomes false (e.g. the hot key or button is released). +The condition becoming true is called activation and becoming false is +called deactivation. +Allowed individual actions are: +

      +
    • keystroke(key[,screens]) +
    • keyDown(key[,screens]) +
    • keyUp(key[,screens]) +

      + Synthesizes the modifiers and key given in key + which has the same form as described in the + keystroke option. If given, + screens lists the screen or screens to + direct the event to, regardless of the active screen. If not + given then the event is directed to the active screen only. + (Due to a bug, keys cannot be directed to the server while on a + client screen.) +

      + keyDown synthesizes a key press and + keyUp synthesizes a key release. + keystroke synthesizes a key press on + activation and a release on deactivation and is equivalent to + a keyDown on activation and + keyUp on deactivation. +

      + screens is either * + to indicate all screens or a colon (:) separated list of screen + names. (Note that the screen name must have already been encountered + in the configuration file so you'll probably want to put actions at + the bottom of the file.) +

      +

    • mousebutton(button) +
    • mouseDown(button) +
    • mouseUp(button) +

      + Synthesizes the modifiers and mouse button given in + button + which has the same form as described in the + mousebutton option. +

      + mouseDown synthesizes a mouse press and + mouseUp synthesizes a mouse release. + mousebutton synthesizes a mouse press on + activation and a release on deactivation and is equivalent to + a mouseDown on activation and + mouseUp on deactivation. +

      +

    • lockCursorToScreen(mode) +

      + Locks the cursor to or unlocks the cursor from the active screen. + mode can be off + to unlock the cursor, on to lock the + cursor, or toggle to toggle the current + state. The default is toggle. If the + configuration has no lockCursorToScreen + action and Scroll Lock is not used as a hot key then Scroll Lock + toggles cursor locking. +

      +

    • switchToScreen(screen) +

      + Jump to screen with name or alias screen. +

      +

    • switchInDirection(dir) +

      + Switch to the screen in the direction dir, + which may be one of left, + right, up or + down. +

      +

    • keyboardBroadcast(mode[,screens]) +

      + Turns broadcasting of keystrokes to multiple screens on and off. When + turned on all key presses and releases are sent to all of the screens + listed in screens. If not given, empty or + * then keystrokes are broadcast to all screens. + (However, due to a bug, keys cannot be sent to the server while on a + client screen.) +

      + mode can be off + to turn broadcasting off, on to turn it + on, or toggle to toggle the current + state. The default is toggle. +

      + screens is either * + to indicate all screens or a colon (:) separated list of screen + names. (Note that the screen name must have already been encountered + in the configuration file so you'll probably want to put actions at + the bottom of the file.) +

      + Multiple keyboardBroadcast actions may be + configured with different screens. The most + recently performed action defines the screens to broadcast to. +

      +

    +

    +Examples: +

      +
    • keystroke(alt+left) = switchInDirection(left) +

      + Switches to the screen to left when the left arrow key is pressed + in combination with the Alt key. +

      +

    • keystroke(shift+control+alt+super) = switchToScreen(moe) +

      + Switches to screen moe when all of the + Shift, Control, Alt, and Super modifier keys are pressed together. +

      +

    • keystroke(alt+f1) = ; lockCursorToScreen(toggle) +

      + Toggles locking the cursor to the screen when Alt+F1 is released. +

      +

    • mousebutton(2) = mouseDown(control+1) ; mouseUp(control+1) +

      + While on a secondary screen clicking the middle mouse button will + become a Control click of the primary button. +

      +

    • keystroke(super+f1) = keystroke(super+L,larry), keystroke(control+alt+delete,curly) +

      + Pressing Super+F1 (on any screen) will synthesize Super+L on screen + larry and Control+Alt+Delete on screen + curly. +

      +

    +

    +Valid key names are: +

      +
    • AppMail +
    • AppMedia +
    • AppUser1 +
    • AppUser2 +
    • AudioDown +
    • AudioMute +
    • AudioNext +
    • AudioPlay +
    • AudioPrev +
    • AudioStop +
    • AudioUp +
    • BackSpace +
    • Begin +
    • Break +
    • Cancel +
    • CapsLock +
    • Clear +
    • Delete +
    • Down +
    • Eject +
    • End +
    • Escape +
    • Execute +
    • F1 +
    • F2 +
    • F3 +
    • F4 +
    • F5 +
    • F6 +
    • F7 +
    • F8 +
    • F9 +
    • F10 +
    • F11 +
    • F12 +
    • F13 +
    • F14 +
    • F15 +
    • F16 +
    • F17 +
    • F18 +
    • F19 +
    • F20 +
    • F21 +
    • F22 +
    • F23 +
    • F24 +
    • F25 +
    • F26 +
    • F27 +
    • F28 +
    • F29 +
    • F30 +
    • F31 +
    • F32 +
    • F33 +
    • F34 +
    • F35 +
    • Find +
    • Help +
    • Home +
    • Insert +
    • KP_0 +
    • KP_1 +
    • KP_2 +
    • KP_3 +
    • KP_4 +
    • KP_5 +
    • KP_6 +
    • KP_7 +
    • KP_8 +
    • KP_9 +
    • KP_Add +
    • KP_Begin +
    • KP_Decimal +
    • KP_Delete +
    • KP_Divide +
    • KP_Down +
    • KP_End +
    • KP_Enter +
    • KP_Equal +
    • KP_F1 +
    • KP_F2 +
    • KP_F3 +
    • KP_F4 +
    • KP_Home +
    • KP_Insert +
    • KP_Left +
    • KP_Multiply +
    • KP_PageDown +
    • KP_PageUp +
    • KP_Right +
    • KP_Separator +
    • KP_Space +
    • KP_Subtract +
    • KP_Tab +
    • KP_Up +
    • Left +
    • LeftTab +
    • Linefeed +
    • Menu +
    • NumLock +
    • PageDown +
    • PageUp +
    • Pause +
    • Print +
    • Redo +
    • Return +
    • Right +
    • ScrollLock +
    • Select +
    • Sleep +
    • Space +
    • SysReq +
    • Tab +
    • Undo +
    • Up +
    • WWWBack +
    • WWWFavorites +
    • WWWForward +
    • WWWHome +
    • WWWRefresh +
    • WWWSearch +
    • WWWStop +
    • Space +
    • Exclaim +
    • DoubleQuote +
    • Number +
    • Dollar +
    • Percent +
    • Ampersand +
    • Apostrophe +
    • ParenthesisL +
    • ParenthesisR +
    • Asterisk +
    • Plus +
    • Comma +
    • Minus +
    • Period +
    • Slash +
    • Colon +
    • Semicolon +
    • Less +
    • Equal +
    • Greater +
    • Question +
    • At +
    • BracketL +
    • Backslash +
    • BracketR +
    • Circumflex +
    • Underscore +
    • Grave +
    • BraceL +
    • Bar +
    • BraceR +
    • Tilde +
    +Additionally, a name of the form \uXXXX where +XXXX is a hexadecimal number is interpreted as +a unicode character code. +Key and modifier names are case-insensitive. Keys that don't exist on +the keyboard or in the default keyboard layout will not work. +

    + + + diff --git a/doc/contact.html b/doc/contact.html new file mode 100644 index 00000000..1b378b85 --- /dev/null +++ b/doc/contact.html @@ -0,0 +1,44 @@ + + + + + + + + Synergy Contact Info + + +

    +Use the following addresses to contact the synergy project: +

    + + + + + + + + + + + + + + + + +
    Bug reports: Add Synergy Bug
    Help: synergy-help@groundhog.pair..no_spamcom
    General: crs23@users.sourceforge..no_spamnet
    +

    +To avoid spam bots, the above email addresses have ".no_spam" +hidden near the end. If you copy and paste the text be sure to +remove it. +

    +Please check the + +bug list before reporting a bug. You may also find answers at the +synergy forums. +Emails for help asking questions answered on this site will go unanswered. +

    + + + diff --git a/doc/developer.html b/doc/developer.html new file mode 100644 index 00000000..acfdff9a --- /dev/null +++ b/doc/developer.html @@ -0,0 +1,81 @@ + + + + + + + + Synergy Developer Documentation + + +

    +Synergy is reasonably well commented so reading the source code +should be enough to understand particular pieces. See the +doc/PORTING +file in the synergy source code for more high-level information. +

    +

    How it works

    +

    +The theory behind synergy is simple: the server captures mouse, +keyboard, clipboard, and screen saver events and forwards them to +one or more clients. If input is directed to the server itself +then the input is delivered normally. In practice, however, many +complications arise. +

    +First, different keyboard mappings can produce different characters. +Synergy attempts to generate the same character on the client as +would've been generated on the server, including appropriate modifier +keys (like Control and Alt). Non-character keys like Shift are also +synthesized if possible. Sometimes the client simply cannot create +the character or doesn't have a corresponding non-character key and +synergy must discard the event. Note that synergy won't necessarily +synthesize an event for the corresponding key on the client's +keyboard. For example, if the client or server can't distinguish +between the left and right shift keys then synergy can't be certain +to synthesize the shift on the same side of the keyboard as the user +pressed. +

    +Second, different systems have different clipboards and clipboard +formats. The X window system has a system-wide selection and +clipboard (and yet other buffers) while Microsoft Windows has only +a system-wide clipboard. Synergy has to choose which of these +buffers correspond to one another. Furthermore, different systems +use different text encodings and line breaks. Synergy mediates and +converts between them. +

    +Finally, there are no standards across operating systems for some +operations that synergy requires. Among these are: intercepting +and synthesizing events; enabling, disabling, starting and stopping +the screen saver; detecting when the screen saver starts; reading +and writing the clipboard(s). +

    +All this means that synergy must be customized to each operating +system (or windowing system in the case of X windows). Synergy +breaks platform differences into two groups. The first includes +the mundane platform dependent things: file system stuff, +multithreading, network I/O, multi-byte and wide character +conversion, time and sleeping, message display and logging, and +running a process detached from a terminal. This code lives in +lib/arch. +

    +The second includes screen and window management handling, user +event handling, event synthesis, the clipboards, and the screen +saver. This code lives in lib/platform. +

    +For both groups, there are particular classes or interfaces that +must be inherited and implemented for each platform. See the +doc/PORTING file in the synergy source +code for more information. +

    +

    Auto-generated Documentation

    +

    +Synergy can automatically generate documentation from the comments +in the code using doxygen. +Use make doxygen to build it yourself +from the source code into the doc/doxygen/html +directory. +

    +

    + + + diff --git a/doc/doxygen.cfg.in b/doc/doxygen.cfg.in new file mode 100644 index 00000000..4abf52f9 --- /dev/null +++ b/doc/doxygen.cfg.in @@ -0,0 +1,898 @@ +# Doxyfile 1.2.13.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = @PACKAGE@ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @VERSION@ + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc/doxygen + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Chinese, Croatian, Czech, Danish, Dutch, Finnish, French, +# German, Greek, Hungarian, Italian, Japanese, Korean, Norwegian, Polish, +# Portuguese, Romanian, Russian, Slovak, Slovene, Spanish and Swedish. + +OUTPUT_LANGUAGE = English + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these class will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. + +STRIP_FROM_PATH = + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower case letters. If set to YES upper case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are adviced to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explict @brief command for a brief description. + +JAVADOC_AUTOBRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. + +INHERIT_DOCS = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consist of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp +# *.h++ *.idl + +FILE_PATTERNS = *.cpp *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse. + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 3 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the Html help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript and frames is required (for instance Mozilla, Netscape 4.0+, +# or Internet explorer 4.0+). Note that for large projects the tree generation +# can take a very long time. In such cases it is better to disable this feature. +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimised for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assigments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_XML = NO + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_PREDEF_ONLY tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line and do not end with a semicolon. Such function macros are typically +# used for boiler-plate code, and will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tagfiles. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in Html, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superceded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yield more powerful graphs. + +CLASS_DIAGRAMS = NO + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = @HAVE_DOT@ + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermedate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO + +# The CGI_NAME tag should be the name of the CGI script that +# starts the search engine (doxysearch) with the correct parameters. +# A script with this name will be generated by doxygen. + +CGI_NAME = + +# The CGI_URL tag should be the absolute URL to the directory where the +# cgi binaries are located. See the documentation of your http daemon for +# details. + +CGI_URL = + +# The DOC_URL tag should be the absolute URL to the directory where the +# documentation is located. If left blank the absolute path to the +# documentation, with file:// prepended to it, will be used. + +DOC_URL = + +# The DOC_ABSPATH tag should be the absolute path to the directory where the +# documentation is located. If left blank the directory on the local machine +# will be used. + +DOC_ABSPATH = + +# The BIN_ABSPATH tag must point to the directory where the doxysearch binary +# is installed. + +BIN_ABSPATH = + +# The EXT_DOC_PATHS tag can be used to specify one or more paths to +# documentation generated for other projects. This allows doxysearch to search +# the documentation for these projects as well. + +EXT_DOC_PATHS = diff --git a/doc/faq.html b/doc/faq.html new file mode 100644 index 00000000..b9696391 --- /dev/null +++ b/doc/faq.html @@ -0,0 +1,266 @@ + + + + + + + + Synergy Frequently Asked Questions + + +

    +

    Synergy Frequently Asked Questions

    +

    +

    Questions

    +
      +
    1. Why doesn't ctrl+alt+del work on secondary screens? +
    2. Can the server and client be using different operating systems? +
    3. What's the difference between synergy and x2x, x2vnc, etc? +
    4. What does "Cannot initialize hook library" mean? +
    5. What security/encryption does synergy provide? +
    6. What should I call my screens in the configuration? +
    7. Why do my Caps-Lock, Num-Lock, Scroll-Lock keys act funny? +
    8. Can synergy share the display in addition to the mouse and keyboard? +
    9. Can synergy do drag and drop between computers? +
    10. Do AltGr or Mode-Switch or ISO_Level3_Shift work? +
    11. Why isn't synergy ported to platform XYZ? +
    12. My client can't connect. What's wrong? +
    13. Linking fails on Solaris. What's wrong? +
    14. The screen saver never starts. Why not? +
    15. I can't switch screens anymore for no apparent reason. Why? +
    16. I get the error 'Xlib: No protocol specified'. Why? +
    17. The cursor goes to secondary screen but won't come back. Why? +
    18. The cursor wraps from one edge of the screen to the opposite. Why? +
    19. How do I stop my game from minimizing when I leave the screen? +
    +

    Answers

    +
      +
    1. Why doesn't ctrl+alt+del work on secondary screens? +

      + Synergy isn't able to capture ctrl+alt+del on PC compatible + primary screens because it's handled completely differently than + other keystrokes. However, when the mouse is on a client + screen, pressing ctrl+alt+pause will simulate ctrl+alt+del + on the client. (A client running on Windows NT, 2000, or XP + must be configured to autostart when the computer starts for + this to work.) +

      + On a primary screen running on an OS X system, you can use + ctrl+command+del. Using the pause key isn't necessary since OS X + doesn't treat ctrl+command+del differently. And using the pause + key isn't usually possible because there isn't one on most OS X + systems. Use command instead of option/alt because + the command key, not the option/alt key, maps to alt on windows. + The reason is because the command key is in the same physical + location and performs the same general function (menu shortcuts) + as alt on a windows system. This mapping can be modified in + the configuration. +

      + On mac laptops, the key labeled "delete" is actually backspace + and ctrl+command+delete won't work. However fn+delete really + is delete so fn+ctrl+command+delete will act as ctrl+alt+del + on a windows secondary screen. +

      +
    2. Can the server and client be using different operating systems? +

      + Yes. The synergy network protocol is platform neutral so + synergy doesn't care what operating systems are running on + the server and clients. +

      +
    3. What's the difference between synergy and +x2x, x2vnc, etc? +

      + Unlike x2x, synergy supports any number of computers and + it doesn't require X on Microsoft Windows platforms. It + also has more advanced clipboard support and synchronizes + screensavers. x2vnc is also limited to two computers, + requires the separate vnc package, and is really only + appropriate for using an X system to control a non-X system. + However, the right tool for the job is whatever tool works + best for you. +

      +
    4. What does "Cannot initialize hook library" mean? +

      + This error can occur on a synergy server running on a + Microsoft Windows operating system. It means that synergy + is already running or possibly was not shut down properly. + If it's running then first end the synergy task. If it's + not then try logging off and back on or rebooting then + starting synergy again. +

      +
    5. What security/encryption does synergy provide? +

      + Synergy provides no built-in encryption or authentication. + Given that, synergy should not be used on or over any untrusted + network, especially the Internet. It's generally fine for home + networks. Future versions may provide built-in encryption and + authentication. +

      + Strong encryption and authentication is available through SSH + (secure shell). Run the SSH daemon (i.e. server) on the same + computer that you run the synergy server. It requires no + special configuration to support synergy. On each synergy + client system, run SSH with port forwarding: +

      +

      +        ssh -f -N -L 24800:server-hostname:24800 server-hostname
      +
      +

      + where server-hostname is the name of the + SSH/synergy server. + Once ssh authenticates itself, start the synergy client + normally except use localhost or + 127.0.0.1 as the server's + address. SSH will then encrypt all communication on behalf of + synergy. Authentication is handled by the SSH authentication. +

      + A free implementation of SSH for Linux and many Unix systems is + OpenSSH. For + Windows there's a port of OpenSSH using + Cygwin. +

      +
    6. What should I call my screens in the configuration? +

      + You can use any unique name in the configuration file for each + screen but it's easiest to use the hostname of the computer. + That's the computer name not including the domain. For example, + a computer with the fully qualified domain name xyz.foo.com has + the hostname xyz. There should also be an alias for xyz to + xyz.foo.com. If you don't use the computer's hostname, you + have to tell synergy the name of the screen using a command line + option, or the startup dialog on Windows. +

      + Some systems are configured to report the fully qualified domain + name as the hostname. For those systems it will be easier to use + the FQDN as the screen name. Also note that a Mac OS X system + named xyz may report its hostname as + xyz.local. If that's the case for you + then use xyz.local as the screen name. +

      +
    7. Why do my Caps-Lock, Num-Lock, Scroll-Lock keys act funny? +

      + Some systems treat the Caps-Lock, Num-Lock, and Scroll-Lock keys + differently than all the others. Whereas most keys report going down + when physically pressed and going up when physically released, on + these systems the Caps-Lock and Num-Lock keys report going down + when being activated and going up when being deactivated. That + is, when you press and release, say, Caps-Lock to activate it, it + only reports going down, and when you press and release to + deactivate it, it only reports going up. This confuses synergy. +

      + You can solve the problem by changing your configuration file. + In the screens section, following each screen that has the + problem, any or all of these lines as appropriate: +

      +

      +        halfDuplexCapsLock = true
      +        halfDuplexNumLock = true
      +        halfDuplexScrollLock = true
      +
      +

      + Then restart synergy on the server or reload the configuration. +

      +
    8. Can synergy share the display in addition to the mouse and keyboard? +

      + No. Synergy is a KM solution not a KVM (keyboard, video, mouse) + solution. However, future versions will probably support KVM. + Hopefully, this will make synergy suitable for managing large + numbers of headless servers. +

      +
    9. Can synergy do drag and drop between computers? +

      + No. That's a very cool idea and it'll be explored. However, it's + also clearly difficult and may take a long time to implement. +

      +
    10. Does AltGr/Mode-Switch/ISO_Level3_Shift work? +

      + Yes, as of 1.0.12 synergy has full support for AltGr/Mode-switch. + That includes support for most (all?) European keyboard layouts. + All systems should be using the same keyboard layout, though, for + all characters to work. (Any character missing from a client's + layout cannot be generated by synergy.) There is experimental + support for ISO_Level3_Shift in 1.1.3. +

      +
    11. Why isn't synergy ported to platform XYZ? +

      + Probably because the developers don't have access to platform XYZ + and/or are unfamiliar with development on XYZ. Also, synergy has + inherently non-portable aspects so there's a not insignificant + effort involved in porting. +

      +
    12. My client can't connect. What's wrong? +

      + A common mistake when starting the client is to give the wrong + server host name. The last synergyc command line option (Unix) + or the "Server Host Name" edit field (Windows) should be the + host name (or IP address) of the server not the client's host + name. If you get the error connection failed: cannot connect + socket followed by the attempt to connect was forcefully + rejected or connection refused then the server isn't started, + can't bind the address, or the client is connecting to the wrong + host name/address or port. See the + troublshooting page for more help. +

      +
    13. Linking fails on Solaris. What's wrong? +

      + Did you add +

      +

      +        --x-includes=/usr/openwin/include --x-libraries=/usr/openwin/lib
      +
      +

      + to the configure command line? Solaris puts + the X11 includes and libraries in an unusual place and the above lets + synergy find them. +

      +
    14. The screen saver never starts. Why not? +

      + If the synergy server is on X Windows then the screen saver will + not start while the mouse is on a client screen. This is a + consequence of how X Windows, synergy and xscreensaver work. +

      +
    15. I can't switch screens anymore for no apparent reason. Why? +

      + This should not happen with 1.1.3 and up. Earlier versions of + synergy would not allow switching screens when a key was down and + sometimes it would believe a key was down when it was not. +

      +
    16. I get the error 'Xlib: No protocol specified'. Why? +

      + You're running synergy without authorization to connect to the + X display. Typically the reason is running synergy as root when + logged in as non-root. Just run synergy as the same user that's + logged in. +

      +
    17. The cursor goes to secondary screen but won't come back. Why? +

      + Your configuration is incorrect. You must indicate the neighbors + of every screen. Just because you've configured 'Apple' to be to + the left of 'Orange' does not mean that 'Orange' is to the right + of 'Apple'. You must provide both in the configuration. +

      +
    18. The cursor wraps from one edge of the screen to the opposite. Why? +

      + Because you told it to. If you list 'Orange' to be to the left of + 'Orange' then moving the mouse off the left edge of 'Orange' will + make it jump to the right edge. Remove the offending line from the + configuration if you don't want that behavior. +

      +
    19. How do I stop my game from minimizing when I leave the screen? +

      + Many full screen applications, particularly games, automatically + minimize when they're no longer the active (foreground) application + on Microsoft Windows. The synergy server normally becomes the foreground + when you switch to another screen in order to more reliably capture all + user input causing those full screen applications to minimize. To + prevent synergy from stealing the foreground just click "Options..." + and check "Don't take foreground window on Windows servers." If you + turn this on then be aware that synergy may not function correctly when + certain programs, particularly the command prompt, are the foreground + when you switch to other screens. Simply make a different program the + foreground before switching to work around that. +

      +
    + + + diff --git a/doc/history.html b/doc/history.html new file mode 100644 index 00000000..48f921c3 --- /dev/null +++ b/doc/history.html @@ -0,0 +1,30 @@ + + + + + + + + Synergy History + + +

    +

    Synergy History

    +

    +The first incarnation of synergy was CosmoSynergy, created by +Richard Lee and Adam Feder then at Cosmo Software, Inc., a +subsidiary of SGI (nee Silicon Graphics, Inc.), at the end of +1996. They wrote it, and Chris Schoeneman contributed, to +solve a problem: most of the engineers in Cosmo Software had +both an Irix and a Windows box on their desks and switchboxes +were expensive and annoying. CosmoSynergy was a great success +but Cosmo Software declined to productize it and the company +was later closed. +

    +Synergy is a from-scratch reimplementation of CosmoSynergy. +It provides most of the features of the original and adds a +few improvements. +

    + + + diff --git a/doc/home.html b/doc/home.html new file mode 100644 index 00000000..df0775db --- /dev/null +++ b/doc/home.html @@ -0,0 +1,61 @@ + + + + + + + + Synergy + + +

    +

    Introduction

    +synergy: [noun] a mutually advantageous conjunction of distinct elements +

    +Synergy lets you easily share a single mouse and keyboard between +multiple computers with different operating systems, each with its +own display, without special hardware. It's intended for users +with multiple computers on their desk since each system uses its +own monitor(s). +

    +Redirecting the mouse and keyboard is as simple as moving the mouse +off the edge of your screen. Synergy also merges the clipboards of +all the systems into one, allowing cut-and-paste between systems. +Furthermore, it synchronizes screen savers so they all start and stop +together and, if screen locking is enabled, only one screen requires +a password to unlock them all. Learn more +about how it works. +

    +Synergy is open source and released under the +GNU Public License (GPL). +

    +

    System Requirements

    +

    +

      +
    • Microsoft Windows 95, Windows 98, Windows Me (the Windows 95 family) +
    • Microsoft Windows NT, Windows 2000, Windows XP (the Windows NT family) +
    • Mac OS X 10.2 or higher +
    • Unix +
        +
      • X Windows version 11 revision 4 or up +
      • XTEST extension
        + (use "xdpyinfo | grep XTEST" to check for XTEST) +
      +
    +All systems must support TCP/IP networking. +

    +"Unix" includes Linux, Solaris, Irix and other variants. Synergy has +only been extensively tested on Linux and may not work completely or +at all on other versions of Unix. Patches are welcome (including +patches that package binaries) at the +patches page. +

    +The Mac OS X port is incomplete. It does not synchronize the screen saver, +only text clipboard data works (i.e. HTML and bitmap data do not work), +the cursor won't hide when not on the screen, and there may be problems +with mouse wheel acceleration. Other problems should be +filed as bugs. +

    + + + diff --git a/doc/images/logo.gif b/doc/images/logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..d9750ba3fe8ecb33b94feaa5233c4c3e3030913a GIT binary patch literal 1827 zcmbu7X*-*V0)}6a(}u=QJC?Rdlo?CW6l3d|P?u#|U9K~OlB&=srkz?^T2(@j#8CTA z&>~_h5gNu4sU;bNs-mIR+CnW6rMCCXoWF3M5BJCW%l+6p+L@W-#sDW^0RWgxCWS&7 z9v=3|0qv3+;+j!fK>=d1SSS=mL`0;grw0cI`}z4%snquN_Nl2U9*;+-(+>_0@p-vv zZ@|MN@aX8Eg6|z(+k1F))HF)i-515T=q9!q{#F21^ng({)p2#Tv`$dGyZ^8d9A2#R zDq^>MjCD$5rMA9Dhds&TjGhmDoYQ67HabtKXWYzeS`vNOKR6uu{JBj)dQJ!J$Z@>M z<(!TYMt^@~1vs+E6z_shs(GTFopmu-#RV5Px6|8(1p4tPDC)jslJV|zP;1^O1`?;q~$?jP{RLDtCg$hyAw zbF=&@a6-5_y9zc8@OuPc^U%=Z=KG-;@ZfN7Zy)TP0H;^LoxME|4-XIof&XvFZ_xh< z^#9>MO@QMd!9SW96B|b&Q{w+fNPLl$oRXTBo{^cAos*lFUqCG^Dt`H@q_m7yPOqq} zs;+rmTgRwpHZ;CrH8ua)(%RO}?%=%bjK<${_wDNKQgKy2CnO$ldK5g!*es#TPQE2}d~>zn(u5h8e5b57ZWzzw%v4N? z=)IdskXKSWxv<+|+$*he2^Nez2_XOlq#?dC09yi5*65}Do#j`krUrDV?8~f_HPJW# zocqg32?LDXy3T#k%7zgIOlu4C>?^J{(UPIwj|Y2k#Fq*`9N8^Hi)!&KDf#b19$P<@ zP>Xjy&>}>o5qirt?}s$J4USSIV-vIqF=>$D^7a}Jd~xiG(u?DX@|Og*$F5~cApU)$ z$%yc&DuHFdMmlCHyP*c!aLoY(oBl;zgcd+}3d=o(Wci;V5X33+W=mmQ|7U+z8ectY z>HZ-88cm^_Eg7SMU`N5EG1C&UbZ1Z89fhw2YJd^O6}q7UV?#1^2!+Vl)=+hLxAMf1^f`E!u3 zStgjzd4VR54mi&tJSsiwflk)#;O<`+3j*hPT!psfU$F|s%DLr9q#x9DkW@~odXc=W z2%rakGhfQnvWeG?C@w?N)#^r?$p6y9*1Aggbr7OXSVjlwR`}^}6eDWXbh)*7SsQ&S z5sGnzgq)tnRP{hCkdT2!a#c*(wJQ`AFL+>=P9j7IASL*vx3U-(r`l_^FGJ?q zEYYkfvc>NlrpP^WChH+~UC~s}P#*EKFo|deKh+s-Ay@~oQ0X8e%2fP4hw4*zOpD9> z*!CilBF(xkfn$bY;Ju_3C2$elWB0ilN~xllA^rzU^l@kSXtRTMR$ymLqCE_XlttGM z_>XD$UT`)^M&r%~S&k+z^MzRCrCt-#L3qdqnuj3 zN_s`oqX-x;$nk&T!|VS& vY%6aXPBZIqZZfae3Am*+2Bx!oh~$h+L} zs2L8xojGG|XJ-fC9R=4P66gPA>E(xBJlEP90D!mkRp7YXr7z#|`vQ`7dqqVns_AfC|J2l!wzjsWrly-V4z3L!A0HPI2K85#DDTmP_j8AahHmFBb8!G= zWo0h+y%M@eNl8goN%ryU$HVVgr5zJoE>2ondSiW^bwD+z;&H923>?SRk(P!)Ahast z=MQr@oSU#G5$6V&dq3-t*67CNmggtI0sYqII%K%qU`65<`Q82fT)%D7q{6!Xe!v|} zd4bdU`w1CbF4xViKRrDihr{*b0D8KIhKBmrzbutJ+&z)*3DnHl0 z`CN#1c6ROu03011dtVJtyjxiK_5;`7U(h(je)C>Tir)=~1N8F_!qxW4=W+oTuAQ;A zw#K=^Q?OWC%AIfDzUAbnZBKb&Y;64L)2E^SXWL=hc!ynHUM7*!bd7bFmX-iG?)&BU z`}gn1^>bgpf8EH&$8~1a$%;02-SY*?dd@KrY}ANYRo`eG(0nR&(j3 zXSc^mDDSvqR+;lOQTBlMcxPp``jo4>)Mi6rREgxiSF9cmldx1SGU{Lm9c4_ zFG}#Tb>(Bl*WYz#e!o>d(HtPuGVvWxd(#?WGPB**QT=NM-jeelbgq`x#wq$L_$qq8z0_!!(gZjV7R5V&LbGu$Tz1SL4U;B`Kq~b7MTR z4q{buMd9~svxV;}y*;!GWkDt}cAvgrtfr6ku<>Oc#jMuRM@R_Q|)xJ9My`*mZ-gnK070oXL0v_p)EUOzRmY6*D4A4E|l;6l)m4|cACnk`SY>uR7i)~DgeXv+#kDRJ~@}!Z}(-9 z@YwL7Wke|~&ZW2WT*yaMm+C(BtBRgUi-%Ijx-*KyB)MFZc3J5ug!z!`zTNjO93L1= zWIsQ(aI?&O3|#;G*5&*y+8-T`o={nvn?$B_<|-9;czfm=O~&pNJBNCKoA59F6`NFE zSJSivJ>q}<68;?7C(Jk8I%8OIxXV^DAfjk*{>Hq+*XJ9kotBV=(vj;n3xoCVY2O{3 zA0ueX?FlEcRX5AN>!wn*&Fe9TdiDhkLCaIuCWU z%p6^o+DVTpbI)f8UlJF6h*A9Y*<%Y$!cF(c1=7J^Mi9P&2N4c%Nl?a-I2)=`BtTiN zYlo~6@cO=R8D{Uf9JeD^&3eVKfJl+!z&5677f@Ma@(|Z=I%${uYP_X8|B)F$yDV5p zQ(OOB%G+9#>t9%V&qe|yY)B$XE^Ue94W_ak0U-bQ$|Ti!GwDp(w3;bE^tD-q$r8_} z>ZkT3n#Vs;Jj3iT;4Gwo(uH({yHJO!Dz*GHPRi$)TU3lx?se#H*MQM;t({YKaRyVr zTL`EKP^{*dZcE|_+HHN1#{YS0d*WL`<@j4$ z&2<=RN`T(OHIXNuHN_;jVIQ+%yh|-=!;qCAUAHeGKk^NmiW_&i&cDXw4utOEWOge)JN}KF6WVq<>jty$ z(A=CvaFB{MqBB=zU>&V>Mn-{yG&V;|2qnw&5q1jAfT1W>8lna%wx!3R2?kbHVL&<# zwkZmFdhgRn zW$`_?j1?phB72#qi!_zy`;(d^OUtV8w}E>RKH&Z>FD+9qW)@2(ub-vUOv}u6mwMCo z+ek;Y%i1qJ&>q(tslWcEu-emvf8P5%j&t@>(1iMBGk@Ex(IU~ujI!$o^b^VbNqK1k@5uWW9Xv48&Zt_s&!SxPV=>7(>#E1I)vz=a%r?!sD ze{1Npqxs+Tc|QItT1HSwXv=Q?an3IHhOOB>>ZVl_m^;G?6F;?XUrW6Jxf2aLNoOc3 zMF~cp8Ez7OqIKh0X=eHl0gyK=US75G(vM#zqD!G}x1Z;t=B1KloM)4T5w64s1oIYT z_Dz4!w^8q{Enqis9}HJ00(Mibt%6S&4^3AWsoc9Ep_z2zKYpe0>kXJ zjbQT22}BD;MNWWYWW0;gP^F1{v*`^rnqigxS8Y*Se2f*O7?rpbs7{n@08EzdAMOaf zviqb?{fDO!@hLxC)FF9bal}WAUD*R|iD<5ryqI6=>9=pOFXeW#zuY><(0C^&O+sq1 zvBEV`y6?y;_YYg;DPNSaK63b3I)fooV2Dz_J<|TY?}^Ol1&O`q zw=wfbfZV6^vuTXoZM!QEo^=;JXX}%rFECeeYX|ERQG0J|{NdF&adQv^@V&B}@Hw;a z)O#!SW;X1~*AU{8sF#UC_KK)OmD|N7za#o8{joKcE;S2T4MS_JjdSd zsc(7_>gQkBJpQreTVuAJ!o3dG#C&9Z<{WxCa%^n;)1_}Ma~E$08Vd^nJmMJ(7e9EP z9%X}1uC-|%yBX@H|CT}4X_wPlFu3rD^O~EruVb}uF*V9y>f+Xf)+_sOgIW!a?CQAP zce(1*>TAU#y2H1xg^5w9kfX+?N6>>QJ4kuzM`vs=qhIWVekqE(KA-2*^lC2Z%gC9~ zh1R*IyDeh(p2deQe$>?W>~GmoRr+|&cbmiLn`56FwwW*7xtTNpivITKg5PqRfAgDm zp@6-huoWToADLn)0eE=W>g}5bxjR}3EjGUhxC(=J`?%kFKOsJ5oP6|tJ7|NYANDz~ zgEluE6;$IG_66Y;Iv)r5UfucYDy8Bv^kzlN^M$bckCq?Lhaa!!eA4*FJXy3_iTW`m zes}$ZSIeh%aOkvE_{RP6u+O0>Klgg?+*`36#m z^?`lEgL5MftQ%t9dEA%H4!oioySDBAH66oCqzCQ+lGk-YJc}ZH&r9|?NYaL5o9gf1 zYfuc(2`)Somzy0c;~yu3jq^~A4{?mw{}|WCwFr(Xl1iKf~i1D{|Q22oOPf;k( zA{5abHMTbn+Zp3A97j+|n0}AC3QQ>OMD=zg*vuvnKZ}>lhF8r-*2pH%f#E@p!Od$4 zG}q8ZQp6L-#Cl+4D==7dk3ww&s&8ale!P?k6=K2=_N79GiqNkD(631731D&)LwG_b zdCW3-2$(#(h934ro2;N`8qi}S$;*!Dam!>jDft61W!*EyL<0H^m^@8No^(VPttEw7 zN-n9Ud<{rh)=4(O2+$<3jzz&nNUSUwE8mD!WMg+UVu5n0`fsti_NjXUQ&q{Sdr$&< z0x`S15@q~TWvQvV=fIONtS=Xa?P&j!pW|RZzl_gvT!HX zaYie+Q@VIjPU0t_La zIpLWIf~TCAyPRO4oS?2fAS@8l${|LSk~Ws{F~DPEfc-(w5TFoYBtLJblk4GbZFRyv6#GGj>0MG?1w(zkWtoEk+> zt|y5ip<^7;KMm%{ApQnRXnIw0EQu`bzIVb{>Bdo{~_Co;tFzF;ldbym)3s9z= zAw{{1SkJW=#Ojj5=ZLkA05cAe5r|LfRE+W>2^nU{G^Q2QXROU(uY)p+8VLnnk|cU& zC`Z_H9r~t`D2Jk)^b$A|XaxiChtv6tSXn7nlGCf=3Uofnc^tEt{8cV!T`oty@u8=Rh6Fn@_+$n}{8=K79ATs@v8Ic6b4XFHS*svYFCV|c3ibE~b#XmwA3RCknjZ!u zMX|D0W~1>kt5n|&oNW`CzBfP9D=kD`?6h9G-yDJD%0DJ2lEmPvC?-@F<3hRKX(3+Z zFga4B6-kJdYh_KsT4g`6l8n|Z{)8gyq6!U)3$L(?!mM*VymHE{giTSnGBAZ?NIlqH zG|4IqA>&Vb6<;64rH?A^n8bN>my+hPF9(L_IhWL?mxU(~o*?);8A2pO;oYwMd+hm> z5P~$8NU#LuN)Tbpu(WQJh?*C_SK7Ae~;8TtXF<5QL5b#20YAk$FyCG*FkM*aA*GxNv88 z;J1VdaB#_)Ac7w!ohvU=!4}!QQZXYzL9)uFQ|Xgx#VZ@MR_+Rfm`6(W#vAAts$i-K z^pm{P7w?Ll-?8fnB~A8K{;py)L*YV1ZJ|W%43)nUBwVdlji%EZM(K9N#M;hm zq#BO3MjeQrsUWhKQFu|;x?9#Lu3;gKQgGK9k5(`F25;PM)hHxSnc?IIM}Pz`&}T5< zTV7%lLDbM@`b;2XUX2`OpCuHLC*Q5u2jH*gMkHWR)p5ZzSVSJdn?!_Tzp(3;_C9JK ztF0XcZj1`L!h|Z}#9LOGKWsAZcGc&7ujMZ*eQP6W=hfWO9WNfl=rO8nQ>$P=a#otL z%wU2@FkygNd(R7;+gMA3wL&PQQ3&aQ967GB0q@;187#>~kbvVfuTf~MS8-xj-N;=b zos4$9N9_jV?Z2*Boz73-Cd?)lOh)!VJ) zvLn~qw)wOKE#?~cv?SY{v&`sB;6Cb18t+UtVEScXks2L9s}9>TYKvT1bC8t+L%?+r zcg(pEl+>K-VkO_5-3qyq@~FFPyqor;6PtwfU)ahiDU*%pn6au(b54}hdzc|lGKk0v zf_5^Y{C)JSh0gofKc|LiT0u>07xf|HMq}0F;J@3crw%tqVJ<)P4#kNDe8q7W;)-C|_$i*RFG&6=Su+5%%`{=N)?XZ#W zFh5dYCyaTO0d1KOmf?)G9UTt~6*?x`2D(9vy)jZ&0=+K*_B9laTVbJ)!Whlg`X*6_ zK;F|tfpX;qownE6c?o1m)Qxc{?lb zF{HnE*smJkb%+y#5xlt~ZY#sQr3rv)0c>H=I!(ZFTaoiH!1G;z<`uppjImyfPz(%` z00ZAN1UlKj-JK=)RCJ`Sq|d`}M3Mu}=G`s{J`6{IMjt`BifkJfROn-7MdEHol9z(p z^U>{Gdi(tb#r+ILy^v9K#%Srq(O>254;g@mbbvYs(t-rF7y_>%Ip@Qsj%SGK83Kx6 z!{WaFnJ6rKm)vg zXei>if9Xkbd(a0k+3akfhntjp_1~U+Sl!lwCQLB^ieX0Nat5~ z1*wD!v01`X42~XRRr;amEf%=9zrBBlsPDkn7stTc5Fd|*EZIG98KVKKA^>q8KE6Aa z?!jRl5d+mCfpHv`c_S1B11TCbQC_F{*b8x21bvWL1FEn$L%_XBcu;BGhy(g@NSG9k z)uFCG+`sN)59GS?2?KD(VV&l8-{f!ayS6a(t!3Uae7>C4&iCfcQ3Da@Fi!AL?+R-;@cO@WMhG0u1b`;4k1du3=5`?=_Pivg4oEMyQ=#Val z3!Ic?PJ-gv9rFxUWe>34Szsn_H9SA`{FN0BewOCWem-qO(!O}HaevyLJ{oO;y^=s^&i zbsZbhWUC`iT*|*d{4i2{m2}M?-_d!QK{*URrIN76l8M@~@7$tVhFgcxuSV}vja8YZ z;1aCT3uPJn4qE59)4h)5>F&v2LXkgo6x~w1hXhyiZg99H{=FJd)1n(+<;UuW0`yj+ z>i3FP`yBW&A^Fu1t76xkcWGkdE>7nrfzp)r{Ge&lYhFosvONwz<-Knpl5QcjNgm>+Zhu_2X zD$4}uMjh#r=V0q#Lx#I7#d6g_`Wn?QKdLNwzWva%+`X=4kU!Mi_UPh0hl)q5q@a`f z>jgQ>YTp~4iuglk}mp;&&%TzvDe1QFAz_2;%~XH2&$Gudr;+ar(LPRA-#YoF=Xh<20f zmD1LhOuBrhQr?OXcxWh2w@g6kL6oPVf{T!`YW#^<4JW6QP}bdRDpx^oukA3Xl^XR5 z&Z-*~Ns-#PD3G|vXMC;u!GiRi?K0^XZN?wi;C$~naNVVY_;%N@_^-%1@70s_JSgv{ zG+rvC3{)T4ij$hDxzga{YgZNTaKSwFz#`p5%IfnB18q{uZ0wR1%KRrBJ}ab&scHA>~{jf{OD&4poxS~tOTj@S5W=7F&$J<4?(s&S%GAiYc%m$Y}~I(XX+Ts)}>rA z%)7efV!SNe7@mNLc{uCoX@aN{j5AZojSTRVmdvY6@^N=lfaJB^$U`LWO?s#3;Mt*< zMVEw+D{prgu{hQ*iSb{|h5eKjGu*KsiyV*LK3UXuOzRf*R>;(Lb(E!T4lD7>RGtko z)!aVz6fRfg8r;swdSrJWy3{>i;r5EPbz^3Hv(;3W=;{SnNPGr*LyBDy~Ss$ z6j*zvCb|Fw$UU|*`|+q=kHcx?!I**rTs&`!j6I$44w`$UFYv^j zg0zeUcMtuE_0&7hPG^16xN7)EkHCNR=&v6A)uX?9^jDAm>d{|4`m0BO_2~bu9=#8g zRSK!u8~c(Ncr760N_^}Gi`Z>fVhg8ZH=*c>;+7{NH!S0F&r1eaN(SkK+*ymfHwzA* z1xE(NM#o=x;Aj=d1%~s`3F*nBr1&rOaegCl_Z?9YKoluHHr*0Ma7T%##wS`{LI+so z$x8BsCOg22OHYuHMbUIn^nm#MqWD6c*y4c1Dpmg)$KZ;9#L5v=^&0A`P7UTofG%MF^R@Of7eNQuOq0-0dHj|)NT{p z)T4DQOfGeoZYpdYBd(jOi%C^$Ox0kgs-Xn5P<(20m^~n@CJ&IfNUSdsJJ1lMvYuur zhuZt3{Xin zs<`Wc1UojiazxOVO$g=)yCC>oB#15ufD3@==PGuCO_;+5?4%W1rQ{42?YV@UH*ie^|an>0*y|za}{%8@VP`l z{39SP5+YS7P^ha|ASyky3!C4CJsF4>rHh^lOx$E7R{;mF3?o+of>YWh5IzG=kOu|l z>Spd{D>enD-vIHHi_ecEEWi<_p+J>fIK`il$z}^SHYz^~gqRtU1phO{i!Du+} zgB7`ljhCq;2{FiW?6d-lOn$voA97|nh*-+QB*QEoCf!(v&Voog*pyRV8R!0mNzUtd ziep-wEPh~)+yqQl(x$3zV1-5r$^c6F9Jnw-NYPL-R6-;)LdXvw5=^Ejkp&Xqz~w+v z;yPqgk}%m8>De*5dHD5g*r?)GY+9yV<}q^4xuzUnUE;mphE%DW%QK{!Ibn%zqSPo> z#~#1k8P9`-a6^7S9zL-ELmbM5xxACzNn4ciSs-e*M}96UElOT29F=)Pmv|cm+GCjI z&t(7=4JkKK6g%A_9XV3EE-4mHCOHGl!2Izb@^Y6V1yvX*pRZ??bK5gVnXf3vsyHSg z-;R}2S&XB(;;fx0Bo0+yuXqws6zNsy+oUL0Qn0N_@xEF@&zfXNLJ2m%q_n%_`drC_ zCgT4tNv@P@-GnhlX^R>uxwse=NXpzGrGWBQ5{hr6KP;J*?6J=01D6}a%jxsN!@ms) z`p1wkrFz~4MzC_3Q9u_6zf~UJt5n$HB`}7BI?ok+)k|2DC-jz_@0U;iA&>jzUF16_ zT&+v@H>8Bx^9%Th)qzB&^^`=pW2HJi#hV6=U)xDVcWok$PBpK&TQPb>>c zD7y>BAAwYDY!o_tPdiP?^rKTr^x6xho0z1dR_sQpj?yK6=%RfHvijfz_}WPOX;pG_ zOfd~kRt~DqAl3&ZR^t=$pKye)!zforMQZ12i!tDQ36Z95y4V;%nu2eaYqs~wQvmbd zu;!P%fX^~a+k(x8**99K<*d&aQhFMd8|!`7p>Xy}V!S8uKqTuOl9HU8s9J3Vbn2;2Ba~An`ss z6EL0`%8i!7dHE>r%+*Hzx<_fpZt77t9om()h~2xebvC`42S`iuH5oi`O6Z8~ z5>R1Pl59FZH34? zs0UI6g8{yx17Bf)7!C`C0e0E<2YXfD%^)SIlT*ogbA)JcLYvGk`KiZk-i~vB9n$`Y`70? zQHGYJ1b4*9HKwWqwd^`$Q0>=3hm6J*`84?%t}pg44O!X(^^trQw!?QGx8FU=ysrs3 zvchMoA>7LvyJ5)pc>8$U&*AeDBVWHa8itOW&t(_}9R%q&KCa)2A;@pAQ4^9pexf00@2lXoe_- z0UkpPvjH>ZFcGL26Tt!d{sP^J;QhmXLC^O+4FjqX5~5InDxCL`m)^#Ny;X&X+(Qh~ z?0>rvouwWoa_9u3!>)*&oYNKJY?M5oVL4y^dY&q_bUbPKn<(o9?Tx;{LJI;YSTQYd z$(TT2F#54@6$ZHd7}AUpwMA-ryl(G0A;P}`8ssbzIefWD(1zBg8wK^h=eg1NrM{?8 zVez9M68q8d_M+Rl9Ps?>m&6aH%g3mzJDiM8jFu#q$iu!%z%J6Oa&JWy3_uk&DMN76*}EJ(d@9k$f<2`}5al zIFB4Uh{40*YdYM2p%LRZ8qvXuP5@yRXhmG&KhVf1Dg(g+#qCuTcEf&T zx+NJW$sLOAnY2D{abC$RS;gu3)Nehyb9dU>hNxxnE7vJ`k48H8>W93!s=|Q`z3^Xm z;JeP-N+K>KjL6+iuCO5ruWyKad6Qss`yBK+`?Gbz7Nra)DQf$oQ_ZCc22k$R(uMkq zl!PmUr)PI42Y&Gz&9lU+66J-&_qdSgrO0?mk@fc_UGF;XNx_TEV{KFH&jdB>y6wT< zu8-Ln_kEkG|663Nc8QZTGwtvLH*f}>iWuT94Eeaeaeg<)gp6e!tO>J2d^39m)(sNu zdcJ;pcF?Rl$%*D=N8!3o;(DqVSmDizUj$oE3nKEYD-d7wI+o5{KDV#jQ9G`5R7OeA zUAfd`iCa4wl_%tzaz7*DU%b?`}(L?9uD_3@&lu^?J3V7}}R5}$!$}xY2FpeZD`?{6!DO`kH6;-)c zgKSL6OS@&QW|Gstd!eeAma$l;T=GbrF?Ma+$CicuqR&Nc74UC4Q3GllHBAHRvZKiB z(l&P_Tnl}{GOm2JBT+q?dQ+Hp48e=eTozt<=c0|I@<-P_C=E5{%Bl8)wrG>VRI9vn z38TDSPX~-wjm!qqkq)OJJ9SPdK~AW0B4J@{fBd+sezy5ZsjI3$7ow@wRz>G#r0(JL z(pyf~Pc~KQY1Km$^q1qoFM2a*Xq4)~#8Wr>GLHo=mzUP0x2Kv8-`W%Hxu)Kef9$6q zuHOiBNXDUdYu}WSnY*@ehNcU-=D9lJ&>BMa$X)1u9RgNEQbHQ5V*L`-ayKI22 zd|>t;aJ2YL;bzhSEjtsrwBK-q*o31Z-I?8+aP)Q)j<(C~e6;fVQm0k4n|{J64;Fb^ zx7Vk7;;?V_=_AXkH(wc~N9I)~R3EdEMUIxrS0@|e0#beYBMsCGMRDXMl+`Ss@D(Yidlh=?OcHx~;MAfQ^Ldmr7APh5Kg)~*! zM?|=#pZTfB4m_X(`h4qnV1{)43lbm-jNY}F)omljq-gipkbI;8BwNqpA zR6w;OX0|z|d#+8n?>)M1wW&wh?iUAN23Vgd(H1Fnzove1{Xg|+-CA+{6tVeHzWO<` zm1`<W&vArpxvHShnqTwbSW?A$znx5v_fdub;mAgr`T3*ZZ`*d3vPc zq5EW0k4|U8;hTEY4-*#rT^;>(qrcVB-|FaZb@aD7`dc0St&aXyM}MoM|I5`;h^#~S z@9HSznxiz2br$abQyo3}T^$`5jII3^OG9p!Mw?BNrDWJFI3ghSwoZ8TNSuCDoD4TU z{&r^^FFBG0qC8bm=(V^M)i_lA1#ioEqA99vOT1r0JTEZH8HpmO#*#d(iUTg@HN+JJ zB=oE%2xiC8hZ71t6O9@YY>E=Ii%|60#2vt-8qdUrh9J#*IyEFzkE&BbMFp||&y7@^QupYl z>L62Pk*O-hn4M0EGOHNh6-*^CSTQh7hf7Y|ib|0C9UJki=om_pOU3f6$dZh+mJ_sQ z<4&+~hZ(q&D4f=M>XubZlVKW@6rsn)Uvk22K_%EB2=4#tjXZz%MjpK0$dKSJC*~?A zc=va26vXR|sKl^2BCj`!;)Oo;fXGH-sMBV36a^y%7n9DeW6#flo#5E)_jpoZ8q5lN z6;AxO*a-PMHUbcDu?6pRk$W7l7P|N`Dz0V(&_NeHjKMX_2{xl}{cEXdvT1~_KyqNl zcD7>19Qf9MQKDQFs30)iACTz|5}`Ta^o_B)NQfDiP6}Tq2_X}BP!z-_g*B4UPNY6b za+@ysP-9SM;6nvA*$pI;6G)))peSOqI&xo!PI&QZEQ;N~l!(`4SvkeG%_TW^p#~kL z)*7?76+d*-<3$!9|Z>r1Nc8W zWg9p>lv&Ku3{c#P$er*e^Wmm;vRbV!*tOO7~ZXk&wCWNIG^;PxA9cz2;yj{Nqk?X|+d8z=OU#kSMVtz*w^%WUFymS-GG8?v2$?D&EzS`= z^a^?hp)mX6I4+8GWE9$D6aQ$T;K3S367=X9{E_?|_(ExIpc;|KMWu5DKe@~eg-rjs zdT&TWbyERHo^hm!Z+a~5ePZ3U?+yNHfgirN5cFHe;KTuB?JX~GIjUAup|#SPgzqLs zHj<*dh(RD)G@I1BN_@W|BI^%%Z{0H6%2od2Y|bc9N`v^vie9<=%Ze~h&i2;YujBe0 zb_f~Nl`nK{8Qh-LTkWCHYksJ=$A!7ezV}c_?`p_E(Rgo~E&arQgCfJ>*6&yZwM||qzWs=}>_iHM15sX&@lCIC$Dbb^SNx{I&tN=X zaTzlCZAHe9hJ0?c`!2Qn{kEdxJS(!_w4&O~v8Eft=Ocy{LmJEHhQl=f(~9_cRunO^ z(o;Lez?CLan%Dp%iC30zJP(Q3OLPbB1Z7P0_4wn0cQ@#=PZ$z2uk=E!dK8DwyAEjm ze69D#ithf)il*AVH35L%R#dJDF!--lg!p$W!c59#2|hv(OD^VlFoyrFF+yxw5#N}g z*!Cw`+PGltPRfmTZp1%<(UC=V0(3NI`rg}p4HKJm4Ep)&_c36Y%DynHG1 z9pOWq!RvS4EsBSF=gxEHjzQ;_?FN>Q4t>mQ|MGasE)2B(_{}C1QD=1jKoNC_?K;ha zBEgn+9u!^u4Mj|?g~Pow{|QBg@9)2!4Ei-m;z1FHMPm$a21XJr8N!t%=ULRbzH%?ya zV=pJyr6<2z`S5Q3!`!j{Ps`g!uO1!M3t3(}1}*P@^CvLc`X?{~aP;g~gnkD`kF(la zcZgnE{=(!1M)s?ENsGL|sP}hZ6e#xbJq?y2@d};P8;$E_-1?S2}#Mzi`RBe(vV+0-z+Au zzLxyx9W)?#*b88|3}Lny=qO@D?Tr9v;$zrJx{i}bB?pujAt+NUy4N01YKRqb1!U5H zRoaUfF$C@cB(gm1KvGjNktcKvCX3#|x57^vJd??D9K70~NV{|G&Z|LQb@VScI)9#) zcc%E`yW^y3*S4tjifKEN+U@x=uiNiW;CJ1sm3gv1Kc1rbey!<_7p5ZImRsI%(BmnL zVpWmnDA`8d&8Npq-d*|yoRn<$iguQAs95&8lgqUBso@U=b_p`5Q(lA1ml=_K&0q@> z5))2K*SO@n7aQg6-uW45mLI3S|2|=&vOU1RA>0VMO>8eIC;gC-ftR{Y0RsdPaw|Xa zIQ9#-{7}}3N0=omllfOh0WY&Wn_%W>MF%a9*W-;AM9rSvX!xg{8tH!i*MF|xB<5yJ z+)MzNzntBY=ls4Xblzq@e~bEz>mkanmNphBoRKfP>rTdaiKUkVdoiz}!JNVqi%e^h!0IpgCgGO{k zL|KBag5@N0fkKy(F`|{i*UxCiAHp#Fji(L=eXvr^T~yV>7gRrQx>eRLrn*X-^&?(v zdv`)VyEjqmao&(m$uVseQL8eB%&coLnk{zBx+5l z+pMa=5ow7wo*J<~VeOu_9c4XN#jGk^tP|+&(e}xy+fD6;&Kn%O zxe@cYwxG&e%m#Y2hYZc>+NXj4JYGGZ!zX%&W!z_cP+E8k_9@W64k>iXs-ox)=ZQ1( zjP^5*`((l%x0sn=}HUU5JRBu#=IuQj5aKM%TE2h$tGzQb^7PGd{4^Ruh*}U57GB#^(`%Xl-HQ0T&O;_UT2uqQJd#dZQ6Bxo7rKfZ~aHtr?}#= zSyytR`HXYPUl|R~2;+TWH&%fhE6_R0(?JQ7k?LzIy6s~6D%cpm<|W$5Tb5SHV8fv- z(p%J(KzkE3`Lg*(RCp5~4HN#1;jqdO8CZIdeh_w8d9VLh1E);hopAPT72f!~8aw*6 zH%Cq=1RY#~k#e)S78f|R zrQg&~4+O2)f(LB1r^z0+NAc!S!Ha`IpQDS`Z-32oC+vM*`%&&Lwd=s`-Jg3mLL*NK z2f9me`H)^VpPUYF1r$JC-K5snxBonI3Z($#2<&7?vpA`O(ChZma!|*O=R__E8Dk%_ z=|(n|$HWUTuAOpGeWTyn(jE;&9>~aMCA8_SU$cw**<~*7Z@F_uIJn5x{f zfAC&`B$l~mDzotbW#OCye$}D7t<64h7j{zo@yJevDpNtXA6Mo20?dD5kP6B!ZouIY zbIG<;o|%bgYmu_h^zih1(&d=`gNSCSuSs3=Iwby$E;rS4+B z-KuFNMND!?qx|DWVW;kzn7G01P|pv^o2-1g`VygbnECG6)Z${5d$>f`NxS4W?(>uy#@1oz+7QMjzc zUo`qVRrhzQ?(bCH->JI4Q+0o*>i$mE{hg}&zdluG5iT1Uxrs*K!mf2jw#UVSEyH|N z{r~t7Z>ny0K&+om_}y9XeM`x}k=S4!26@KyM8>rpifbZ6Q&Bi8q#`mqp zAC*OU>Y&0JlnIs+jt%jlysn6adgvIOu9}cn6ki~lK*S}GRAUQO6XJmJXNnT&+Y+nd zlW>fs1BWYHEqErgrF}&xM?Nn#cUF5HtD%1nq36( zb>+>_p(oao*s{qZK=go);K&;Kr6qbMKDpl!J)w$z8<0Gw%A27Rd;&;e&!XQJp7*>`q@YPjOwYv9ZGuoXMr<7;A%~UH#Y&(sTi3;<$pZf|Rj0Fpm0iKAbYVpT zvAJsrHkMdkY@{`Zfukhd?Iq)2X$Me}vRt`MA~N7j)yWASlfxOy;STebvIB7jfvMYy zF$LdJc~f<|U6|Yv!81jfX?Gx(GZR z@^2*Ea01+2Cq%)BKE=e~b%GETx3vqG{S6o2mD)RqIju|hcN`*bF40L4+}MKG;P{?F zjD8?)jFVRN2ZvZ`rE-F$U1^1EfHo>MBfzTfM z&Vod;P+0vo%%%_Vq^6OW!&{`I5<=$)!EEAPc)}#tilk~4TpuXaM9z|Clg@xdQc-ve z&xfcPJRdr{4jq7FbLY}3Kh`*HgZ98_BX7hZf@XWtP3qh1!cU+?ufJ_*GrD~(AY z9A8iW=|$xN3M_utMdn@k3Wx#|Hm$|mYDK=RO|L@Jsp6hB-Ya6Z(g}jp<2ERWH!0&tjmBJM>zwc>6zJsl znm%u_D=XPiLA62@LIjAYI$A_@&*ZF^--0Iqf(oVr8yF>(-Zog@3;4~V2uXGr6-kHx zQxsJ==kFT>{B*(FVHu2N3Gf;M6UHIikE@a@L9y^|5Fh z`~aOiBe6M8#|3ZB(_Quw8%ck-FF13$gg7xraP7|DR3g_*Yzgt;cX1dCO0#J)?^$7(m^0|eodHvG6UYWjf#9&>bZ!sZRj~Lpec(W@voI?x@ zglGq*0yZE*icaTO+7OEE`wiL;y8JsD*|r0r*d4#E$TWn>i;XNE30f}l7Waxanp>}o zrTw&d+H)nHt5DF8kmlsle0{8VMgE!C7N&St`Nf`MJQLH?Q?II4?bn#ohlKPc7AUX? z>fV6ig(og$j};ItmowT5zqCggbjG-J^3X`n_L&&h>zSvom3Kz#cXGPZMp~8EW?f|B zRd97ovA`Ic)=2Dhb0ak?@` zx+i`1QP?0bDj>+-L`1Y?Cm|S!T;<>Tc_{9kEHJYq*kE|CI4-n4)lAT?zI6y zv-#P!*NU;#>u}0JXWA8qqnq2tx?}tu8XaY(T5l{gEu~QWgl-@)s zg2ql0W2Y$61QJ>TffS0NL+DiqMUa{R(veUE6_DO+fN1`Ag6_Sa{hW7pCg)wwn1z*< zK~_HZ_xiCoZt%z@mJ|##N9wxbk{#n!9&=DeIyJ*PTj-L#fFK=8>!o^GGfv7Ew6Ws}h+vkuibjzU*ATN=)F zYD6>~$?H7!ghTB|0{}>Y%&9s}0FY7|{+g;g(B2E#Ec!b(vW(e^jreYHneb8`iQ%?) zBO`9&E%8~gD}J}0*MYF61-o_1p^?` zhaZKuqlC%4w^L5Ug(rm{cV?;!G~aM)z&5rscIp;}4sIEe$nTxHpax*44#G5~7s{Yx z|1_ko|1>1k_p(Xs&Afvw%7g!7Zv@rqUY`>?zP|mP>e#Zw7+w08Asq}K3eFwMI8MGG+KF;{&VKO?@)Hq*!TH~P2L8&6QeX@yT~k!P_u`&07?2+ zX~cJt0Z3b=QG5Q}fq#`ozVCyi24A{x@hw1})o_Q@adM8ne+2(vk~o6pOFp>>A=k|e zo*T=!7@y)hn{}e$YyRxP_Sx^elQsY#2`oEYWGvKGdNkDhbD{3;)S<_}7V6r!0BL;Q z7W8*%G?Y)(1^|iJHJ&uxy#+{g_@c4X%zD-0ENyY=?UgULhCbCIPJ1k^pIEw>KVcX- zu~9wbIy!V@a|v`3TITUoKx#VC&?3cqNc^~KrDq@~T*pAY}l8(EIiRhhjJ{8w+p^Lm_D1^W8Hit6Ke zVf0GJ+piLfklXy<`t!LT&fLLA)Nh)%8(hBJ7~=HoLbY1!Nn0~H zlkylt-J^%Dc?YHRG>G6e+ZG4$O6nsor6a^bOscIq2+~QVMd+hK1G=*Y>MgaqkXN zojTM9m+hAf%q@kz&e|hrxNRj|t|QK9B2r(@D!sJV`a(zS=|h_>F~OERul(Yn&Y>9T zjJlPg2+Q%w&=Dy&>6XC&J2vpH!}rb8W=W;1#?kp<3YsdF`$IIBBK4M#D-UUtS}S!X z$eOvgfr=+cQ*E=g`JZ`sf{!K88n^9aUsjM{=ddH@y_iCV@u^Q((oyv|$@?67V3xCu zp;|a286N2Iz9U6^_TLLjuWic?ZWPRhS(REob}lO2du9Ge0~f{(SR03UN{3#0?D*^j z&AhH#C|C+|#daSyjDu_N$o)v(>B9S8?PE(D7flWzHdLxpTTNs3-Ez@zHA@@1*PCfE z5o3~*KUElH^&){HO`vxn!Cg9BHN9P@gq%v1&m2Zu3fhMY+~l!UYWj>c6FTpH+CnxS z8hKsOP;bDGjV|wC@aWq>XR{n!kyDFuZL~ypu4EF8B5Xn~05$XizPJ zk5f2e?=a_zYkg=PdwIHC;Nf#?x5Uo*w7=_#Lr*|8=TZ96)!*LwT#dAvD?B|l;CThP zFM3j!_b5DrM%njsW~xH(r&vQv3-1}piXEOkI`*ind9TkmpYA<-!J}qRmGeOFS;diK zu{Zo?@}?U*w{fQM?dKpps^^TSuj#{CeC%JoV>>gw=Erg}RjjLCK;M5i`wh~I+FL>I z25Ji}Oy-i0C>S4Z!rV-{Ty}1!h}7NIg<*cZn>`<1S`Q^#^SezCgq@Ev!;iRyt#LfQ zA`m6o2;$*Bn#HRu!J~SXv+y8-E^y%K!R$s}_GN$mwQT1{MtWS@z*HT-RQr>~KvOkS zPv%seqUjj7QW)+*`ewEMhAF&a0sxD5g>-vEw*(SCX(J>8LMy7Ys zJTS?#_*R=6hoWXoZ>R_nI^Ay^%~t5Rx^Fcq7C@u9&~+vn!REKnDDrF~W2(+Rz}yp< zs?&#Z|FNS#cJ#-N{@BqUJNjcsf9&Xw9sRMR|Lb=2$Y0bdkYPv1{jU!O-V6<9$zlxE zHGB+KEb?blEp4X}o2QGw{aF421^XfOgzZAHC>p;g6V4uR1x zLYaa@hs7L62#TvS1PK_b(-ULYv&Wpa#~jnaoTr z@VhX&$SjPUfWn9v>mbH=-X)eXMYkLWOwrv2me7DHIym+YFhvK$!WFO_MC^S9?nhwk zQ!wuSIu@>ud%28(;H`m<$wmynog&GPh;?@Pk2N|XbB*o>A`WP7Y%3-C*JDg7dG*$j zTWfT>M)A5j@jBpmULrD_5>5Mzixx}RL6Zd9CFg%QQUU_}989=FOmF~mWoDx_-!mNv zh6@4XNPr{Jm*WD|v3?3T?`-6Z3$hxArmPv(DI}~Kq2ute@It&39>3im+(YB?AjWpW z6NM=RJw&|0@6-q=vZ`E+c!ney2adYpE!X2!)Srusqrc!|WykUH|JtO3CAv`J1n_JR z6}UrOlI~C>r3+cW$W$-^cgF~O*NgQLKuJUfC8@clf`_0$OthVVP7;1DK~CNY9Gp>) zV`$Pceh;8Y(gdJpQ8otl=`g(B#I~X|Ilm0%I33ZzD;1z5L9%2AiuiCSt=8p?rFi;T zV3Tk>(^s7REF=yFXp%HDK9VLl(p?By;H;X#Ob$lO876r*O^fV2HI(sym(ufbz9)pLM~i;jqk63_NGCh{TEuDN2`>oNK+ zX$E9Tup%aFBZ;ppGgmw&Uom^<|A!`}fir+pRpMfKw-7k21NL5acAR3Wk3IG@gmuCt zdjXky$2jxzEVfNC{Mux0rz?6|Bi#d*#{i|SU!YV$O|h@cK@G*7Ld0_t;@fmt-Mlf3 z5Q&H}A+y}_#u&P$N`NpgXaz^bV%tOu533h8BjP*+2;34}DRjV<{0gUZ2ymTzz?G&U zsVf?#I5IZX)#$WIDb71pPPLTVm=$XSvQRH;s358E#HfbFkyT56-98d1&s=8QK1u^~ zlD+ev4sq8EVT={BNS+vd^_gWvW|^P;(l~Wd~Bx< zVXBMaOT2RoU%KYPH$hh<>V8jL-oQ?(W5uh|+O0C*Qm{{4QaeG21Bd687_!Y81ln1%J zzWTMB1!(RipL&fMs0bh?=did87{P_7lw~BxdnWOY3)T;T)ph`7h^M`%lf3Ve_7qn6 z)FrJ0j-_4@eAiViRsB-o0f#9wII?7(IkItVI{&!IE}_Yxy2(oEl@Pduv0*3Be9f`> zhH0~jWpk2r5n8d{!}x`+ef^zk$%5>9u}yv+a=qxC#4TEZqODOAO+6Cz@VTS{ zwO4|4Xr+N>>YR7>vJcPX{!9r}cB?CB(Wii3n5tG?e0rpM{d+yFv1uzf%1WTxnzjle z@W8e-iG_6}a=ODT>nTGQ%Deh-TKyXyu-kwX%g`!pG zwnq*(ixNu>+zJw*$r3uHcPVV*!`avgEMX4vp%V8vp>cb6Wm01!;7(?MJ9V|MYIRy} zw)4-BB2?>?s<+M}b^iSAPD<^~x56-TZIVLX_4j?MYL$}d0^NS5f(?&fH|I8z8()8J zZ;}~*Z3^w&som4z-dQ}-eD+_-5~vqJ2gdDSUC!$zA1+|$G)jH5Q!A^8v0AAi3fNVj zREfg0mxX<%Ceo~=Jw6uF0NSgK7ZFVn7cG~I)%GC8=Go$Y1tryZv z2lR=~)Tg6>KB@K|R0i`aRW%y70qxQQhiapmv}?13)08(FWTFND5@?`~?3 ziLdCs>@;wGzJE9Gz>3y@BQSMmN!3`UY4|k(Q+KvXoEcAsvJ!{v>l$yGz4}|9P`~u) z=q;{CHLZ_baN1@`2dn!Q+p_g<)q08*q(HDA_gBJ=`llbaJ809+jY!G@gLljQl6f=- z0HEk_7JKLrakMensVS8yQ0EzocW(iKTG%hks%scD zQa-GMDU*mV0RT#RP@abYQ1Wv{0k`6XBHszW#YkK%65$)Q-D!V8xe2S^8TW)kMw>RW z(CG2u7!T^asyVmOGAF%G|J){x74?C&j{TM8TUOPk`*03cX=To)&Zez1Nl%#-Q=;!o z((7r9_P75&lVmxWd#i^_rEcdxD<)7x1yC_rz9DtJ%6~j?a0a}c#vR4`vF{1T-JkPM z?LIzKnQg0Y>|dNuF>iV!)gvA89vAW6S86)&;xtzJ!x83{B-+o$tt&~-6L}+nD@k1+ ztbr>@#2Lnwq@Ja@*Nth5e_u(;{W*JHWzNpL4i4iwx&SF+GL&-1$^(#{W$2m@JQvGe9|bkX4GnPRrMrv>f(p3g2{Xfpimz)x;|+$0qQGX)0z}7#$x>k z1E4-zNiG8b#ZkaGkyI4?R(Er9CkQD*|we#xpEx6!`K@iPI6yaiC8 zO+JN90HCUV1JvP}sonXuvQnKxQT?&>ppOruRtKeizVBRk{C3q+iz{er@lNLhU0{uI zCdqPb>r9eH?FTIt=qA5_Mm|)|lOuS45LQ^_n&-?V-GE;ghYZ3`6Wy)ToM8gLf}?9j zw(qRTuEG<~bM_v*nv~6gh{(}LdFvbY)t`&fN61ma#TGB2Vig+_h3)sn?Xx!2an-bf ziL|nEYG_CN<|!60z5RR^YOk7QCJ`;788Qu%@boYhy=t#7i<84|7{QSs-+hjQOD7vA z1se1=1>TD^vdNlwY{FM3{AKP63R|riUlxoiXkST(15H!sv>S~CyP^w|9 z`J?LvIVvPu(Iig)Y{03h}-TTgv${?(LCJUfedX%Por$IPi*F zkt?N9_&eA3<}3TB548vkH=$c~lnmKnYM-D-x~J@>k6f~;y7o+D%FnD+SWiGYewcmR z!kIC@)KegtRKdlA?;Ururbb|dfPje*=wbXZ?ZWJy6vpOV!ZsHE-HU)g@g>{rR@9zx zxVyN^LPtZ9n@!*_NRBagXNJ{d%-t>D%Vs(Ljxl#P<%m+L+G|6XDoSS> zjCRbSq=k0#tUPymsBouO=y;oZ(|$dlc7xt4HW8=acsz#wCMbD%2^oN(UW^5#0(X+a zKdYu7Fn#isk-yrdXk%w9hb8LlHDjP%y8AvFa)`py#FhN|)V-mPQicL@JS1-bpQaqI zpIkS0?iDI-hrA!v6$$4E`9{2o;^mSM@f@#JRC4Yyrgy^+wja4A+Sq)g>BzATo7^s& z&=X$h-;&v(G zbycf?4{`A}6xH1M#a*8UBvO!PYV1lajy#<9h*D;;KVnr(GeDb}=O49o@~^Hss3FRI zj1Y|INa2u;7HQdD?d^NanV7woko8x#^x%zCl!NP+^VWy+nc0#rBU>u(mHG^1O9u;q zYzcREZR8Ps=~?rU6i} zI_-Hdjx_&X={nx{Leky1inQx$?zFZ`gu4>CM<#dN6l;g26yx$7GY%xu42&JD2|l~q z_W{9?`@I`yf#7KBSkoILYv9aLYP&8oI0A<1w$2=xdu9l6t)$)5{xE~QYku~*Fj-IK zE(`Q2=jZ1v99buMG%Wq4`iSh;r}czpEyNjP{0AM6gYo z?4In4C%mmO8TX@DSeOpQ^53J;2E%9NBL~#jv7TW`?BTkFsJ|PgbyTArnl6o2N1?YG zCSVy&3c(_!9xba7Eejw79M$N22BMAz*hiIus`X?dl%L==x@C;$9W-DgyvvM(NxA|U zeR}{BfXDbT#+8`Uh-gJxm=ZqhAmC7x7@l>+|Dr=ZuEgG%!BpF!t&GBK)bT15$wL4k z(b)f^YEorR-ofJV*$j$;&cqm3^6FOpq9`4gcwJchNriacx_F%#9LX8oRH3D8gm0#y zH1EdZvSZ=noRZ_59;zUZWjs!rkSfO2Nl7px5-2nvv0@MQ!uio>5WwW!Z3WytFfJe) zo79?w#Uy!y6JEK5vfCuIcs&=cOt1$^C|Ya+5H`UX!*>qr&|yj(?+igVJ6``cNEuNu zH7-ViTOd`)BDfK+u#S5L*87ThE+@`zhlrCEASC^Zr9fQH*#vDDYycd$ymcpOnW(!# zM2G=mhusIq2Eaf9WR`Zpw38dej=E%E-R_xS}cWO<5X>`Cd-o@!3B=?CaD z7zHuDy`<;%U?xz(nLuSpaKsaK%w);a;vUXm@6KRvD`2g>)84Jewbx@a6tMy0sckID zR%B9!cUGuk7*a7d+&Gp9$%YYP6%+~TGY}1YOa`!$=bid|BP*Fi6c$gv>wsx5!kk)9 zGuOy@2Ep7z=5V`&N7Hi7uIHeUasR%h1VaHsd6_f@QFY8_0YsGn5EY)3PG%65g=;#8 zKn`0QX^1KrKqySxhEwBcIM-U3e;^+sZL+MYa;Tj08%R;5x*Wk^N%p zs8XA2vHUOyio}%Qt51T{1gRC`#;n>Bm?UESNr&_x2B8p{IPS!t{vem5@vNNO{Qb z#V3dB8oUJG?@!sLRS)&5{f>z1?0Vts0>RI`5YZy56S42aun)mFPnR@~!nzmIxOQEv z#ReDYJJw{{LIKGJ;Ey+L5ToIzBo zEe{{Jc)GWkz*~}t1i9)=1aSFEskKcoUa9&Oy(?LSzQNDGQKMPk^h%VH#@fPfl>aO~ zC-z#c?Oa@j5AKI6iG8GzPL>ozl1f}btht5Z?u8m*<-FBR0#H2j#EKJC1Yv^QOXqFl8ya??&KzHgLaqp_vB8yi2=lvz=KF0keShur%nY>Th4?-D- zYT9dwWQpZIEk!IRG)-cI>t$E}Ws|I5M1_d%Yv)uP72Fs(ta7W@RkD{Z*~K8L@qvfh zZECk(o{7f;M8)sKi6Vl|07PZkcS;#7o6tP~!2p5OC#@{G!sKoxOrbsIlNRuaWs?}H z7Idv6j6$W1JIJU3$)W+#-hoHAn&H~LcXxI@SZD?^snT4w(mXa-Ad`x2zEU_`Jvv;Q z$Z;{@jaowNUpsrpw|4g6eGgHCuiCM}F8y34jZQVV9BL}OGRd0`&he!+mK$SVzy|VF zhx*%l-e|pX9+5l>V3oidl^;Xy%v#FLak%V9Q~8tT3Z_*#GZy#80juIj8i(+W*h-K1 z=kywejkG4l{AE=-fK^G4^1qD(PWhe6t}d~U=M`X&?I_tl2iDqO5*rbJ%(7;m)CXbe z`@5ugd5^c#yqB}Io3Z*`IZrr_biDn{+q}h9#=p7hrs*)N>Li1!y2e;7oBzpGY_I== ztK45t8P$!ydQxTR!`b|wT-5<^m1VI#rO$RwOan-!=sF#zbcQmY4bcUfA24q;!RFt- zfR3%2Ot^nQ z*iwjdrL}X}QqAm19Qkz+OCmQC#(t@bRk$8{TWUmYXQN+O{rBv@?=!X8EdbS2NFGFI zq|NI`bdoAMLV74%%0AMUBT5AkePZTit>i_erTL$8H%ylfEBETX9n!0vN#dJL@dcYM zPB51DU`v;`!YQ3$TH!3Pyk~3P41`lJn9FbL(RAIee6{Eh;`#SvjAyk ziq%Q_t&fjD)3Tf6$r_7tgz1loi{AYozAP@%rP-E$Hm!MlmR$PqRchkY;!yn08Q@Hl z^`%*J;7k*I`Fiy-aHgsB_nD?*sb&!i=9wnq%GQ}CqoozVt(tu20k@LB-PHiZQ>uL` zmloQ#;wj5dp*t5hNT1dypEOl2WhuWg+?8N(a!Jx+M)&zFFsrL4HI}OlyuV+pPD0t} z6I*m;v_)5{lN#e+E;8v#=s>f`F5dFn-^EWtc8q_$I==$Yl?;=vUPpdYjEcLt-0QnV zSIfP}nRIo+z54x$-*iPU{AE{gGG}-F58ZL_=26b~FM+QRKcDyfaI@s5!&o>4VzgdP zD-W#A^QOe{3E1Xu%C1?A>jZLfY}2n$P1X^tr7Zoaonj2re{w3t2!=J_w|14G*RIHb^6FCR@}eX2rc8Un`WNw&_8=F z*VgIT@GQxSv1J#&(&&OPy8K3LiH{;l+$KQ@t-A>H+ zi{zh;&$3E0iEzJ5GPLRIUYWcwS|yTeSM}GKrmX3Dkwc4iQ+%59uB-=Y)`}0DI6A=n zO6XRac&eX}5=v3^mYieis|y3m6G1zUeb#3KD}=Y7>YmuYP}=7lbLX_Un@z_te<~%E zc=tYlR&RsuIvWN(yc3lCE;x`I_rbk^J5&AfMrbP0(BYlj?gGQz>@3CIi+jgjL2qkm zz>pbQ39s)x<4z~IS_tcL``59 ztymxpm2L%PGsgDL)Ds}U*xsN(E~iwYDloQ3>@(=KEV*;;{DnN_%E0@W{uZf6JgY=G zA?gFH6RPj%o)6M`=IZk46WnBx{4^Dl-}jkxlHFkrAgz^F235Uow>CRA`k2b>q{7ta zY;5;)tu#jE0-e;2yc5~-pX)3i6HV&%hhvvmwx<`57Xk}=&w9>j9_f+&gl2gTv;@%xE z(41szk7)^Tu2XFc;8qGXSGrYo#R4kt=^=LoEm3UeY5C3Ioc6KJW$cb2^tI-O`<+QkeqkDE4pheLx+xw+X1D0(<5XLoPP%fb@Vq*c?Co_{TZj zW7AKfMP_;LZZu#vC-)r&fU3>J_}D#8cfNfJ%Ofg<{=5ea=JxQY?)>4g%yOpd9UByP z{)ptGB!N&?&fT!nT-u&=P12-!(D`>e?Y&_;6b74wGd!CQ3zte8o>M-H7r)FKsH66! z$RgwrEB@3}NQ_c{l&=gdx==nuh*M=;DA2P}8aJ)q;i3{svjY_z@{2I?MEL6o8}9Lt z_TzsHb3n-&Z)*n*CMDf*Jyv?z?$yd^wPvfxdkI&eG|v`NjWf6U$mLy^drzrw9zuQ7 zeE;6^QLFUD-4bEKX}3KDA`bN*;E74Qwnw@0v{nz?DHIsKTW}UJi&Tv9tiHUl>1j*@ z6{BA|MdC&3Z>tCV{7>TZGX`Y1yO*#5(KmaJB-ENCF&6K#bu~HtF;GT}+%dta14^8Hdwd%Q5&1CzJYrdyV^YJ4?vT ztY_ElLJA*-+>^iLJ$UZ`F4)!1=poL}bMQ(~MM&^q$Y5-!A6+dd02hL_3Ed}(2vtKM z?GPUFh&X+Oe+2^TY1H-(_>B&EUmlr?L!5L*WQHQs)`D`UL$U@X3$lD}dxkX}4J*w; zHXi@&QOjWs(_wYfVXvISDU2z-@OH!Sj1 zk8_=0{Pz*2LkK|uIv({uPV5<~V|LS`$d=IwYp613Zxw*2@GI}XVL)C0 znS~XeiH)m_Znr`Kmh&G%W%)~}0J(DYifyy{wY!(~dv~u=jJ=W)Q$_>5q{T#n@p~2U z^6F2p_ypenN~V$#;52)}9qB|MnaTyDHT6*HbYOdr*-SCE_x?(z6p$-kMvK|l$n~fu zmjtWqXe#YFq!JG}6_FO3#2DcFd`=$`mdz(O#Vu2&4Wr#`hK<=k9`1hcT$?%NAAXBpVq6w_g6HDiMIH&}nCk zlRE@bwy3JXB_+=}fpy(S$vZ=LJjD$3ORLgcQUR^XoZ*Ddra};Gx`^a$WJyPAI-{dP zCF>&6fR0KM=%{p(!|CE#C&V()Mj6*hKF+TA-Co)6P#LcgaWHTk4#qkp#uZ-2l1Yip zp%HN6(SN5@_;d@`Bt}ZLP6AS@(_~2zLVD>2Dbgq!t(YBykG^f2ZG$J_Ng1lfgkv+1 z<1R5-2rvX0mnF`J8;T2>jjx`~(xzmNZ15X`F}AMhqvXserLb}r$+ z%}SL$zl+NUMR-Jr9R{ISXXCurNe|Sq#j~VSWb6a4^otH4vSLx$P+ogcmgq3)HaL5o zmak}#y*w1XO3Ltn7ck-~KjPL}U*Axwy;mNMzCi?RQ%fxQ-B9|kxjwNJ3DvAq`1CVm z7Om_O&9)fnw)}jx;xgOfy?jCnW%Kz+4=fyLC~8`H&n7GPcUW z$UUs+0RrgOiiC_=KT}A4Gq`%`jN*#0{P9pWqq6;8#qoq#GO-K@t-8D+;XaTqz^_mQ z7%Y!FEH8pcQrnL?jn7C?!|1q_zH>DaMk0wm<+IcmK+7q@Qn9eFBG9djG@H`pMZ|Sg zMC23zN1rskNwGd92wI`O;&Vo8rGN}aG8D_gCqP6`IcOrmvuKMhpeC& z+m(arsUl{(@ZAR>OQ7V?7Gzl#@)8O^iWNPjCEcT??rCwpLij^(}nYPKVw{G#&NJ2-2JBr-i z7ORhX*B0{voIgfr)GTJvQpE&|lRNyWal$FG;%&FHS_d3+N2@zGM>>DHw~jfYg66i@ z_>_r)Uhu}3yFWJC4tW6-S58Ciu@l%VZq|;%q?d&)<&Om;*S1%Vw*cp%=vqB;d95sF zuM3B{kL1Q~orgN`qsQ$o=R>+QxDC|&JiJnmQj*p#DMigO$cfkE?L48JKU*!!nEJEq z+~eJeNaz%K*D15y{Y0{7&y#*TUdlf0I;AI^k3e9AC2yD|F9HO<%AEQ;9uaqAfBgeW z;m+@ICdk_DWES|&b`F78hLDt}%RIiyFCvDRQ-9LfdX0|L{Vg&-`jmhK&aB@S*3aN9 zRDWDY=e~uuqOc}26!ZkB_c{m+@f!dRM%^$Qh+b}(e2ftV4o1Pc!-0uW-Z zCa%d^pp=hp$-AqEJL>x8kWR>m&WN3@KeYO#YX)VUXoEm>MeNW}9o4971){4kCC;Im z(UH+ndwN$3zN6pXo)bX?K|o-t&adjq`4$)HR#T2cbDa-v!K*pUC`TWHU9K$LK8N+# z%(?HyBc5m5)G=roKEjy&D;#NB7>Q&;R)pzj-$F}&V(S|wWFY~_5_k_l*6xe%_x>EU z6?l8~aZ`@h+mE;YhOEns*}t)axxK*bA2427fiEeqDb<_ZmBwe-jL1?tYTgmV3<9sJ*m&{xL zom}}%m0X++*($D7N8x-Q#V)pP6<0;f;;J)gzH4zV5c>Y##T9CLyr%zZZclN}-*{Dz zTLkbbD-Uuxw+-JBom7X4fOQI-p0%}{tqPqLURkQGm3-p9j0|5s$4iSI9f6&gEAySh z!ls;=tyQ0S>sD*ko5))Sv{nKigMrqnZl2Lv6|agdwG3>vR`fzJQit`e)FQIBKRmJt zh^?F#Ad^hGg7MBgBF>O%-hX?YVm7jruhNPD{7#w;Qn2jn{w2iyi=!0nBHz0J=nBAA zR|@8w0k-m6329#e*sAw8TeX~M-MhtBg{xa^r99Shz=FLc9r(b29-}_7D8Ds>b6(^9 zJR=nzYRS8E9CGIQ&=-|4lcnco79AF2>s=bm?{=+$Qcto%4qJ{;sXksUensGu8<=nbL0K5|sK;;Z^;3|7Q7d zb{lQDezYpib%MCkQgfUkrL#J=p@sDQ z1)K)Eg9NzuVQdN^W@qP1faSku#rnYQsfO#@DpL=iJ8`v0uMzjXCB|Q$=M@Mm4G927 z|6oUpLV?jg?wvwz#C~ZzCpNG>{@x+7Ld?1CX^I6Et2E`Z`PU|@ZpCq#Kyh{0^pl^4 z5qt8W(p<+^RKV?L8Z%YBxxYsLDiq@Lw}UKoSZ7eDBK6)F*jSzZc1VW*Ms%x8kC9MP zRu9iPQy{jIAI2c9ETByxuWhvY4SI1tFp9sD--5riufKDwl#7=CISXGN6>iLqD~I9& z_5w?P*RcIwv>SPSha7NO9A~wXTxALrS6+WVHgfFt2K(!>_wl#+Oa`9OnW5Famod|M zJ9Jokd$5;Ih3OrMsTSTk5|ut4SYYYtfe@`NJ*XDft%^2N7m6*>B%!Z~MHpPgRrYDs zD~EDk3{&Rr510-bXma`52#S_HBeT8rUYLQjQPil{{iHVuU=y4*{oaFtFl&NYM%%{3 zOu6;*p=eUu*2>?tInSzXbr(Trp8_L)YZ1ZDcs}=fZvUpD>#`Ms8Jd@?4sN=O8NDuS z;4_k2Ke4$CK2UAYV6ELjR7kQkJbtuKAoSC-q_s>5F$r-tnGM>bv6vmz`fS^_?VN#% zrr)6H`yV;zFeh0-e&fGiyZ9<-AtfMx_@MNo^F}O75B=%9?#tkdj`pAxNzXujwE(_d zFu&@QA~M@E9br{&mc#Cz4YQO5Z%s_==!xFIPQ!PHGgO-nCncwdR2pA?K^EcnvICyM zxuc;i!T$-qs-r|!F*AuLo-?lSvw*%A3>1(RyAx9Nz-hMxPwRPL=MQ`$RyE8vYisAP zHRl1`NN?AVwKFH1I}qkJrfN_xL;bzhXr#@1>7CiCA7T2)7$vaCM^6gZY1N144_@5& z@>8aJ(6*TKKwZW8rA~=?mnmI(7p*bK3*agDv`a9t+%xZ2(Uxh~4rpJYSaJ3$5;o(w z342Yi?LCV}jBv`tyR_ao@d?!vp0QiPztt^6?-PpFICZ#^*6!|oHvZlClv*35{gz=2 z9??Ff-Z|6$!2EF1MB2yQ%Bymhreb6j58n92Q$#kAtQW9M)1bXJcy*Zbsht~^p{H$9 zgwmZ2fJ`d%Jt$RTQvS44z2%eOBYP@aIaCe3d!m#2(&U_6jID;O<486c2cIYy^`}`T zOnvs3nRKyuveDZ!W0Q4i&#a}tw@upKbJ+&hUC$;WDSn_o3x9v;>JMH0p{qZ1^@pzh z(A6Kh`a@U$_vxx(hg)M=kVB|n)}CM&yI|DAV5!02(r@nV$#=IK2H$fI&bI{gDnMTz zE>a(&jeX|n>5zX~F!ox=;kA%}9if`^l+fd?p{}8!QG+2T81QIJWoEn!7e=dZ_@hTyAEZD`lc4K92z;oBrPa7G`}L8BGBC>Xm+UJIA7trb9XN`h`(f*B%#S31#LjF9RTa~U6#lSvqvL3aoySO*yDrM@v$^qfjklW`FVsGfgh1b^9sv& zlgLL!yDB7=uE!Mvq}7!Kq*w0i(3N%U4xDGll)h&OdIh21?&T6iB*A518l2m zhEtMG#!9)O)udw;G*a4DGHx?dEJelG3qXq1odTp-do)P64a|LGf)^oFs4~XaHSW+l z>D_W>fdcj}FcsL9#5g=v81JzjZw$^k1jZr6@Xy5X0rog|#f)#j(Ww<&vjZ*wmELwa z#hsknEKWFzAZ07Y<&Ylc&63`Ma*9dVQ^@!;GY~^BOf>>5E}s5UJbxL2ix*Gqbs(9m zXMf({w-<|bC1iY2%+7E~eFKhDTerl1%exNFEfP;N6M)M4K^^_T*DP80!dUmzai1E8 z0995EEz7NvcS$kdpN4Bj;)3zHO|I#Y2%M{UCWk=Y{1K z)T*qzvu(t}H?>rW3LYMEXIK0bF$#fl|h}U|0M@7wi;3P~0ru81cBc?M_OoMjq#K!=btM zx$5+ooJJ4R^O;a;vQmpTq-`ysa|58OTQf zorD7U)FY4%fn%M*Mbxj3fsM1b-r$?zWW?eS!O>SFPtw8;jzw0eO$#xFy z*z@u2K)rQ=RK*VMZtvcC&1nFrw?uBGDu1PXN$4h3cf)zxWCZ#WwEE5@zVUXV8fiDk zZW8`=+bV_K{gIKRP&68!w2b0^pU>QIWOh{q!8b^R*7TSe_lS6CJ;dRG1dS-oT;lpbT>*~}{9n95&h-FPkXj3nH z<5Yfb4t%|;?9D>Lv97wa!&8dLsc1+dpj~@ZI?EHO*v|vo>pN$kyqPx}&e%G0^lIB55LvHJE+oNW)lh}=FCs_H&0J(5T z{m0B8GzkE&7Bc~w=e(HV`u63K0k78-x&U>n*;4cs^j&=K$M+%dHRyREX#Y4*EgW2{ z!+oW2npQiX^~=1*VouCxcu=>@3(zq}vjtmP@?DycqU!!vvz5y?F4Hk_uDuXW{F}YH zMOOg!qU1sR!&{y#01}UmfOW54f7|A}`gnT2vv$dw+TB)9J#e6}gzhxFpbfnI!l*A8 zQ9I4ZwF;Ngw{k7jF&b(Xd>PWulkKC92sj-o>S#)&P20Ze+NB0np2V ziCz$X0bO=JIlHcL-5_@08dHTT^BR+}7MlV(ha2U(Q{&nSD(5;v4o?`A1FkXgiUpe( z!p&lzS`2VcB%kJu@>d(sLa6r&r9Zt2hxDyqqlV$#AU{fA#69(Jbw^(GEEOoL?8`hR zhx+Gi3-j|G1vSsl+uPFn@~>+?bRT{}A0eOfNzZqx^e~v{xo|!=w9@0RoU3mu=c@KE zNV5}c#e>>eajJp?_BcL@EzGgTX6W;b3Hsx&oU7=%1S997rxaN{SUQo_b1pSR(B$7E zgfb=s{h9dmMW%SkFvW|VAznVe!3$X@G|2$36HM@0j+`~N?Jr~%hU>5v9NMp%JD_+* z*=_)`kYBZYS3s}->+GCPot#8&X~R#2`RC13b}YqR@;vooChT384Ypr^-a2Ay8FR3# z%kCa;IW2s!@tA==CE~dBqY{;hng2<=GBg#^VoC~=xBZF>m{+ZDDG1&quMX;vJ|XiL*1N{j_Z;D({#SfL*9vfqf#$yLT%p;10VQ zsi61eIUGvU)VEGg+y8{k{KZo(LGE;EsAXHWG$Yq?*(J^mdqh6 zWEuJ7<*=SEl}S_~zwPw>MhgSC2E3V4 zx2|7hF~!|~$JGQhaNNrI=oP!`>0ge?uEO>rll!?xE#p97r7tCj=0R6@;5 zBc8ZfKrJ^O%3UeauCWWAbs%XrotJv+#eaQnJ=7xhXJ3UvqGqYZy!nDxjq*FqUweY= zFVzmSsQC(dmrcWTCY%Qzwl!}L*RhwpkWU_&?0B&7n0(#vJ@jEAFqx;T!zOO|Ag96l zRMHnrqN(oQBSm2Ippc_U_Z#+^hy|Wi5vA+mJ>PS|3(1BDx$@;(Ofmxl?k z-neEJ_Px!KUagjgH%sH%gVr-Ly^Or6bycIU+wleYOViIXlV|tj1Za+;HPaN!U{rM9 zCB4ALU+1i(d1kbi3=T-}tl2V{%X+s}l*g}gRxqXDTqC`G+UrIZSNI|?1{tcEdKuef zSjUpm2IQS8bADPri!6bL?c4`khq6Y(21=5z4Ss9Wx^W6u3GLubDN@wFp^I<3cH^+Y z4E6XT1lP-c!z9Hk-?rysZ`j?m>&GXLDqIOZ8+Ak}jJsXI{64VomuWnC%yP#3ELkWi zGa*yUjK*#A{%}%e-^~;6XcmuVKRy2rJ#!N5+V#-mYSMyf`Uyc~*P}z2q;1O11{Pj= zw{INTf8*dy#=;+pvG6A{qvBR!4wO}i>aeY{s(Gudy5)Tq_a2m~IjLaoRBs)@S*FBb zu2Ap36si6!eJ59I^pH)4;yIGl6Gf{%>DIY7&PC}>y4u!k^cT6D%e}g1zkR*;`O=JY zdDpMIT^KcP|HE8=3azBjKg{)qx&AQMALjbQTz{DB4|DzhfVtjm%lZb0_h%w(?D_5OVC2Q|pY+J!kNCOl*z6T2%D$-{{u4WiK!kZ?_i z8(*Ra9|WNQ!C!^oNeKi}B1Qs=3r>u(gQ7?7ha7_zk3fKh!7>sgUcx?6GpR;1u`n^1 zoS0NXN^0hVK1D;@6_c7hq2-xUCzg@;*3q(2!>Z zo%)%S_7k1_buQ)Ij&}hEi~EAXZ#EuYAgBL%$Vw7^cr%@12iG|a*Ghvc?-W|f5Y7`2 z_7hqe>Pi{vfOoOf(tXv^dlg|vDM>WNAbG9r0YTZnWn4y(y(|$pP;pt?Bd$wwUB@6C zFbIuE#Engab0b1iE4`^bBSq8yfEKLP?%|bDq`xFW29jy2h6?(Nyn=Vg>k&X+`lujD z;Q&dlxE=C>0^~(Rr!cq43r^W4uk=QAvOODO6CEvwNh-qNHqqEdG$90oq@Z1yAa}2H zz0C~C#s~hAxJwk7Kqe^qU*si%-zG0MbUhd?4aP}pA)Fe8t~dTkUN+#Y3;J2s{8>W8 zEE^2AWHjTv7r25I5>#bRP#Be=c;SNncK@ms!rv~54`dJ)M!C8|;E5pumqoBSjhGrB zdR}9XtuNT$Tl{^Eut*T%hGj zQQ~{cp9e^-mn31P$j#7!6kn8gzX%l$kQYmYw_Pb8Fs}|K&k=3(F>1PI8saoHhZa>B zyhC24BJ@G2pu`pnb}Dpk3C3#p>{vR61l!*_Zf1N*jx_ zjkj=tjLh=YOfxVZt(M~uS~?wBlBiWY+E`ZRfWS3Xu-K4xT7~zh71;S=y{)V}{QgnJ zFn62sS%=KI;*ucA|1){Lv@g2fDD)Lt0kOyQfG{bNW!+o(NPf)y;zDj#TA8|W*$=8r zF}9jsjIPkmKDuckB3*e&t8!i&kTJLGk% zF;`ly_=bI=;Qo|KSyt%5T_uabuLf9NtWtw*ycwM$N1RKESz3 zEV?!XSL!%l?cThvr=Yw2ndnWf2iU?%C{|B7`(7AC(WDLD`TP*2xqgiA zJMbpE1wMj_S_yfzB8i@EZr{Sz@#y6GEWBbda-4LY^N+Ux=LH`(_&T&azxgBm1pW^N zY;&|1sJ^KAmhMvoi=POS#Nf0r-VSV5j|H+QGO1dHPl!*kT7}(mmZwvsZp2MhaX?K1S|f^=gMIw2D8t6Ti?2I2v= zh@D2iyGYkH%DwN~Nne14vA2%kr5YF0#iUziyus3#+Ejr8)rC?M6UxbE88uMZxHp?o zY4zaoUbnGE8(62?16q+UwdCgB`X9Y)vVFjSAbFx&cC&l>F?go5O-VV6HD58XvsZY- zzWCL+OfNV|XDoME&}GYHZ}mj4oN1qeXSwZv=k6{H}xA&AV*F2eZ12KFl+lv)O9WvycBqLpj&3La>%!vlc)Xl`LWvurnFzjX@^?}$j3Ph&ryw* z1|nrgxP=CzOi=&wuH+r>a#BS@jC;~Xt5R;T-BEs~*2IqW>)~d+a4X*SE|Onk>8^M6 zp&8KLD>lO>S;JNIVQ}jJfL+m(<7PsGdM!=$7(uk5Ef&hL$$zUPYI=6MVJPHRlt8G_51|Jf)^0@5z$X$wNdcqBqtCab@(c4D&5ae?1Y zr*6V8OC#;s4e5{S)LATa8@rSVy(AeL%5C_7C2d7@>XYM4K7d`UYBsOhx!7IoQlM?9 zvSsdIm);3B^53yb|DV{^H(y5JGWBOP!$<&jf&PhIdjafnuDu^Jy0su_zPbOK$#A$H zmF4^=cI7X=%WLVaKF)DkcfH3NMe;6rMNXk2n0m{9lGoHu=JhjTg$w8)Kq4vTI9V;xdz8ew0sE)oJ19 z;)&jbH+oAPtTvgRp5@ZV!-Xbm;K||Gw@ZcixtUwbsJ^+>oz#ot33WI1+Q<@G22!s` zX2tedt(BeBYaMJ(Jw<7^nx#eKu`G++IgVK3y9m_1)6T@jTTDD81rVL8(CL^;IdHQM)`6koswOcc3ufTd(Qo4eB?E`ELg&7wJxG zdP-{vtgV4t8~x@>{U@i!->xOEP~Wl4mI3oJ*u&iIyeiI9x5-PNsm<6V?2woB6YBNf zgudQo-PrEDw#o%X{ybZCA=W}e2_DCwBIJNVE6?uryUR)C&%FjDw$D}_x9k%|qU_ow_Q2H2n03 zxW3=xe%Y(F-@n};Qyw4tz-DOCz6L0?+{cHOZrJXbp|*FN!14PV*2i`ZeklWVKz2^Tt++kCq<$OuR5`DId)Fzip%zSa?&^+riCS(KUWd26 z>+xUSW%NR93Gl89=K=2umWa_L-+Uc+yg}4D-~vx~jtaT@voh~VyU(FzL>ZuQGa0;|LC5zdsaPc@F#cy1A>oLHEezRKh+HfsgUYpG&W5$8HwumG*=#O zdFtqhS`wvBcg-CVR}Kw`gXtg}J5rW>pHmvLVy$wO{0{Go6eb$#`jM5_cIvLU-_HRD z4mZhC0Sz2`d{@o&IaL>)sZc`%EAAcr!Qw@N0>u{9gdYXEsR?t6v7`4aG#!4?IZG@% zP4Vydjq8yKSBw+k>NS7n)+s!|6ptfli!#Vf?79ZvnSo}-YFyKP;N6MI!? zaM1#t+t=cbo@~8j*6>6ZGFKoc*j`eyV_iPy;M>+^*PqK%F7zC*F2?l+y}jZ=w?c;x z4_G|jXPWu^%5OgxoH`zk#$7Yiv0gm1S7Q9j@8;ml#TWYw9ba3Ij0Jdz83&2Tk$uf> zFkZ<#$^)heJ@YrN%ZqPy?@klmdV;XeaYp4SVGN2PJ$B02!PDZu)x^~4s3nBy_a+|K zUjdpfruV+HF`CaD%$I4blYFEYJJ()2_~|NrjrV1b7DM^N6<>$5EHj@A{{AAlL+CuM z?^5i(*)wEwy)vQ8`#zFmZ%g1Qa#$Pp$0XO(yAy$R>Bi;jr+!nnOgtq2V)XewluNv) zM^76aY?sckE1(#_x=*BgSXKbXvzipP?ytU0s)$;9BXF@rPW#jTr{mw$BfTcl6F+?2 zxSc+~K>nc+781(;!20Bz(!&R#w#>H!%fot)x*n8&EtBT-H#!In&RzvGHo| zQ%+12Jl6|%a?(NV;@->3^)E~5uFDg{Sfzxcw6qtJi_W}Y!*`MVCe8~tKQ<D8AYF%HXiIW9VI$m)B2 zBP(ui&>1GXX`a3q06CJqBf^g~50nQ$K0W#5?IJ7bRwvP96#9GVbpGi<^ziXO_CF+E z$n#isU5Z!8iuGM9T~()}JB?WzG>-YGuU%Bt6j>^rRaJnDk35@*PJA0^SZJJzr zembR1)Z_O!N`}`!xe9VrP+rz=(=^PBik^8tBlPhwM-Y7bkX2#H^+q9^=VFM}%sa{p zU~_AYi-;}!9p%kN^N?R+gdc*Q=Q}=`#~l8WHBS1dV@olQQ$7P+?mc#SPv~pwViCOb z<)oW^o4d?)#0v}_GnT2d%2#o}BYYDW5d2@}0tN&NH2Z);tJuo6Av5<o~!Hsp)%mAJW7V5Rq;(qRXz=+W@>~7R~K~S zG^HaZDAk3%NvC2!vPLx|OQcqYvLp4($KFDV+-O~++Ty{ID>&~-YHi6tg|p6WS>w9W z(Q?oJVuVCp*?5u13^nWfy|T&XVD6`r-x1|gZIPE|WliiMerX~awNlO|1H^m|F_&|BT1dCXy)6|H=vsm^nx;kRCbUvvG3iFV!< zt|%v3?mXo+As33A{QJ3*Ht z=9=}(FC!CEf810N$OG+B`<(Vq=mNn9Fg@j6)u^*z$0$#iS6oN&0zt43lZUhE9}ww*T+iw?WgjdmaDh#hKtEe%xP>i9k}IxpC)!%kx%mykH7z;{-)`b<+E+-zwSP~ z^J7|+SNP?r{<3|4#OTg5Q2)k=Yw6rw46%l?vi)BkDJ0PXmxh? zJUN(6Z1b2i*aLf=s<)Om<-y$J-_(DBbDczcG;dzfcFmUP5log2$5Yoy>88j`fDK9?L-q&66LZzqL4puNbW~2Ye9@M)2#Md~j0NMmRP2 zxk`jTtSw{aH>YP^<3~PaqOw2mYHrfkLe^4#G=J>n2t5dm_+p}R&Ot3T6itd)&$|0l z%*m;9nyR;6^T#mrZd>!j!olX2gqD?%{ofhQZX5ICt)K2feher**&OiI(@5GAjxc<( z)mss9u_9G$$fr$V;t`0o-j=$yI{f1M`vd%DG82!#6i!Lcwn4u4|HkTqv3|~1oHv~{ zkNCO5`nAFPgywKI@D%Rg5+7iZ$yBv#^V+e-wAkGtRmp&VD5BN^mTL zH@-c=>(|iz)WSHIF3~%Q@zBC}cSCW%t~jt~^w29Am4wg$&oGZUP;hV{raqz2UHnml zOo(P^w1j;u`VJx}(Timm|5z~r)*zp<5uc`+7(S91X%`G3*(S{a`-G5(!w|Ri?S?By z;!&Qbbpaq=Bq$1?uV85W2Gp@0sx%0#K||{$k_egrZY9|zBnjsy0q3*)gYWw_!002e zo())^MDiQWOvYPw;HB?RUjh-gW43RCu0yH0^CGQrWb~rfk7+r;lP+&Fcz(a%?!a3H!%n%vYZWN zSd=+2@*o(4Go#2Hqo813!Z+L5Z$=6PMhe^%;q8L(hdT>HDQHs$I;>IV7!%>$DC7eE z6TO@%Sr^)~oFG|3EIER+60SxG1@^)jYdvThC7c|U31fm_e?zYq5dKaGKG6A(2!7sP zr3{_ha4lfKkcce-V+#TIQo|I4VDh}Mgb?VyP_XDcrfM^Nu88oYE0g|#aCi*y_x&7sc$@~~a3@28JuLP65TYG7N3bn?t2nYrl!hr*7 zFJdlGdj-rFXpIS80cx)R0K*8zQVEx)^A56_2qO4tl9Or&=qa7&BMjvevFME4cYvU4NH%0`rN zSBgrS=SdHlC6AQKo@*DG2IO~Q5amRKeIp*dngf$8n-(ceA1$7?FRuq7^Bu@6GSZb< z817BZgO-3p2`0s&FezBiNXaS)@maeR_J46NI)8C^qtNNmN;nfU!op%ujb+2d1%F^L z5e_77weoWdWjr*Q8ts}PZA{}>wiu+w(Q{+=Dh9EKpFZ(KO>x@-|zlH21T#i@s2D(Cy7FH>!Z*>o%zDg-Tr+ z1i-iKd?vOm1X^p9l&gddEg_w$ZyGm-DDXGDrNLvqSNc2ibd45;lksghP>}^FkWrCs zR4wG3>xs$Q{{wNiDVH2ttPoJ|!sKT#3u3)XO|tV_BYN>h>uqDQH$SP&sQ#Wn z<@v2WUby-pTKTEPHg%I%?s)iFmTwMhQ}8(rUBJM1=rnphZXpyA5k;6h3g&?Wo5N#) z0*VZpS@=Z1Etgr?O~yomu&Mxkt^NsJ(Cw>ZBH*%@*gP&f!Lx(I0!X#yVL6c~2&~qkkZWrR|4vB}mn3meDCL`{2!HX;L#GSQB)k>E_Lz&?EFF zUs@H6CVcL-xZ~^nOMR;_xU6yS`thu9kAaWN;!h$!*q66=>f3iQ5N$ zQioWxG|_YYnpyqY-89K?c+5f%Y~%HFC_6r>8nHk@)#T!RFuM9A9!GEvc)R=pY`sjz z{*2JTYjn>LQUKtq#+Lru=Z4xR`j09BLz=H5cH=L`kpExt7yNj)TsZQr$w8gZ1--au zDu9IT9f<}cjIG~Dx!+b~NZhf%^>M$g9LKGnRAwK`7lDcL>Vg)Ax;G`)T%d9>EIE9C27 zhU}DGT=%qRC}p)7yBABT7%lTL!ialUUr@mkVddx$ z0o%#Gq$OInwN*BqO*- z9Vu%@4W=UR4iOOlVXz#4!N~9K2)-FtLmt3qjf3mf{OUax_#e{h)|Rp~b1jQ*G{lC|DC}iKm4Ql z5?Opk;Bt97&0$<|XLWin1C-6izgL7kjlo&&{0`#G>om?)-aYLw33F&%5-1yi{oGSU zmx(0HoNkw?)P0{C4*aG#uB0#x&1nB-Ftz#h6U)uJ4A#dec{+d1jJ8{Tt?e+_-nLcI zJ*!pt5C34Wv^#YDMJfNG2ZUGe{te`~Zb9W@n>?Cbh!h{Gy98;is z?ek55gfUjPB~0|26N@VGHwlwF|M{i=XPFaEAEu3!uTF2rUm|0)2;RYzw{CG2WjgOD=`6EKlJ8-`0G|1?KTj9^)dd8zu>D? z^sl?|SKw~^rS{F%;u{cu$*g<=;;$!D{bRtXE{n0aw$IbM64t))F?>yiCA=xGy!n~? zOQhN|%i_yy&nBVmi%G@uT*`9h&tl#Pz%geibqYl1+qFE~ucXc0>9e z2iM%lV1TWLR#_}l(NHA5#8XoJ*1N92fjDChm0No|jwhAqUa>BZ1+uB*T%0lVJp2Ax zQ@mDS6R0!e&h`~7LD^8AYlm(3$_;7<&CMOMrHO)1U#^tc(G4jc^D}B$N zpvxkC2wDQmZ+4=Q_%RB16}mq>{|j)`iw{bAoL`lgZacek)QbgN_0n+KzUn2HF28ft zEBhj#VXA*fe9uv_mCZNabBp|*lBr_fWUU~!`e^lReUmqrHEB@&ce%Em^8%oa?DQYFTIwL`a({4`ShzZcd$3B|c` zGj2!;$ zW#!i=w}R4WHRtNKXt^VaO0(S1qvF9@&n<{j#IuPZ!}^D(R@780trF!Q!d^yQtZlHq z=4xOwXs6>*d&tgS)${5=D1N=u(M$dww{yDqSI}Q)x`;&IlpB#$m6O?kg%QI;9ONC| z_3T=hZ~cSNg41oa%7BFhvhLng9w<{6|83dFw$1-p81W6}xZ)~+VVgb(gBVShTjq*2 zYJ7Dub{%i*hD5ClG!7y+R@#(!5Y{lAakQ)bef;ZJ3`4@8+f9`%H`{p!LI)hRlYFH( zb`fW|MnV)MLcdCSW>=q~AWR%WDSUx-0hhfBDuTBjk~)x~ikip^>gjM4$nV!3l)h{IHGD(m z9=`U5>vy-$Y5uyO@6lbwj^G>>Z;KJC?dPssZTjjLFD%ICaK`O&+Coh`AJl?t(U(1I zRJF{jLE37d$CZWn-}vjI7*-^N(w||@;FhQ}aXId;W$e&?r@k&|M)k2|hm*fVMBonY`W$aujPl-#o?pitgL*@8nNDS;8%16F43Y&lUdSCh#b=fCMzln5#;~d~GTx0% zIztmFr4_P2}iL+6ragCQLAWb9@_7I@Dx$UM$lb!%ZkWj156QmTS6ROcK&W% zWbEfDjNh^*U9l;)%J;S@3$+1yu42tTpyw*KvDHKry0+4HPITdI$~ + + + + + + + Synergy + + + + diff --git a/doc/license.html b/doc/license.html new file mode 100644 index 00000000..5e748123 --- /dev/null +++ b/doc/license.html @@ -0,0 +1,313 @@ + + + + + + + + Synergy License and Copyright + + +

    +

    Synergy License and Copyright

    +

    +Synergy is copyright (C) 2002 Chris Schoeneman.
    +Synergy is distributed under the GNU GENERAL PUBLIC LICENSE. +

    +

    GNU GENERAL PUBLIC LICENSE

    +Version 2, June 1991 +

    +Copyright (C) 1989, 1991 Free Software Foundation, Inc.
    +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. +

    +

    Preamble

    +

    + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. +

    + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. +

    + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. +

    + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. +

    + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. +

    + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. +

    + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. +

    + The precise terms and conditions for copying, distribution and +modification follow. +

    +

    GNU GENERAL PUBLIC LICENSE
    +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

    +

    + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". +

    +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. +

    + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. +

    +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. +

    + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: +

    + +
         +

    + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. +

    + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. +

    + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) +

    +

    +

    +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. +

    +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. +

    +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. +

    + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: +

    + +
         +

    + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, +

    + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, +

    + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) +

    +

    +

    +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. +

    +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. +

    + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. +

    + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. +

    + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. +

    + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. +

    +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. +

    +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. +

    +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. +

    + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. +

    + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. +

    +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. +

    + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. +

    +NO WARRANTY +

    + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE +IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE +COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR +IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. +

    + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED +TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY +WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED +ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, +SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF +THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT +LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. +

    +END OF TERMS AND CONDITIONS +

    + + + diff --git a/doc/news.html b/doc/news.html new file mode 100644 index 00000000..b3729ec6 --- /dev/null +++ b/doc/news.html @@ -0,0 +1,599 @@ + + + + + + + + Synergy News + + +

    +Apr-02-2006 - Synergy 1.3.1 released +

    +Made following changes: +

      +
    • Hot key screen switching now restores last cursor position +
    • Fixed loss of hot keys when reloading configuration +
    • Fixed autorepeating on win32 (no longer sending repeating key releases) +
    • Fixed autorepeating on X11 (non-repeating keys were repeating) +
    • Fixed AltGr issues on X11 +
    • Fixed modifier mapping bug on OS X client (caused wrong characters) +
    • Fixed one way for modifiers to get stuck active on all platforms +
    • Fixed bugs in win32 GUI +
    • Removed alloca() from unix code (should fix FreeBSD build) +
    • Added more debugging output for network problems +
    • Fixed failure to detect some errors on X11 +
    +

    +Mar-22-2006 - Synergy 1.3.0 released +

    +Made following additions: +

      +
    • Console window on win32 can now be closed (reopened from tray menu) +
    • Can now change logging level on the fly from win32 tray menu +
    • Added client keep alive (lost connections are now detected reliably) +
    • Added support for linking portions of screen edges +
    • Added version number to UI in win32 +
    • Added GUI for hot key configuration on win32 +
    • Hot keys can now perform actions on press and/or release +
    • Added key down, key up, mouse down, and mouse up hot key actions +
    • Key actions can be directed to particular screens +
    • Hot keys can each perform multiple actions +
    +

    +Made following fixes: +

      +
    • Fixed AltGr key mappings (again) +
    • Fixed assertion when pasting on X11 +
    • Fixed modifier keys in VMware on X11 +
    • OS X server now treats sends option/alt as AltGr or super depending on key +
    • Improved handling of AltGr on win32 +
    • Fixed not removing client when connection is lost +
    • Clients now detect loss of connection to server and reconnect +
    • Fixed mouse jumping on OS X multimonitor systems +
    • Closing console on win32 no longer quits synergy +
    • Fixed Num Lock breaking certain keys +
    • Fixed Scroll Lock not locking cursor to screen +
    • Fixed mapping of delete key on X11 +
    • Fixed loss of clipboard after a particular copy/paste sequence +
    • Fixed compatibility with windows 95/98/Me (ToUnicodeEx) +
    • Fixed bad argument to function on OS X +
    • Fixed error parsing comments in configuration +
    • Fixed autorepeat on win32 servers +
    • Fixed X11 keyboard focus bug when reentering screen +
    • Fixed (suppressed) hot key autorepeating +
    • Fixed mousebutton action when Caps/Num/Scroll Lock were on +
    • Added documentation on firewalls +
    • Fixed documentation formatting on IE6 +
    +

    +Hot keys support has one known major bug: key actions cannot be directed +to the server (primary) screen. The configuration file syntax has changed +from earlier versions; users will have to modify the configurations by +hand. +

    +Dec-18-2005 - Synergy 1.2.7 released +

    +Made following changes: +

      +
    • Added preliminary support for configurable hot keys (Lorenz Schori) +
    • Major rewrite of keyboard handling code +
    • Fixed non-US keyboard handling (AltGr and ISO_Level3_Shift) +
    • Now supporting all installed keyboard layouts simultaneously +
    • Fixed bug in handling remapped caps-lock on X11 +
    • Fixed control and alt keys getting stuck on on X11 +
    • Fixed desktop focus problems requiring extra clicks on win32 +
    • Fixed alt key event getting passed to server when on client on win32 +
    • Synergy would prevent alt+numpad character entry; this is fixed +
    • Fixed suppression of xscreensaver 2.21 on X11 +
    • Fixed middle mouse button dragging on OSX server (Brian Kendall) +
    • Fixed caps/num/scroll lock toggles getting out of sync +
    • Enhanced support for converting clipboard text to the Latin-1 encoding +
    • Added autostart documentation for KDE users +
    • Added more details about using Terminal for OSX users +
    • Fixed crash when using --help on certain platforms +
    +

    +The hot key support is known to have bugs. The configuration file +syntax for hot keys is likely to change and the documentation for it +is minimal. The graphical UI on windows does not provide any support +for editing hot keys. +

    +Nov-12-2005 - Synergy 1.2.6 released +

    +Made following changes: +

      +
    • Fixed permission problem saving autostart configuration in win32 launcher +
    • Disabled buggy fix for loss of clipboard change detection +
    • Restored pthread signal autoconf code +
    +

    +Oct-17-2005 - Synergy 1.2.5 released +

    +Made following changes: +

      +
    • Win32 launcher now saves configuration automatically +
    • Fixed failure to save autostart configuration on win32 +
    • Fixed output bottom-right configuration flag +
    • Now properly releasing keys when leaving a client screen +
    • Fixed stuck-Alt on win32 +
    • Fixed 64-bit problem with clipboard on X11 +
    • Fixed BadAtom bug on X11 +
    • Fixed loss of detection of clipboard changes on win32 +
    • Added support for the MightyMouse +
    • Added support for buttons 4 and 5 on OSX +
    • Now shutting down win32 services when uninstalling +
    +

    +Aug-07-2005 - Synergy 1.2.4 released +

    +Made following changes: +

      +
    • Fixed gcc 4.0 warnings +
    • Fixed autoconf/automake problems +
    • Fixed scroll-lock on X windows +
    • Added option to suppress foreground window grabbing on win32 +
    • Fixed --daemon option on win32 client +
    • Fixed --no-restart on client +
    • Updated OS X autostart documentation +
    +

    +Jul-27-2005 - Synergy 1.2.3 released +

    +Made following changes: +

      +
    • Added OS X screensaver synchronization support (Lorenz Schori) +
    • Added OS X sleep support (Lorenz Schori) +
    • Added OS X fast user switching support (Lorenz Schori) +
    • Fixed international keyboard support on OS X (Lorenz Schori) +
    • Now capturing global hotkeys (e.g. cmd+tab, etc) on OS X (Lorenz Schori) +
    • Added support for SO_REUSEADDR (Don Eisele) +
    • Added "dead" corners feature +
    • Fixed "resource temporarily unavailable" warning when quiting on OS X +
    • Win32 now defaults to WARNING log level to avoid console window +
    • Now disabling foreground window on win32 when leaving server (Brent Priddy) +
    +

    +Jan-26-2005 - Synergy 1.2.2 released +

    +Made following changes: +

      +
    • Fixed major OS X modifier key handling bug +
    • Fixed handling of ISO_Level3_Shift on X11 +
    +

    +Jan-04-2005 - Synergy 1.2.1 released +

    +Made following changes: +

      +
    • Fixed major OS X keyboard handling bug +
    • Fixed some minor documentation bugs +
    +

    +Dec-30-2004 - Synergy 1.2.0 released +

    +Made following changes: +

      +
    • Improved support for moving laptops between networks (Brent Priddy) +
    • Added ISO_Level3_Shift support on X windows +
    • Now doing PageUp/PageDown if no mouse wheel on X windows (Tom Chadwick) +
    • Fixed handling of number pad number keys on Windows 95/98/Me +
    • Fixed handling of non-existant 4th and 5th mouse buttons on Windows +
    • Added support for Unicode keyboard layouts on OS X +
    • Fixed memory leak on OS X +
    • Added OS X autostart documentation (Tor Slettnes) +
    +

    +Nov-12-2004 - Synergy 1.1.10 released +

    +Made following changes: +

      +
    • Fixed race in condition variable wrapper; caused synergy to hang randomly +
    • Fixed modifier key and caps-lock handling on OSX +
    • System info log message now filtered like all other messages +
    +

    +Nov-07-2004 - Synergy 1.1.9 released +

    +Made following changes: +

      +
    • Fixed compiler error on gcc 3.4 and later +
    • Worked around minor gcc -O3 compiler bug +
    • Now logging system info at startup +
    • Config file errors now logged as errors rather than debug warnings +
    • Added half-duplex scroll lock option +
    • Fixed tracking of half-duplex toggle key state +
    • Now accepting screen names ending in dot (.) for OS X convenience +
    • OS X key mapping now loaded from system resources rather than hard coded +
    • Fixed multimonitor OS X pimary screen bug; multimon OS X should now work +
    • Added experimental workaround for laggy mouse when running linux -> OS X +
    • Fixed bug in win32 installer packaging +
    • Fixed unrequested continuous mouse wheel scrolling on win32 +
    • Added win32 GUI to set server address to listen on +
    • Fixed resource leak on win32 +
    • Fixed screensaver detection on windows 2000 and XP +
    • Fixed flickering mouse on multimon windows NT/2000/XP +
    • Fixed quiting when powerdvd stops playing (may fix other situations, too) +
    • Added tray icon menu item to force clients to reconnect +
    • Fixed handling of number pad keys with num-lock off on win32 +
    • Fixed shift key not working when a console windows has focus on win32 server +
    • Improved configure of Xinerama and DPMS +
    • Improved portability (removed recursive mutexes and _*_SOURCE defines) +
    • Now handling DPMS headers without prototypes +
    • Fixed dead key and AltGr+shift handling on X11 +
    • Fixed use of freed memory on unix +
    • Fixed AltGr mapping to Ctrl and not Ctrl+Alt on X11 without Alt_R mapped +
    • Added -display option for X11 +
    • Added support for X11 compose key (Multi_key) +
    +

    +Aug-05-2004 - Synergy 1.1.8 released +

    +Made following changes: +

      +
    • Removed key event capture on X11 (was breaking terminal keyboard input) +
    • Worked around win32 command prompt stealing shift key events +
    • Fixed handling of pause key on win32 +
    • Fixed handling of backslash on win32 internation keyboard mapping +
    • Fixed handling of ctrl and alt keys on NT/2k/XP +
    • Fixed XCode project (removed cross-compile) +
    • Worked around select() bug in OS X +
    • Worked around bug in ifstream on OS X +
    • Fixed handling of modifier keys on OS X synergy server +
    • Fixed handling of space key on OS X synergy server +
    • Fixed handling of key autorepeat on OS X server +
    • Fixed mouse wheel drift on OS X client +
    • Reorganized documentation and converted to HTML +
    +

    +Jun-13-2004 - Synergy 1.1.7 released +

    +Made following changes: +

      +
    • Added OS X precompiled header file forgotten in last build +
    • Fixed bug in fix for 'unexpected async reply' on X11 +
    • Removed dependency on "browser" service on win32 +
    • Fixed assertion failure when connection fails immediately +
    • Fixed failure to connect on AIX +
    • Fixed error in conversion from multibyte to wide characters +
    • Maybe fixed win32 screen saver detection +
    +

    +May-26-2004 - Synergy 1.1.6 released +

    +Made following changes: +

      +
    • Added preliminary Mac OS X support (client and server) +
    • Fixed ctrl+alt+del emulation on win32 +
    • Fixed ctrl+alt+del on win32 server acting on both client and server +
    • Fixed handling of screen resolution changes on win32 +
    • Fixed 'unexpected async reply' on X11 +
    • Added dependency to win32 service to avoid startup race condition +
    • Fixed reference count bug +
    • Keyboard input focus now restored on X11 (fixes loss of input in some games) +
    +

    +The OS X port does not yet support: +

      +
    • HTML and bitmap clipboard data +
    • Screen saver synchronization +
    • Non-US English keyboards +
    +

    +May-05-2004 - Synergy 1.1.5 released +

    +Made following changes: +

      +
    • No longer switching screens when a mouse button is down +
    • Worked around win32 mouse hook bug, fixing switch on double tap +
    • Added support for HTML and bitmap (image/bmp) clipboard data +
    • Physical mouse no longer necessary on win32 secondary screens to see cursor +
    • Added experimental relative mouse moves on secondary screen option +
    • Fixed win32 lock up when closing server with clients still connected +
    • Fixed bug in handling duplicate connections +
    • Fixed pthread mutex initialization +
    • Made synergy dependent on NetBT on win32 (for service startup order) +
    • Automake fixes; now mostly works on darwin and MinGW +
    • Fixed builds on Solaris 9, FreeBSD, and OpenBSD +
    • Partial support for MSYS/MinGW builds (NOT COMPLETE) +
    • Partial merge of OS X port (NOT COMPLETE) +
    +

    +Mar-31-2004 - Synergy 1.1.4 released +

    +Made following changes: +

      +
    • Fixed lookup of hosts by name of win32 +
    • Reverted tray icon code to 1.0.15 version; seems to fix the bugs +
    • Fixed crash when caps, num, or scroll lock not in key map on X11 +
    • Fixed double tap and wait to switch features +
    +

    +Mar-28-2004 - Synergy 1.1.3 released +

    +Made following changes: +

      +
    • Major code refactoring; reduced use of threads, added event queue +
    • Removed unused HTTP support code +
    • No longer interfering with mouse when scroll lock is toggled on +
    • Fixed minor mispositioning of mouse on win32 +
    • Unix portability fixes +
    • Added support for power management +
    • Improved keyboard handling and bug fixes +
    • Fixed dead key handling +
    +

    +Note: the tray icon on windows is known to not work correctly when +running the synergy server on Windows 95/95/Me. +

    +Aug-24-2003 - Synergy 1.0.14 released +

    +Made following changes: +

      +
    • Fixed bugs in setting win32 process/thread priority +
    • Fixed resource leak in opening win32 system log +
    • Fixed win32 launcher not getting non-default advanced options +
    • Synergy log copied to clipboard now transferred to other screens +
    • Hack to work around lesstif clipboard removed (fixes pasting on X) +
    +

    +Jul-20-2003 - Synergy 1.0.12 released +

    +Made following changes: +

    +This release finally completes support for non-ASCII characters, +fully supporting most (all?) European keyboard layouts including +dead key composition. This release includes changes from several +experimental versions (1.0.9, 1.0.11, 1.1.0, 1.1.1, 1.1.2, and +1.1.3). +

    +Made following changes: +

      +
    • Added non-ASCII support to win32 and X11 +
    • Added dead key support to win32 and X11 +
    • Fixed AltGr handling +
    • Added ctrl+alt+del simulation using ctrl+alt+pause +
    • Fixed loss of key event when user releases ctrl+alt+del +
    • Fixed incorrect synthesis of pointer-keys event on X11 +
    • Fixed Xinerama support +
    • Made some clipboard fixes on win32 and X11 +
    • Add tray icon menu item to copy log to clipboard +
    • Fixed mouse warping on unconnected client +
    • Stopped unconnected client from filling up event logs +
    +

    +May-10-2003 - Synergy 1.0.8 released +

    +Made following changes: +

    +

      +
    • Fixed hook forwarding (fixing interaction with objectbar) +
    • Fixed "Windows" key handling and added support Win+E, Win+F, etc +
    • Added win 95/98/me support for Alt+Tab, Alt+Esc, Ctrl+Esc +
    • Fixed scroll lock locking to server screen +
    • Fixed screen flashing on X11 and Windows +
    • Fixed compile problem on 64 bit systems +
    • Fixed Xinerama support +
    • Now allowing screen names that include underscores +
    • Improved non-ASCII key handling on Windows +
    • Fixed lagginess +
    • Fixed failure to capture all mouse input on Windows +
    • Fixed auto-repeat bugs on X11 +
    • Added option to disable screen saver synchronization +
    • Added support for 4th and 5th mouse buttons on Windows +
    • Added support for "Internet" and "Multimedia" keys +
    • Fixed jumping from client to itself (mouse wrapping) +
    +

    +Apr-26-2003 - Added roadmap +

    +There's now a roadmap for Synergy +describing the plans for further development. +

    +Apr-26-2003 - Added Paypal donation page +

    +There's now a donate button for those +who'd like to make a monetary contribution to the further +development of Synergy. +

    +Apr-26-2003 - Development update +

    +Synergy 1.0.8 will include fixes for the following problems. +These are already fixed and some are in development version 1.0.7. +

    +

      +
    • Mouse events at edge of screen are stolen +
    • Windows key doesn't work on clients +
    • Alt+[Shift+]Tab, Alt+[Shift+]Esc, Ctrl+Esc don't work on Win 95/98/Me +
    • Scroll lock doesn't lock to Windows server screen +
    • Screen flashes every 5 seconds on some X11 systems +
    • Synergy doesn't work properly with Xinerama +
    • Screen names with underscores are not allowed +
    +

    +Synergy 1.0.8 will probably include fixes for these problems: +

    +

      +
    • AltGr/Mode_switch doesn't work +
    • Non-ASCII keys aren't supported +
    • Synergy performs badly on a busy Windows system +
    • Unexpected key repeats on X11 clients +
    +

    +Synergy 1.0.8 should be available in the first half of May. +

    +Mar-27-2003 - Synergy 1.0.6 released +

    +Made following changes: +

    +

      +
    • Added tray icon on win32 +
    • Fixed multi-monitor support on win32 +
    • Fixed win32 screen saver detection on NT/2k/XP +
    • Added per-screen options to remap modifier keys +
    • Added global options for restricting screen jumping +
    • Added global option for detecting unresponsive clients +
    • Added more logging for why screen jump won't happen +
    • Fixed problem sending the CLIPBOARD to motif/lesstif apps +
    • Win32 launcher now remembers non-config-file state +
    +

    +In addition, the version number scheme has been changed. Given a +version number X.Y.Z, release versions will always have Y and Z +even while development versions will have Y and Z odd. +

    +Mar-27-2003 - Synergy featured in Linux Journal. +

    +The April 2003 issue of Linux Journal includes an article on Synergy. +Written by Chris Schoeneman, it describes configuring synergy between +two linux systems. +

    +Mar-27-2003 - Contributions to Synergy. +

    +Many thanks to Girard Thibaut for providing a version of the win32 +launch dialog translated into French. I hope to integrate these +changes into future releases. +

    +Thanks also to "wrhodes" who provided source files for +building an InstallShield installer for Synergy. They'll be +integrated into an upcoming release. +

    +Feb-18-2003 - Synergy 1.0.3 released +

    +Made following changes: +

    +

      +
    • Added support for X11 keymaps with only uppercase letters +
    • Fixed memory leaks +
    • Added documentation on using synergy with SSH +
    • Fixed unnecessary left-handed mouse button swapping +
    • Fixed debug build error on win32 +
    • Reduced frequency of large cursor jumps when leaving win32 server +
    • Changed cursor motion on win32 multimon to relative moves only +
    +

    +Jan-25-2003 - Synergy 1.0.2 released +

    +Made following changes: +

    +

      +
    • Fixed out-of-bounds array lookup in the BSD and Windows network code +
    • Added ability to set screen options from Windows launch dialog +
    +

    +Jan-22-2003 - Synergy 1.0.1 released +

    +Made following changes: +

    +

      +
    • Fixed running as a service on Windows NT family +
    +

    +Jan-20-2003 - Synergy 1.0.0 released +

    +Made following changes: +

    +

      +
    • Refactored to centralize platform dependent code +
    • Added support for mouse wheel on Windows NT (SP3 and up) +
    • Portability improvements +
    • Added more documentation +
    • Fixes for working with xscreensaver +
    • Fixes for circular screen links +
    +

    +This release has been tested on Linux and Windows. It builds and +is believed to run on Solaris and FreeBSD. It is believed to +build and run on Irix and AIX. It builds but does not work on +MacOS X. +

    +Dec-25-2002 - Synergy 0.9.14 released +

    +Made following changes: +

    +

      +
    • Fixed solaris compile problems (untested) +
    • Fixed irix compile problems (untested) +
    • Fixed windows client not reconnecting when server dies bug +
    • Fixed loss of ctrl+alt from windows server to non-windows clients +
    • Fixed handling of password protected windows client screen saver +
    • Now handling any number of pointer buttons on X11 +
    • Toggle key states now restored when leaving clients +
    • Added support for per-screen config options +
    • Added config options for half-duplex toggle keys on X11 +
    • Enabled class diagrams in doxygen documentation +
    +

    +Nov-05-2002 - Synergy 0.9.13 released +

    +Made following changes: +

    +

      +
    • Fixed solaris compile problems (untested) +
    • Fixed MacOS X compile problems (semi-functional) +
    • Fixed gcc-3.2 compile problems +
    • Fixed some thread startup and shutdown bugs +
    • Server now quits if bind() fails with an error other than in use +
    • Fixed bug in moving mouse on Win98 without multiple monitors +
    • Fixed bug in handling TCP socket errors on read and write +
    • Fixed spurious screen saver activation on X11 +
    • Unix platforms can now read Win32 configuration files +
    • Minor error reporting fixes +
    +

    +Sep-14-2002 - Synergy 0.9.12 released +

    +Made following changes: +

    +

      +
    • Win32 was not reporting log messages properly when run from synergy.exe +
    • Network error messages weren't reporting useful information +
    • Synergy won't build on gcc 3.2; added workaround for known problem +
    • X11 wasn't handling some keys/key combinations correctly +
    • Added option to change logging level when testing from synergy.exe +
    +

    +Sep-04-2002 - Synergy 0.9.11 released +

    +Fixed following bugs: +

    +

      +
    • Worked around missing SendInput() on windows 95/NT 4 prior to SP3 +
    • Fixed keyboard mapping on X11 synergy client +
    +

    +Sep-02-2002 - Synergy 0.9.10 released +

    +Fixed following bugs: +

    +

      +
    • The Pause/Break and keypad Enter buttons were not working correctly on windows +
    • Configuration options were being lost on windows after a reboot +
    • Added support for AltGr/ModeSwitch keys +
    • Added support for auto-start on windows when not administrator +
    • Improved autoconf +
    • Added workaround for lack of sstream header on g++ 2.95. +
    +

    +Aug-18-2002 - Synergy 0.9.9 released +

    +Fixed three bugs: +

    +

      +
    • The PrintScrn button was not working correctly on windows +
    • The Win32 server could hang when a client disconnected +
    • Using the mouse wheel could hang the X server +
    +

    +Aug-11-2002 - Synergy 0.9.8 released +

    +Supports any number of clients under Linux or Windows 95 or NT4 +or later. Includes mouse and keyboard sharing, clipboard +synchronization and screen saver synchronization. Supports ASCII +keystrokes, 5 button mouse with wheel, and Unicode text clipboard +format. +

    + + + diff --git a/doc/roadmap.html b/doc/roadmap.html new file mode 100644 index 00000000..7f8681bc --- /dev/null +++ b/doc/roadmap.html @@ -0,0 +1,92 @@ + + + + + + + + Synergy Roadmap + + +

    +

    Synergy Roadmap

    +

    +This page describes the planned development of Synergy. There are +no dates or deadlines. Instead, you'll find the features to come +and the rough order they'll arrive. +

    +

    Short term

    +

    +Synergy should work seamlessly. When it works correctly, it works +transparently so you don't even think about it. When it breaks, +you're forced out of the illusion of a unified desktop. The first +priority is fixing those bugs that break the illusion. +

    +Some of these bugs are pretty minor and some people would rather +have new features first. But I'd rather fix the current +foundation before building on it. That's not to say features +won't get added until after bug fixes; sometimes it's just too +tempting to code up a feature. +

    +The highest priority feature is currently splitting synergy into +front-ends and a back-end. The back-end does the real work. The +front-ends are console, GUI, or background applications that +communicate with the back-end, either controlling it or receiving +notifications from it. +

    +On win32, there'd be a front-end for the tray icon and a dialog to +start, stop, and control the back-end. OS X and X11 would have +similar front-ends. Splitting out the front-end has the added +benefit on X11 of keeping the back-end totally independent of +choice of GUI toolkit (KDE, Gnome, etc.) +

    +One can also imagine a front-end that does nothing but put monitors +into power-saving mode when the cursor is not on them. If you have +one monitor auto-senses two inputs, this would automatically switch +the display when you move the cursor to one screen or another. +

    +

    Medium term

    +

    +Some features fit well into Synergy's current design and may simply +enhance it's current capabilities. +

    +

      +
    • Configurable hot key to pop up a screen switch menu +
    • Configure screen saver synchronization on or off +
    • Graphical interface configuration and control on all platforms +
    • Graphical status feedback on all platforms +
    • More supported clipboard formats (particularly rich text) +
    +

    +A popup menu would be new for Synergy, which currently doesn't have +to do any drawing. That opens up many possibilities. Ideally, +front-ends request hot keys from the back-end and then tell the back +end what to do when they're invoked. This keeps the back-end +independent of the user interface. +

    +

    Long term

    +

    +Two features stand out as long term goals: +

    +

      +
    • Support N computers on +M monitors +
    • Drag and drop across computers +
    +

    +The first feature means sharing a monitor or monitors the way the +keyboard and mouse are shared. With this, Synergy would be a full +KVM solution. Not only would it support a few computers sharing +one screen (still using the mouse to roll from one screen to +another), but it should also support dozens of computers to provide +a solution for server farm administrators. In this capacity, it +may need to support text (as opposed to bitmap graphics) screens. +

    +The second feature would enhance the unified desktop illusion. It +would make it possible to drag a file and possibly other objects +to another screen. The object would be copied (or moved). I expect +this to be a very tricky feature. +

    + + + diff --git a/doc/running.html b/doc/running.html new file mode 100644 index 00000000..4ad2d594 --- /dev/null +++ b/doc/running.html @@ -0,0 +1,394 @@ + + + + + + + + Synergy User Guide + + +

    +

    Running Synergy

    +

    +Synergy lets you use one keyboard and mouse across multiple computers. +To do so it requires that all the computers are connected to each other +via TCP/IP networking. Most systems come with this installed. +

    +

    Step 1 - Choose a server

    +

    +The first step is to pick which keyboard and mouse you want to share. +The computer with that keyboard and mouse is called the "primary +screen" and it runs the synergy server. All of the other computers +are "secondary screens" and run the synergy client. +

    +

    Step 2 - Install the software

    +

    +Second, you install the software. Choose the appropriate package +and install it. For example, on Windows you would run +SynergyInstaller. You must install the +software on all the computers that will share the mouse and keyboard +(clients and server). On OS X you'll just have a folder with some +documentation and two programs. You can put this folder anywhere. +

    +

    Step 3 - Configure and start the server

    +

    +Next you configure the server. You'll tell synergy the name of +the primary and secondary screens, which screens are next to which, +and choose desired options. On Windows there's a dialog box for +setting the configuration. On other systems you'll create a simple +text file. +

    + +Note that when you tell synergy that screen A +is to the left of screen B this does not +imply that B is to the right of +A. You must explicitly indicate both +relations. If you don't do both then when you're running synergy you'll +find you're unable to leave one of the screens. +

    +Windows
    +On Windows run synergy by double clicking on the +synergy file. This brings up a dialog. +Configure the server: +

      +
    • Click the Share this computer's keyboard and mouse (server) radio button +
    • Click the Screens & Links Configure... button +
    • Click the + button to add the server to the + Screens list +
        +
      • Enter the name of server (the computer's name is the recommended name) +
      • Optionally enter other names the server is known by +
      • Click OK +
      +
    • Use the + button to add your other computers +
        +
      • Using a computer's name as its screen name is recommended +
      • Choose desired screen options on the Add Screen dialog +
      +
    • Use the controls under Links to link screens together +
        +
      • Click (once) on the server's name in the Screens list +
      • Choose the screen to the left of the server; use --- + if there is no screen to the left of the server +
      • Choose the screens to the right, above and below the server +
      • Repeat the above steps for all the other screens +
      +
    • Click OK to close the Screens & Links dialog +
    • Use Options... to set desired options +
    • If the server's screen name is not the server's computer name: +
        +
      • Click Advanced... +
      • Enter the server's screen name next to + Screen Name +
      • Click OK +
      +
    +

    +Now click Test. The server will start and +you'll see a console window with log messages telling you about synergy's +progress. If an error occurs you'll get one or more dialog boxes telling +you what the errors are; read the errors to determine the problem then +correct them and try Test again. See Step 5 +for typical errors. +

    +Unix or Mac OS X
    +Create a text file named synergy.conf with the +following: +

    +    section: screens
    +       screen1:
    +       screen2:
    +    end
    +    section: links
    +       screen1:
    +           right = screen2
    +       screen2:
    +           left = screen1
    +    end
    +
    +Replace each occurrence of screen1 with the host name +of the primary screen computer (as reported by the +hostname program) and screen2 +with the host name of a secondary screen computer. In the above example, +screen2 is to the right of +screen1 and screen1 is to the +left of screen2. If necessary you should replace +right and left with +left, right, +up, or down. If you +have more than two computers you can add those too: add each computer's host +name in the screens section and add the +appropriate links. See the configuration +guide for more configuration possibilities. +

    +Now start the server. Normally synergy wants to run "in the background." +It detaches from the terminal and doesn't have a visible window, effectively +disappearing from view. Until you're sure your configuration works, you +should start synergy "in the foreground" using the -f +command line option. +

    +On unix type the command below in a shell. If synergys is not in your +PATH then use the full pathname. +

    +    synergys -f --config synergy.conf
    +
    +On OS X open Terminal in the Utilities folder in the Applications folder. +Drag the synergys program from the synergy folder onto the Terminal window. +The path to the synergys program will appear. Add the following to the +same line, type a space at the end of the line but don't press enter: +
    +    -f --config 
    +
    +Now drag the synergy.conf file onto the Terminal window and press enter. +Check the reported messages for errors. Use ctrl+c to stop synergy if +it didn't stop automatically, correct any problems, and start it again. +

    +

    Step 4 - Start the clients

    +

    +Next you start the client on each computer that will share the server's +keyboard and mouse. +

    +Windows
    +On Windows run synergy by double clicking on the +synergy file. This brings up a dialog. +Configure the client: +

      +
    • Click the Use another computer's shared keyboard and mouse (client) radio button +
    • Enter the server's computer name next to Other Computer's Host Name +
        +
      • This is not the server's screen name, unless you made that the + server's host name as recommended +
      +
    • If the client's screen name is not the client's computer name: +
        +
      • Click Advanced... +
      • Enter the client's screen name next to Screen Name +
      • Click OK +
      +
    +

    +Now click Test. +

    +Unix or Mac OS X
    +To start a client on unix, enter the following: +

    +    synergyc -f server-host-name
    +
    +where server-host-name is replaced by the host +name of the computer running the synergy server. If synergyc is not in +your PATH then use the full pathname. +

    +On OS X open Terminal in the Utilities folder in the Applications folder. +Drag the synergyc program from the synergy folder onto the Terminal window. +The path to the synergys program will appear. Add the following to the +same line and press enter: +

    +    -f server-host-name
    +
    +

    +When you added the client to the server's configuration you chose a +name for the client. If that name was not client's host name then +you must tell the client the name you used. Instead of the above +command use this instead: +

    +    synergyc -f --name name server-host-name
    +
    +where name is the name for the client in +the server's configuration. (On OS X drag the synergyc program to the +Terminal window rather than typing synergyc.) +

    +

    Step 5 - Test

    +

    +Clients should immediately report a successful connection or one or +more error messages. Some typical problems and possible solutions are +below. See the troubleshooting and the +FAQ pages for more help. +

      +
    • failed to open screen (X11 only) +

      + Check permission to open the X display;
      + check that the DISPLAY environment variable is set
      + use the --display command line option. +

      +

    • address already in use +

      + Another program (maybe another copy of synergy) is using the synergy port; + stop the other program or choose a different port in the + Advanced... dialog. If you change the port + you must make the same change on all of the clients, too. +

      +

    • connection forcefully rejected +

      + The synergy client successfully contacted the server but synergy wasn't + running or it's running on a different port. You may also see this if + there's a firewall blocking the host or port. Make sure synergy is + running on the server and check for a firewall. +

      +

    • already connected +

      + Check that the synergy client isn't already running. +

      +

    • refused client +

      + Add the client to the server's configuration file. +

      +

    • connection timed out +

      + Check that server-host-name is correct.
      + Check that you don't have a firewall blocking the server or synergy port. +

      +

    • connection failed +

      + Check that server-host-name is correct. +

      +

    +If you get the error "Xlib: No protocol specified" +you're probably running synergy as root while logged in as another user. +X11 may prevent this for security reasons. Either run synergy as the same +user that's logged in or (not recommended) use +"xhost +" to allow anyone to connect +to the display. +

    +When successful you should be able to move the mouse off the appropriate +edges of your server's screen and have it appear on a client screen. +Try to move the mouse to each screen and check all the configured links. +Check the mouse buttons and wheel and try the keyboard on each client. +You can also cut-and-paste text, HTML, and images across computers (HTML +and images are not supported on OS X yet). +

    +

    Step 6 - Run

    +

    +Once everything works correctly, stop all the clients then the server. +Then start the server with the Start button +on Windows and without the -f option on Unix +and Mac OS X. Finally start the clients similarly. On Windows before +clicking Start you may want to set the +Logging Level to +Warning so the logging window doesn't pop +up (because you currently can't close it, just minimize it). +

    +You can also configure synergy to start automatically when your computer +starts or when you log in. See the autostart +guide for more information. +

    +

    Command Line Options Guide

    +

    +Common Command Line Options
    +The following options are supported by synergys +and synergyc. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     -d,--debug level  use debugging level level
     --daemonrun as a daemon (Unix) or background (Windows)
     -f,--no-daemonrun in the foreground
      --display display  connect to X server at display (X11 only)
     -n,--name nameuse name instead of the hostname
     --restartautomatically restart on failures
     -1,--no-restartdo not restart on failure
     -h,--helpprint help and exit
     --versionprint version information and exit
    +

    +Debug levels are from highest to lowest: FATAL, +ERROR, WARNING, +NOTE, INFO, +DEBUG, DEBUG1, and +DEBUG2. Only messages at or above the given +level are logged. Messages are logged to a terminal window when +running in the foreground. Unix logs messages to syslog when running +as a daemon. The Windows NT family logs messages to the event log +when running as a service. The Windows 95 family shows FATAL log +messages in a message box and others in a terminal window when running +as a service. +

    +The --name option lets the client or server +use a name other than its hostname for its screen. This name is used +when checking the configuration. +

    +Neither the client nor server will automatically restart if an error +occurs that is sure to happen every time. For example, the server +will exit immediately if it can't find itself in the configuration. +On X11 both the client and server will also terminate if the +connection to the X server is lost (usually because it died). +

    +Server Command Line Options
    +

    +

    +    synergys [options]
    +
    +The server accepts the common options and: +

    + + + + + + + + + + + +
     -a,--address address  listen for connections on address address
     -c,--config pathname  read configuration from pathname
    +

    +address has one of the following forms: +

    +    hostname
    +    :port
    +    hostname:port
    +
    +hostname is a hostname or IP address of a network +interface on the server system (e.g. somehost +or 192.168.1.100). port +is a port number from 1 to 65535. hostname defaults to +the system's hostname and port defaults to 24800. +

    +Client Command Line Options
    +

    +

    +    synergyc [options] address[:port]
    +
    +address is the hostname or IP address of +the server and port is the optional network +port on the server to connect to. The client accepts the +common options. +

    + + + diff --git a/doc/security.html b/doc/security.html new file mode 100644 index 00000000..c8013c27 --- /dev/null +++ b/doc/security.html @@ -0,0 +1,55 @@ + + + + + + + + Synergy Network Security Guide + + +

    +

    Authentication and Encryption

    +Synergy does not do any authentication or encryption. Any computer +can connect to the synergy server if it provides a screen name known +to the server, and all data is transferred between the server and the +clients unencrypted which means that anyone can, say, extract the +key presses used to type a password. Therefore, synergy should not +be used on untrusted networks. +

    +However, there are tools that can add authentication and encryption +to synergy without modifying either those tools or synergy. One +such tool is SSH (which stands for secure shell). A free implementation +of SSH is called OpenSSH and runs +on Linux, many Unixes, and Windows (in combination with +Cygwin). +

    +

    Configuring the Server

    +Install the OpenSSH server on the same computer as the synergy server. +Configure the OpenSSH server as usual (synergy doesn't demand any +special options in OpenSSH) and start it. Start the synergy server as +usual; the synergy server requires no special options to work with +OpenSSH. +

    +

    Configuring the Clients

    +Install the OpenSSH client on each synergy client computer. Then, on +each client, start the OpenSSH client using port forwarding: +

    +  ssh -f -N -L 24800:server-hostname:24800 server-hostname
    +
    +The server-hostname is the name or address +of the computer with the OpenSSH and synergy servers. +The 24800 is the default network port used by synergy; if you use +a different port then replace both instances of 24800 with the port +number that you use. Finally, start the synergy client normally +except use localhost as the server host +name. For example: +
    +  synergyc -f localhost
    +
    +Synergy will then run normally except all communication is passed +through OpenSSH which decrypts/encrypts it on behalf of synergy. +

    + + + diff --git a/doc/synergy.css b/doc/synergy.css new file mode 100644 index 00000000..f020aa59 --- /dev/null +++ b/doc/synergy.css @@ -0,0 +1,166 @@ +body { + font-family: arial, helvetica, sans-serif; + font-size: small; + font-weight: normal; + margin-left: 0in; + margin-right: 0in; +} + +/* show underline on light blue links only on hover */ +a { + text-decoration: none; + color: #6699ff; +} +a:hover { + text-decoration: underline; +} + +/* heading */ +h3 { + display: block; + margin-top: 0em; + margin-bottom: 1.25em; + font-weight: bold; + font-variant: small-caps; + font-size: 125%; +} + +/* subheading */ +h4 { + display: block; + margin-top: 0em; + margin-bottom: 1em; + font-weight: bold; + font-variant: small-caps; + font-size: 100%; +} + +/* emphasis */ +b { + font-weight: bold; +} + +/* formatted code */ +pre { + display: block; + white-space: pre; + font-family: courier new; + font-size: 87.5%; +} + +.banner { + font-weight: normal; + font-variant: small-caps; + font-size: 400%; + width: 100%; + padding: 0px; + margin: 0px; + border: 0px; +} +.banner a { + color: #000000; +} +.banner a:hover { + text-decoration: none; + color: #000000; +} +.bannerb { + color: #ffffff; + background-color: #ffffff; + width: 100%; + height: 1px; + padding: 0px; + margin: 0px; + border-bottom: solid #6699ff 1px; +} + +.nav { + font-size: x-small; + font-weight: normal; + background-color: #d4d4d4; + + padding: 2px 0px 2px 0px; + margin: 0px; + border-bottom: solid #d4d4d4 300px; +} +.nav a:hover { + text-decoration: none; + color: #666666; +} +.nav td { + padding-right: 20px; + padding-left: 5px; + text-indent: 1em; +} +.nav .section { + width: 120px; + text-indent: 0em; + border-top: 0px; + border-left: 0px; + border-right: 0px; + border-bottom: solid #aaaaaa 1px; + padding-bottom: 0px; + font-weight: bold; + color: #777777; +} + +.main { + font-size: small; + font-weight: normal; + margin-left: 0.1in; + margin-right: 0.25in; +} + +.main table { + font-size: small; + font-weight: normal; + margin-left: 0.1in; + margin-right: 0.25in; +} + +.date { + font-weight: bold; +} + +.arg { + font-style: italic; + font-family: courier new; +} + +.userinput { + display: block; + white-space: pre; + font-family: courier new; + font-size: 87.5%; + font-weight: bold; +} + +.code { + font-family: courier new; +} + +.code table { + font-size: small; +} + +/* block of code */ +.codeblock { + display: block; + white-space: pre; + font-family: courier new; + font-size: 87.5%; + border: 1px solid #000000; + padding: 1em; + padding-top: 0em; + margin: 1em; + background-color: #cccccc; + color: #000000; +} + +.fakelink { + color: #6699ff; +} + +.hide { + display:none +} diff --git a/doc/tips.html b/doc/tips.html new file mode 100644 index 00000000..9f8e9e24 --- /dev/null +++ b/doc/tips.html @@ -0,0 +1,81 @@ + + + + + + + + Synergy Tips and Tricks + + +

    +

    Tips and Tricks

    +

      +
    • + Be aware that not all keystrokes can be handled by synergy. In + particular, ctrl+alt+del is not handled. However, synergy can + convert ctrl+alt+pause into ctrl+alt+del on the client side. + (Synergy must be configured to autostart when the computer starts + on the client for this to work on the Windows NT family.) Some + non-standard keys may not work, especially "multimedia" buttons, + though several are correctly handled. +

      +

    • + A screen can be its own neighbor. That allows a screen to "wrap". + For example, if a configuration linked the left and right sides of + a screen to itself then moving off the left of the screen would put + the mouse at the right of the screen and vice versa. +

      +

    • + You cannot switch screens when the Scroll Lock is toggled on. Use + this to prevent unintentional switching. You can configure other + hot keys to do this instead; see + lockCursorToScreen. +

      +

    • + Turn off mouse driven virtual desktop switching on X windows. It + will interfere with synergy. Use keyboard shortcuts instead. +

      +

    • + Synergy's screen saver synchronization works best with xscreensaver + under X windows. Synergy works better with xscreensaver if it is + using one of the screen saver extensions. Prior to xscreensaver 4.0 + you can use -mit-extension, + -sgi-extension, or + -xidle-extension + command line options to enable an extension (assuming your server has + the extension). Starting with 4.0 you must enable the corresponding + option in your .xscreensaver file. +

      +

    • + Synergy automatically converts newlines in clipboard text (Unix + expects \n to end each line while Windows + expects \r\n). +

      +

    • + Clients can be started and stopped at any time. When a screen is + not connected, the mouse will jump over that screen as if the mouse + had moved all the way across it and jumped to the next screen. +

      +

    • + A client's keyboard and mouse are fully functional while synergy is + running. You can use them in case synergy locks up. +

      +

    • + Strong authentication and encryption is available by using SSH. See + the security guide for more information. + Synergy does not otherwise provide secure communications and it should + not be used on or over untrusted networks. +

      +

    • + Synergy doesn't work if a 16-bit Windows application has the focus + on Windows 95/98/Me. This is due to limitations of Windows. One + commonly used 16-bit application is the command prompt + (command.exe) + and this includes synergy's log window when running in test mode. +

      +

    +

    + + + diff --git a/doc/toc.html b/doc/toc.html new file mode 100644 index 00000000..3c43bd7c --- /dev/null +++ b/doc/toc.html @@ -0,0 +1,43 @@ + + + + + + + + Synergy TOC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/todo.html b/doc/todo.html new file mode 100644 index 00000000..02a12a07 --- /dev/null +++ b/doc/todo.html @@ -0,0 +1,70 @@ + + + + + Synergy To Do List + + +

    Synergy To Do List

    +

    +This page describes the planned development of Synergy. There are +no dates or deadlines. Instead, you'll find the features to come +and the rough order they can be expected to arrive. +

    + +

    Short term

    +

    +Synergy should work seamlessly. When it works correctly, it works +transparently so you don't even think about it. When it breaks, +you're forced out of the illusion of a unified desktop. The first +priority is fixing those bugs that break the illusion. +

    +

    +Some of these bugs are pretty minor and some people would rather +have new features first. But I'd rather fix the current +foundation before building on it. That's not to say features +won't get added until after bug fixes; sometimes it's just too +tempting to code up a feature. +

    + +

    Medium term

    +

    +Some features fit well into Synergy's current design and may simply +enhance it's current capabilities. +

      +
    • Configurable hot key screen switching +
    • Configurable hot key to lock to a screen +
    • Configurable hot key to pop up a screen switch menu +
    • Configure screen saver synchronization on or off +
    • Graphical interface configuration and control on all platforms +
    • Graphical status feedback on all platforms +
    • More supported clipboard formats (particularly rich text) +
    +

    + +

    Long term

    +

    +Two features stand out as long term goals: +

      +
    • Support N computers on +M monitors +
    • Drag and drop across computers +
    +

    +

    +The first feature means sharing a monitor or monitors the way the +keyboard and mouse are shared. With this, Synergy would be a full +KVM solution. Not only would it support a few computers sharing +one screen (still using the mouse to roll from one screen to +another), but it should also support dozens of computers to provide +a solution for server farm administrators. In this capacity, it +may need to support text (as opposed to bitmap graphics) screens. +

    +

    +The second feature would enhance the unified desktop illusion. It +would make it possible to drag a file and possibly other objects +to another screen. The object would be copied (or moved). I expect +this to be a very tricky feature. +

    + + diff --git a/doc/trouble.html b/doc/trouble.html new file mode 100644 index 00000000..f2923a5b --- /dev/null +++ b/doc/trouble.html @@ -0,0 +1,204 @@ + + + + + + + + Synergy Troubleshooting + + +

    +

    Synergy Troubleshooting

    +

    Problems

    +
      +
    1. Cannot read configuration +
    2. Connection forcefully rejected +
    3. Connection timed out +
    4. Cannot listen for clients +
    5. Unknown screen name "XXX" +
    6. Server refused client with name "XXX" +
      A client with name "XXX" is not in the map +
    7. Server already has a connected client with name "XXX" +
      A client with name "XXX" is already connected +
    8. Server has incompatible version +
    9. The cursor goes to secondary screen but won't come back +
    +

    Solutions

    +
      +
    1. Cannot read configuration +

      +There's an error in the configuration file. This error is always +accompanied by another message describing the problem. Use that +message and the configuration documentation +to determine the fix. +

      +
    2. Connection forcefully rejected +

      +The client was able to contact the server computer but the server was +not listening for clients. Possible reasons are: +

      +
        +
      • The client is using the wrong server +

        +Make sure the client is using the hostname or IP address of the computer +running the synergy server. +

        +
      • Synergy isn't running on the server +

        +Make sure the synergy server is running on the server computer. Make +sure the server is ready to accept connections. If another program is +using synergy's port (24800 by default) then synergy can't start unless +you specify a different port. +

        +
      • The client is using the wrong port +

        +Synergy uses port 24800 by default but you can specify a different port. +If you do use a different port you must use that port on the server and +all clients. +

        +
      +
    3. Connection timed out +

      +The most likely reasons for this are: +

      +
        +
      • A firewall +

        +A firewall is a program or device that deliberately blocks network +connections for security reasons. Typically, they'll silently drop +packets they don't want rather than sending a rejection to the sender. +This makes it more difficult for intruders to break in. +

        +When synergy traffic hits a firewall and gets dropped, eventually the +synergy client will give up waiting for a response and time out. To +allow synergy traffic through first find all the firewalls on the +network between and on the synergy client and server computers. +

        +A firewall on the server or any network device between the server and +any client should allow packets to TCP port 24800. (Port 24800 is the +default; use whichever port you've selected.) You'll have to consult +the manual for your operating system, device, or firewall software to +find out how to do this. +

        +Usually you'll won't need to adjust a firewall on client machines. +That's because firewalls normally allow incoming traffic on any port +they've initiated a connection on. The reasoning is, of course, if +you started a conversation you probably want to hear the reply. +

        +
      • The network is down or busy +

        +Correct the network problem and try again. You might try +ping to see if the two computers can see +each other on the network. +

        +
      • The server is frozen +

        +If the synergy server is running but locked up or very busy then the +client may get this message. If the server is locked up then you'll +probably have to restart it. If it's just very busy then the client +should successfully connect automatically once the server settles down. +

        +
      +
    4. Cannot listen for clients +

      +Synergy tried to start listening for clients but the network port is +unavailable for some reason. Typical reasons are: +

      +
        +
      • No network devices +

        +You must have a TCP/IP network device installed and enabled to use +synergy. +

        +
      • A synergy server is already running +

        +Check that a synergy server isn't already running. +

        +
      • Another program is using synergy's port +

        +Only one program at a time can listen for connections on a given port. +If the specific error is that the address is already in use and you've +ruled out the other causes, then it's likely another program is already +using synergy's port. By default synergy uses port 24800. Try having +synergy use a different port number, like 24801 or 24900. Note that +the server and all clients must use the same port number. Alternatively, +find the other program and stop it or have it use another port. +

        +
      +
    5. Unknown screen name "XXX" +

      +This error can be reported when reading the configuration; see +cannot read configuration. If the configuration +was read successfully and you get this error then it means that the +server's screen is not in the configuration. All screens must be listed +in the configuration. +

      +A common reason for this is when you haven't used the system's hostname +as its screen name. By default, synergy uses the hostname as the screen +name. If you used a different screen name in the configuration then you +must tell synergy what that name is. Let's say the hostname is +frederick but the configuration defines a screen +named fred. Then you must tell the server +that its screen name is fred by using the +--name fred command line option or setting +the screen name in the advanced options dialog to +fred. +

      +Alternatively, you can specify one name as an alias of another. See +the configuration documentation +for details. +

      +Another common reason for this is a mismatch between what you think the +hostname is and what synergy thinks it is. Typically this is a problem +with fully qualified domain names (FQDN). Perhaps you think your system +is named fred but synergy thinks it's +fred.nowhere.com or +fred.local. You can use either solution above +to fix this. +

      +
    6. Server refused client with name "XXX" +
      A client with name "XXX" is not in the map +

      +The client is using a screen name not in the server's configuration. +This is essentially the same problem as Unknown +screen name "XXX" and has the same solutions: specify another +screen name or add an alias. +

      +
    7. Server already has a connected client with name "XXX" +
      A client with name "XXX" is already connected +

      +This happens when: +

      +
        +
      • Two clients try use the same screen name +

        +Each client must have a unique screen name. Configure at least one +client to use a different screen name. +

        +
      • One client reconnects without cleanly disconnecting +

        +It's possible for a client to disconnect without the server knowing, +usually by being disconnected from the network or possibly by going +to sleep or even crashing. The server is left thinking the client is +still connected so when the client reconnects the server will think +this is a different client using the same name. Synergy will usually +detect and correct this problem within a few seconds. If it doesn't +then restart the server. +

        +
      +
    8. Server has incompatible version +

      +You're using different versions of synergy on the client and server. +You should use the same version on all systems. +

      +
    9. The cursor goes to secondary screen but won't come back +

      +This is FAQ #17 and is also mentioned in +the documentation for using synergy +and configuration. +

      +
    + + + diff --git a/examples/synergy.conf b/examples/synergy.conf new file mode 100644 index 00000000..2586dfaf --- /dev/null +++ b/examples/synergy.conf @@ -0,0 +1,37 @@ +# sample synergy configuration file +# +# comments begin with the # character and continue to the end of +# line. comments may appear anywhere the syntax permits. + +section: screens + # three hosts named: moe, larry, and curly + moe: + larry: + curly: +end + +section: links + # larry is to the right of moe and curly is above moe + moe: + right = larry + up = curly + + # moe is to the left of larry and curly is above larry. + # note that curly is above both moe and larry and moe + # and larry have a symmetric connection (they're in + # opposite directions of each other). + larry: + left = moe + up = curly + + # larry is below curly. if you move up from moe and then + # down, you'll end up on larry. + curly: + down = larry +end + +section: aliases + # curly is also known as shemp + curly: + shemp +end diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 00000000..2a57133c --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,34 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +SUBDIRS = \ + common \ + arch \ + base \ + mt \ + io \ + net \ + synergy \ + platform \ + client \ + server \ + $(NULL) + +EXTRA_DIST = \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/lib/arch/CArch.cpp b/lib/arch/CArch.cpp new file mode 100644 index 00000000..80c613ab --- /dev/null +++ b/lib/arch/CArch.cpp @@ -0,0 +1,639 @@ +/* + * 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 "common.h" +#include "CArch.h" + +#undef ARCH_CONSOLE +#undef ARCH_DAEMON +#undef ARCH_FILE +#undef ARCH_LOG +#undef ARCH_MULTITHREAD +#undef ARCH_NETWORK +#undef ARCH_SLEEP +#undef ARCH_STRING +#undef ARCH_SYSTEM +#undef ARCH_TASKBAR +#undef ARCH_TIME + +// include appropriate architecture implementation +#if SYSAPI_WIN32 +# include "CArchConsoleWindows.h" +# include "CArchDaemonWindows.h" +# include "CArchFileWindows.h" +# include "CArchLogWindows.h" +# include "CArchMiscWindows.h" +# include "CArchMultithreadWindows.h" +# include "CArchNetworkWinsock.h" +# include "CArchSleepWindows.h" +# include "CArchStringWindows.h" +# include "CArchSystemWindows.h" +# include "CArchTaskBarWindows.h" +# include "CArchTimeWindows.h" +#elif SYSAPI_UNIX +# include "CArchConsoleUnix.h" +# include "CArchDaemonUnix.h" +# include "CArchFileUnix.h" +# include "CArchLogUnix.h" +# if HAVE_PTHREAD +# include "CArchMultithreadPosix.h" +# endif +# include "CArchNetworkBSD.h" +# include "CArchSleepUnix.h" +# include "CArchStringUnix.h" +# include "CArchSystemUnix.h" +# include "CArchTaskBarXWindows.h" +# include "CArchTimeUnix.h" +#endif + +#if !defined(ARCH_CONSOLE) +# error unsupported platform for console +#endif + +#if !defined(ARCH_DAEMON) +# error unsupported platform for daemon +#endif + +#if !defined(ARCH_FILE) +# error unsupported platform for file +#endif + +#if !defined(ARCH_LOG) +# error unsupported platform for logging +#endif + +#if !defined(ARCH_MULTITHREAD) +# error unsupported platform for multithreading +#endif + +#if !defined(ARCH_NETWORK) +# error unsupported platform for network +#endif + +#if !defined(ARCH_SLEEP) +# error unsupported platform for sleep +#endif + +#if !defined(ARCH_STRING) +# error unsupported platform for string +#endif + +#if !defined(ARCH_SYSTEM) +# error unsupported platform for system +#endif + +#if !defined(ARCH_TASKBAR) +# error unsupported platform for taskbar +#endif + +#if !defined(ARCH_TIME) +# error unsupported platform for time +#endif + +// +// CArch +// + +CArch* CArch::s_instance = NULL; + +CArch::CArch(ARCH_ARGS* args) +{ + // only once instance of CArch + assert(s_instance == NULL); + s_instance = this; + + // create architecture implementation objects + m_mt = new ARCH_MULTITHREAD; + m_system = new ARCH_SYSTEM; + m_file = new ARCH_FILE; + m_log = new ARCH_LOG; + m_net = new ARCH_NETWORK; + m_sleep = new ARCH_SLEEP; + m_string = new ARCH_STRING; + m_time = new ARCH_TIME; + m_console = new ARCH_CONSOLE(args); + m_daemon = new ARCH_DAEMON; + m_taskbar = new ARCH_TASKBAR(args); + +#if SYSAPI_WIN32 + CArchMiscWindows::init(); +#endif +} + +CArch::~CArch() +{ + // clean up + delete m_taskbar; + delete m_daemon; + delete m_console; + delete m_time; + delete m_string; + delete m_sleep; + delete m_net; + delete m_log; + delete m_file; + delete m_system; + delete m_mt; + + // no instance + s_instance = NULL; +} + +CArch* +CArch::getInstance() +{ + assert(s_instance != NULL); + + return s_instance; +} + +void +CArch::openConsole(const char* title) +{ + m_console->openConsole(title); +} + +void +CArch::closeConsole() +{ + m_console->closeConsole(); +} + +void +CArch::showConsole(bool showIfEmpty) +{ + m_console->showConsole(showIfEmpty); +} + +void +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, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers) +{ + m_daemon->installDaemon(name, description, pathname, + commandLine, dependencies, allUsers); +} + +void +CArch::uninstallDaemon(const char* name, bool allUsers) +{ + m_daemon->uninstallDaemon(name, allUsers); +} + +int +CArch::daemonize(const char* name, DaemonFunc func) +{ + return m_daemon->daemonize(name, func); +} + +bool +CArch::canInstallDaemon(const char* name, bool allUsers) +{ + return m_daemon->canInstallDaemon(name, allUsers); +} + +bool +CArch::isDaemonInstalled(const char* name, bool allUsers) +{ + return m_daemon->isDaemonInstalled(name, allUsers); +} + +const char* +CArch::getBasename(const char* pathname) +{ + return m_file->getBasename(pathname); +} + +std::string +CArch::getUserDirectory() +{ + return m_file->getUserDirectory(); +} + +std::string +CArch::getSystemDirectory() +{ + return m_file->getSystemDirectory(); +} + +std::string +CArch::concatPath(const std::string& prefix, const std::string& suffix) +{ + return m_file->concatPath(prefix, suffix); +} + +void +CArch::openLog(const char* name) +{ + m_log->openLog(name); +} + +void +CArch::closeLog() +{ + m_log->closeLog(); +} + +void +CArch::showLog(bool showIfEmpty) +{ + m_log->showLog(showIfEmpty); +} + +void +CArch::writeLog(ELevel level, const char* msg) +{ + m_log->writeLog(level, msg); +} + +CArchCond +CArch::newCondVar() +{ + return m_mt->newCondVar(); +} + +void +CArch::closeCondVar(CArchCond cond) +{ + m_mt->closeCondVar(cond); +} + +void +CArch::signalCondVar(CArchCond cond) +{ + m_mt->signalCondVar(cond); +} + +void +CArch::broadcastCondVar(CArchCond cond) +{ + m_mt->broadcastCondVar(cond); +} + +bool +CArch::waitCondVar(CArchCond cond, CArchMutex mutex, double timeout) +{ + return m_mt->waitCondVar(cond, mutex, timeout); +} + +CArchMutex +CArch::newMutex() +{ + return m_mt->newMutex(); +} + +void +CArch::closeMutex(CArchMutex mutex) +{ + m_mt->closeMutex(mutex); +} + +void +CArch::lockMutex(CArchMutex mutex) +{ + m_mt->lockMutex(mutex); +} + +void +CArch::unlockMutex(CArchMutex mutex) +{ + m_mt->unlockMutex(mutex); +} + +CArchThread +CArch::newThread(ThreadFunc func, void* data) +{ + return m_mt->newThread(func, data); +} + +CArchThread +CArch::newCurrentThread() +{ + return m_mt->newCurrentThread(); +} + +CArchThread +CArch::copyThread(CArchThread thread) +{ + return m_mt->copyThread(thread); +} + +void +CArch::closeThread(CArchThread thread) +{ + m_mt->closeThread(thread); +} + +void +CArch::cancelThread(CArchThread thread) +{ + m_mt->cancelThread(thread); +} + +void +CArch::setPriorityOfThread(CArchThread thread, int n) +{ + m_mt->setPriorityOfThread(thread, n); +} + +void +CArch::testCancelThread() +{ + m_mt->testCancelThread(); +} + +bool +CArch::wait(CArchThread thread, double timeout) +{ + return m_mt->wait(thread, timeout); +} + +bool +CArch::isSameThread(CArchThread thread1, CArchThread thread2) +{ + return m_mt->isSameThread(thread1, thread2); +} + +bool +CArch::isExitedThread(CArchThread thread) +{ + return m_mt->isExitedThread(thread); +} + +void* +CArch::getResultOfThread(CArchThread thread) +{ + return m_mt->getResultOfThread(thread); +} + +IArchMultithread::ThreadID +CArch::getIDOfThread(CArchThread thread) +{ + return m_mt->getIDOfThread(thread); +} + +void +CArch::setSignalHandler(ESignal signal, SignalFunc func, void* userData) +{ + m_mt->setSignalHandler(signal, func, userData); +} + +void +CArch::raiseSignal(ESignal signal) +{ + m_mt->raiseSignal(signal); +} + +CArchSocket +CArch::newSocket(EAddressFamily family, ESocketType type) +{ + return m_net->newSocket(family, type); +} + +CArchSocket +CArch::copySocket(CArchSocket s) +{ + return m_net->copySocket(s); +} + +void +CArch::closeSocket(CArchSocket s) +{ + m_net->closeSocket(s); +} + +void +CArch::closeSocketForRead(CArchSocket s) +{ + m_net->closeSocketForRead(s); +} + +void +CArch::closeSocketForWrite(CArchSocket s) +{ + m_net->closeSocketForWrite(s); +} + +void +CArch::bindSocket(CArchSocket s, CArchNetAddress addr) +{ + m_net->bindSocket(s, addr); +} + +void +CArch::listenOnSocket(CArchSocket s) +{ + m_net->listenOnSocket(s); +} + +CArchSocket +CArch::acceptSocket(CArchSocket s, CArchNetAddress* addr) +{ + return m_net->acceptSocket(s, addr); +} + +bool +CArch::connectSocket(CArchSocket s, CArchNetAddress name) +{ + return m_net->connectSocket(s, name); +} + +int +CArch::pollSocket(CPollEntry pe[], int num, double timeout) +{ + return m_net->pollSocket(pe, num, timeout); +} + +void +CArch::unblockPollSocket(CArchThread thread) +{ + m_net->unblockPollSocket(thread); +} + +size_t +CArch::readSocket(CArchSocket s, void* buf, size_t len) +{ + return m_net->readSocket(s, buf, len); +} + +size_t +CArch::writeSocket(CArchSocket s, const void* buf, size_t len) +{ + return m_net->writeSocket(s, buf, len); +} + +void +CArch::throwErrorOnSocket(CArchSocket s) +{ + m_net->throwErrorOnSocket(s); +} + +bool +CArch::setNoDelayOnSocket(CArchSocket s, bool noDelay) +{ + return m_net->setNoDelayOnSocket(s, noDelay); +} + +bool +CArch::setReuseAddrOnSocket(CArchSocket s, bool reuse) +{ + return m_net->setReuseAddrOnSocket(s, reuse); +} + +std::string +CArch::getHostName() +{ + return m_net->getHostName(); +} + +CArchNetAddress +CArch::newAnyAddr(EAddressFamily family) +{ + return m_net->newAnyAddr(family); +} + +CArchNetAddress +CArch::copyAddr(CArchNetAddress addr) +{ + return m_net->copyAddr(addr); +} + +CArchNetAddress +CArch::nameToAddr(const std::string& name) +{ + return m_net->nameToAddr(name); +} + +void +CArch::closeAddr(CArchNetAddress addr) +{ + m_net->closeAddr(addr); +} + +std::string +CArch::addrToName(CArchNetAddress addr) +{ + return m_net->addrToName(addr); +} + +std::string +CArch::addrToString(CArchNetAddress addr) +{ + return m_net->addrToString(addr); +} + +IArchNetwork::EAddressFamily +CArch::getAddrFamily(CArchNetAddress addr) +{ + return m_net->getAddrFamily(addr); +} + +void +CArch::setAddrPort(CArchNetAddress addr, int port) +{ + m_net->setAddrPort(addr, port); +} + +int +CArch::getAddrPort(CArchNetAddress addr) +{ + return m_net->getAddrPort(addr); +} + +bool +CArch::isAnyAddr(CArchNetAddress addr) +{ + return m_net->isAnyAddr(addr); +} + +bool +CArch::isEqualAddr(CArchNetAddress a, CArchNetAddress b) +{ + return m_net->isEqualAddr(a, b); +} + +void +CArch::sleep(double timeout) +{ + m_sleep->sleep(timeout); +} + +int +CArch::vsnprintf(char* str, int size, const char* fmt, va_list ap) +{ + return m_string->vsnprintf(str, size, fmt, ap); +} + +int +CArch::convStringMBToWC(wchar_t* dst, const char* src, UInt32 n, bool* errors) +{ + return m_string->convStringMBToWC(dst, src, n, errors); +} + +int +CArch::convStringWCToMB(char* dst, const wchar_t* src, UInt32 n, bool* errors) +{ + return m_string->convStringWCToMB(dst, src, n, errors); +} + +IArchString::EWideCharEncoding +CArch::getWideCharEncoding() +{ + return m_string->getWideCharEncoding(); +} + +std::string +CArch::getOSName() const +{ + return m_system->getOSName(); +} + +void +CArch::addReceiver(IArchTaskBarReceiver* receiver) +{ + m_taskbar->addReceiver(receiver); +} + +void +CArch::removeReceiver(IArchTaskBarReceiver* receiver) +{ + m_taskbar->removeReceiver(receiver); +} + +void +CArch::updateReceiver(IArchTaskBarReceiver* receiver) +{ + m_taskbar->updateReceiver(receiver); +} + +double +CArch::time() +{ + return m_time->time(); +} diff --git a/lib/arch/CArch.h b/lib/arch/CArch.h new file mode 100644 index 00000000..644f015c --- /dev/null +++ b/lib/arch/CArch.h @@ -0,0 +1,218 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCH_H +#define CARCH_H + +#include "IArchConsole.h" +#include "IArchDaemon.h" +#include "IArchFile.h" +#include "IArchLog.h" +#include "IArchMultithread.h" +#include "IArchNetwork.h" +#include "IArchSleep.h" +#include "IArchString.h" +#include "IArchSystem.h" +#include "IArchTaskBar.h" +#include "IArchTime.h" + +/*! +\def ARCH +This macro evaluates to the singleton CArch object. +*/ +#define ARCH (CArch::getInstance()) + +#define ARCH_ARGS void + +//! Delegating mplementation of architecture dependent interfaces +/*! +This class is a centralized interface to all architecture dependent +interface implementations (except miscellaneous functions). It +instantiates an implementation of each interface and delegates calls +to each method to those implementations. Clients should use the +\c ARCH macro to access this object. Clients must also instantiate +exactly one of these objects before attempting to call any method, +typically at the beginning of \c main(). +*/ +class CArch : public IArchConsole, + public IArchDaemon, + public IArchFile, + public IArchLog, + public IArchMultithread, + public IArchNetwork, + public IArchSleep, + public IArchString, + public IArchSystem, + public IArchTaskBar, + public IArchTime { +public: + CArch(ARCH_ARGS* args = NULL); + ~CArch(); + + // + // accessors + // + + //! Return the singleton instance + /*! + The client must have instantiated exactly once CArch object before + calling this function. + */ + static CArch* getInstance(); + + // IArchConsole overrides + virtual void openConsole(const char*); + 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, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers); + virtual void uninstallDaemon(const char* name, bool allUsers); + virtual int daemonize(const char* name, DaemonFunc func); + virtual bool canInstallDaemon(const char* name, bool allUsers); + virtual bool isDaemonInstalled(const char* name, bool allUsers); + + // IArchFile overrides + virtual const char* getBasename(const char* pathname); + virtual std::string getUserDirectory(); + virtual std::string getSystemDirectory(); + virtual std::string concatPath(const std::string& prefix, + const std::string& suffix); + + // IArchLog overrides + virtual void openLog(const char*); + virtual void closeLog(); + virtual void showLog(bool showIfEmpty); + virtual void writeLog(ELevel, const char*); + + // IArchMultithread overrides + virtual CArchCond newCondVar(); + virtual void closeCondVar(CArchCond); + virtual void signalCondVar(CArchCond); + virtual void broadcastCondVar(CArchCond); + virtual bool waitCondVar(CArchCond, CArchMutex, double timeout); + virtual CArchMutex newMutex(); + virtual void closeMutex(CArchMutex); + virtual void lockMutex(CArchMutex); + virtual void unlockMutex(CArchMutex); + virtual CArchThread newThread(ThreadFunc, void*); + virtual CArchThread newCurrentThread(); + virtual CArchThread copyThread(CArchThread); + virtual void closeThread(CArchThread); + virtual void cancelThread(CArchThread); + virtual void setPriorityOfThread(CArchThread, int n); + virtual void testCancelThread(); + virtual bool wait(CArchThread, double timeout); + virtual bool isSameThread(CArchThread, CArchThread); + virtual bool isExitedThread(CArchThread); + virtual void* getResultOfThread(CArchThread); + virtual ThreadID getIDOfThread(CArchThread); + virtual void setSignalHandler(ESignal, SignalFunc, void*); + virtual void raiseSignal(ESignal); + + // IArchNetwork overrides + virtual CArchSocket newSocket(EAddressFamily, ESocketType); + virtual CArchSocket copySocket(CArchSocket s); + virtual void closeSocket(CArchSocket s); + virtual void closeSocketForRead(CArchSocket s); + virtual void closeSocketForWrite(CArchSocket s); + virtual void bindSocket(CArchSocket s, CArchNetAddress addr); + virtual void listenOnSocket(CArchSocket s); + virtual CArchSocket acceptSocket(CArchSocket s, CArchNetAddress* addr); + virtual bool connectSocket(CArchSocket s, CArchNetAddress name); + virtual int pollSocket(CPollEntry[], int num, double timeout); + virtual void unblockPollSocket(CArchThread thread); + virtual size_t readSocket(CArchSocket s, void* buf, size_t len); + virtual size_t writeSocket(CArchSocket s, + const void* buf, size_t len); + virtual void throwErrorOnSocket(CArchSocket); + virtual bool setNoDelayOnSocket(CArchSocket, bool noDelay); + virtual bool setReuseAddrOnSocket(CArchSocket, bool reuse); + virtual std::string getHostName(); + virtual CArchNetAddress newAnyAddr(EAddressFamily); + virtual CArchNetAddress copyAddr(CArchNetAddress); + virtual CArchNetAddress nameToAddr(const std::string&); + virtual void closeAddr(CArchNetAddress); + virtual std::string addrToName(CArchNetAddress); + virtual std::string addrToString(CArchNetAddress); + virtual EAddressFamily getAddrFamily(CArchNetAddress); + virtual void setAddrPort(CArchNetAddress, int port); + virtual int getAddrPort(CArchNetAddress); + virtual bool isAnyAddr(CArchNetAddress); + virtual bool isEqualAddr(CArchNetAddress, CArchNetAddress); + + // IArchSleep overrides + virtual void sleep(double timeout); + + // IArchString overrides + virtual int vsnprintf(char* str, + int size, const char* fmt, va_list ap); + virtual int convStringMBToWC(wchar_t*, + const char*, UInt32 n, bool* errors); + virtual int convStringWCToMB(char*, + const wchar_t*, UInt32 n, bool* errors); + virtual EWideCharEncoding + getWideCharEncoding(); + + // IArchSystem overrides + virtual std::string getOSName() const; + + // IArchTaskBar + virtual void addReceiver(IArchTaskBarReceiver*); + virtual void removeReceiver(IArchTaskBarReceiver*); + virtual void updateReceiver(IArchTaskBarReceiver*); + + // IArchTime overrides + virtual double time(); + +private: + static CArch* s_instance; + + IArchConsole* m_console; + IArchDaemon* m_daemon; + IArchFile* m_file; + IArchLog* m_log; + IArchMultithread* m_mt; + IArchNetwork* m_net; + IArchSleep* m_sleep; + IArchString* m_string; + IArchSystem* m_system; + IArchTaskBar* m_taskbar; + IArchTime* m_time; +}; + +//! Convenience object to lock/unlock an arch mutex +class CArchMutexLock { +public: + CArchMutexLock(CArchMutex mutex) : m_mutex(mutex) + { + ARCH->lockMutex(m_mutex); + } + ~CArchMutexLock() + { + ARCH->unlockMutex(m_mutex); + } + +private: + CArchMutex m_mutex; +}; + +#endif diff --git a/lib/arch/CArchConsoleUnix.cpp b/lib/arch/CArchConsoleUnix.cpp new file mode 100644 index 00000000..dcb6e961 --- /dev/null +++ b/lib/arch/CArchConsoleUnix.cpp @@ -0,0 +1,60 @@ +/* + * 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 "CArchConsoleUnix.h" +#include + +// +// 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"; +} diff --git a/lib/arch/CArchConsoleUnix.h b/lib/arch/CArchConsoleUnix.h new file mode 100644 index 00000000..f93630bd --- /dev/null +++ b/lib/arch/CArchConsoleUnix.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHCONSOLEUNIX_H +#define CARCHCONSOLEUNIX_H + +#include "IArchConsole.h" + +#define ARCH_CONSOLE CArchConsoleUnix + +//! Unix implementation of IArchConsole +class CArchConsoleUnix : public IArchConsole { +public: + CArchConsoleUnix(void*); + 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 new file mode 100644 index 00000000..14d418ac --- /dev/null +++ b/lib/arch/CArchConsoleWindows.cpp @@ -0,0 +1,438 @@ +/* + * 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 + +// +// 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 = m_numCharacters; + range.cpMax = 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; + 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; +} diff --git a/lib/arch/CArchConsoleWindows.h b/lib/arch/CArchConsoleWindows.h new file mode 100644 index 00000000..0d59e6ef --- /dev/null +++ b/lib/arch/CArchConsoleWindows.h @@ -0,0 +1,77 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHCONSOLEWINDOWS_H +#define CARCHCONSOLEWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "IArchConsole.h" +#include "IArchMultithread.h" +#include "stddeque.h" +#include + +#define ARCH_CONSOLE CArchConsoleWindows + +//! Win32 implementation of IArchConsole +class CArchConsoleWindows : public IArchConsole { +public: + CArchConsoleWindows(void*); + 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/CArchDaemonNone.cpp b/lib/arch/CArchDaemonNone.cpp new file mode 100644 index 00000000..0281f365 --- /dev/null +++ b/lib/arch/CArchDaemonNone.cpp @@ -0,0 +1,66 @@ +/* + * 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 "CArchDaemonNone.h" + +// +// CArchDaemonNone +// + +CArchDaemonNone::CArchDaemonNone() +{ + // do nothing +} + +CArchDaemonNone::~CArchDaemonNone() +{ + // do nothing +} + +void +CArchDaemonNone::installDaemon(const char*, + const char*, + const char*, + const char*, + const char*, + bool) +{ + // do nothing +} + +void +CArchDaemonNone::uninstallDaemon(const char*, bool) +{ + // do nothing +} + +int +CArchDaemonNone::daemonize(const char* name, DaemonFunc func) +{ + // simply forward the call to func. obviously, this doesn't + // do any daemonizing. + return func(1, &name); +} + +bool +CArchDaemonNone::canInstallDaemon(const char*, bool) +{ + return false; +} + +bool +CArchDaemonNone::isDaemonInstalled(const char*, bool) +{ + return false; +} diff --git a/lib/arch/CArchDaemonNone.h b/lib/arch/CArchDaemonNone.h new file mode 100644 index 00000000..1c196c5d --- /dev/null +++ b/lib/arch/CArchDaemonNone.h @@ -0,0 +1,47 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHDAEMONNONE_H +#define CARCHDAEMONNONE_H + +#include "IArchDaemon.h" + +#define ARCH_DAEMON CArchDaemonNone + +//! Dummy implementation of IArchDaemon +/*! +This class implements IArchDaemon for a platform that does not have +daemons. The install and uninstall functions do nothing, the query +functions return false, and \c daemonize() simply calls the passed +function and returns its result. +*/ +class CArchDaemonNone : public IArchDaemon { +public: + CArchDaemonNone(); + virtual ~CArchDaemonNone(); + + // IArchDaemon overrides + virtual void installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers); + virtual void uninstallDaemon(const char* name, bool allUsers); + virtual int daemonize(const char* name, DaemonFunc func); + virtual bool canInstallDaemon(const char* name, bool allUsers); + virtual bool isDaemonInstalled(const char* name, bool allUsers); +}; + +#endif diff --git a/lib/arch/CArchDaemonUnix.cpp b/lib/arch/CArchDaemonUnix.cpp new file mode 100644 index 00000000..93d50d4d --- /dev/null +++ b/lib/arch/CArchDaemonUnix.cpp @@ -0,0 +1,78 @@ +/* + * 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 "CArchDaemonUnix.h" +#include "XArchUnix.h" +#include +#include +#include +#include +#include + +// +// CArchDaemonUnix +// + +CArchDaemonUnix::CArchDaemonUnix() +{ + // do nothing +} + +CArchDaemonUnix::~CArchDaemonUnix() +{ + // do nothing +} + +int +CArchDaemonUnix::daemonize(const char* name, DaemonFunc func) +{ + // fork so shell thinks we're done and so we're not a process + // group leader + switch (fork()) { + case -1: + // failed + throw XArchDaemonFailed(new XArchEvalUnix(errno)); + + case 0: + // child + break; + + default: + // parent exits + exit(0); + } + + // become leader of a new session + setsid(); + + // chdir to root so we don't keep mounted filesystems points busy + chdir("/"); + + // mask off permissions for any but owner + umask(077); + + // close open files. we only expect stdin, stdout, stderr to be open. + close(0); + close(1); + close(2); + + // attach file descriptors 0, 1, 2 to /dev/null so inadvertent use + // of standard I/O safely goes in the bit bucket. + open("/dev/null", O_RDONLY); + open("/dev/null", O_RDWR); + dup(1); + + // invoke function + return func(1, &name); +} diff --git a/lib/arch/CArchDaemonUnix.h b/lib/arch/CArchDaemonUnix.h new file mode 100644 index 00000000..923004e1 --- /dev/null +++ b/lib/arch/CArchDaemonUnix.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. + */ + +#ifndef CARCHDAEMONUNIX_H +#define CARCHDAEMONUNIX_H + +#include "CArchDaemonNone.h" + +#undef ARCH_DAEMON +#define ARCH_DAEMON CArchDaemonUnix + +//! Unix implementation of IArchDaemon +class CArchDaemonUnix : public CArchDaemonNone { +public: + CArchDaemonUnix(); + virtual ~CArchDaemonUnix(); + + // IArchDaemon overrides + virtual int daemonize(const char* name, DaemonFunc func); +}; + +#endif diff --git a/lib/arch/CArchDaemonWindows.cpp b/lib/arch/CArchDaemonWindows.cpp new file mode 100644 index 00000000..ab42ceab --- /dev/null +++ b/lib/arch/CArchDaemonWindows.cpp @@ -0,0 +1,770 @@ +/* + * 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 "CArchDaemonWindows.h" +#include "CArch.h" +#include "CArchMiscWindows.h" +#include "XArchWindows.h" +#include "stdvector.h" + +// +// CArchDaemonWindows +// + +CArchDaemonWindows* CArchDaemonWindows::s_daemon = NULL; + +CArchDaemonWindows::CArchDaemonWindows() +{ + m_quitMessage = RegisterWindowMessage("SynergyDaemonExit"); +} + +CArchDaemonWindows::~CArchDaemonWindows() +{ + // do nothing +} + +int +CArchDaemonWindows::runDaemon(RunFunc runFunc) +{ + assert(s_daemon != NULL); + + return s_daemon->doRunDaemon(runFunc); +} + +void +CArchDaemonWindows::daemonRunning(bool running) +{ + // if s_daemon is NULL we assume we're running on the windows + // 95 family and we just ignore this call so the caller doesn't + // have to go through the trouble of not calling it on the + // windows 95 family. + if (s_daemon != NULL) { + s_daemon->doDaemonRunning(running); + } +} + +UINT +CArchDaemonWindows::getDaemonQuitMessage() +{ + if (s_daemon != NULL) { + return s_daemon->doGetDaemonQuitMessage(); + } + else { + return 0; + } +} + +void +CArchDaemonWindows::daemonFailed(int result) +{ + // if s_daemon is NULL we assume we're running on the windows + // 95 family and we just ignore this call so the caller doesn't + // have to go through the trouble of not calling it on the + // windows 95 family. + if (s_daemon != NULL) { + throw XArchDaemonRunFailed(result); + } +} + +void +CArchDaemonWindows::installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers) +{ + // if not for all users then use the user's autostart registry. + // key. if windows 95 family then use windows 95 services key. + if (!allUsers || CArchMiscWindows::isWindows95Family()) { + // open registry + HKEY key = (allUsers && CArchMiscWindows::isWindows95Family()) ? + open95ServicesKey() : openUserStartupKey(); + if (key == NULL) { + // can't open key + throw XArchDaemonInstallFailed(new XArchEvalWindows); + } + + // construct entry + std::string value; + value += "\""; + value += pathname; + value += "\" "; + value += commandLine; + + // install entry + CArchMiscWindows::setValue(key, name, value); + + // clean up + CArchMiscWindows::closeKey(key); + } + + // windows NT family services + else { + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + // can't open service manager + throw XArchDaemonInstallFailed(new XArchEvalWindows); + } + + // create the service + SC_HANDLE service = CreateService(mgr, + name, + name, + 0, + SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + pathname, + NULL, + NULL, + dependencies, + NULL, + NULL); + if (service == NULL) { + // can't create service + DWORD err = GetLastError(); + if (err != ERROR_SERVICE_EXISTS) { + CloseServiceHandle(mgr); + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + } + + // done with service and manager + CloseServiceHandle(service); + CloseServiceHandle(mgr); + + // open the registry key for this service + HKEY key = openNTServicesKey(); + key = CArchMiscWindows::addKey(key, name); + if (key == NULL) { + // can't open key + DWORD err = GetLastError(); + try { + uninstallDaemon(name, allUsers); + } + catch (...) { + // ignore + } + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + + // set the description + CArchMiscWindows::setValue(key, _T("Description"), description); + + // set command line + key = CArchMiscWindows::addKey(key, _T("Parameters")); + if (key == NULL) { + // can't open key + DWORD err = GetLastError(); + CArchMiscWindows::closeKey(key); + try { + uninstallDaemon(name, allUsers); + } + catch (...) { + // ignore + } + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + CArchMiscWindows::setValue(key, _T("CommandLine"), commandLine); + + // done with registry + CArchMiscWindows::closeKey(key); + } +} + +void +CArchDaemonWindows::uninstallDaemon(const char* name, bool allUsers) +{ + // if not for all users then use the user's autostart registry. + // key. if windows 95 family then use windows 95 services key. + if (!allUsers || CArchMiscWindows::isWindows95Family()) { + // open registry + HKEY key = (allUsers && CArchMiscWindows::isWindows95Family()) ? + open95ServicesKey() : openUserStartupKey(); + if (key == NULL) { + // can't open key. daemon is probably not installed. + throw XArchDaemonUninstallNotInstalled(new XArchEvalWindows); + } + + // remove entry + CArchMiscWindows::deleteValue(key, name); + + // clean up + CArchMiscWindows::closeKey(key); + } + + // windows NT family services + else { + // remove parameters for this service. ignore failures. + HKEY key = openNTServicesKey(); + key = CArchMiscWindows::openKey(key, name); + if (key != NULL) { + CArchMiscWindows::deleteKey(key, _T("Parameters")); + CArchMiscWindows::closeKey(key); + } + + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + // can't open service manager + throw XArchDaemonUninstallFailed(new XArchEvalWindows); + } + + // open the service. oddly, you must open a service to delete it. + SC_HANDLE service = OpenService(mgr, name, DELETE | SERVICE_STOP); + if (service == NULL) { + DWORD err = GetLastError(); + CloseServiceHandle(mgr); + if (err != ERROR_SERVICE_DOES_NOT_EXIST) { + throw XArchDaemonUninstallFailed(new XArchEvalWindows(err)); + } + throw XArchDaemonUninstallNotInstalled(new XArchEvalWindows(err)); + } + + // stop the service. we don't care if we fail. + SERVICE_STATUS status; + ControlService(service, SERVICE_CONTROL_STOP, &status); + + // delete the service + const bool okay = (DeleteService(service) == 0); + const DWORD err = GetLastError(); + + // clean up + CloseServiceHandle(service); + CloseServiceHandle(mgr); + + // handle failure. ignore error if service isn't installed anymore. + if (!okay && isDaemonInstalled(name, allUsers)) { + if (err == ERROR_IO_PENDING) { + // this seems to be a spurious error + return; + } + if (err != ERROR_SERVICE_MARKED_FOR_DELETE) { + throw XArchDaemonUninstallFailed(new XArchEvalWindows(err)); + } + throw XArchDaemonUninstallNotInstalled(new XArchEvalWindows(err)); + } + } +} + +int +CArchDaemonWindows::daemonize(const char* name, DaemonFunc func) +{ + assert(name != NULL); + assert(func != NULL); + + // windows 95 family services + if (CArchMiscWindows::isWindows95Family()) { + typedef DWORD (WINAPI *RegisterServiceProcessT)(DWORD, DWORD); + + // mark this process as a service so it's not killed when the + // user logs off. + HINSTANCE kernel = LoadLibrary("kernel32.dll"); + if (kernel == NULL) { + throw XArchDaemonFailed(new XArchEvalWindows); + } + RegisterServiceProcessT RegisterServiceProcess = + reinterpret_cast( + GetProcAddress(kernel, + "RegisterServiceProcess")); + if (RegisterServiceProcess == NULL) { + // missing RegisterServiceProcess function + DWORD err = GetLastError(); + FreeLibrary(kernel); + throw XArchDaemonFailed(new XArchEvalWindows(err)); + } + if (RegisterServiceProcess(0, 1) == 0) { + // RegisterServiceProcess failed + DWORD err = GetLastError(); + FreeLibrary(kernel); + throw XArchDaemonFailed(new XArchEvalWindows(err)); + } + FreeLibrary(kernel); + + // now simply call the daemon function + return func(1, &name); + } + + // windows NT family services + else { + // save daemon function + m_daemonFunc = func; + + // construct the service entry + SERVICE_TABLE_ENTRY entry[2]; + entry[0].lpServiceName = const_cast(name); + entry[0].lpServiceProc = &CArchDaemonWindows::serviceMainEntry; + entry[1].lpServiceName = NULL; + entry[1].lpServiceProc = NULL; + + // hook us up to the service control manager. this won't return + // (if successful) until the processes have terminated. + s_daemon = this; + if (StartServiceCtrlDispatcher(entry) == 0) { + // StartServiceCtrlDispatcher failed + s_daemon = NULL; + throw XArchDaemonFailed(new XArchEvalWindows); + } + + s_daemon = NULL; + return m_daemonResult; + } +} + +bool +CArchDaemonWindows::canInstallDaemon(const char* /*name*/, bool allUsers) +{ + // if not for all users then use the user's autostart registry. + // key. if windows 95 family then use windows 95 services key. + if (!allUsers || CArchMiscWindows::isWindows95Family()) { + // check if we can open the registry key + HKEY key = (allUsers && CArchMiscWindows::isWindows95Family()) ? + open95ServicesKey() : openUserStartupKey(); + CArchMiscWindows::closeKey(key); + return (key != NULL); + } + + // windows NT family services + else { + // check if we can open service manager for write + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + return false; + } + CloseServiceHandle(mgr); + + // check if we can open the registry key + HKEY key = openNTServicesKey(); +// key = CArchMiscWindows::addKey(key, name); +// key = CArchMiscWindows::addKey(key, _T("Parameters")); + CArchMiscWindows::closeKey(key); + + return (key != NULL); + } +} + +bool +CArchDaemonWindows::isDaemonInstalled(const char* name, bool allUsers) +{ + // if not for all users then use the user's autostart registry. + // key. if windows 95 family then use windows 95 services key. + if (!allUsers || CArchMiscWindows::isWindows95Family()) { + // check if we can open the registry key + HKEY key = (allUsers && CArchMiscWindows::isWindows95Family()) ? + open95ServicesKey() : openUserStartupKey(); + if (key == NULL) { + return false; + } + + // check for entry + const bool installed = !CArchMiscWindows::readValueString(key, + name).empty(); + + // clean up + CArchMiscWindows::closeKey(key); + + return installed; + } + + // windows NT family services + else { + // check parameters for this service + HKEY key = openNTServicesKey(); + key = CArchMiscWindows::openKey(key, name); + key = CArchMiscWindows::openKey(key, _T("Parameters")); + if (key != NULL) { + const bool installed = !CArchMiscWindows::readValueString(key, + _T("CommandLine")).empty(); + CArchMiscWindows::closeKey(key); + if (!installed) { + return false; + } + } + + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + return false; + } + + // open the service + SC_HANDLE service = OpenService(mgr, name, GENERIC_READ); + + // clean up + if (service != NULL) { + CloseServiceHandle(service); + } + CloseServiceHandle(mgr); + + return (service != NULL); + } +} + +HKEY +CArchDaemonWindows::openNTServicesKey() +{ + static const char* s_keyNames[] = { + _T("SYSTEM"), + _T("CurrentControlSet"), + _T("Services"), + NULL + }; + + return CArchMiscWindows::addKey(HKEY_LOCAL_MACHINE, s_keyNames); +} + +HKEY +CArchDaemonWindows::open95ServicesKey() +{ + static const char* s_keyNames[] = { + _T("Software"), + _T("Microsoft"), + _T("Windows"), + _T("CurrentVersion"), + _T("RunServices"), + NULL + }; + + return CArchMiscWindows::addKey(HKEY_LOCAL_MACHINE, s_keyNames); +} + +HKEY +CArchDaemonWindows::openUserStartupKey() +{ + static const char* s_keyNames[] = { + _T("Software"), + _T("Microsoft"), + _T("Windows"), + _T("CurrentVersion"), + _T("Run"), + NULL + }; + + return CArchMiscWindows::addKey(HKEY_CURRENT_USER, s_keyNames); +} + +bool +CArchDaemonWindows::isRunState(DWORD state) +{ + switch (state) { + case SERVICE_START_PENDING: + case SERVICE_CONTINUE_PENDING: + case SERVICE_RUNNING: + return true; + + default: + return false; + } +} + +int +CArchDaemonWindows::doRunDaemon(RunFunc run) +{ + // should only be called from DaemonFunc + assert(m_serviceMutex != NULL); + assert(run != NULL); + + // create message queue for this thread + MSG dummy; + PeekMessage(&dummy, NULL, 0, 0, PM_NOREMOVE); + + int result = 0; + ARCH->lockMutex(m_serviceMutex); + m_daemonThreadID = GetCurrentThreadId(); + while (m_serviceState != SERVICE_STOPPED) { + // wait until we're told to start + while (!isRunState(m_serviceState) && + m_serviceState != SERVICE_STOP_PENDING) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + + // run unless told to stop + if (m_serviceState != SERVICE_STOP_PENDING) { + ARCH->unlockMutex(m_serviceMutex); + try { + result = run(); + } + catch (...) { + ARCH->lockMutex(m_serviceMutex); + setStatusError(0); + m_serviceState = SERVICE_STOPPED; + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + ARCH->unlockMutex(m_serviceMutex); + throw; + } + ARCH->lockMutex(m_serviceMutex); + } + + // notify of new state + if (m_serviceState == SERVICE_PAUSE_PENDING) { + m_serviceState = SERVICE_PAUSED; + } + else { + m_serviceState = SERVICE_STOPPED; + } + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + } + ARCH->unlockMutex(m_serviceMutex); + return result; +} + +void +CArchDaemonWindows::doDaemonRunning(bool running) +{ + ARCH->lockMutex(m_serviceMutex); + if (running) { + m_serviceState = SERVICE_RUNNING; + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + } + ARCH->unlockMutex(m_serviceMutex); +} + +UINT +CArchDaemonWindows::doGetDaemonQuitMessage() +{ + return m_quitMessage; +} + +void +CArchDaemonWindows::setStatus(DWORD state) +{ + setStatus(state, 0, 0); +} + +void +CArchDaemonWindows::setStatus(DWORD state, DWORD step, DWORD waitHint) +{ + assert(s_daemon != NULL); + + SERVICE_STATUS status; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS; + status.dwCurrentState = state; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_PAUSE_CONTINUE | + SERVICE_ACCEPT_SHUTDOWN; + status.dwWin32ExitCode = NO_ERROR; + status.dwServiceSpecificExitCode = 0; + status.dwCheckPoint = step; + status.dwWaitHint = waitHint; + SetServiceStatus(s_daemon->m_statusHandle, &status); +} + +void +CArchDaemonWindows::setStatusError(DWORD error) +{ + assert(s_daemon != NULL); + + SERVICE_STATUS status; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS; + status.dwCurrentState = SERVICE_STOPPED; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_PAUSE_CONTINUE | + SERVICE_ACCEPT_SHUTDOWN; + status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + status.dwServiceSpecificExitCode = error; + status.dwCheckPoint = 0; + status.dwWaitHint = 0; + SetServiceStatus(s_daemon->m_statusHandle, &status); +} + +void +CArchDaemonWindows::serviceMain(DWORD argc, LPTSTR* argvIn) +{ + typedef std::vector ArgList; + typedef std::vector Arguments; + const char** argv = const_cast(argvIn); + + // create synchronization objects + m_serviceMutex = ARCH->newMutex(); + m_serviceCondVar = ARCH->newCondVar(); + + // register our service handler function + m_statusHandle = RegisterServiceCtrlHandler(argv[0], + &CArchDaemonWindows::serviceHandlerEntry); + if (m_statusHandle == 0) { + // cannot start as service + m_daemonResult = -1; + ARCH->closeCondVar(m_serviceCondVar); + ARCH->closeMutex(m_serviceMutex); + return; + } + + // tell service control manager that we're starting + m_serviceState = SERVICE_START_PENDING; + setStatus(m_serviceState, 0, 10000); + + // if no arguments supplied then try getting them from the registry. + // the first argument doesn't count because it's the service name. + Arguments args; + ArgList myArgv; + if (argc <= 1) { + // read command line + std::string commandLine; + HKEY key = openNTServicesKey(); + key = CArchMiscWindows::openKey(key, argvIn[0]); + key = CArchMiscWindows::openKey(key, _T("Parameters")); + if (key != NULL) { + commandLine = CArchMiscWindows::readValueString(key, + _T("CommandLine")); + } + + // if the command line isn't empty then parse and use it + if (!commandLine.empty()) { + // parse, honoring double quoted substrings + std::string::size_type i = commandLine.find_first_not_of(" \t"); + while (i != std::string::npos && i != commandLine.size()) { + // find end of string + std::string::size_type e; + if (commandLine[i] == '\"') { + // quoted. find closing quote. + ++i; + e = commandLine.find("\"", i); + + // whitespace must follow closing quote + if (e == std::string::npos || + (e + 1 != commandLine.size() && + commandLine[e + 1] != ' ' && + commandLine[e + 1] != '\t')) { + args.clear(); + break; + } + + // extract + args.push_back(commandLine.substr(i, e - i)); + i = e + 1; + } + else { + // unquoted. find next whitespace. + e = commandLine.find_first_of(" \t", i); + if (e == std::string::npos) { + e = commandLine.size(); + } + + // extract + args.push_back(commandLine.substr(i, e - i)); + i = e + 1; + } + + // next argument + i = commandLine.find_first_not_of(" \t", i); + } + + // service name goes first + myArgv.push_back(argv[0]); + + // get pointers + for (size_t i = 0; i < args.size(); ++i) { + myArgv.push_back(args[i].c_str()); + } + + // adjust argc/argv + argc = myArgv.size(); + argv = &myArgv[0]; + } + } + + try { + // invoke daemon function + m_daemonResult = m_daemonFunc(static_cast(argc), argv); + } + catch (XArchDaemonRunFailed& e) { + setStatusError(e.m_result); + m_daemonResult = -1; + } + catch (...) { + setStatusError(1); + m_daemonResult = -1; + } + + // clean up + ARCH->closeCondVar(m_serviceCondVar); + ARCH->closeMutex(m_serviceMutex); +} + +void WINAPI +CArchDaemonWindows::serviceMainEntry(DWORD argc, LPTSTR* argv) +{ + s_daemon->serviceMain(argc, argv); +} + +void +CArchDaemonWindows::serviceHandler(DWORD ctrl) +{ + assert(m_serviceMutex != NULL); + assert(m_serviceCondVar != NULL); + + ARCH->lockMutex(m_serviceMutex); + + // ignore request if service is already stopped + if (s_daemon == NULL || m_serviceState == SERVICE_STOPPED) { + if (s_daemon != NULL) { + setStatus(m_serviceState); + } + ARCH->unlockMutex(m_serviceMutex); + return; + } + + switch (ctrl) { + case SERVICE_CONTROL_PAUSE: + m_serviceState = SERVICE_PAUSE_PENDING; + setStatus(m_serviceState, 0, 5000); + PostThreadMessage(m_daemonThreadID, m_quitMessage, 0, 0); + while (isRunState(m_serviceState)) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + break; + + case SERVICE_CONTROL_CONTINUE: + // FIXME -- maybe should flush quit messages from queue + m_serviceState = SERVICE_CONTINUE_PENDING; + setStatus(m_serviceState, 0, 5000); + ARCH->broadcastCondVar(m_serviceCondVar); + break; + + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + m_serviceState = SERVICE_STOP_PENDING; + setStatus(m_serviceState, 0, 5000); + PostThreadMessage(m_daemonThreadID, m_quitMessage, 0, 0); + ARCH->broadcastCondVar(m_serviceCondVar); + while (isRunState(m_serviceState)) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + break; + + default: + // unknown service command + // fall through + + case SERVICE_CONTROL_INTERROGATE: + setStatus(m_serviceState); + break; + } + + ARCH->unlockMutex(m_serviceMutex); +} + +void WINAPI +CArchDaemonWindows::serviceHandlerEntry(DWORD ctrl) +{ + s_daemon->serviceHandler(ctrl); +} diff --git a/lib/arch/CArchDaemonWindows.h b/lib/arch/CArchDaemonWindows.h new file mode 100644 index 00000000..ed09fab9 --- /dev/null +++ b/lib/arch/CArchDaemonWindows.h @@ -0,0 +1,134 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHDAEMONWINDOWS_H +#define CARCHDAEMONWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "IArchDaemon.h" +#include "IArchMultithread.h" +#include "stdstring.h" +#include +#include + +#define ARCH_DAEMON CArchDaemonWindows + +//! Win32 implementation of IArchDaemon +class CArchDaemonWindows : public IArchDaemon { +public: + typedef int (*RunFunc)(void); + + CArchDaemonWindows(); + virtual ~CArchDaemonWindows(); + + //! Run the daemon + /*! + When the client calls \c daemonize(), the \c DaemonFunc should call this + function after initialization and argument parsing to perform the + daemon processing. The \c runFunc should perform the daemon's + main loop, calling \c daemonRunning(true) when it enters the main loop + (i.e. after initialization) and \c daemonRunning(false) when it leaves + the main loop. The \c runFunc is called in a new thread and when the + daemon must exit the main loop due to some external control the + getDaemonQuitMessage() is posted to the thread. This function returns + what \c runFunc returns. \c runFunc should call \c daemonFailed() if + the daemon fails. + */ + static int runDaemon(RunFunc runFunc); + + //! Indicate daemon is in main loop + /*! + The \c runFunc passed to \c runDaemon() should call this function + to indicate when it has entered (\c running is \c true) or exited + (\c running is \c false) the main loop. + */ + static void daemonRunning(bool running); + + //! Indicate failure of running daemon + /*! + The \c runFunc passed to \c runDaemon() should call this function + to indicate failure. \c result is returned by \c daemonize(). + */ + static void daemonFailed(int result); + + //! Get daemon quit message + /*! + The windows NT daemon tells daemon thread to exit by posting this + message to it. The thread must, of course, have a message queue + for this to work. + */ + static UINT getDaemonQuitMessage(); + + // IArchDaemon overrides + virtual void installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers); + virtual void uninstallDaemon(const char* name, bool allUsers); + virtual int daemonize(const char* name, DaemonFunc func); + virtual bool canInstallDaemon(const char* name, bool allUsers); + virtual bool isDaemonInstalled(const char* name, bool allUsers); + +private: + static HKEY openNTServicesKey(); + static HKEY open95ServicesKey(); + static HKEY openUserStartupKey(); + + int doRunDaemon(RunFunc runFunc); + void doDaemonRunning(bool running); + UINT doGetDaemonQuitMessage(); + + static void setStatus(DWORD state); + static void setStatus(DWORD state, DWORD step, DWORD waitHint); + static void setStatusError(DWORD error); + + static bool isRunState(DWORD state); + + void serviceMain(DWORD, LPTSTR*); + static void WINAPI serviceMainEntry(DWORD, LPTSTR*); + + void serviceHandler(DWORD ctrl); + static void WINAPI serviceHandlerEntry(DWORD ctrl); + +private: + class XArchDaemonRunFailed { + public: + XArchDaemonRunFailed(int result) : m_result(result) { } + + public: + int m_result; + }; + +private: + static CArchDaemonWindows* s_daemon; + + CArchMutex m_serviceMutex; + CArchCond m_serviceCondVar; + DWORD m_serviceState; + bool m_serviceHandlerWaiting; + bool m_serviceRunning; + + DWORD m_daemonThreadID; + DaemonFunc m_daemonFunc; + int m_daemonResult; + + SERVICE_STATUS_HANDLE m_statusHandle; + + UINT m_quitMessage; +}; + +#endif diff --git a/lib/arch/CArchFileUnix.cpp b/lib/arch/CArchFileUnix.cpp new file mode 100644 index 00000000..89bb51dc --- /dev/null +++ b/lib/arch/CArchFileUnix.cpp @@ -0,0 +1,98 @@ +/* + * 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 "CArchFileUnix.h" +#include +#include +#include +#include +#include + +// +// CArchFileUnix +// + +CArchFileUnix::CArchFileUnix() +{ + // do nothing +} + +CArchFileUnix::~CArchFileUnix() +{ + // do nothing +} + +const char* +CArchFileUnix::getBasename(const char* pathname) +{ + if (pathname == NULL) { + return NULL; + } + + const char* basename = strrchr(pathname, '/'); + if (basename != NULL) { + return basename + 1; + } + else { + return pathname; + } +} + +std::string +CArchFileUnix::getUserDirectory() +{ + char* buffer = NULL; + std::string dir; +#if HAVE_GETPWUID_R + struct passwd pwent; + struct passwd* pwentp; +#if defined(_SC_GETPW_R_SIZE_MAX) + long size = sysconf(_SC_GETPW_R_SIZE_MAX); + if (size == -1) { + size = BUFSIZ; + } +#else + long size = BUFSIZ; +#endif + buffer = new char[size]; + getpwuid_r(getuid(), &pwent, buffer, size, &pwentp); +#else + struct passwd* pwentp = getpwuid(getuid()); +#endif + if (pwentp != NULL && pwentp->pw_dir != NULL) { + dir = pwentp->pw_dir; + } + delete[] buffer; + return dir; +} + +std::string +CArchFileUnix::getSystemDirectory() +{ + return "/etc"; +} + +std::string +CArchFileUnix::concatPath(const std::string& prefix, + const std::string& suffix) +{ + std::string path; + path.reserve(prefix.size() + 1 + suffix.size()); + path += prefix; + if (path.size() == 0 || path[path.size() - 1] != '/') { + path += '/'; + } + path += suffix; + return path; +} diff --git a/lib/arch/CArchFileUnix.h b/lib/arch/CArchFileUnix.h new file mode 100644 index 00000000..41d00e90 --- /dev/null +++ b/lib/arch/CArchFileUnix.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHFILEUNIX_H +#define CARCHFILEUNIX_H + +#include "IArchFile.h" + +#define ARCH_FILE CArchFileUnix + +//! Unix implementation of IArchFile +class CArchFileUnix : public IArchFile { +public: + CArchFileUnix(); + virtual ~CArchFileUnix(); + + // IArchFile overrides + virtual const char* getBasename(const char* pathname); + virtual std::string getUserDirectory(); + virtual std::string getSystemDirectory(); + virtual std::string concatPath(const std::string& prefix, + const std::string& suffix); +}; + +#endif diff --git a/lib/arch/CArchFileWindows.cpp b/lib/arch/CArchFileWindows.cpp new file mode 100644 index 00000000..5debb17b --- /dev/null +++ b/lib/arch/CArchFileWindows.cpp @@ -0,0 +1,132 @@ +/* + * 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 "CArchFileWindows.h" +#include +#include +#include +#include + +// +// CArchFileWindows +// + +CArchFileWindows::CArchFileWindows() +{ + // do nothing +} + +CArchFileWindows::~CArchFileWindows() +{ + // do nothing +} + +const char* +CArchFileWindows::getBasename(const char* pathname) +{ + if (pathname == NULL) { + return NULL; + } + + // check for last slash + const char* basename = strrchr(pathname, '/'); + if (basename != NULL) { + ++basename; + } + else { + basename = pathname; + } + + // check for last backslash + const char* basename2 = strrchr(pathname, '\\'); + if (basename2 != NULL && basename2 > basename) { + basename = basename2 + 1; + } + + return basename; +} + +std::string +CArchFileWindows::getUserDirectory() +{ + // try %HOMEPATH% + TCHAR dir[MAX_PATH]; + DWORD size = sizeof(dir) / sizeof(TCHAR); + DWORD result = GetEnvironmentVariable(_T("HOMEPATH"), dir, size); + if (result != 0 && result <= size) { + // sanity check -- if dir doesn't appear to start with a + // drive letter and isn't a UNC name then don't use it + // FIXME -- allow UNC names + if (dir[0] != '\0' && (dir[1] == ':' || + ((dir[0] == '\\' || dir[0] == '/') && + (dir[1] == '\\' || dir[1] == '/')))) { + return dir; + } + } + + // get the location of the personal files. that's as close to + // a home directory as we're likely to find. + ITEMIDLIST* idl; + if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_PERSONAL, &idl))) { + TCHAR* path = NULL; + if (SHGetPathFromIDList(idl, dir)) { + DWORD attr = GetFileAttributes(dir); + if (attr != 0xffffffff && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + path = dir; + } + + IMalloc* shalloc; + if (SUCCEEDED(SHGetMalloc(&shalloc))) { + shalloc->Free(idl); + shalloc->Release(); + } + + if (path != NULL) { + return path; + } + } + + // use root of C drive as a default + return "C:"; +} + +std::string +CArchFileWindows::getSystemDirectory() +{ + // get windows directory + char dir[MAX_PATH]; + if (GetWindowsDirectory(dir, sizeof(dir)) != 0) { + return dir; + } + else { + // can't get it. use C:\ as a default. + return "C:"; + } +} + +std::string +CArchFileWindows::concatPath(const std::string& prefix, + const std::string& suffix) +{ + std::string path; + path.reserve(prefix.size() + 1 + suffix.size()); + path += prefix; + if (path.size() == 0 || + (path[path.size() - 1] != '\\' && + path[path.size() - 1] != '/')) { + path += '\\'; + } + path += suffix; + return path; +} diff --git a/lib/arch/CArchFileWindows.h b/lib/arch/CArchFileWindows.h new file mode 100644 index 00000000..617b1c40 --- /dev/null +++ b/lib/arch/CArchFileWindows.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHFILEWINDOWS_H +#define CARCHFILEWINDOWS_H + +#include "IArchFile.h" + +#define ARCH_FILE CArchFileWindows + +//! Win32 implementation of IArchFile +class CArchFileWindows : public IArchFile { +public: + CArchFileWindows(); + virtual ~CArchFileWindows(); + + // IArchFile overrides + virtual const char* getBasename(const char* pathname); + virtual std::string getUserDirectory(); + virtual std::string getSystemDirectory(); + virtual std::string concatPath(const std::string& prefix, + const std::string& suffix); +}; + +#endif diff --git a/lib/arch/CArchLogUnix.cpp b/lib/arch/CArchLogUnix.cpp new file mode 100644 index 00000000..093d89f9 --- /dev/null +++ b/lib/arch/CArchLogUnix.cpp @@ -0,0 +1,79 @@ +/* + * 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 "CArchLogUnix.h" +#include + +// +// CArchLogUnix +// + +CArchLogUnix::CArchLogUnix() +{ + // do nothing +} + +CArchLogUnix::~CArchLogUnix() +{ + // do nothing +} + +void +CArchLogUnix::openLog(const char* name) +{ + openlog(name, 0, LOG_DAEMON); +} + +void +CArchLogUnix::closeLog() +{ + closelog(); +} + +void +CArchLogUnix::showLog(bool) +{ + // do nothing +} + +void +CArchLogUnix::writeLog(ELevel level, const char* msg) +{ + // convert level + int priority; + switch (level) { + case kERROR: + priority = LOG_ERR; + break; + + case kWARNING: + priority = LOG_WARNING; + break; + + case kNOTE: + priority = LOG_NOTICE; + break; + + case kINFO: + priority = LOG_INFO; + break; + + default: + priority = LOG_DEBUG; + break; + } + + // log it + syslog(priority, "%s", msg); +} diff --git a/lib/arch/CArchLogUnix.h b/lib/arch/CArchLogUnix.h new file mode 100644 index 00000000..91070b45 --- /dev/null +++ b/lib/arch/CArchLogUnix.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHLOGUNIX_H +#define CARCHLOGUNIX_H + +#include "IArchLog.h" + +#define ARCH_LOG CArchLogUnix + +//! Unix implementation of IArchLog +class CArchLogUnix : public IArchLog { +public: + CArchLogUnix(); + virtual ~CArchLogUnix(); + + // IArchLog overrides + virtual void openLog(const char* name); + virtual void closeLog(); + virtual void showLog(bool); + virtual void writeLog(ELevel, const char*); +}; + +#endif diff --git a/lib/arch/CArchLogWindows.cpp b/lib/arch/CArchLogWindows.cpp new file mode 100644 index 00000000..0ac89131 --- /dev/null +++ b/lib/arch/CArchLogWindows.cpp @@ -0,0 +1,90 @@ +/* + * 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 "CArchLogWindows.h" +#include "CArchMiscWindows.h" +#include + +// +// CArchLogWindows +// + +CArchLogWindows::CArchLogWindows() : m_eventLog(NULL) +{ + // do nothing +} + +CArchLogWindows::~CArchLogWindows() +{ + // do nothing +} + +void +CArchLogWindows::openLog(const char* name) +{ + if (m_eventLog == NULL && !CArchMiscWindows::isWindows95Family()) { + m_eventLog = RegisterEventSource(NULL, name); + } +} + +void +CArchLogWindows::closeLog() +{ + if (m_eventLog != NULL) { + DeregisterEventSource(m_eventLog); + m_eventLog = NULL; + } +} + +void +CArchLogWindows::showLog(bool) +{ + // do nothing +} + +void +CArchLogWindows::writeLog(ELevel level, const char* msg) +{ + if (m_eventLog != NULL) { + // convert priority + WORD type; + switch (level) { + case kERROR: + type = EVENTLOG_ERROR_TYPE; + break; + + case kWARNING: + type = EVENTLOG_WARNING_TYPE; + break; + + default: + type = EVENTLOG_INFORMATION_TYPE; + break; + } + + // log it + // FIXME -- win32 wants to use a message table to look up event + // strings. log messages aren't organized that way so we'll + // just dump our string into the raw data section of the event + // so users can at least see the message. note that we use our + // level as the event category. + ReportEvent(m_eventLog, type, static_cast(level), + 0, // event ID + NULL, + 0, + strlen(msg) + 1, // raw data size + NULL, + const_cast(msg));// raw data + } +} diff --git a/lib/arch/CArchLogWindows.h b/lib/arch/CArchLogWindows.h new file mode 100644 index 00000000..e8812536 --- /dev/null +++ b/lib/arch/CArchLogWindows.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHLOGWINDOWS_H +#define CARCHLOGWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "IArchLog.h" +#include + +#define ARCH_LOG CArchLogWindows + +//! Win32 implementation of IArchLog +class CArchLogWindows : public IArchLog { +public: + CArchLogWindows(); + virtual ~CArchLogWindows(); + + // IArchLog overrides + virtual void openLog(const char* name); + virtual void closeLog(); + virtual void showLog(bool showIfEmpty); + virtual void writeLog(ELevel, const char*); + +private: + HANDLE m_eventLog; +}; + +#endif diff --git a/lib/arch/CArchMiscWindows.cpp b/lib/arch/CArchMiscWindows.cpp new file mode 100644 index 00000000..e2fb2dce --- /dev/null +++ b/lib/arch/CArchMiscWindows.cpp @@ -0,0 +1,416 @@ +/* + * 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()), + 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()), + 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; +} diff --git a/lib/arch/CArchMiscWindows.h b/lib/arch/CArchMiscWindows.h new file mode 100644 index 00000000..95a1d136 --- /dev/null +++ b/lib/arch/CArchMiscWindows.h @@ -0,0 +1,191 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHMISCWINDOWS_H +#define CARCHMISCWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "common.h" +#include "stdstring.h" +#include "stdset.h" +#include + +//! Miscellaneous win32 functions. +class CArchMiscWindows { +public: + enum EValueType { + kUNKNOWN, + kNO_VALUE, + kUINT, + kSTRING, + kBINARY + }; + enum EBusyModes { + kIDLE = 0x0000, + kSYSTEM = 0x0001, + kDISPLAY = 0x0002 + }; + + typedef int (*RunFunc)(void); + + //! Initialize + static void init(); + + //! Test if windows 95, et al. + /*! + Returns true iff the platform is win95/98/me. + */ + static bool isWindows95Family(); + + //! Test if windows 95, et al. + /*! + Returns true iff the platform is win98 or win2k or higher (i.e. + not windows 95 or windows NT). + */ + static bool isWindowsModern(); + + //! Set the application icons + /*! + Set the application icons. + */ + static void setIcons(HICON largeIcon, HICON smallIcon); + + //! Get the application icons + /*! + Get the application icons. + */ + static void getIcons(HICON& largeIcon, HICON& smallIcon); + + //! Run the daemon + /*! + Delegates to CArchDaemonWindows. + */ + static int runDaemon(RunFunc runFunc); + + //! Indicate daemon is in main loop + /*! + Delegates to CArchDaemonWindows. + */ + static void daemonRunning(bool running); + + //! Indicate failure of running daemon + /*! + Delegates to CArchDaemonWindows. + */ + static void daemonFailed(int result); + + //! Get daemon quit message + /*! + Delegates to CArchDaemonWindows. + */ + static UINT getDaemonQuitMessage(); + + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* child); + + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* const* keyPath); + + //! Open/create and return a registry key, closing the parent key + static HKEY addKey(HKEY parent, const TCHAR* child); + + //! Open/create and return a registry key, closing the parent key + static HKEY addKey(HKEY parent, const TCHAR* const* keyPath); + + //! Close a key + static void closeKey(HKEY); + + //! Delete a key (which should have no subkeys) + static void deleteKey(HKEY parent, const TCHAR* name); + + //! Delete a value + static void deleteValue(HKEY parent, const TCHAR* name); + + //! Test if a value exists + static bool hasValue(HKEY key, const TCHAR* name); + + //! Get type of value + static EValueType typeOfValue(HKEY key, const TCHAR* name); + + //! Set a string value in the registry + static void setValue(HKEY key, const TCHAR* name, + const std::string& value); + + //! Set a DWORD value in the registry + static void setValue(HKEY key, const TCHAR* name, DWORD value); + + //! Set a BINARY value in the registry + /*! + Sets the \p name value of \p key to \p value.data(). + */ + static void setValueBinary(HKEY key, const TCHAR* name, + const std::string& value); + + //! Read a string value from the registry + static std::string readValueString(HKEY, const TCHAR* name); + + //! Read a DWORD value from the registry + static DWORD readValueInt(HKEY, const TCHAR* name); + + //! Read a BINARY value from the registry + static std::string readValueBinary(HKEY, const TCHAR* name); + + //! Add a dialog + static void addDialog(HWND); + + //! Remove a dialog + static void removeDialog(HWND); + + //! Process dialog message + /*! + Checks if the message is destined for a dialog. If so the message + is passed to the dialog and returns true, otherwise returns false. + */ + static bool processDialog(MSG*); + + //! Disable power saving + static void addBusyState(DWORD busyModes); + + //! Enable power saving + static void removeBusyState(DWORD busyModes); + +private: + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* child, bool create); + + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* const* keyPath, + bool create); + + //! Read a string value from the registry + static std::string readBinaryOrString(HKEY, const TCHAR* name, DWORD type); + + //! Set thread busy state + static void setThreadExecutionState(DWORD); + + static DWORD WINAPI dummySetThreadExecutionState(DWORD); + +private: + typedef std::set CDialogs; + typedef DWORD (WINAPI *STES_t)(DWORD); + + static CDialogs* s_dialogs; + static DWORD s_busyState; + static STES_t s_stes; + static HICON s_largeIcon; + static HICON s_smallIcon; +}; + +#endif diff --git a/lib/arch/CArchMultithreadPosix.cpp b/lib/arch/CArchMultithreadPosix.cpp new file mode 100644 index 00000000..ec11fc50 --- /dev/null +++ b/lib/arch/CArchMultithreadPosix.cpp @@ -0,0 +1,806 @@ +/* + * 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 "CArchMultithreadPosix.h" +#include "CArch.h" +#include "XArch.h" +#include +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif +#include + +#define SIGWAKEUP SIGUSR1 + +#if !HAVE_PTHREAD_SIGNAL + // boy, is this platform broken. forget about pthread signal + // handling and let signals through to every process. synergy + // will not terminate cleanly when it gets SIGTERM or SIGINT. +# define pthread_sigmask sigprocmask +# define pthread_kill(tid_, sig_) kill(0, (sig_)) +# define sigwait(set_, sig_) +# undef HAVE_POSIX_SIGWAIT +# define HAVE_POSIX_SIGWAIT 1 +#endif + +static +void +setSignalSet(sigset_t* sigset) +{ + sigemptyset(sigset); + sigaddset(sigset, SIGHUP); + sigaddset(sigset, SIGINT); + sigaddset(sigset, SIGTERM); + sigaddset(sigset, SIGUSR2); +} + +// +// CArchThreadImpl +// + +class CArchThreadImpl { +public: + CArchThreadImpl(); + +public: + int m_refCount; + IArchMultithread::ThreadID m_id; + pthread_t m_thread; + IArchMultithread::ThreadFunc m_func; + void* m_userData; + bool m_cancel; + bool m_cancelling; + bool m_exited; + void* m_result; + void* m_networkData; +}; + +CArchThreadImpl::CArchThreadImpl() : + m_refCount(1), + m_id(0), + m_func(NULL), + m_userData(NULL), + m_cancel(false), + m_cancelling(false), + m_exited(false), + m_result(NULL), + m_networkData(NULL) +{ + // do nothing +} + + +// +// CArchMultithreadPosix +// + +CArchMultithreadPosix* CArchMultithreadPosix::s_instance = NULL; + +CArchMultithreadPosix::CArchMultithreadPosix() : + m_newThreadCalled(false), + m_nextID(0) +{ + assert(s_instance == NULL); + + s_instance = this; + + // no signal handlers + for (size_t i = 0; i < kNUM_SIGNALS; ++i) { + m_signalFunc[i] = NULL; + m_signalUserData[i] = NULL; + } + + // create mutex for thread list + m_threadMutex = newMutex(); + + // create thread for calling (main) thread and add it to our + // list. no need to lock the mutex since we're the only thread. + m_mainThread = new CArchThreadImpl; + m_mainThread->m_thread = pthread_self(); + insert(m_mainThread); + + // install SIGWAKEUP handler. this causes SIGWAKEUP to interrupt + // system calls. we use that when cancelling a thread to force it + // to wake up immediately if it's blocked in a system call. we + // won't need this until another thread is created but it's fine + // to install it now. + struct sigaction act; + sigemptyset(&act.sa_mask); +# if defined(SA_INTERRUPT) + act.sa_flags = SA_INTERRUPT; +# else + act.sa_flags = 0; +# endif + act.sa_handler = &threadCancel; + sigaction(SIGWAKEUP, &act, NULL); + + // set desired signal dispositions. let SIGWAKEUP through but + // ignore SIGPIPE (we'll handle EPIPE). + sigset_t sigset; + sigemptyset(&sigset); + sigaddset(&sigset, SIGWAKEUP); + pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); + sigemptyset(&sigset); + sigaddset(&sigset, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &sigset, NULL); +} + +CArchMultithreadPosix::~CArchMultithreadPosix() +{ + assert(s_instance != NULL); + + closeMutex(m_threadMutex); + s_instance = NULL; +} + +void +CArchMultithreadPosix::setNetworkDataForCurrentThread(void* data) +{ + lockMutex(m_threadMutex); + CArchThreadImpl* thread = find(pthread_self()); + thread->m_networkData = data; + unlockMutex(m_threadMutex); +} + +void* +CArchMultithreadPosix::getNetworkDataForThread(CArchThread thread) +{ + lockMutex(m_threadMutex); + void* data = thread->m_networkData; + unlockMutex(m_threadMutex); + return data; +} + +CArchMultithreadPosix* +CArchMultithreadPosix::getInstance() +{ + return s_instance; +} + +CArchCond +CArchMultithreadPosix::newCondVar() +{ + CArchCondImpl* cond = new CArchCondImpl; + int status = pthread_cond_init(&cond->m_cond, NULL); + (void)status; + assert(status == 0); + return cond; +} + +void +CArchMultithreadPosix::closeCondVar(CArchCond cond) +{ + int status = pthread_cond_destroy(&cond->m_cond); + (void)status; + assert(status == 0); + delete cond; +} + +void +CArchMultithreadPosix::signalCondVar(CArchCond cond) +{ + int status = pthread_cond_signal(&cond->m_cond); + (void)status; + assert(status == 0); +} + +void +CArchMultithreadPosix::broadcastCondVar(CArchCond cond) +{ + int status = pthread_cond_broadcast(&cond->m_cond); + (void)status; + assert(status == 0); +} + +bool +CArchMultithreadPosix::waitCondVar(CArchCond cond, + CArchMutex mutex, double timeout) +{ + // we can't wait on a condition variable and also wake it up for + // cancellation since we don't use posix cancellation. so we + // must wake up periodically to check for cancellation. we + // can't simply go back to waiting after the check since the + // condition may have changed and we'll have lost the signal. + // so we have to return to the caller. since the caller will + // always check for spurious wakeups the only drawback here is + // performance: we're waking up a lot more than desired. + static const double maxCancellationLatency = 0.1; + if (timeout < 0.0 || timeout > maxCancellationLatency) { + timeout = maxCancellationLatency; + } + + // see if we should cancel this thread + testCancelThread(); + + // get final time + struct timeval now; + gettimeofday(&now, NULL); + struct timespec finalTime; + finalTime.tv_sec = now.tv_sec; + finalTime.tv_nsec = now.tv_usec * 1000; + long timeout_sec = (long)timeout; + long timeout_nsec = (long)(1.0e+9 * (timeout - timeout_sec)); + finalTime.tv_sec += timeout_sec; + finalTime.tv_nsec += timeout_nsec; + if (finalTime.tv_nsec >= 1000000000) { + finalTime.tv_nsec -= 1000000000; + finalTime.tv_sec += 1; + } + + // wait + int status = pthread_cond_timedwait(&cond->m_cond, + &mutex->m_mutex, &finalTime); + + // check for cancel again + testCancelThread(); + + switch (status) { + case 0: + // success + return true; + + case ETIMEDOUT: + return false; + + default: + assert(0 && "condition variable wait error"); + return false; + } +} + +CArchMutex +CArchMultithreadPosix::newMutex() +{ + pthread_mutexattr_t attr; + int status = pthread_mutexattr_init(&attr); + assert(status == 0); + CArchMutexImpl* mutex = new CArchMutexImpl; + status = pthread_mutex_init(&mutex->m_mutex, &attr); + assert(status == 0); + return mutex; +} + +void +CArchMultithreadPosix::closeMutex(CArchMutex mutex) +{ + int status = pthread_mutex_destroy(&mutex->m_mutex); + (void)status; + assert(status == 0); + delete mutex; +} + +void +CArchMultithreadPosix::lockMutex(CArchMutex mutex) +{ + int status = pthread_mutex_lock(&mutex->m_mutex); + + switch (status) { + case 0: + // success + return; + + case EDEADLK: + assert(0 && "lock already owned"); + break; + + case EAGAIN: + assert(0 && "too many recursive locks"); + break; + + default: + assert(0 && "unexpected error"); + break; + } +} + +void +CArchMultithreadPosix::unlockMutex(CArchMutex mutex) +{ + int status = pthread_mutex_unlock(&mutex->m_mutex); + + switch (status) { + case 0: + // success + return; + + case EPERM: + assert(0 && "thread doesn't own a lock"); + break; + + default: + assert(0 && "unexpected error"); + break; + } +} + +CArchThread +CArchMultithreadPosix::newThread(ThreadFunc func, void* data) +{ + assert(func != NULL); + + // initialize signal handler. we do this here instead of the + // constructor so we can avoid daemonizing (using fork()) + // when there are multiple threads. clients can safely + // use condition variables and mutexes before creating a + // new thread and they can safely use the only thread + // they have access to, the main thread, so they really + // can't tell the difference. + if (!m_newThreadCalled) { + m_newThreadCalled = true; +#if HAVE_PTHREAD_SIGNAL + startSignalHandler(); +#endif + } + + lockMutex(m_threadMutex); + + // create thread impl for new thread + CArchThreadImpl* thread = new CArchThreadImpl; + thread->m_func = func; + thread->m_userData = data; + + // create the thread. pthread_create() on RedHat 7.2 smp fails + // if passed a NULL attr so use a default attr. + pthread_attr_t attr; + int status = pthread_attr_init(&attr); + if (status == 0) { + status = pthread_create(&thread->m_thread, &attr, + &CArchMultithreadPosix::threadFunc, thread); + pthread_attr_destroy(&attr); + } + + // check if thread was started + if (status != 0) { + // failed to start thread so clean up + delete thread; + thread = NULL; + } + else { + // add thread to list + insert(thread); + + // increment ref count to account for the thread itself + refThread(thread); + } + + // note that the child thread will wait until we release this mutex + unlockMutex(m_threadMutex); + + return thread; +} + +CArchThread +CArchMultithreadPosix::newCurrentThread() +{ + lockMutex(m_threadMutex); + CArchThreadImpl* thread = find(pthread_self()); + unlockMutex(m_threadMutex); + assert(thread != NULL); + return thread; +} + +void +CArchMultithreadPosix::closeThread(CArchThread thread) +{ + assert(thread != NULL); + + // decrement ref count and clean up thread if no more references + if (--thread->m_refCount == 0) { + // detach from thread (unless it's the main thread) + if (thread->m_func != NULL) { + pthread_detach(thread->m_thread); + } + + // remove thread from list + lockMutex(m_threadMutex); + assert(findNoRef(thread->m_thread) == thread); + erase(thread); + unlockMutex(m_threadMutex); + + // done with thread + delete thread; + } +} + +CArchThread +CArchMultithreadPosix::copyThread(CArchThread thread) +{ + refThread(thread); + return thread; +} + +void +CArchMultithreadPosix::cancelThread(CArchThread thread) +{ + assert(thread != NULL); + + // set cancel and wakeup flags if thread can be cancelled + bool wakeup = false; + lockMutex(m_threadMutex); + if (!thread->m_exited && !thread->m_cancelling) { + thread->m_cancel = true; + wakeup = true; + } + unlockMutex(m_threadMutex); + + // force thread to exit system calls if wakeup is true + if (wakeup) { + pthread_kill(thread->m_thread, SIGWAKEUP); + } +} + +void +CArchMultithreadPosix::setPriorityOfThread(CArchThread thread, int /*n*/) +{ + assert(thread != NULL); + + // FIXME +} + +void +CArchMultithreadPosix::testCancelThread() +{ + // find current thread + lockMutex(m_threadMutex); + CArchThreadImpl* thread = findNoRef(pthread_self()); + unlockMutex(m_threadMutex); + + // test cancel on thread + testCancelThreadImpl(thread); +} + +bool +CArchMultithreadPosix::wait(CArchThread target, double timeout) +{ + assert(target != NULL); + + lockMutex(m_threadMutex); + + // find current thread + CArchThreadImpl* self = findNoRef(pthread_self()); + + // ignore wait if trying to wait on ourself + if (target == self) { + unlockMutex(m_threadMutex); + return false; + } + + // ref the target so it can't go away while we're watching it + refThread(target); + + unlockMutex(m_threadMutex); + + try { + // do first test regardless of timeout + testCancelThreadImpl(self); + if (isExitedThread(target)) { + closeThread(target); + return true; + } + + // wait and repeat test if there's a timeout + if (timeout != 0.0) { + const double start = ARCH->time(); + do { + // wait a little + ARCH->sleep(0.05); + + // repeat test + testCancelThreadImpl(self); + if (isExitedThread(target)) { + closeThread(target); + return true; + } + + // repeat wait and test until timed out + } while (timeout < 0.0 || (ARCH->time() - start) <= timeout); + } + + closeThread(target); + return false; + } + catch (...) { + closeThread(target); + throw; + } +} + +bool +CArchMultithreadPosix::isSameThread(CArchThread thread1, CArchThread thread2) +{ + return (thread1 == thread2); +} + +bool +CArchMultithreadPosix::isExitedThread(CArchThread thread) +{ + lockMutex(m_threadMutex); + bool exited = thread->m_exited; + unlockMutex(m_threadMutex); + return exited; +} + +void* +CArchMultithreadPosix::getResultOfThread(CArchThread thread) +{ + lockMutex(m_threadMutex); + void* result = thread->m_result; + unlockMutex(m_threadMutex); + return result; +} + +IArchMultithread::ThreadID +CArchMultithreadPosix::getIDOfThread(CArchThread thread) +{ + return thread->m_id; +} + +void +CArchMultithreadPosix::setSignalHandler( + ESignal signal, SignalFunc func, void* userData) +{ + lockMutex(m_threadMutex); + m_signalFunc[signal] = func; + m_signalUserData[signal] = userData; + unlockMutex(m_threadMutex); +} + +void +CArchMultithreadPosix::raiseSignal(ESignal signal) +{ + lockMutex(m_threadMutex); + if (m_signalFunc[signal] != NULL) { + m_signalFunc[signal](signal, m_signalUserData[signal]); + pthread_kill(m_mainThread->m_thread, SIGWAKEUP); + } + else if (signal == kINTERRUPT || signal == kTERMINATE) { + ARCH->cancelThread(m_mainThread); + } + unlockMutex(m_threadMutex); +} + +void +CArchMultithreadPosix::startSignalHandler() +{ + // set signal mask. the main thread blocks these signals and + // the signal handler thread will listen for them. + sigset_t sigset, oldsigset; + setSignalSet(&sigset); + pthread_sigmask(SIG_BLOCK, &sigset, &oldsigset); + + // fire up the INT and TERM signal handler thread. we could + // instead arrange to catch and handle these signals but + // we'd be unable to cancel the main thread since no pthread + // calls are allowed in a signal handler. + pthread_attr_t attr; + int status = pthread_attr_init(&attr); + if (status == 0) { + status = pthread_create(&m_signalThread, &attr, + &CArchMultithreadPosix::threadSignalHandler, + NULL); + pthread_attr_destroy(&attr); + } + if (status != 0) { + // can't create thread to wait for signal so don't block + // the signals. + pthread_sigmask(SIG_UNBLOCK, &oldsigset, NULL); + } +} + +CArchThreadImpl* +CArchMultithreadPosix::find(pthread_t thread) +{ + CArchThreadImpl* impl = findNoRef(thread); + if (impl != NULL) { + refThread(impl); + } + return impl; +} + +CArchThreadImpl* +CArchMultithreadPosix::findNoRef(pthread_t thread) +{ + // linear search + for (CThreadList::const_iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if ((*index)->m_thread == thread) { + return *index; + } + } + return NULL; +} + +void +CArchMultithreadPosix::insert(CArchThreadImpl* thread) +{ + assert(thread != NULL); + + // thread shouldn't already be on the list + assert(findNoRef(thread->m_thread) == NULL); + + // set thread id. note that we don't worry about m_nextID + // wrapping back to 0 and duplicating thread ID's since the + // likelihood of synergy running that long is vanishingly + // small. + thread->m_id = ++m_nextID; + + // append to list + m_threadList.push_back(thread); +} + +void +CArchMultithreadPosix::erase(CArchThreadImpl* thread) +{ + for (CThreadList::iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if (*index == thread) { + m_threadList.erase(index); + break; + } + } +} + +void +CArchMultithreadPosix::refThread(CArchThreadImpl* thread) +{ + assert(thread != NULL); + assert(findNoRef(thread->m_thread) != NULL); + ++thread->m_refCount; +} + +void +CArchMultithreadPosix::testCancelThreadImpl(CArchThreadImpl* thread) +{ + assert(thread != NULL); + + // update cancel state + lockMutex(m_threadMutex); + bool cancel = false; + if (thread->m_cancel && !thread->m_cancelling) { + thread->m_cancelling = true; + thread->m_cancel = false; + cancel = true; + } + unlockMutex(m_threadMutex); + + // unwind thread's stack if cancelling + if (cancel) { + throw XThreadCancel(); + } +} + +void* +CArchMultithreadPosix::threadFunc(void* vrep) +{ + // get the thread + CArchThreadImpl* thread = reinterpret_cast(vrep); + + // setup pthreads + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); + + // run thread + s_instance->doThreadFunc(thread); + + // terminate the thread + return NULL; +} + +void +CArchMultithreadPosix::doThreadFunc(CArchThread thread) +{ + // default priority is slightly below normal + setPriorityOfThread(thread, 1); + + // wait for parent to initialize this object + lockMutex(m_threadMutex); + unlockMutex(m_threadMutex); + + void* result = NULL; + try { + // go + result = (*thread->m_func)(thread->m_userData); + } + + catch (XThreadCancel&) { + // client called cancel() + } + catch (...) { + // note -- don't catch (...) to avoid masking bugs + lockMutex(m_threadMutex); + thread->m_exited = true; + unlockMutex(m_threadMutex); + closeThread(thread); + throw; + } + + // thread has exited + lockMutex(m_threadMutex); + thread->m_result = result; + thread->m_exited = true; + unlockMutex(m_threadMutex); + + // done with thread + closeThread(thread); +} + +void +CArchMultithreadPosix::threadCancel(int) +{ + // do nothing +} + +void* +CArchMultithreadPosix::threadSignalHandler(void*) +{ + // detach + pthread_detach(pthread_self()); + + // add signal to mask + sigset_t sigset; + setSignalSet(&sigset); + + // also wait on SIGABRT. on linux (others?) this thread (process) + // will persist after all the other threads evaporate due to an + // assert unless we wait on SIGABRT. that means our resources (like + // the socket we're listening on) are not released and never will be + // until the lingering thread is killed. i don't know why sigwait() + // should protect the thread from being killed. note that sigwait() + // doesn't actually return if we receive SIGABRT and, for some + // reason, we don't have to block SIGABRT. + sigaddset(&sigset, SIGABRT); + + // we exit the loop via thread cancellation in sigwait() + for (;;) { + // wait +#if HAVE_POSIX_SIGWAIT + int signal = 0; + sigwait(&sigset, &signal); +#else + sigwait(&sigset); +#endif + + // if we get here then the signal was raised + switch (signal) { + case SIGINT: + ARCH->raiseSignal(kINTERRUPT); + break; + + case SIGTERM: + ARCH->raiseSignal(kTERMINATE); + break; + + case SIGHUP: + ARCH->raiseSignal(kHANGUP); + break; + + case SIGUSR2: + ARCH->raiseSignal(kUSER); + break; + + default: + // ignore + break; + } + } + + return NULL; +} diff --git a/lib/arch/CArchMultithreadPosix.h b/lib/arch/CArchMultithreadPosix.h new file mode 100644 index 00000000..4e587cfc --- /dev/null +++ b/lib/arch/CArchMultithreadPosix.h @@ -0,0 +1,113 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHMULTITHREADPOSIX_H +#define CARCHMULTITHREADPOSIX_H + +#include "IArchMultithread.h" +#include "stdlist.h" +#include + +#define ARCH_MULTITHREAD CArchMultithreadPosix + +class CArchCondImpl { +public: + pthread_cond_t m_cond; +}; + +class CArchMutexImpl { +public: + pthread_mutex_t m_mutex; +}; + +//! Posix implementation of IArchMultithread +class CArchMultithreadPosix : public IArchMultithread { +public: + CArchMultithreadPosix(); + virtual ~CArchMultithreadPosix(); + + //! @name manipulators + //@{ + + void setNetworkDataForCurrentThread(void*); + + //@} + //! @name accessors + //@{ + + void* getNetworkDataForThread(CArchThread); + + static CArchMultithreadPosix* getInstance(); + + //@} + + // IArchMultithread overrides + virtual CArchCond newCondVar(); + virtual void closeCondVar(CArchCond); + virtual void signalCondVar(CArchCond); + virtual void broadcastCondVar(CArchCond); + virtual bool waitCondVar(CArchCond, CArchMutex, double timeout); + virtual CArchMutex newMutex(); + virtual void closeMutex(CArchMutex); + virtual void lockMutex(CArchMutex); + virtual void unlockMutex(CArchMutex); + virtual CArchThread newThread(ThreadFunc, void*); + virtual CArchThread newCurrentThread(); + virtual CArchThread copyThread(CArchThread); + virtual void closeThread(CArchThread); + virtual void cancelThread(CArchThread); + virtual void setPriorityOfThread(CArchThread, int n); + virtual void testCancelThread(); + virtual bool wait(CArchThread, double timeout); + virtual bool isSameThread(CArchThread, CArchThread); + virtual bool isExitedThread(CArchThread); + virtual void* getResultOfThread(CArchThread); + virtual ThreadID getIDOfThread(CArchThread); + virtual void setSignalHandler(ESignal, SignalFunc, void*); + virtual void raiseSignal(ESignal); + +private: + void startSignalHandler(); + + CArchThreadImpl* find(pthread_t thread); + CArchThreadImpl* findNoRef(pthread_t thread); + void insert(CArchThreadImpl* thread); + void erase(CArchThreadImpl* thread); + + void refThread(CArchThreadImpl* rep); + void testCancelThreadImpl(CArchThreadImpl* rep); + + void doThreadFunc(CArchThread thread); + static void* threadFunc(void* vrep); + static void threadCancel(int); + static void* threadSignalHandler(void* vrep); + +private: + typedef std::list CThreadList; + + static CArchMultithreadPosix* s_instance; + + bool m_newThreadCalled; + + CArchMutex m_threadMutex; + CArchThread m_mainThread; + CThreadList m_threadList; + ThreadID m_nextID; + + pthread_t m_signalThread; + SignalFunc m_signalFunc[kNUM_SIGNALS]; + void* m_signalUserData[kNUM_SIGNALS]; +}; + +#endif diff --git a/lib/arch/CArchMultithreadWindows.cpp b/lib/arch/CArchMultithreadWindows.cpp new file mode 100644 index 00000000..3adcd46a --- /dev/null +++ b/lib/arch/CArchMultithreadWindows.cpp @@ -0,0 +1,699 @@ +/* + * 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. + */ + +#if defined(_MSC_VER) && !defined(_MT) +# error multithreading compile option is required +#endif + +#include "CArchMultithreadWindows.h" +#include "CArch.h" +#include "XArch.h" +#include + +// +// note -- implementation of condition variable taken from: +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html +// titled "Strategies for Implementing POSIX Condition Variables +// on Win32." it also provides an implementation that doesn't +// suffer from the incorrectness problem described in our +// corresponding header but it is slower, still unfair, and +// can cause busy waiting. +// + +// +// CArchThreadImpl +// + +class CArchThreadImpl { +public: + CArchThreadImpl(); + ~CArchThreadImpl(); + +public: + int m_refCount; + HANDLE m_thread; + DWORD m_id; + IArchMultithread::ThreadFunc m_func; + void* m_userData; + HANDLE m_cancel; + bool m_cancelling; + HANDLE m_exit; + void* m_result; + void* m_networkData; +}; + +CArchThreadImpl::CArchThreadImpl() : + m_refCount(1), + m_thread(NULL), + m_id(0), + m_func(NULL), + m_userData(NULL), + m_cancelling(false), + m_result(NULL), + m_networkData(NULL) +{ + m_exit = CreateEvent(NULL, TRUE, FALSE, NULL); + m_cancel = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +CArchThreadImpl::~CArchThreadImpl() +{ + CloseHandle(m_exit); + CloseHandle(m_cancel); +} + + +// +// CArchMultithreadWindows +// + +CArchMultithreadWindows* CArchMultithreadWindows::s_instance = NULL; + +CArchMultithreadWindows::CArchMultithreadWindows() +{ + assert(s_instance == NULL); + s_instance = this; + + // no signal handlers + for (size_t i = 0; i < kNUM_SIGNALS; ++i) { + m_signalFunc[i] = NULL; + m_signalUserData[i] = NULL; + } + + // create mutex for thread list + m_threadMutex = newMutex(); + + // create thread for calling (main) thread and add it to our + // list. no need to lock the mutex since we're the only thread. + m_mainThread = new CArchThreadImpl; + m_mainThread->m_thread = NULL; + m_mainThread->m_id = GetCurrentThreadId(); + insert(m_mainThread); +} + +CArchMultithreadWindows::~CArchMultithreadWindows() +{ + s_instance = NULL; + + // clean up thread list + for (CThreadList::iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + delete *index; + } + + // done with mutex + delete m_threadMutex; +} + +void +CArchMultithreadWindows::setNetworkDataForCurrentThread(void* data) +{ + lockMutex(m_threadMutex); + CArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + thread->m_networkData = data; + unlockMutex(m_threadMutex); +} + +void* +CArchMultithreadWindows::getNetworkDataForThread(CArchThread thread) +{ + lockMutex(m_threadMutex); + void* data = thread->m_networkData; + unlockMutex(m_threadMutex); + return data; +} + +HANDLE +CArchMultithreadWindows::getCancelEventForCurrentThread() +{ + lockMutex(m_threadMutex); + CArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + return thread->m_cancel; +} + +CArchMultithreadWindows* +CArchMultithreadWindows::getInstance() +{ + return s_instance; +} + +CArchCond +CArchMultithreadWindows::newCondVar() +{ + CArchCondImpl* cond = new CArchCondImpl; + cond->m_events[CArchCondImpl::kSignal] = CreateEvent(NULL, + FALSE, FALSE, NULL); + cond->m_events[CArchCondImpl::kBroadcast] = CreateEvent(NULL, + TRUE, FALSE, NULL); + cond->m_waitCountMutex = newMutex(); + cond->m_waitCount = 0; + return cond; +} + +void +CArchMultithreadWindows::closeCondVar(CArchCond cond) +{ + CloseHandle(cond->m_events[CArchCondImpl::kSignal]); + CloseHandle(cond->m_events[CArchCondImpl::kBroadcast]); + closeMutex(cond->m_waitCountMutex); + delete cond; +} + +void +CArchMultithreadWindows::signalCondVar(CArchCond cond) +{ + // is anybody waiting? + lockMutex(cond->m_waitCountMutex); + const bool hasWaiter = (cond->m_waitCount > 0); + unlockMutex(cond->m_waitCountMutex); + + // wake one thread if anybody is waiting + if (hasWaiter) { + SetEvent(cond->m_events[CArchCondImpl::kSignal]); + } +} + +void +CArchMultithreadWindows::broadcastCondVar(CArchCond cond) +{ + // is anybody waiting? + lockMutex(cond->m_waitCountMutex); + const bool hasWaiter = (cond->m_waitCount > 0); + unlockMutex(cond->m_waitCountMutex); + + // wake all threads if anybody is waiting + if (hasWaiter) { + SetEvent(cond->m_events[CArchCondImpl::kBroadcast]); + } +} + +bool +CArchMultithreadWindows::waitCondVar(CArchCond cond, + CArchMutex mutex, double timeout) +{ + // prepare to wait + const DWORD winTimeout = (timeout < 0.0) ? INFINITE : + static_cast(1000.0 * timeout); + + // make a list of the condition variable events and the cancel event + // for the current thread. + HANDLE handles[4]; + handles[0] = cond->m_events[CArchCondImpl::kSignal]; + handles[1] = cond->m_events[CArchCondImpl::kBroadcast]; + handles[2] = getCancelEventForCurrentThread(); + + // update waiter count + lockMutex(cond->m_waitCountMutex); + ++cond->m_waitCount; + unlockMutex(cond->m_waitCountMutex); + + // release mutex. this should be atomic with the wait so that it's + // impossible for another thread to signal us between the unlock and + // the wait, which would lead to a lost signal on broadcasts. + // however, we're using a manual reset event for broadcasts which + // stays set until we reset it, so we don't lose the broadcast. + unlockMutex(mutex); + + // wait for a signal or broadcast + DWORD result = WaitForMultipleObjects(3, handles, FALSE, winTimeout); + + // cancel takes priority + if (result != WAIT_OBJECT_0 + 2 && + WaitForSingleObject(handles[2], 0) == WAIT_OBJECT_0) { + result = WAIT_OBJECT_0 + 2; + } + + // update the waiter count and check if we're the last waiter + lockMutex(cond->m_waitCountMutex); + --cond->m_waitCount; + const bool last = (result == WAIT_OBJECT_0 + 1 && cond->m_waitCount == 0); + unlockMutex(cond->m_waitCountMutex); + + // reset the broadcast event if we're the last waiter + if (last) { + ResetEvent(cond->m_events[CArchCondImpl::kBroadcast]); + } + + // reacquire the mutex + lockMutex(mutex); + + // cancel thread if necessary + if (result == WAIT_OBJECT_0 + 2) { + ARCH->testCancelThread(); + } + + // return success or failure + return (result == WAIT_OBJECT_0 + 0 || + result == WAIT_OBJECT_0 + 1); +} + +CArchMutex +CArchMultithreadWindows::newMutex() +{ + CArchMutexImpl* mutex = new CArchMutexImpl; + InitializeCriticalSection(&mutex->m_mutex); + return mutex; +} + +void +CArchMultithreadWindows::closeMutex(CArchMutex mutex) +{ + DeleteCriticalSection(&mutex->m_mutex); + delete mutex; +} + +void +CArchMultithreadWindows::lockMutex(CArchMutex mutex) +{ + EnterCriticalSection(&mutex->m_mutex); +} + +void +CArchMultithreadWindows::unlockMutex(CArchMutex mutex) +{ + LeaveCriticalSection(&mutex->m_mutex); +} + +CArchThread +CArchMultithreadWindows::newThread(ThreadFunc func, void* data) +{ + lockMutex(m_threadMutex); + + // create thread impl for new thread + CArchThreadImpl* thread = new CArchThreadImpl; + thread->m_func = func; + thread->m_userData = data; + + // create thread + unsigned int id; + thread->m_thread = reinterpret_cast(_beginthreadex(NULL, 0, + threadFunc, (void*)thread, 0, &id)); + thread->m_id = static_cast(id); + + // check if thread was started + if (thread->m_thread == 0) { + // failed to start thread so clean up + delete thread; + thread = NULL; + } + else { + // add thread to list + insert(thread); + + // increment ref count to account for the thread itself + refThread(thread); + } + + // note that the child thread will wait until we release this mutex + unlockMutex(m_threadMutex); + + return thread; +} + +CArchThread +CArchMultithreadWindows::newCurrentThread() +{ + lockMutex(m_threadMutex); + CArchThreadImpl* thread = find(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + assert(thread != NULL); + return thread; +} + +void +CArchMultithreadWindows::closeThread(CArchThread thread) +{ + assert(thread != NULL); + + // decrement ref count and clean up thread if no more references + if (--thread->m_refCount == 0) { + // close the handle (main thread has a NULL handle) + if (thread->m_thread != NULL) { + CloseHandle(thread->m_thread); + } + + // remove thread from list + lockMutex(m_threadMutex); + assert(findNoRefOrCreate(thread->m_id) == thread); + erase(thread); + unlockMutex(m_threadMutex); + + // done with thread + delete thread; + } +} + +CArchThread +CArchMultithreadWindows::copyThread(CArchThread thread) +{ + refThread(thread); + return thread; +} + +void +CArchMultithreadWindows::cancelThread(CArchThread thread) +{ + assert(thread != NULL); + + // set cancel flag + SetEvent(thread->m_cancel); +} + +void +CArchMultithreadWindows::setPriorityOfThread(CArchThread thread, int n) +{ + struct CPriorityInfo { + public: + DWORD m_class; + int m_level; + }; + static const CPriorityInfo s_pClass[] = { + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_IDLE }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_IDLE }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_TIME_CRITICAL} + }; +#if defined(_DEBUG) + // don't use really high priorities when debugging + static const size_t s_pMax = 13; +#else + static const size_t s_pMax = sizeof(s_pClass) / sizeof(s_pClass[0]) - 1; +#endif + static const size_t s_pBase = 8; // index of normal priority + + assert(thread != NULL); + + size_t index; + if (n > 0 && s_pBase < (size_t)n) { + // lowest priority + index = 0; + } + else { + index = (size_t)((int)s_pBase - n); + if (index > s_pMax) { + // highest priority + index = s_pMax; + } + } + SetPriorityClass(GetCurrentProcess(), s_pClass[index].m_class); + SetThreadPriority(thread->m_thread, s_pClass[index].m_level); +} + +void +CArchMultithreadWindows::testCancelThread() +{ + // find current thread + lockMutex(m_threadMutex); + CArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + + // test cancel on thread + testCancelThreadImpl(thread); +} + +bool +CArchMultithreadWindows::wait(CArchThread target, double timeout) +{ + assert(target != NULL); + + lockMutex(m_threadMutex); + + // find current thread + CArchThreadImpl* self = findNoRef(GetCurrentThreadId()); + + // ignore wait if trying to wait on ourself + if (target == self) { + unlockMutex(m_threadMutex); + return false; + } + + // ref the target so it can't go away while we're watching it + refThread(target); + + unlockMutex(m_threadMutex); + + // convert timeout + DWORD t; + if (timeout < 0.0) { + t = INFINITE; + } + else { + t = (DWORD)(1000.0 * timeout); + } + + // wait for this thread to be cancelled or woken up or for the + // target thread to terminate. + HANDLE handles[2]; + handles[0] = target->m_exit; + handles[1] = self->m_cancel; + DWORD result = WaitForMultipleObjects(2, handles, FALSE, t); + + // cancel takes priority + if (result != WAIT_OBJECT_0 + 1 && + WaitForSingleObject(handles[1], 0) == WAIT_OBJECT_0) { + result = WAIT_OBJECT_0 + 1; + } + + // release target + closeThread(target); + + // handle result + switch (result) { + case WAIT_OBJECT_0 + 0: + // target thread terminated + return true; + + case WAIT_OBJECT_0 + 1: + // this thread was cancelled. does not return. + testCancelThreadImpl(self); + + default: + // timeout or error + return false; + } +} + +bool +CArchMultithreadWindows::isSameThread(CArchThread thread1, CArchThread thread2) +{ + return (thread1 == thread2); +} + +bool +CArchMultithreadWindows::isExitedThread(CArchThread thread) +{ + // poll exit event + return (WaitForSingleObject(thread->m_exit, 0) == WAIT_OBJECT_0); +} + +void* +CArchMultithreadWindows::getResultOfThread(CArchThread thread) +{ + lockMutex(m_threadMutex); + void* result = thread->m_result; + unlockMutex(m_threadMutex); + return result; +} + +IArchMultithread::ThreadID +CArchMultithreadWindows::getIDOfThread(CArchThread thread) +{ + return static_cast(thread->m_id); +} + +void +CArchMultithreadWindows::setSignalHandler( + ESignal signal, SignalFunc func, void* userData) +{ + lockMutex(m_threadMutex); + m_signalFunc[signal] = func; + m_signalUserData[signal] = userData; + unlockMutex(m_threadMutex); +} + +void +CArchMultithreadWindows::raiseSignal(ESignal signal) +{ + lockMutex(m_threadMutex); + if (m_signalFunc[signal] != NULL) { + m_signalFunc[signal](signal, m_signalUserData[signal]); + ARCH->unblockPollSocket(m_mainThread); + } + else if (signal == kINTERRUPT || signal == kTERMINATE) { + ARCH->cancelThread(m_mainThread); + } + unlockMutex(m_threadMutex); +} + +CArchThreadImpl* +CArchMultithreadWindows::find(DWORD id) +{ + CArchThreadImpl* impl = findNoRef(id); + if (impl != NULL) { + refThread(impl); + } + return impl; +} + +CArchThreadImpl* +CArchMultithreadWindows::findNoRef(DWORD id) +{ + CArchThreadImpl* impl = findNoRefOrCreate(id); + if (impl == NULL) { + // create thread for calling thread which isn't in our list and + // add it to the list. this won't normally happen but it can if + // the system calls us under a new thread, like it does when we + // run as a service. + impl = new CArchThreadImpl; + impl->m_thread = NULL; + impl->m_id = GetCurrentThreadId(); + insert(impl); + } + return impl; +} + +CArchThreadImpl* +CArchMultithreadWindows::findNoRefOrCreate(DWORD id) +{ + // linear search + for (CThreadList::const_iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if ((*index)->m_id == id) { + return *index; + } + } + return NULL; +} + +void +CArchMultithreadWindows::insert(CArchThreadImpl* thread) +{ + assert(thread != NULL); + + // thread shouldn't already be on the list + assert(findNoRefOrCreate(thread->m_id) == NULL); + + // append to list + m_threadList.push_back(thread); +} + +void +CArchMultithreadWindows::erase(CArchThreadImpl* thread) +{ + for (CThreadList::iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if (*index == thread) { + m_threadList.erase(index); + break; + } + } +} + +void +CArchMultithreadWindows::refThread(CArchThreadImpl* thread) +{ + assert(thread != NULL); + assert(findNoRefOrCreate(thread->m_id) != NULL); + ++thread->m_refCount; +} + +void +CArchMultithreadWindows::testCancelThreadImpl(CArchThreadImpl* thread) +{ + assert(thread != NULL); + + // poll cancel event. return if not set. + const DWORD result = WaitForSingleObject(thread->m_cancel, 0); + if (result != WAIT_OBJECT_0) { + return; + } + + // update cancel state + lockMutex(m_threadMutex); + bool cancel = !thread->m_cancelling; + thread->m_cancelling = true; + ResetEvent(thread->m_cancel); + unlockMutex(m_threadMutex); + + // unwind thread's stack if cancelling + if (cancel) { + throw XThreadCancel(); + } +} + +unsigned int __stdcall +CArchMultithreadWindows::threadFunc(void* vrep) +{ + // get the thread + CArchThreadImpl* thread = reinterpret_cast(vrep); + + // run thread + s_instance->doThreadFunc(thread); + + // terminate the thread + return 0; +} + +void +CArchMultithreadWindows::doThreadFunc(CArchThread thread) +{ + // wait for parent to initialize this object + lockMutex(m_threadMutex); + unlockMutex(m_threadMutex); + + void* result = NULL; + try { + // go + result = (*thread->m_func)(thread->m_userData); + } + + catch (XThreadCancel&) { + // client called cancel() + } + catch (...) { + // note -- don't catch (...) to avoid masking bugs + SetEvent(thread->m_exit); + closeThread(thread); + throw; + } + + // thread has exited + lockMutex(m_threadMutex); + thread->m_result = result; + unlockMutex(m_threadMutex); + SetEvent(thread->m_exit); + + // done with thread + closeThread(thread); +} diff --git a/lib/arch/CArchMultithreadWindows.h b/lib/arch/CArchMultithreadWindows.h new file mode 100644 index 00000000..d009c842 --- /dev/null +++ b/lib/arch/CArchMultithreadWindows.h @@ -0,0 +1,115 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHMULTITHREADWINDOWS_H +#define CARCHMULTITHREADWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "IArchMultithread.h" +#include "stdlist.h" +#include + +#define ARCH_MULTITHREAD CArchMultithreadWindows + +class CArchCondImpl { +public: + enum { kSignal = 0, kBroadcast }; + + HANDLE m_events[2]; + mutable int m_waitCount; + CArchMutex m_waitCountMutex; +}; + +class CArchMutexImpl { +public: + CRITICAL_SECTION m_mutex; +}; + +//! Win32 implementation of IArchMultithread +class CArchMultithreadWindows : public IArchMultithread { +public: + CArchMultithreadWindows(); + virtual ~CArchMultithreadWindows(); + + //! @name manipulators + //@{ + + void setNetworkDataForCurrentThread(void*); + + //@} + //! @name accessors + //@{ + + HANDLE getCancelEventForCurrentThread(); + + void* getNetworkDataForThread(CArchThread); + + static CArchMultithreadWindows* getInstance(); + + //@} + + // IArchMultithread overrides + virtual CArchCond newCondVar(); + virtual void closeCondVar(CArchCond); + virtual void signalCondVar(CArchCond); + virtual void broadcastCondVar(CArchCond); + virtual bool waitCondVar(CArchCond, CArchMutex, double timeout); + virtual CArchMutex newMutex(); + virtual void closeMutex(CArchMutex); + virtual void lockMutex(CArchMutex); + virtual void unlockMutex(CArchMutex); + virtual CArchThread newThread(ThreadFunc, void*); + virtual CArchThread newCurrentThread(); + virtual CArchThread copyThread(CArchThread); + virtual void closeThread(CArchThread); + virtual void cancelThread(CArchThread); + virtual void setPriorityOfThread(CArchThread, int n); + virtual void testCancelThread(); + virtual bool wait(CArchThread, double timeout); + virtual bool isSameThread(CArchThread, CArchThread); + virtual bool isExitedThread(CArchThread); + virtual void* getResultOfThread(CArchThread); + virtual ThreadID getIDOfThread(CArchThread); + virtual void setSignalHandler(ESignal, SignalFunc, void*); + virtual void raiseSignal(ESignal); + +private: + CArchThreadImpl* find(DWORD id); + CArchThreadImpl* findNoRef(DWORD id); + CArchThreadImpl* findNoRefOrCreate(DWORD id); + void insert(CArchThreadImpl* thread); + void erase(CArchThreadImpl* thread); + + void refThread(CArchThreadImpl* rep); + void testCancelThreadImpl(CArchThreadImpl* rep); + + void doThreadFunc(CArchThread thread); + static unsigned int __stdcall threadFunc(void* vrep); + +private: + typedef std::list CThreadList; + + static CArchMultithreadWindows* s_instance; + + CArchMutex m_threadMutex; + + CThreadList m_threadList; + CArchThread m_mainThread; + + SignalFunc m_signalFunc[kNUM_SIGNALS]; + void* m_signalUserData[kNUM_SIGNALS]; +}; + +#endif diff --git a/lib/arch/CArchNetworkBSD.cpp b/lib/arch/CArchNetworkBSD.cpp new file mode 100644 index 00000000..af474e86 --- /dev/null +++ b/lib/arch/CArchNetworkBSD.cpp @@ -0,0 +1,974 @@ +/* + * 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 "CArchNetworkBSD.h" +#include "CArch.h" +#include "CArchMultithreadPosix.h" +#include "XArchUnix.h" +#if HAVE_UNISTD_H +# include +#endif +#include +#include +#if !defined(TCP_NODELAY) +# include +#endif +#include +#include +#include +#include + +#if HAVE_POLL +# include +#else +# if HAVE_SYS_SELECT_H +# include +# endif +# if HAVE_SYS_TIME_H +# include +# endif +#endif + +#if !HAVE_INET_ATON +# include +#endif + +static const int s_family[] = { + PF_UNSPEC, + PF_INET +}; +static const int s_type[] = { + SOCK_DGRAM, + SOCK_STREAM +}; + +#if !HAVE_INET_ATON +// parse dotted quad addresses. we don't bother with the weird BSD'ism +// of handling octal and hex and partial forms. +static +in_addr_t +inet_aton(const char* cp, struct in_addr* inp) +{ + unsigned int a, b, c, d; + if (sscanf(cp, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) { + return 0; + } + if (a >= 256 || b >= 256 || c >= 256 || d >= 256) { + return 0; + } + unsigned char* incp = (unsigned char*)inp; + incp[0] = (unsigned char)(a & 0xffu); + incp[1] = (unsigned char)(b & 0xffu); + incp[2] = (unsigned char)(c & 0xffu); + incp[3] = (unsigned char)(d & 0xffu); + return inp->s_addr; +} +#endif + +// +// CArchNetworkBSD +// + +CArchNetworkBSD::CArchNetworkBSD() +{ + // create mutex to make some calls thread safe + m_mutex = ARCH->newMutex(); +} + +CArchNetworkBSD::~CArchNetworkBSD() +{ + ARCH->closeMutex(m_mutex); +} + +CArchSocket +CArchNetworkBSD::newSocket(EAddressFamily family, ESocketType type) +{ + // create socket + int fd = socket(s_family[family], s_type[type], 0); + if (fd == -1) { + throwError(errno); + } + try { + setBlockingOnSocket(fd, false); + } + catch (...) { + close(fd); + throw; + } + + // allocate socket object + CArchSocketImpl* newSocket = new CArchSocketImpl; + newSocket->m_fd = fd; + newSocket->m_refCount = 1; + return newSocket; +} + +CArchSocket +CArchNetworkBSD::copySocket(CArchSocket s) +{ + assert(s != NULL); + + // ref the socket and return it + ARCH->lockMutex(m_mutex); + ++s->m_refCount; + ARCH->unlockMutex(m_mutex); + return s; +} + +void +CArchNetworkBSD::closeSocket(CArchSocket s) +{ + assert(s != NULL); + + // unref the socket and note if it should be released + ARCH->lockMutex(m_mutex); + const bool doClose = (--s->m_refCount == 0); + ARCH->unlockMutex(m_mutex); + + // close the socket if necessary + if (doClose) { + if (close(s->m_fd) == -1) { + // close failed. restore the last ref and throw. + int err = errno; + ARCH->lockMutex(m_mutex); + ++s->m_refCount; + ARCH->unlockMutex(m_mutex); + throwError(err); + } + delete s; + } +} + +void +CArchNetworkBSD::closeSocketForRead(CArchSocket s) +{ + assert(s != NULL); + + if (shutdown(s->m_fd, 0) == -1) { + if (errno != ENOTCONN) { + throwError(errno); + } + } +} + +void +CArchNetworkBSD::closeSocketForWrite(CArchSocket s) +{ + assert(s != NULL); + + if (shutdown(s->m_fd, 1) == -1) { + if (errno != ENOTCONN) { + throwError(errno); + } + } +} + +void +CArchNetworkBSD::bindSocket(CArchSocket s, CArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (bind(s->m_fd, &addr->m_addr, addr->m_len) == -1) { + throwError(errno); + } +} + +void +CArchNetworkBSD::listenOnSocket(CArchSocket s) +{ + assert(s != NULL); + + // hardcoding backlog + if (listen(s->m_fd, 3) == -1) { + throwError(errno); + } +} + +CArchSocket +CArchNetworkBSD::acceptSocket(CArchSocket s, CArchNetAddress* addr) +{ + assert(s != NULL); + + // if user passed NULL in addr then use scratch space + CArchNetAddress dummy; + if (addr == NULL) { + addr = &dummy; + } + + // create new socket and address + CArchSocketImpl* newSocket = new CArchSocketImpl; + *addr = new CArchNetAddressImpl; + + // accept on socket + ACCEPT_TYPE_ARG3 len = (ACCEPT_TYPE_ARG3)((*addr)->m_len); + int fd = accept(s->m_fd, &(*addr)->m_addr, &len); + (*addr)->m_len = (socklen_t)len; + if (fd == -1) { + int err = errno; + delete newSocket; + delete *addr; + *addr = NULL; + if (err == EAGAIN) { + return NULL; + } + throwError(err); + } + + try { + setBlockingOnSocket(fd, false); + } + catch (...) { + close(fd); + delete newSocket; + delete *addr; + *addr = NULL; + throw; + } + + // initialize socket + newSocket->m_fd = fd; + newSocket->m_refCount = 1; + + // discard address if not requested + if (addr == &dummy) { + ARCH->closeAddr(dummy); + } + + return newSocket; +} + +bool +CArchNetworkBSD::connectSocket(CArchSocket s, CArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (connect(s->m_fd, &addr->m_addr, addr->m_len) == -1) { + if (errno == EISCONN) { + return true; + } + if (errno == EINPROGRESS) { + return false; + } + throwError(errno); + } + return true; +} + +#if HAVE_POLL + +int +CArchNetworkBSD::pollSocket(CPollEntry pe[], int num, double timeout) +{ + assert(pe != NULL || num == 0); + + // return if nothing to do + if (num == 0) { + if (timeout > 0.0) { + ARCH->sleep(timeout); + } + return 0; + } + + // allocate space for translated query + struct pollfd* pfd = new struct pollfd[1 + num]; + + // translate query + for (int i = 0; i < num; ++i) { + pfd[i].fd = (pe[i].m_socket == NULL) ? -1 : pe[i].m_socket->m_fd; + pfd[i].events = 0; + if ((pe[i].m_events & kPOLLIN) != 0) { + pfd[i].events |= POLLIN; + } + if ((pe[i].m_events & kPOLLOUT) != 0) { + pfd[i].events |= POLLOUT; + } + } + int n = num; + + // add the unblock pipe + const int* unblockPipe = getUnblockPipe(); + if (unblockPipe != NULL) { + pfd[n].fd = unblockPipe[0]; + pfd[n].events = POLLIN; + ++n; + } + + // prepare timeout + int t = (timeout < 0.0) ? -1 : static_cast(1000.0 * timeout); + + // do the poll + n = poll(pfd, n, t); + + // reset the unblock pipe + if (n > 0 && unblockPipe != NULL && (pfd[num].revents & POLLIN) != 0) { + // the unblock event was signalled. flush the pipe. + char dummy[100]; + do { + read(unblockPipe[0], dummy, sizeof(dummy)); + } while (errno != EAGAIN); + + // don't count this unblock pipe in return value + --n; + } + + // handle results + if (n == -1) { + if (errno == EINTR) { + // interrupted system call + ARCH->testCancelThread(); + delete[] pfd; + return 0; + } + delete[] pfd; + throwError(errno); + } + + // translate back + for (int i = 0; i < num; ++i) { + pe[i].m_revents = 0; + if ((pfd[i].revents & POLLIN) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((pfd[i].revents & POLLOUT) != 0) { + pe[i].m_revents |= kPOLLOUT; + } + if ((pfd[i].revents & POLLERR) != 0) { + pe[i].m_revents |= kPOLLERR; + } + if ((pfd[i].revents & POLLNVAL) != 0) { + pe[i].m_revents |= kPOLLNVAL; + } + } + + delete[] pfd; + return n; +} + +#else + +int +CArchNetworkBSD::pollSocket(CPollEntry pe[], int num, double timeout) +{ + int i, n; + + // prepare sets for select + n = 0; + fd_set readSet, writeSet, errSet; + fd_set* readSetP = NULL; + fd_set* writeSetP = NULL; + fd_set* errSetP = NULL; + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_ZERO(&errSet); + for (i = 0; i < num; ++i) { + // reset return flags + pe[i].m_revents = 0; + + // set invalid flag if socket is bogus then go to next socket + if (pe[i].m_socket == NULL) { + pe[i].m_revents |= kPOLLNVAL; + continue; + } + + int fdi = pe[i].m_socket->m_fd; + if (pe[i].m_events & kPOLLIN) { + FD_SET(pe[i].m_socket->m_fd, &readSet); + readSetP = &readSet; + if (fdi > n) { + n = fdi; + } + } + if (pe[i].m_events & kPOLLOUT) { + FD_SET(pe[i].m_socket->m_fd, &writeSet); + writeSetP = &writeSet; + if (fdi > n) { + n = fdi; + } + } + if (true) { + FD_SET(pe[i].m_socket->m_fd, &errSet); + errSetP = &errSet; + if (fdi > n) { + n = fdi; + } + } + } + + // add the unblock pipe + const int* unblockPipe = getUnblockPipe(); + if (unblockPipe != NULL) { + FD_SET(unblockPipe[0], &readSet); + readSetP = &readSet; + if (unblockPipe[0] > n) { + n = unblockPipe[0]; + } + } + + // if there are no sockets then don't block forever + if (n == 0 && timeout < 0.0) { + timeout = 0.0; + } + + // prepare timeout for select + struct timeval timeout2; + struct timeval* timeout2P; + if (timeout < 0.0) { + timeout2P = NULL; + } + else { + timeout2P = &timeout2; + timeout2.tv_sec = static_cast(timeout); + timeout2.tv_usec = static_cast(1.0e+6 * + (timeout - timeout2.tv_sec)); + } + + // do the select + n = select((SELECT_TYPE_ARG1) n + 1, + SELECT_TYPE_ARG234 readSetP, + SELECT_TYPE_ARG234 writeSetP, + SELECT_TYPE_ARG234 errSetP, + SELECT_TYPE_ARG5 timeout2P); + + // reset the unblock pipe + if (n > 0 && unblockPipe != NULL && FD_ISSET(unblockPipe[0], &readSet)) { + // the unblock event was signalled. flush the pipe. + char dummy[100]; + do { + read(unblockPipe[0], dummy, sizeof(dummy)); + } while (errno != EAGAIN); + } + + // handle results + if (n == -1) { + if (errno == EINTR) { + // interrupted system call + ARCH->testCancelThread(); + return 0; + } + throwError(errno); + } + n = 0; + for (i = 0; i < num; ++i) { + if (pe[i].m_socket != NULL) { + if (FD_ISSET(pe[i].m_socket->m_fd, &readSet)) { + pe[i].m_revents |= kPOLLIN; + } + if (FD_ISSET(pe[i].m_socket->m_fd, &writeSet)) { + pe[i].m_revents |= kPOLLOUT; + } + if (FD_ISSET(pe[i].m_socket->m_fd, &errSet)) { + pe[i].m_revents |= kPOLLERR; + } + } + if (pe[i].m_revents != 0) { + ++n; + } + } + + return n; +} + +#endif + +void +CArchNetworkBSD::unblockPollSocket(CArchThread thread) +{ + const int* unblockPipe = getUnblockPipeForThread(thread); + if (unblockPipe != NULL) { + char dummy = 0; + write(unblockPipe[1], &dummy, 1); + } +} + +size_t +CArchNetworkBSD::readSocket(CArchSocket s, void* buf, size_t len) +{ + assert(s != NULL); + + ssize_t n = read(s->m_fd, buf, len); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) { + return 0; + } + throwError(errno); + } + return n; +} + +size_t +CArchNetworkBSD::writeSocket(CArchSocket s, const void* buf, size_t len) +{ + assert(s != NULL); + + ssize_t n = write(s->m_fd, buf, len); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) { + return 0; + } + throwError(errno); + } + return n; +} + +void +CArchNetworkBSD::throwErrorOnSocket(CArchSocket s) +{ + assert(s != NULL); + + // get the error from the socket layer + int err = 0; + socklen_t size = (socklen_t)sizeof(err); + if (getsockopt(s->m_fd, SOL_SOCKET, SO_ERROR, + (optval_t*)&err, &size) == -1) { + err = errno; + } + + // throw if there's an error + if (err != 0) { + throwError(err); + } +} + +void +CArchNetworkBSD::setBlockingOnSocket(int fd, bool blocking) +{ + assert(fd != -1); + + int mode = fcntl(fd, F_GETFL, 0); + if (mode == -1) { + throwError(errno); + } + if (blocking) { + mode &= ~O_NONBLOCK; + } + else { + mode |= O_NONBLOCK; + } + if (fcntl(fd, F_SETFL, mode) == -1) { + throwError(errno); + } +} + +bool +CArchNetworkBSD::setNoDelayOnSocket(CArchSocket s, bool noDelay) +{ + assert(s != NULL); + + // get old state + int oflag; + socklen_t size = (socklen_t)sizeof(oflag); + if (getsockopt(s->m_fd, IPPROTO_TCP, TCP_NODELAY, + (optval_t*)&oflag, &size) == -1) { + throwError(errno); + } + + int flag = noDelay ? 1 : 0; + size = (socklen_t)sizeof(flag); + if (setsockopt(s->m_fd, IPPROTO_TCP, TCP_NODELAY, + (optval_t*)&flag, size) == -1) { + throwError(errno); + } + + return (oflag != 0); +} + +bool +CArchNetworkBSD::setReuseAddrOnSocket(CArchSocket s, bool reuse) +{ + assert(s != NULL); + + // get old state + int oflag; + socklen_t size = (socklen_t)sizeof(oflag); + if (getsockopt(s->m_fd, SOL_SOCKET, SO_REUSEADDR, + (optval_t*)&oflag, &size) == -1) { + throwError(errno); + } + + int flag = reuse ? 1 : 0; + size = (socklen_t)sizeof(flag); + if (setsockopt(s->m_fd, SOL_SOCKET, SO_REUSEADDR, + (optval_t*)&flag, size) == -1) { + throwError(errno); + } + + return (oflag != 0); +} + +std::string +CArchNetworkBSD::getHostName() +{ + char name[256]; + if (gethostname(name, sizeof(name)) == -1) { + name[0] = '\0'; + } + else { + name[sizeof(name) - 1] = '\0'; + } + return name; +} + +CArchNetAddress +CArchNetworkBSD::newAnyAddr(EAddressFamily family) +{ + // allocate address + CArchNetAddressImpl* addr = new CArchNetAddressImpl; + + // fill it in + switch (family) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + ipAddr->sin_family = AF_INET; + ipAddr->sin_port = 0; + ipAddr->sin_addr.s_addr = INADDR_ANY; + addr->m_len = (socklen_t)sizeof(struct sockaddr_in); + break; + } + + default: + delete addr; + assert(0 && "invalid family"); + } + + return addr; +} + +CArchNetAddress +CArchNetworkBSD::copyAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + // allocate and copy address + return new CArchNetAddressImpl(*addr); +} + +CArchNetAddress +CArchNetworkBSD::nameToAddr(const std::string& name) +{ + // allocate address + CArchNetAddressImpl* addr = new CArchNetAddressImpl; + + // try to convert assuming an IPv4 dot notation address + struct sockaddr_in inaddr; + memset(&inaddr, 0, sizeof(inaddr)); + if (inet_aton(name.c_str(), &inaddr.sin_addr) != 0) { + // it's a dot notation address + addr->m_len = (socklen_t)sizeof(struct sockaddr_in); + inaddr.sin_family = AF_INET; + inaddr.sin_port = 0; + memcpy(&addr->m_addr, &inaddr, addr->m_len); + } + + else { + // mutexed address lookup (ugh) + ARCH->lockMutex(m_mutex); + struct hostent* info = gethostbyname(name.c_str()); + if (info == NULL) { + ARCH->unlockMutex(m_mutex); + delete addr; + throwNameError(h_errno); + } + + // copy over address (only IPv4 currently supported) + if (info->h_addrtype == AF_INET) { + addr->m_len = (socklen_t)sizeof(struct sockaddr_in); + inaddr.sin_family = info->h_addrtype; + inaddr.sin_port = 0; + memcpy(&inaddr.sin_addr, info->h_addr_list[0], + sizeof(inaddr.sin_addr)); + memcpy(&addr->m_addr, &inaddr, addr->m_len); + } + else { + ARCH->unlockMutex(m_mutex); + delete addr; + throw XArchNetworkNameUnsupported( + "The requested name is valid but " + "does not have a supported address family"); + } + + // done with static buffer + ARCH->unlockMutex(m_mutex); + } + + return addr; +} + +void +CArchNetworkBSD::closeAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + delete addr; +} + +std::string +CArchNetworkBSD::addrToName(CArchNetAddress addr) +{ + assert(addr != NULL); + + // mutexed name lookup (ugh) + ARCH->lockMutex(m_mutex); + struct hostent* info = gethostbyaddr( + reinterpret_cast(&addr->m_addr), + addr->m_len, addr->m_addr.sa_family); + if (info == NULL) { + ARCH->unlockMutex(m_mutex); + throwNameError(h_errno); + } + + // save (primary) name + std::string name = info->h_name; + + // done with static buffer + ARCH->unlockMutex(m_mutex); + + return name; +} + +std::string +CArchNetworkBSD::addrToString(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + ARCH->lockMutex(m_mutex); + std::string s = inet_ntoa(ipAddr->sin_addr); + ARCH->unlockMutex(m_mutex); + return s; + } + + default: + assert(0 && "unknown address family"); + return ""; + } +} + +IArchNetwork::EAddressFamily +CArchNetworkBSD::getAddrFamily(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (addr->m_addr.sa_family) { + case AF_INET: + return kINET; + + default: + return kUNKNOWN; + } +} + +void +CArchNetworkBSD::setAddrPort(CArchNetAddress addr, int port) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + ipAddr->sin_port = htons(port); + break; + } + + default: + assert(0 && "unknown address family"); + break; + } +} + +int +CArchNetworkBSD::getAddrPort(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + return ntohs(ipAddr->sin_port); + } + + default: + assert(0 && "unknown address family"); + return 0; + } +} + +bool +CArchNetworkBSD::isAnyAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + return (ipAddr->sin_addr.s_addr == INADDR_ANY && + addr->m_len == (socklen_t)sizeof(struct sockaddr_in)); + } + + default: + assert(0 && "unknown address family"); + return true; + } +} + +bool +CArchNetworkBSD::isEqualAddr(CArchNetAddress a, CArchNetAddress b) +{ + return (a->m_len == b->m_len && + memcmp(&a->m_addr, &b->m_addr, a->m_len) == 0); +} + +const int* +CArchNetworkBSD::getUnblockPipe() +{ + CArchMultithreadPosix* mt = CArchMultithreadPosix::getInstance(); + CArchThread thread = mt->newCurrentThread(); + const int* p = getUnblockPipeForThread(thread); + ARCH->closeThread(thread); + return p; +} + +const int* +CArchNetworkBSD::getUnblockPipeForThread(CArchThread thread) +{ + CArchMultithreadPosix* mt = CArchMultithreadPosix::getInstance(); + int* unblockPipe = (int*)mt->getNetworkDataForThread(thread); + if (unblockPipe == NULL) { + unblockPipe = new int[2]; + if (pipe(unblockPipe) != -1) { + try { + setBlockingOnSocket(unblockPipe[0], false); + mt->setNetworkDataForCurrentThread(unblockPipe); + } + catch (...) { + delete[] unblockPipe; + unblockPipe = NULL; + } + } + else { + delete[] unblockPipe; + unblockPipe = NULL; + } + } + return unblockPipe; +} + +void +CArchNetworkBSD::throwError(int err) +{ + switch (err) { + case EINTR: + ARCH->testCancelThread(); + throw XArchNetworkInterrupted(new XArchEvalUnix(err)); + + case EACCES: + case EPERM: + throw XArchNetworkAccess(new XArchEvalUnix(err)); + + case ENFILE: + case EMFILE: + case ENODEV: + case ENOBUFS: + case ENOMEM: + case ENETDOWN: +#if defined(ENOSR) + case ENOSR: +#endif + throw XArchNetworkResource(new XArchEvalUnix(err)); + + case EPROTOTYPE: + case EPROTONOSUPPORT: + case EAFNOSUPPORT: + case EPFNOSUPPORT: + case ESOCKTNOSUPPORT: + case EINVAL: + case ENOPROTOOPT: + case EOPNOTSUPP: + case ESHUTDOWN: +#if defined(ENOPKG) + case ENOPKG: +#endif + throw XArchNetworkSupport(new XArchEvalUnix(err)); + + case EIO: + throw XArchNetworkIO(new XArchEvalUnix(err)); + + case EADDRNOTAVAIL: + throw XArchNetworkNoAddress(new XArchEvalUnix(err)); + + case EADDRINUSE: + throw XArchNetworkAddressInUse(new XArchEvalUnix(err)); + + case EHOSTUNREACH: + case ENETUNREACH: + throw XArchNetworkNoRoute(new XArchEvalUnix(err)); + + case ENOTCONN: + throw XArchNetworkNotConnected(new XArchEvalUnix(err)); + + case EPIPE: + throw XArchNetworkShutdown(new XArchEvalUnix(err)); + + case ECONNABORTED: + case ECONNRESET: + throw XArchNetworkDisconnected(new XArchEvalUnix(err)); + + case ECONNREFUSED: + throw XArchNetworkConnectionRefused(new XArchEvalUnix(err)); + + case EHOSTDOWN: + case ETIMEDOUT: + throw XArchNetworkTimedOut(new XArchEvalUnix(err)); + + default: + throw XArchNetwork(new XArchEvalUnix(err)); + } +} + +void +CArchNetworkBSD::throwNameError(int err) +{ + static const char* s_msg[] = { + "The specified host is unknown", + "The requested name is valid but does not have an IP address", + "A non-recoverable name server error occurred", + "A temporary error occurred on an authoritative name server", + "An unknown name server error occurred" + }; + + switch (err) { + case HOST_NOT_FOUND: + throw XArchNetworkNameUnknown(s_msg[0]); + + case NO_DATA: + throw XArchNetworkNameNoAddress(s_msg[1]); + + case NO_RECOVERY: + throw XArchNetworkNameFailure(s_msg[2]); + + case TRY_AGAIN: + throw XArchNetworkNameUnavailable(s_msg[3]); + + default: + throw XArchNetworkName(s_msg[4]); + } +} diff --git a/lib/arch/CArchNetworkBSD.h b/lib/arch/CArchNetworkBSD.h new file mode 100644 index 00000000..bba60272 --- /dev/null +++ b/lib/arch/CArchNetworkBSD.h @@ -0,0 +1,101 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHNETWORKBSD_H +#define CARCHNETWORKBSD_H + +#include "IArchNetwork.h" +#include "IArchMultithread.h" +#if HAVE_SYS_TYPES_H +# include +#endif +#if HAVE_SYS_SOCKET_H +# include +#endif + +#if !HAVE_SOCKLEN_T +typedef int socklen_t; +#endif + +// old systems may use char* for [gs]etsockopt()'s optval argument. +// this should be void on modern systems but char is forwards +// compatible so we always use it. +typedef char optval_t; + +#define ARCH_NETWORK CArchNetworkBSD + +class CArchSocketImpl { +public: + int m_fd; + int m_refCount; +}; + +class CArchNetAddressImpl { +public: + CArchNetAddressImpl() : m_len(sizeof(m_addr)) { } + +public: + struct sockaddr m_addr; + socklen_t m_len; +}; + +//! Berkeley (BSD) sockets implementation of IArchNetwork +class CArchNetworkBSD : public IArchNetwork { +public: + CArchNetworkBSD(); + virtual ~CArchNetworkBSD(); + + // IArchNetwork overrides + virtual CArchSocket newSocket(EAddressFamily, ESocketType); + virtual CArchSocket copySocket(CArchSocket s); + virtual void closeSocket(CArchSocket s); + virtual void closeSocketForRead(CArchSocket s); + virtual void closeSocketForWrite(CArchSocket s); + virtual void bindSocket(CArchSocket s, CArchNetAddress addr); + virtual void listenOnSocket(CArchSocket s); + virtual CArchSocket acceptSocket(CArchSocket s, CArchNetAddress* addr); + virtual bool connectSocket(CArchSocket s, CArchNetAddress name); + virtual int pollSocket(CPollEntry[], int num, double timeout); + virtual void unblockPollSocket(CArchThread thread); + virtual size_t readSocket(CArchSocket s, void* buf, size_t len); + virtual size_t writeSocket(CArchSocket s, + const void* buf, size_t len); + virtual void throwErrorOnSocket(CArchSocket); + virtual bool setNoDelayOnSocket(CArchSocket, bool noDelay); + virtual bool setReuseAddrOnSocket(CArchSocket, bool reuse); + virtual std::string getHostName(); + virtual CArchNetAddress newAnyAddr(EAddressFamily); + virtual CArchNetAddress copyAddr(CArchNetAddress); + virtual CArchNetAddress nameToAddr(const std::string&); + virtual void closeAddr(CArchNetAddress); + virtual std::string addrToName(CArchNetAddress); + virtual std::string addrToString(CArchNetAddress); + virtual EAddressFamily getAddrFamily(CArchNetAddress); + virtual void setAddrPort(CArchNetAddress, int port); + virtual int getAddrPort(CArchNetAddress); + virtual bool isAnyAddr(CArchNetAddress); + virtual bool isEqualAddr(CArchNetAddress, CArchNetAddress); + +private: + const int* getUnblockPipe(); + const int* getUnblockPipeForThread(CArchThread); + void setBlockingOnSocket(int fd, bool blocking); + void throwError(int); + void throwNameError(int); + +private: + CArchMutex m_mutex; +}; + +#endif diff --git a/lib/arch/CArchNetworkWinsock.cpp b/lib/arch/CArchNetworkWinsock.cpp new file mode 100644 index 00000000..ac40596a --- /dev/null +++ b/lib/arch/CArchNetworkWinsock.cpp @@ -0,0 +1,934 @@ +/* + * 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 "CArchNetworkWinsock.h" +#include "CArch.h" +#include "CArchMultithreadWindows.h" +#include "IArchMultithread.h" +#include "XArchWindows.h" +#include + +static const int s_family[] = { + PF_UNSPEC, + PF_INET +}; +static const int s_type[] = { + SOCK_DGRAM, + SOCK_STREAM +}; + +static SOCKET (PASCAL FAR *accept_winsock)(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen); +static int (PASCAL FAR *bind_winsock)(SOCKET s, const struct sockaddr FAR *addr, int namelen); +static int (PASCAL FAR *close_winsock)(SOCKET s); +static int (PASCAL FAR *connect_winsock)(SOCKET s, const struct sockaddr FAR *name, int namelen); +static int (PASCAL FAR *gethostname_winsock)(char FAR * name, int namelen); +static int (PASCAL FAR *getsockerror_winsock)(void); +static int (PASCAL FAR *getsockopt_winsock)(SOCKET s, int level, int optname, void FAR * optval, int FAR *optlen); +static u_short (PASCAL FAR *htons_winsock)(u_short v); +static char FAR * (PASCAL FAR *inet_ntoa_winsock)(struct in_addr in); +static unsigned long (PASCAL FAR *inet_addr_winsock)(const char FAR * cp); +static int (PASCAL FAR *ioctl_winsock)(SOCKET s, int cmd, void FAR * data); +static int (PASCAL FAR *listen_winsock)(SOCKET s, int backlog); +static u_short (PASCAL FAR *ntohs_winsock)(u_short v); +static int (PASCAL FAR *recv_winsock)(SOCKET s, void FAR * buf, int len, int flags); +static int (PASCAL FAR *select_winsock)(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout); +static int (PASCAL FAR *send_winsock)(SOCKET s, const void FAR * buf, int len, int flags); +static int (PASCAL FAR *setsockopt_winsock)(SOCKET s, int level, int optname, const void FAR * optval, int optlen); +static int (PASCAL FAR *shutdown_winsock)(SOCKET s, int how); +static SOCKET (PASCAL FAR *socket_winsock)(int af, int type, int protocol); +static struct hostent FAR * (PASCAL FAR *gethostbyaddr_winsock)(const char FAR * addr, int len, int type); +static struct hostent FAR * (PASCAL FAR *gethostbyname_winsock)(const char FAR * name); +static int (PASCAL FAR *WSACleanup_winsock)(void); +static int (PASCAL FAR *WSAFDIsSet_winsock)(SOCKET, fd_set FAR * fdset); +static WSAEVENT (PASCAL FAR *WSACreateEvent_winsock)(void); +static BOOL (PASCAL FAR *WSACloseEvent_winsock)(WSAEVENT); +static BOOL (PASCAL FAR *WSASetEvent_winsock)(WSAEVENT); +static BOOL (PASCAL FAR *WSAResetEvent_winsock)(WSAEVENT); +static int (PASCAL FAR *WSAEventSelect_winsock)(SOCKET, WSAEVENT, long); +static DWORD (PASCAL FAR *WSAWaitForMultipleEvents_winsock)(DWORD, const WSAEVENT FAR*, BOOL, DWORD, BOOL); +static int (PASCAL FAR *WSAEnumNetworkEvents_winsock)(SOCKET, WSAEVENT, LPWSANETWORKEVENTS); + +#undef FD_ISSET +#define FD_ISSET(fd, set) WSAFDIsSet_winsock((SOCKET)(fd), (fd_set FAR *)(set)) + +#define setfunc(var, name, type) var = (type)netGetProcAddress(module, #name) + +static HMODULE s_networkModule = NULL; + +static +FARPROC +netGetProcAddress(HMODULE module, LPCSTR name) +{ + FARPROC func = ::GetProcAddress(module, name); + if (!func) { + throw XArchNetworkSupport(""); + } + return func; +} + +CArchNetAddressImpl* +CArchNetAddressImpl::alloc(size_t size) +{ + size_t totalSize = size + ADDR_HDR_SIZE; + CArchNetAddressImpl* addr = (CArchNetAddressImpl*)malloc(totalSize); + addr->m_len = size; + return addr; +} + + +// +// CArchNetworkWinsock +// + +CArchNetworkWinsock::CArchNetworkWinsock() +{ + static const char* s_library[] = { "ws2_32.dll" }; + + assert(WSACleanup_winsock == NULL); + assert(s_networkModule == NULL); + + // try each winsock library + for (size_t i = 0; i < sizeof(s_library) / sizeof(s_library[0]); ++i) { + try { + init((HMODULE)::LoadLibrary(s_library[i])); + m_mutex = ARCH->newMutex(); + return; + } + catch (XArchNetwork&) { + // ignore + } + } + + // can't initialize any library + throw XArchNetworkSupport("Cannot load winsock library"); +} + +CArchNetworkWinsock::~CArchNetworkWinsock() +{ + if (s_networkModule != NULL) { + WSACleanup_winsock(); + ::FreeLibrary(s_networkModule); + + WSACleanup_winsock = NULL; + s_networkModule = NULL; + } + ARCH->closeMutex(m_mutex); +} + +void +CArchNetworkWinsock::init(HMODULE module) +{ + if (module == NULL) { + throw XArchNetworkSupport(""); + } + + // get startup function address + int (PASCAL FAR *startup)(WORD, LPWSADATA); + setfunc(startup, WSAStartup, int(PASCAL FAR*)(WORD, LPWSADATA)); + + // startup network library + WORD version = MAKEWORD(2 /*major*/, 0 /*minor*/); + WSADATA data; + int err = startup(version, &data); + if (data.wVersion != version) { + throw XArchNetworkSupport(new XArchEvalWinsock(err)); + } + if (err != 0) { + // some other initialization error + throwError(err); + } + + // get function addresses + setfunc(accept_winsock, accept, SOCKET (PASCAL FAR *)(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen)); + setfunc(bind_winsock, bind, int (PASCAL FAR *)(SOCKET s, const struct sockaddr FAR *addr, int namelen)); + setfunc(close_winsock, closesocket, int (PASCAL FAR *)(SOCKET s)); + setfunc(connect_winsock, connect, int (PASCAL FAR *)(SOCKET s, const struct sockaddr FAR *name, int namelen)); + setfunc(gethostname_winsock, gethostname, int (PASCAL FAR *)(char FAR * name, int namelen)); + setfunc(getsockerror_winsock, WSAGetLastError, int (PASCAL FAR *)(void)); + setfunc(getsockopt_winsock, getsockopt, int (PASCAL FAR *)(SOCKET s, int level, int optname, void FAR * optval, int FAR *optlen)); + setfunc(htons_winsock, htons, u_short (PASCAL FAR *)(u_short v)); + setfunc(inet_ntoa_winsock, inet_ntoa, char FAR * (PASCAL FAR *)(struct in_addr in)); + setfunc(inet_addr_winsock, inet_addr, unsigned long (PASCAL FAR *)(const char FAR * cp)); + setfunc(ioctl_winsock, ioctlsocket, int (PASCAL FAR *)(SOCKET s, int cmd, void FAR *)); + setfunc(listen_winsock, listen, int (PASCAL FAR *)(SOCKET s, int backlog)); + setfunc(ntohs_winsock, ntohs, u_short (PASCAL FAR *)(u_short v)); + setfunc(recv_winsock, recv, int (PASCAL FAR *)(SOCKET s, void FAR * buf, int len, int flags)); + setfunc(select_winsock, select, int (PASCAL FAR *)(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout)); + setfunc(send_winsock, send, int (PASCAL FAR *)(SOCKET s, const void FAR * buf, int len, int flags)); + setfunc(setsockopt_winsock, setsockopt, int (PASCAL FAR *)(SOCKET s, int level, int optname, const void FAR * optval, int optlen)); + setfunc(shutdown_winsock, shutdown, int (PASCAL FAR *)(SOCKET s, int how)); + setfunc(socket_winsock, socket, SOCKET (PASCAL FAR *)(int af, int type, int protocol)); + setfunc(gethostbyaddr_winsock, gethostbyaddr, struct hostent FAR * (PASCAL FAR *)(const char FAR * addr, int len, int type)); + setfunc(gethostbyname_winsock, gethostbyname, struct hostent FAR * (PASCAL FAR *)(const char FAR * name)); + setfunc(WSACleanup_winsock, WSACleanup, int (PASCAL FAR *)(void)); + setfunc(WSAFDIsSet_winsock, __WSAFDIsSet, int (PASCAL FAR *)(SOCKET, fd_set FAR *)); + setfunc(WSACreateEvent_winsock, WSACreateEvent, WSAEVENT (PASCAL FAR *)(void)); + setfunc(WSACloseEvent_winsock, WSACloseEvent, BOOL (PASCAL FAR *)(WSAEVENT)); + setfunc(WSASetEvent_winsock, WSASetEvent, BOOL (PASCAL FAR *)(WSAEVENT)); + setfunc(WSAResetEvent_winsock, WSAResetEvent, BOOL (PASCAL FAR *)(WSAEVENT)); + setfunc(WSAEventSelect_winsock, WSAEventSelect, int (PASCAL FAR *)(SOCKET, WSAEVENT, long)); + setfunc(WSAWaitForMultipleEvents_winsock, WSAWaitForMultipleEvents, DWORD (PASCAL FAR *)(DWORD, const WSAEVENT FAR*, BOOL, DWORD, BOOL)); + setfunc(WSAEnumNetworkEvents_winsock, WSAEnumNetworkEvents, int (PASCAL FAR *)(SOCKET, WSAEVENT, LPWSANETWORKEVENTS)); + + s_networkModule = module; +} + +CArchSocket +CArchNetworkWinsock::newSocket(EAddressFamily family, ESocketType type) +{ + // create socket + SOCKET fd = socket_winsock(s_family[family], s_type[type], 0); + if (fd == INVALID_SOCKET) { + throwError(getsockerror_winsock()); + } + try { + setBlockingOnSocket(fd, false); + } + catch (...) { + close_winsock(fd); + throw; + } + + // allocate socket object + CArchSocketImpl* socket = new CArchSocketImpl; + socket->m_socket = fd; + socket->m_refCount = 1; + socket->m_event = WSACreateEvent_winsock(); + socket->m_pollWrite = true; + return socket; +} + +CArchSocket +CArchNetworkWinsock::copySocket(CArchSocket s) +{ + assert(s != NULL); + + // ref the socket and return it + ARCH->lockMutex(m_mutex); + ++s->m_refCount; + ARCH->unlockMutex(m_mutex); + return s; +} + +void +CArchNetworkWinsock::closeSocket(CArchSocket s) +{ + assert(s != NULL); + + // unref the socket and note if it should be released + ARCH->lockMutex(m_mutex); + const bool doClose = (--s->m_refCount == 0); + ARCH->unlockMutex(m_mutex); + + // close the socket if necessary + if (doClose) { + if (close_winsock(s->m_socket) == SOCKET_ERROR) { + // close failed. restore the last ref and throw. + int err = getsockerror_winsock(); + ARCH->lockMutex(m_mutex); + ++s->m_refCount; + ARCH->unlockMutex(m_mutex); + throwError(err); + } + WSACloseEvent_winsock(s->m_event); + delete s; + } +} + +void +CArchNetworkWinsock::closeSocketForRead(CArchSocket s) +{ + assert(s != NULL); + + if (shutdown_winsock(s->m_socket, SD_RECEIVE) == SOCKET_ERROR) { + if (getsockerror_winsock() != WSAENOTCONN) { + throwError(getsockerror_winsock()); + } + } +} + +void +CArchNetworkWinsock::closeSocketForWrite(CArchSocket s) +{ + assert(s != NULL); + + if (shutdown_winsock(s->m_socket, SD_SEND) == SOCKET_ERROR) { + if (getsockerror_winsock() != WSAENOTCONN) { + throwError(getsockerror_winsock()); + } + } +} + +void +CArchNetworkWinsock::bindSocket(CArchSocket s, CArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (bind_winsock(s->m_socket, &addr->m_addr, addr->m_len) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } +} + +void +CArchNetworkWinsock::listenOnSocket(CArchSocket s) +{ + assert(s != NULL); + + // hardcoding backlog + if (listen_winsock(s->m_socket, 3) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } +} + +CArchSocket +CArchNetworkWinsock::acceptSocket(CArchSocket s, CArchNetAddress* addr) +{ + assert(s != NULL); + + // create new socket and temporary address + CArchSocketImpl* socket = new CArchSocketImpl; + CArchNetAddress tmp = CArchNetAddressImpl::alloc(sizeof(struct sockaddr)); + + // accept on socket + SOCKET fd = accept_winsock(s->m_socket, &tmp->m_addr, &tmp->m_len); + if (fd == INVALID_SOCKET) { + int err = getsockerror_winsock(); + delete socket; + free(tmp); + *addr = NULL; + if (err == WSAEWOULDBLOCK) { + return NULL; + } + throwError(err); + } + + try { + setBlockingOnSocket(fd, false); + } + catch (...) { + close_winsock(fd); + delete socket; + free(tmp); + *addr = NULL; + throw; + } + + // initialize socket + socket->m_socket = fd; + socket->m_refCount = 1; + socket->m_event = WSACreateEvent_winsock(); + socket->m_pollWrite = true; + + // copy address if requested + if (addr != NULL) { + *addr = ARCH->copyAddr(tmp); + } + + free(tmp); + return socket; +} + +bool +CArchNetworkWinsock::connectSocket(CArchSocket s, CArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (connect_winsock(s->m_socket, &addr->m_addr, + addr->m_len) == SOCKET_ERROR) { + if (getsockerror_winsock() == WSAEISCONN) { + return true; + } + if (getsockerror_winsock() == WSAEWOULDBLOCK) { + return false; + } + throwError(getsockerror_winsock()); + } + return true; +} + +int +CArchNetworkWinsock::pollSocket(CPollEntry pe[], int num, double timeout) +{ + int i; + DWORD n; + + // prepare sockets and wait list + bool canWrite = false; + WSAEVENT* events = (WSAEVENT*)alloca((num + 1) * sizeof(WSAEVENT)); + for (i = 0, n = 0; i < num; ++i) { + // reset return flags + pe[i].m_revents = 0; + + // set invalid flag if socket is bogus then go to next socket + if (pe[i].m_socket == NULL) { + pe[i].m_revents |= kPOLLNVAL; + continue; + } + + // select desired events + long socketEvents = 0; + if ((pe[i].m_events & kPOLLIN) != 0) { + socketEvents |= FD_READ | FD_ACCEPT | FD_CLOSE; + } + if ((pe[i].m_events & kPOLLOUT) != 0) { + socketEvents |= FD_WRITE | FD_CONNECT | FD_CLOSE; + + // if m_pollWrite is false then we assume the socket is + // writable. winsock doesn't signal writability except + // when the state changes from unwritable. + if (!pe[i].m_socket->m_pollWrite) { + canWrite = true; + pe[i].m_revents |= kPOLLOUT; + } + } + + // if no events then ignore socket + if (socketEvents == 0) { + continue; + } + + // select socket for desired events + WSAEventSelect_winsock(pe[i].m_socket->m_socket, + pe[i].m_socket->m_event, socketEvents); + + // add socket event to wait list + events[n++] = pe[i].m_socket->m_event; + } + + // if no sockets then return immediately + if (n == 0) { + return 0; + } + + // add the unblock event + CArchMultithreadWindows* mt = CArchMultithreadWindows::getInstance(); + CArchThread thread = mt->newCurrentThread(); + WSAEVENT* unblockEvent = (WSAEVENT*)mt->getNetworkDataForThread(thread); + ARCH->closeThread(thread); + if (unblockEvent == NULL) { + unblockEvent = new WSAEVENT; + *unblockEvent = WSACreateEvent_winsock(); + mt->setNetworkDataForCurrentThread(unblockEvent); + } + events[n++] = *unblockEvent; + + // prepare timeout + DWORD t = (timeout < 0.0) ? INFINITE : (DWORD)(1000.0 * timeout); + if (canWrite) { + // if we know we can write then don't block + t = 0; + } + + // wait + DWORD result = WSAWaitForMultipleEvents_winsock(n, events, FALSE, t, FALSE); + + // reset the unblock event + WSAResetEvent_winsock(*unblockEvent); + + // handle results + if (result == WSA_WAIT_FAILED) { + if (getsockerror_winsock() == WSAEINTR) { + // interrupted system call + ARCH->testCancelThread(); + return 0; + } + throwError(getsockerror_winsock()); + } + if (result == WSA_WAIT_TIMEOUT && !canWrite) { + return 0; + } + if (result == WSA_WAIT_EVENT_0 + n - 1) { + // the unblock event was signalled + return 0; + } + for (i = 0, n = 0; i < num; ++i) { + // skip events we didn't check + if (pe[i].m_socket == NULL || + (pe[i].m_events & (kPOLLIN | kPOLLOUT)) == 0) { + continue; + } + + // get events + WSANETWORKEVENTS info; + if (WSAEnumNetworkEvents_winsock(pe[i].m_socket->m_socket, + pe[i].m_socket->m_event, &info) == SOCKET_ERROR) { + continue; + } + if ((info.lNetworkEvents & FD_READ) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((info.lNetworkEvents & FD_ACCEPT) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((info.lNetworkEvents & FD_WRITE) != 0) { + pe[i].m_revents |= kPOLLOUT; + + // socket is now writable so don't bothing polling for + // writable until it becomes unwritable. + pe[i].m_socket->m_pollWrite = false; + } + if ((info.lNetworkEvents & FD_CONNECT) != 0) { + if (info.iErrorCode[FD_CONNECT_BIT] != 0) { + pe[i].m_revents |= kPOLLERR; + } + else { + pe[i].m_revents |= kPOLLOUT; + pe[i].m_socket->m_pollWrite = false; + } + } + if ((info.lNetworkEvents & FD_CLOSE) != 0) { + if (info.iErrorCode[FD_CLOSE_BIT] != 0) { + pe[i].m_revents |= kPOLLERR; + } + else { + if ((pe[i].m_events & kPOLLIN) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((pe[i].m_events & kPOLLOUT) != 0) { + pe[i].m_revents |= kPOLLOUT; + } + } + } + if (pe[i].m_revents != 0) { + ++n; + } + } + + return (int)n; +} + +void +CArchNetworkWinsock::unblockPollSocket(CArchThread thread) +{ + // set the unblock event + CArchMultithreadWindows* mt = CArchMultithreadWindows::getInstance(); + WSAEVENT* unblockEvent = (WSAEVENT*)mt->getNetworkDataForThread(thread); + if (unblockEvent != NULL) { + WSASetEvent_winsock(*unblockEvent); + } +} + +size_t +CArchNetworkWinsock::readSocket(CArchSocket s, void* buf, size_t len) +{ + assert(s != NULL); + + int n = recv_winsock(s->m_socket, buf, len, 0); + if (n == SOCKET_ERROR) { + int err = getsockerror_winsock(); + if (err == WSAEINTR || err == WSAEWOULDBLOCK) { + return 0; + } + throwError(err); + } + return static_cast(n); +} + +size_t +CArchNetworkWinsock::writeSocket(CArchSocket s, const void* buf, size_t len) +{ + assert(s != NULL); + + int n = send_winsock(s->m_socket, buf, len, 0); + if (n == SOCKET_ERROR) { + int err = getsockerror_winsock(); + if (err == WSAEINTR) { + return 0; + } + if (err == WSAEWOULDBLOCK) { + s->m_pollWrite = true; + return 0; + } + throwError(err); + } + return static_cast(n); +} + +void +CArchNetworkWinsock::throwErrorOnSocket(CArchSocket s) +{ + assert(s != NULL); + + // get the error from the socket layer + int err = 0; + int size = sizeof(err); + if (getsockopt_winsock(s->m_socket, SOL_SOCKET, + SO_ERROR, &err, &size) == SOCKET_ERROR) { + err = getsockerror_winsock(); + } + + // throw if there's an error + if (err != 0) { + throwError(err); + } +} + +void +CArchNetworkWinsock::setBlockingOnSocket(SOCKET s, bool blocking) +{ + assert(s != 0); + + int flag = blocking ? 0 : 1; + if (ioctl_winsock(s, FIONBIO, &flag) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } +} + +bool +CArchNetworkWinsock::setNoDelayOnSocket(CArchSocket s, bool noDelay) +{ + assert(s != NULL); + + // get old state + BOOL oflag; + int size = sizeof(oflag); + if (getsockopt_winsock(s->m_socket, IPPROTO_TCP, + TCP_NODELAY, &oflag, &size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + // set new state + BOOL flag = noDelay ? 1 : 0; + size = sizeof(flag); + if (setsockopt_winsock(s->m_socket, IPPROTO_TCP, + TCP_NODELAY, &flag, size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + return (oflag != 0); +} + +bool +CArchNetworkWinsock::setReuseAddrOnSocket(CArchSocket s, bool reuse) +{ + assert(s != NULL); + + // get old state + BOOL oflag; + int size = sizeof(oflag); + if (getsockopt_winsock(s->m_socket, SOL_SOCKET, + SO_REUSEADDR, &oflag, &size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + // set new state + BOOL flag = reuse ? 1 : 0; + size = sizeof(flag); + if (setsockopt_winsock(s->m_socket, SOL_SOCKET, + SO_REUSEADDR, &flag, size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + return (oflag != 0); +} + +std::string +CArchNetworkWinsock::getHostName() +{ + char name[256]; + if (gethostname_winsock(name, sizeof(name)) == -1) { + name[0] = '\0'; + } + else { + name[sizeof(name) - 1] = '\0'; + } + return name; +} + +CArchNetAddress +CArchNetworkWinsock::newAnyAddr(EAddressFamily family) +{ + CArchNetAddressImpl* addr = NULL; + switch (family) { + case kINET: { + addr = CArchNetAddressImpl::alloc(sizeof(struct sockaddr_in)); + struct sockaddr_in* ipAddr = TYPED_ADDR(struct sockaddr_in, addr); + ipAddr->sin_family = AF_INET; + ipAddr->sin_port = 0; + ipAddr->sin_addr.s_addr = INADDR_ANY; + break; + } + + default: + assert(0 && "invalid family"); + } + return addr; +} + +CArchNetAddress +CArchNetworkWinsock::copyAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + CArchNetAddressImpl* copy = CArchNetAddressImpl::alloc(addr->m_len); + memcpy(TYPED_ADDR(void, copy), TYPED_ADDR(void, addr), addr->m_len); + return copy; +} + +CArchNetAddress +CArchNetworkWinsock::nameToAddr(const std::string& name) +{ + // allocate address + CArchNetAddressImpl* addr = NULL; + + // try to convert assuming an IPv4 dot notation address + struct sockaddr_in inaddr; + memset(&inaddr, 0, sizeof(inaddr)); + inaddr.sin_family = AF_INET; + inaddr.sin_port = 0; + inaddr.sin_addr.s_addr = inet_addr_winsock(name.c_str()); + if (inaddr.sin_addr.s_addr != INADDR_NONE) { + // it's a dot notation address + addr = CArchNetAddressImpl::alloc(sizeof(struct sockaddr_in)); + memcpy(TYPED_ADDR(void, addr), &inaddr, addr->m_len); + } + + else { + // address lookup + struct hostent* info = gethostbyname_winsock(name.c_str()); + if (info == NULL) { + throwNameError(getsockerror_winsock()); + } + + // copy over address (only IPv4 currently supported) + if (info->h_addrtype == AF_INET) { + addr = CArchNetAddressImpl::alloc(sizeof(struct sockaddr_in)); + memcpy(&inaddr.sin_addr, info->h_addr_list[0], + sizeof(inaddr.sin_addr)); + memcpy(TYPED_ADDR(void, addr), &inaddr, addr->m_len); + } + else { + throw XArchNetworkNameUnsupported( + "The requested name is valid but " + "does not have a supported address family"); + } + } + + return addr; +} + +void +CArchNetworkWinsock::closeAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + free(addr); +} + +std::string +CArchNetworkWinsock::addrToName(CArchNetAddress addr) +{ + assert(addr != NULL); + + // name lookup + struct hostent* info = gethostbyaddr_winsock( + reinterpret_cast(&addr->m_addr), + addr->m_len, addr->m_addr.sa_family); + if (info == NULL) { + throwNameError(getsockerror_winsock()); + } + + // return (primary) name + return info->h_name; +} + +std::string +CArchNetworkWinsock::addrToString(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + return inet_ntoa_winsock(ipAddr->sin_addr); + } + + default: + assert(0 && "unknown address family"); + return ""; + } +} + +IArchNetwork::EAddressFamily +CArchNetworkWinsock::getAddrFamily(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (addr->m_addr.sa_family) { + case AF_INET: + return kINET; + + default: + return kUNKNOWN; + } +} + +void +CArchNetworkWinsock::setAddrPort(CArchNetAddress addr, int port) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + ipAddr->sin_port = htons_winsock(static_cast(port)); + break; + } + + default: + assert(0 && "unknown address family"); + break; + } +} + +int +CArchNetworkWinsock::getAddrPort(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + return ntohs_winsock(ipAddr->sin_port); + } + + default: + assert(0 && "unknown address family"); + return 0; + } +} + +bool +CArchNetworkWinsock::isAnyAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + return (addr->m_len == sizeof(struct sockaddr_in) && + ipAddr->sin_addr.s_addr == INADDR_ANY); + } + + default: + assert(0 && "unknown address family"); + return true; + } +} + +bool +CArchNetworkWinsock::isEqualAddr(CArchNetAddress a, CArchNetAddress b) +{ + return (a == b || (a->m_len == b->m_len && + memcmp(&a->m_addr, &b->m_addr, a->m_len) == 0)); +} + +void +CArchNetworkWinsock::throwError(int err) +{ + switch (err) { + case WSAEACCES: + throw XArchNetworkAccess(new XArchEvalWinsock(err)); + + case WSAEMFILE: + case WSAENOBUFS: + case WSAENETDOWN: + throw XArchNetworkResource(new XArchEvalWinsock(err)); + + case WSAEPROTOTYPE: + case WSAEPROTONOSUPPORT: + case WSAEAFNOSUPPORT: + case WSAEPFNOSUPPORT: + case WSAESOCKTNOSUPPORT: + case WSAEINVAL: + case WSAENOPROTOOPT: + case WSAEOPNOTSUPP: + case WSAESHUTDOWN: + case WSANOTINITIALISED: + case WSAVERNOTSUPPORTED: + case WSASYSNOTREADY: + throw XArchNetworkSupport(new XArchEvalWinsock(err)); + + case WSAEADDRNOTAVAIL: + throw XArchNetworkNoAddress(new XArchEvalWinsock(err)); + + case WSAEADDRINUSE: + throw XArchNetworkAddressInUse(new XArchEvalWinsock(err)); + + case WSAEHOSTUNREACH: + case WSAENETUNREACH: + throw XArchNetworkNoRoute(new XArchEvalWinsock(err)); + + case WSAENOTCONN: + throw XArchNetworkNotConnected(new XArchEvalWinsock(err)); + + case WSAEDISCON: + throw XArchNetworkShutdown(new XArchEvalWinsock(err)); + + case WSAENETRESET: + case WSAECONNABORTED: + case WSAECONNRESET: + throw XArchNetworkDisconnected(new XArchEvalWinsock(err)); + + case WSAECONNREFUSED: + throw XArchNetworkConnectionRefused(new XArchEvalWinsock(err)); + + case WSAEHOSTDOWN: + case WSAETIMEDOUT: + throw XArchNetworkTimedOut(new XArchEvalWinsock(err)); + + case WSAHOST_NOT_FOUND: + throw XArchNetworkNameUnknown(new XArchEvalWinsock(err)); + + case WSANO_DATA: + throw XArchNetworkNameNoAddress(new XArchEvalWinsock(err)); + + case WSANO_RECOVERY: + throw XArchNetworkNameFailure(new XArchEvalWinsock(err)); + + case WSATRY_AGAIN: + throw XArchNetworkNameUnavailable(new XArchEvalWinsock(err)); + + default: + throw XArchNetwork(new XArchEvalWinsock(err)); + } +} + +void +CArchNetworkWinsock::throwNameError(int err) +{ + switch (err) { + case WSAHOST_NOT_FOUND: + throw XArchNetworkNameUnknown(new XArchEvalWinsock(err)); + + case WSANO_DATA: + throw XArchNetworkNameNoAddress(new XArchEvalWinsock(err)); + + case WSANO_RECOVERY: + throw XArchNetworkNameFailure(new XArchEvalWinsock(err)); + + case WSATRY_AGAIN: + throw XArchNetworkNameUnavailable(new XArchEvalWinsock(err)); + + default: + throw XArchNetworkName(new XArchEvalWinsock(err)); + } +} diff --git a/lib/arch/CArchNetworkWinsock.h b/lib/arch/CArchNetworkWinsock.h new file mode 100644 index 00000000..3912ba5b --- /dev/null +++ b/lib/arch/CArchNetworkWinsock.h @@ -0,0 +1,99 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHNETWORKWINSOCK_H +#define CARCHNETWORKWINSOCK_H + +#define WIN32_LEAN_AND_MEAN + +// declare no functions in winsock2 +#define INCL_WINSOCK_API_PROTOTYPES 0 +#define INCL_WINSOCK_API_TYPEDEFS 0 + +#include "IArchNetwork.h" +#include "IArchMultithread.h" +#include +#include + +#define ARCH_NETWORK CArchNetworkWinsock + +class CArchSocketImpl { +public: + SOCKET m_socket; + int m_refCount; + WSAEVENT m_event; + bool m_pollWrite; +}; + +class CArchNetAddressImpl { +public: + static CArchNetAddressImpl* alloc(size_t); + +public: + int m_len; + struct sockaddr m_addr; +}; +#define ADDR_HDR_SIZE offsetof(CArchNetAddressImpl, m_addr) +#define TYPED_ADDR(type_, addr_) (reinterpret_cast(&addr_->m_addr)) + +//! Win32 implementation of IArchNetwork +class CArchNetworkWinsock : public IArchNetwork { +public: + CArchNetworkWinsock(); + virtual ~CArchNetworkWinsock(); + + // IArchNetwork overrides + virtual CArchSocket newSocket(EAddressFamily, ESocketType); + virtual CArchSocket copySocket(CArchSocket s); + virtual void closeSocket(CArchSocket s); + virtual void closeSocketForRead(CArchSocket s); + virtual void closeSocketForWrite(CArchSocket s); + virtual void bindSocket(CArchSocket s, CArchNetAddress addr); + virtual void listenOnSocket(CArchSocket s); + virtual CArchSocket acceptSocket(CArchSocket s, CArchNetAddress* addr); + virtual bool connectSocket(CArchSocket s, CArchNetAddress name); + virtual int pollSocket(CPollEntry[], int num, double timeout); + virtual void unblockPollSocket(CArchThread thread); + virtual size_t readSocket(CArchSocket s, void* buf, size_t len); + virtual size_t writeSocket(CArchSocket s, + const void* buf, size_t len); + virtual void throwErrorOnSocket(CArchSocket); + virtual bool setNoDelayOnSocket(CArchSocket, bool noDelay); + virtual bool setReuseAddrOnSocket(CArchSocket, bool reuse); + virtual std::string getHostName(); + virtual CArchNetAddress newAnyAddr(EAddressFamily); + virtual CArchNetAddress copyAddr(CArchNetAddress); + virtual CArchNetAddress nameToAddr(const std::string&); + virtual void closeAddr(CArchNetAddress); + virtual std::string addrToName(CArchNetAddress); + virtual std::string addrToString(CArchNetAddress); + virtual EAddressFamily getAddrFamily(CArchNetAddress); + virtual void setAddrPort(CArchNetAddress, int port); + virtual int getAddrPort(CArchNetAddress); + virtual bool isAnyAddr(CArchNetAddress); + virtual bool isEqualAddr(CArchNetAddress, CArchNetAddress); + +private: + void init(HMODULE); + + void setBlockingOnSocket(SOCKET, bool blocking); + + void throwError(int); + void throwNameError(int); + +private: + CArchMutex m_mutex; +}; + +#endif diff --git a/lib/arch/CArchSleepUnix.cpp b/lib/arch/CArchSleepUnix.cpp new file mode 100644 index 00000000..35010721 --- /dev/null +++ b/lib/arch/CArchSleepUnix.cpp @@ -0,0 +1,88 @@ +/* + * 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 "CArchSleepUnix.h" +#include "CArch.h" +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif +#if !HAVE_NANOSLEEP +# if HAVE_SYS_SELECT_H +# include +# endif +# if HAVE_SYS_TYPES_H +# include +# endif +# if HAVE_UNISTD_H +# include +# endif +#endif + +// +// CArchSleepUnix +// + +CArchSleepUnix::CArchSleepUnix() +{ + // do nothing +} + +CArchSleepUnix::~CArchSleepUnix() +{ + // do nothing +} + +void +CArchSleepUnix::sleep(double timeout) +{ + ARCH->testCancelThread(); + if (timeout < 0.0) { + return; + } + +#if HAVE_NANOSLEEP + // prep timeout + struct timespec t; + t.tv_sec = (long)timeout; + t.tv_nsec = (long)(1.0e+9 * (timeout - (double)t.tv_sec)); + + // wait + while (nanosleep(&t, &t) < 0) + ARCH->testCancelThread(); +#else + /* emulate nanosleep() with select() */ + double startTime = ARCH->time(); + double timeLeft = timeout; + while (timeLeft > 0.0) { + struct timeval timeout2; + timeout2.tv_sec = static_cast(timeLeft); + timeout2.tv_usec = static_cast(1.0e+6 * (timeLeft - + timeout2.tv_sec)); + select((SELECT_TYPE_ARG1) 0, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG5 &timeout2); + ARCH->testCancelThread(); + timeLeft = timeout - (ARCH->time() - startTime); + } +#endif +} diff --git a/lib/arch/CArchSleepUnix.h b/lib/arch/CArchSleepUnix.h new file mode 100644 index 00000000..939ca401 --- /dev/null +++ b/lib/arch/CArchSleepUnix.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSLEEPUNIX_H +#define CARCHSLEEPUNIX_H + +#include "IArchSleep.h" + +#define ARCH_SLEEP CArchSleepUnix + +//! Unix implementation of IArchSleep +class CArchSleepUnix : public IArchSleep { +public: + CArchSleepUnix(); + virtual ~CArchSleepUnix(); + + // IArchSleep overrides + virtual void sleep(double timeout); +}; + +#endif diff --git a/lib/arch/CArchSleepWindows.cpp b/lib/arch/CArchSleepWindows.cpp new file mode 100644 index 00000000..f6c8bed8 --- /dev/null +++ b/lib/arch/CArchSleepWindows.cpp @@ -0,0 +1,57 @@ +/* + * 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 "CArchSleepWindows.h" +#include "CArch.h" +#include "CArchMultithreadWindows.h" + +// +// CArchSleepWindows +// + +CArchSleepWindows::CArchSleepWindows() +{ + // do nothing +} + +CArchSleepWindows::~CArchSleepWindows() +{ + // do nothing +} + +void +CArchSleepWindows::sleep(double timeout) +{ + ARCH->testCancelThread(); + if (timeout < 0.0) { + return; + } + + // get the cancel event from the current thread. this only + // works if we're using the windows multithread object but + // this is windows so that's pretty certain; we'll get a + // link error if we're not, though. + CArchMultithreadWindows* mt = CArchMultithreadWindows::getInstance(); + if (mt != NULL) { + HANDLE cancelEvent = mt->getCancelEventForCurrentThread(); + WaitForSingleObject(cancelEvent, (DWORD)(1000.0 * timeout)); + if (timeout == 0.0) { + Sleep(0); + } + } + else { + Sleep((DWORD)(1000.0 * timeout)); + } + ARCH->testCancelThread(); +} diff --git a/lib/arch/CArchSleepWindows.h b/lib/arch/CArchSleepWindows.h new file mode 100644 index 00000000..a5a5fa90 --- /dev/null +++ b/lib/arch/CArchSleepWindows.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSLEEPWINDOWS_H +#define CARCHSLEEPWINDOWS_H + +#include "IArchSleep.h" + +#define ARCH_SLEEP CArchSleepWindows + +//! Win32 implementation of IArchSleep +class CArchSleepWindows : public IArchSleep { +public: + CArchSleepWindows(); + virtual ~CArchSleepWindows(); + + // IArchSleep overrides + virtual void sleep(double timeout); +}; + +#endif diff --git a/lib/arch/CArchStringUnix.cpp b/lib/arch/CArchStringUnix.cpp new file mode 100644 index 00000000..e0ad3457 --- /dev/null +++ b/lib/arch/CArchStringUnix.cpp @@ -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. + */ + +#include "CArchStringUnix.h" +#include + +// +// CArchStringUnix +// + +#include "CMultibyte.cpp" +#include "vsnprintf.cpp" + +IArchString::EWideCharEncoding +CArchStringUnix::getWideCharEncoding() +{ + return kUCS4; +} diff --git a/lib/arch/CArchStringUnix.h b/lib/arch/CArchStringUnix.h new file mode 100644 index 00000000..20e5486b --- /dev/null +++ b/lib/arch/CArchStringUnix.h @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSTRINGUNIX_H +#define CARCHSTRINGUNIX_H + +#include "IArchString.h" + +#define ARCH_STRING CArchStringUnix + +//! Unix implementation of IArchString +class CArchStringUnix : public IArchString { +public: + CArchStringUnix(); + virtual ~CArchStringUnix(); + + // IArchString overrides + virtual int vsnprintf(char* str, + int size, const char* fmt, va_list ap); + virtual int convStringMBToWC(wchar_t*, + const char*, UInt32 n, bool* errors); + virtual int convStringWCToMB(char*, + const wchar_t*, UInt32 n, bool* errors); + virtual EWideCharEncoding + getWideCharEncoding(); +}; + +#endif diff --git a/lib/arch/CArchStringWindows.cpp b/lib/arch/CArchStringWindows.cpp new file mode 100644 index 00000000..a3b90765 --- /dev/null +++ b/lib/arch/CArchStringWindows.cpp @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#define WIN32_LEAN_AND_MEAN + +#include "CArchStringWindows.h" +#include +#include + +// +// CArchStringWindows +// + +#include "CMultibyte.cpp" +#define HAVE_VSNPRINTF 1 +#define ARCH_VSNPRINTF _vsnprintf +#include "vsnprintf.cpp" + +IArchString::EWideCharEncoding +CArchStringWindows::getWideCharEncoding() +{ + return kUTF16; +} diff --git a/lib/arch/CArchStringWindows.h b/lib/arch/CArchStringWindows.h new file mode 100644 index 00000000..a67d8431 --- /dev/null +++ b/lib/arch/CArchStringWindows.h @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSTRINGWINDOWS_H +#define CARCHSTRINGWINDOWS_H + +#include "IArchString.h" + +#define ARCH_STRING CArchStringWindows + +//! Win32 implementation of IArchString +class CArchStringWindows : public IArchString { +public: + CArchStringWindows(); + virtual ~CArchStringWindows(); + + // IArchString overrides + virtual int vsnprintf(char* str, + int size, const char* fmt, va_list ap); + virtual int convStringMBToWC(wchar_t*, + const char*, UInt32 n, bool* errors); + virtual int convStringWCToMB(char*, + const wchar_t*, UInt32 n, bool* errors); + virtual EWideCharEncoding + getWideCharEncoding(); +}; + +#endif diff --git a/lib/arch/CArchSystemUnix.cpp b/lib/arch/CArchSystemUnix.cpp new file mode 100644 index 00000000..541038db --- /dev/null +++ b/lib/arch/CArchSystemUnix.cpp @@ -0,0 +1,50 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CArchSystemUnix.h" +#include + +// +// CArchSystemUnix +// + +CArchSystemUnix::CArchSystemUnix() +{ + // do nothing +} + +CArchSystemUnix::~CArchSystemUnix() +{ + // do nothing +} + +std::string +CArchSystemUnix::getOSName() const +{ +#if defined(HAVE_SYS_UTSNAME_H) + struct utsname info; + if (uname(&info) == 0) { + std::string msg; + msg += info.sysname; + msg += " "; + msg += info.release; + msg += " "; + msg += info.version; + msg += " "; + msg += info.machine; + return msg; + } +#endif + return "Unix "; +} diff --git a/lib/arch/CArchSystemUnix.h b/lib/arch/CArchSystemUnix.h new file mode 100644 index 00000000..525aed1c --- /dev/null +++ b/lib/arch/CArchSystemUnix.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSYSTEMUNIX_H +#define CARCHSYSTEMUNIX_H + +#include "IArchSystem.h" + +#define ARCH_SYSTEM CArchSystemUnix + +//! Unix implementation of IArchString +class CArchSystemUnix : public IArchSystem { +public: + CArchSystemUnix(); + virtual ~CArchSystemUnix(); + + // IArchSystem overrides + virtual std::string getOSName() const; +}; + +#endif diff --git a/lib/arch/CArchSystemWindows.cpp b/lib/arch/CArchSystemWindows.cpp new file mode 100644 index 00000000..b634b4bc --- /dev/null +++ b/lib/arch/CArchSystemWindows.cpp @@ -0,0 +1,86 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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. + */ + +#define WIN32_LEAN_AND_MEAN + +#include "CArchSystemWindows.h" +#include + +// +// CArchSystemWindows +// + +CArchSystemWindows::CArchSystemWindows() +{ + // do nothing +} + +CArchSystemWindows::~CArchSystemWindows() +{ + // do nothing +} + +std::string +CArchSystemWindows::getOSName() const +{ + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(info); + if (GetVersionEx(&info)) { + switch (info.dwPlatformId) { + case VER_PLATFORM_WIN32_NT: + if (info.dwMajorVersion == 5 && info.dwMinorVersion == 2) { + return "Microsoft Windows Server 2003"; + } + if (info.dwMajorVersion == 5 && info.dwMinorVersion == 1) { + return "Microsoft Windows Server XP"; + } + if (info.dwMajorVersion == 5 && info.dwMinorVersion == 0) { + return "Microsoft Windows Server 2000"; + } + if (info.dwMajorVersion <= 4) { + return "Microsoft Windows NT"; + } + char buffer[100]; + sprintf(buffer, "Microsoft Windows %d.%d", + info.dwMajorVersion, info.dwMinorVersion); + return buffer; + + case VER_PLATFORM_WIN32_WINDOWS: + if (info.dwMajorVersion == 4 && info.dwMinorVersion == 0) { + if (info.szCSDVersion[1] == 'C' || + info.szCSDVersion[1] == 'B') { + return "Microsoft Windows 95 OSR2"; + } + return "Microsoft Windows 95"; + } + if (info.dwMajorVersion == 4 && info.dwMinorVersion == 10) { + if (info.szCSDVersion[1] == 'A') { + return "Microsoft Windows 98 SE"; + } + return "Microsoft Windows 98"; + } + if (info.dwMajorVersion == 4 && info.dwMinorVersion == 90) { + return "Microsoft Windows ME"; + } + if (info.dwMajorVersion == 4) { + return "Microsoft Windows unknown 95 family"; + } + break; + + default: + break; + } + } + return "Microsoft Windows "; +} diff --git a/lib/arch/CArchSystemWindows.h b/lib/arch/CArchSystemWindows.h new file mode 100644 index 00000000..e23913d0 --- /dev/null +++ b/lib/arch/CArchSystemWindows.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSYSTEMWINDOWS_H +#define CARCHSYSTEMWINDOWS_H + +#include "IArchSystem.h" + +#define ARCH_SYSTEM CArchSystemWindows + +//! Win32 implementation of IArchString +class CArchSystemWindows : public IArchSystem { +public: + CArchSystemWindows(); + virtual ~CArchSystemWindows(); + + // IArchSystem overrides + virtual std::string getOSName() const; +}; + +#endif diff --git a/lib/arch/CArchTaskBarWindows.cpp b/lib/arch/CArchTaskBarWindows.cpp new file mode 100644 index 00000000..29c57b66 --- /dev/null +++ b/lib/arch/CArchTaskBarWindows.cpp @@ -0,0 +1,492 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchTaskBarWindows.h" +#include "CArchMiscWindows.h" +#include "IArchTaskBarReceiver.h" +#include "CArch.h" +#include "XArch.h" +#include +#include + +static const UINT kAddReceiver = WM_USER + 10; +static const UINT kRemoveReceiver = WM_USER + 11; +static const UINT kUpdateReceiver = WM_USER + 12; +static const UINT kNotifyReceiver = WM_USER + 13; +static const UINT kFirstReceiverID = WM_USER + 14; + +// +// CArchTaskBarWindows +// + +CArchTaskBarWindows* CArchTaskBarWindows::s_instance = NULL; +HINSTANCE CArchTaskBarWindows::s_appInstance = NULL; + +CArchTaskBarWindows::CArchTaskBarWindows(void* appInstance) : + m_nextID(kFirstReceiverID) +{ + // save the singleton instance + s_instance = this; + + // save app instance + s_appInstance = reinterpret_cast(appInstance); + + // we need a mutex + m_mutex = ARCH->newMutex(); + + // and a condition variable which uses the above mutex + m_ready = false; + m_condVar = ARCH->newCondVar(); + + // we're going to want to get a result from the thread we're + // about to create to know if it initialized successfully. + // so we lock the condition variable. + ARCH->lockMutex(m_mutex); + + // open a window and run an event loop in a separate thread. + // this has to happen in a separate thread because if we + // create a window on the current desktop with the current + // thread then the current thread won't be able to switch + // desktops if it needs to. + m_thread = ARCH->newThread(&CArchTaskBarWindows::threadEntry, this); + + // wait for child thread + while (!m_ready) { + ARCH->waitCondVar(m_condVar, m_mutex, -1.0); + } + + // ready + ARCH->unlockMutex(m_mutex); +} + +CArchTaskBarWindows::~CArchTaskBarWindows() +{ + if (m_thread != NULL) { + 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 +CArchTaskBarWindows::addDialog(HWND hwnd) +{ + CArchMiscWindows::addDialog(hwnd); +} + +void +CArchTaskBarWindows::removeDialog(HWND hwnd) +{ + CArchMiscWindows::removeDialog(hwnd); +} + +void +CArchTaskBarWindows::addReceiver(IArchTaskBarReceiver* receiver) +{ + // ignore bogus receiver + if (receiver == NULL) { + return; + } + + // add receiver if necessary + CReceiverToInfoMap::iterator index = m_receivers.find(receiver); + if (index == m_receivers.end()) { + // add it, creating a new message ID for it + CReceiverInfo info; + info.m_id = getNextID(); + index = m_receivers.insert(std::make_pair(receiver, info)).first; + + // add ID to receiver mapping + m_idTable.insert(std::make_pair(info.m_id, index)); + } + + // add receiver + PostMessage(m_hwnd, kAddReceiver, index->second.m_id, 0); +} + +void +CArchTaskBarWindows::removeReceiver(IArchTaskBarReceiver* receiver) +{ + // find receiver + CReceiverToInfoMap::iterator index = m_receivers.find(receiver); + if (index == m_receivers.end()) { + return; + } + + // remove icon. wait for this to finish before returning. + SendMessage(m_hwnd, kRemoveReceiver, index->second.m_id, 0); + + // recycle the ID + recycleID(index->second.m_id); + + // discard + m_idTable.erase(index->second.m_id); + m_receivers.erase(index); +} + +void +CArchTaskBarWindows::updateReceiver(IArchTaskBarReceiver* receiver) +{ + // find receiver + CReceiverToInfoMap::const_iterator index = m_receivers.find(receiver); + if (index == m_receivers.end()) { + return; + } + + // update icon and tool tip + PostMessage(m_hwnd, kUpdateReceiver, index->second.m_id, 0); +} + +UINT +CArchTaskBarWindows::getNextID() +{ + if (m_oldIDs.empty()) { + return m_nextID++; + } + UINT id = m_oldIDs.back(); + m_oldIDs.pop_back(); + return id; +} + +void +CArchTaskBarWindows::recycleID(UINT id) +{ + m_oldIDs.push_back(id); +} + +void +CArchTaskBarWindows::addIcon(UINT id) +{ + ARCH->lockMutex(m_mutex); + CIDToReceiverMap::const_iterator index = m_idTable.find(id); + if (index != m_idTable.end()) { + modifyIconNoLock(index->second, NIM_ADD); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::removeIcon(UINT id) +{ + ARCH->lockMutex(m_mutex); + removeIconNoLock(id); + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::updateIcon(UINT id) +{ + ARCH->lockMutex(m_mutex); + CIDToReceiverMap::const_iterator index = m_idTable.find(id); + if (index != m_idTable.end()) { + modifyIconNoLock(index->second, NIM_MODIFY); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::addAllIcons() +{ + ARCH->lockMutex(m_mutex); + for (CReceiverToInfoMap::const_iterator index = m_receivers.begin(); + index != m_receivers.end(); ++index) { + modifyIconNoLock(index, NIM_ADD); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::removeAllIcons() +{ + ARCH->lockMutex(m_mutex); + for (CReceiverToInfoMap::const_iterator index = m_receivers.begin(); + index != m_receivers.end(); ++index) { + removeIconNoLock(index->second.m_id); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::modifyIconNoLock( + CReceiverToInfoMap::const_iterator index, DWORD taskBarMessage) +{ + // get receiver + UINT id = index->second.m_id; + IArchTaskBarReceiver* receiver = index->first; + + // lock receiver so icon and tool tip are guaranteed to be consistent + receiver->lock(); + + // get icon data + HICON icon = reinterpret_cast( + const_cast(receiver->getIcon())); + + // get tool tip + std::string toolTip = receiver->getToolTip(); + + // done querying + receiver->unlock(); + + // prepare to add icon + NOTIFYICONDATA data; + data.cbSize = sizeof(NOTIFYICONDATA); + data.hWnd = m_hwnd; + data.uID = id; + data.uFlags = NIF_MESSAGE; + data.uCallbackMessage = kNotifyReceiver; + data.hIcon = icon; + if (icon != NULL) { + data.uFlags |= NIF_ICON; + } + if (!toolTip.empty()) { + strncpy(data.szTip, toolTip.c_str(), sizeof(data.szTip)); + data.szTip[sizeof(data.szTip) - 1] = '\0'; + data.uFlags |= NIF_TIP; + } + else { + data.szTip[0] = '\0'; + } + + // add icon + if (Shell_NotifyIcon(taskBarMessage, &data) == 0) { + // failed + } +} + +void +CArchTaskBarWindows::removeIconNoLock(UINT id) +{ + NOTIFYICONDATA data; + data.cbSize = sizeof(NOTIFYICONDATA); + data.hWnd = m_hwnd; + data.uID = id; + if (Shell_NotifyIcon(NIM_DELETE, &data) == 0) { + // failed + } +} + +void +CArchTaskBarWindows::handleIconMessage( + IArchTaskBarReceiver* receiver, LPARAM lParam) +{ + // process message + switch (lParam) { + case WM_LBUTTONDOWN: + receiver->showStatus(); + break; + + case WM_LBUTTONDBLCLK: + receiver->primaryAction(); + break; + + case WM_RBUTTONUP: { + POINT p; + GetCursorPos(&p); + receiver->runMenu(p.x, p.y); + break; + } + + case WM_MOUSEMOVE: + // currently unused + break; + + default: + // unused + break; + } +} + +bool +CArchTaskBarWindows::processDialogs(MSG* msg) +{ + // only one thread can be in this method on any particular object + // at any given time. that's not a problem since only our event + // loop calls this method and there's just one of those. + + ARCH->lockMutex(m_mutex); + + // remove removed dialogs + m_dialogs.erase(false); + + // merge added dialogs into the dialog list + for (CDialogs::const_iterator index = m_addedDialogs.begin(); + index != m_addedDialogs.end(); ++index) { + m_dialogs.insert(std::make_pair(index->first, index->second)); + } + m_addedDialogs.clear(); + + ARCH->unlockMutex(m_mutex); + + // check message against all dialogs until one handles it. + // note that we don't hold a lock while checking because + // the message is processed and may make calls to this + // object. that's okay because addDialog() and + // removeDialog() don't change the map itself (just the + // values of some elements). + ARCH->lockMutex(m_mutex); + for (CDialogs::const_iterator index = m_dialogs.begin(); + index != m_dialogs.end(); ++index) { + if (index->second) { + ARCH->unlockMutex(m_mutex); + if (IsDialogMessage(index->first, msg)) { + return true; + } + ARCH->lockMutex(m_mutex); + } + } + ARCH->unlockMutex(m_mutex); + + return false; +} + +LRESULT +CArchTaskBarWindows::wndProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case kNotifyReceiver: { + // lookup receiver + CIDToReceiverMap::const_iterator index = m_idTable.find(wParam); + if (index != m_idTable.end()) { + IArchTaskBarReceiver* receiver = index->second->first; + handleIconMessage(receiver, lParam); + return 0; + } + break; + } + + case kAddReceiver: + addIcon(wParam); + break; + + case kRemoveReceiver: + removeIcon(wParam); + break; + + case kUpdateReceiver: + updateIcon(wParam); + break; + + default: + if (msg == m_taskBarRestart) { + // task bar was recreated so re-add our icons + addAllIcons(); + } + break; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT CALLBACK +CArchTaskBarWindows::staticWndProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + // if msg is WM_NCCREATE, extract the CArchTaskBarWindows* and put + // it in the extra window data then forward the call. + CArchTaskBarWindows* self = NULL; + if (msg == WM_NCCREATE) { + CREATESTRUCT* createInfo; + createInfo = reinterpret_cast(lParam); + self = reinterpret_cast( + createInfo->lpCreateParams); + SetWindowLong(hwnd, 0, reinterpret_cast(self)); + } + else { + // get the extra window data and forward the call + LONG data = GetWindowLong(hwnd, 0); + if (data != 0) { + self = reinterpret_cast( + reinterpret_cast(data)); + } + } + + // forward the message + if (self != NULL) { + return self->wndProc(hwnd, msg, wParam, lParam); + } + else { + return DefWindowProc(hwnd, msg, wParam, lParam); + } +} + +void +CArchTaskBarWindows::threadMainLoop() +{ + // register the task bar restart message + m_taskBarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); + + // register a window class + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_NOCLOSE; + classInfo.lpfnWndProc = &CArchTaskBarWindows::staticWndProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = sizeof(CArchTaskBarWindows*); + classInfo.hInstance = s_appInstance; + classInfo.hIcon = NULL; + classInfo.hCursor = NULL; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = TEXT("SynergyTaskBar"); + classInfo.hIconSm = NULL; + ATOM windowClass = RegisterClassEx(&classInfo); + + // create window + m_hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, + reinterpret_cast(windowClass), + TEXT("Synergy Task Bar"), + WS_POPUP, + 0, 0, 1, 1, + NULL, + NULL, + s_appInstance, + reinterpret_cast(this)); + + // signal ready + ARCH->lockMutex(m_mutex); + m_ready = true; + ARCH->broadcastCondVar(m_condVar); + ARCH->unlockMutex(m_mutex); + + // handle failure + if (m_hwnd == NULL) { + UnregisterClass(reinterpret_cast(windowClass), s_appInstance); + return; + } + + // main loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + if (!processDialogs(&msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + // clean up + removeAllIcons(); + DestroyWindow(m_hwnd); + UnregisterClass(reinterpret_cast(windowClass), s_appInstance); +} + +void* +CArchTaskBarWindows::threadEntry(void* self) +{ + reinterpret_cast(self)->threadMainLoop(); + return NULL; +} diff --git a/lib/arch/CArchTaskBarWindows.h b/lib/arch/CArchTaskBarWindows.h new file mode 100644 index 00000000..67e9af17 --- /dev/null +++ b/lib/arch/CArchTaskBarWindows.h @@ -0,0 +1,110 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHTASKBARWINDOWS_H +#define CARCHTASKBARWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "IArchTaskBar.h" +#include "IArchMultithread.h" +#include "stdmap.h" +#include "stdvector.h" +#include + +#define ARCH_TASKBAR CArchTaskBarWindows + +//! Win32 implementation of IArchTaskBar +class CArchTaskBarWindows : public IArchTaskBar { +public: + CArchTaskBarWindows(void*); + virtual ~CArchTaskBarWindows(); + + //! Add a dialog window + /*! + Tell the task bar event loop about a dialog. Win32 annoyingly + requires messages destined for modeless dialog boxes to be + dispatched differently than other messages. + */ + static void addDialog(HWND); + + //! Remove a dialog window + /*! + Remove a dialog window added via \c addDialog(). + */ + static void removeDialog(HWND); + + // IArchTaskBar overrides + virtual void addReceiver(IArchTaskBarReceiver*); + virtual void removeReceiver(IArchTaskBarReceiver*); + virtual void updateReceiver(IArchTaskBarReceiver*); + +private: + class CReceiverInfo { + public: + UINT m_id; + }; + + typedef std::map CReceiverToInfoMap; + typedef std::map CIDToReceiverMap; + typedef std::vector CIDStack; + typedef std::map CDialogs; + + UINT getNextID(); + void recycleID(UINT); + + void addIcon(UINT); + void removeIcon(UINT); + void updateIcon(UINT); + void addAllIcons(); + void removeAllIcons(); + void modifyIconNoLock(CReceiverToInfoMap::const_iterator, + DWORD taskBarMessage); + void removeIconNoLock(UINT id); + void handleIconMessage(IArchTaskBarReceiver*, LPARAM); + + bool processDialogs(MSG*); + LRESULT wndProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK + staticWndProc(HWND, UINT, WPARAM, LPARAM); + void threadMainLoop(); + static void* threadEntry(void*); + +private: + static CArchTaskBarWindows* s_instance; + static HINSTANCE s_appInstance; + + // multithread data + CArchMutex m_mutex; + CArchCond m_condVar; + bool m_ready; + int m_result; + CArchThread m_thread; + + // child thread data + HWND m_hwnd; + UINT m_taskBarRestart; + + // shared data + CReceiverToInfoMap m_receivers; + CIDToReceiverMap m_idTable; + CIDStack m_oldIDs; + UINT m_nextID; + + // dialogs + CDialogs m_dialogs; + CDialogs m_addedDialogs; +}; + +#endif diff --git a/lib/arch/CArchTaskBarXWindows.cpp b/lib/arch/CArchTaskBarXWindows.cpp new file mode 100644 index 00000000..6934f271 --- /dev/null +++ b/lib/arch/CArchTaskBarXWindows.cpp @@ -0,0 +1,47 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchTaskBarXWindows.h" + +// +// CArchTaskBarXWindows +// + +CArchTaskBarXWindows::CArchTaskBarXWindows(void*) +{ + // do nothing +} + +CArchTaskBarXWindows::~CArchTaskBarXWindows() +{ + // do nothing +} + +void +CArchTaskBarXWindows::addReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} + +void +CArchTaskBarXWindows::removeReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} + +void +CArchTaskBarXWindows::updateReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} diff --git a/lib/arch/CArchTaskBarXWindows.h b/lib/arch/CArchTaskBarXWindows.h new file mode 100644 index 00000000..abf28012 --- /dev/null +++ b/lib/arch/CArchTaskBarXWindows.h @@ -0,0 +1,34 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHTASKBARXWINDOWS_H +#define CARCHTASKBARXWINDOWS_H + +#include "IArchTaskBar.h" + +#define ARCH_TASKBAR CArchTaskBarXWindows + +//! X11 implementation of IArchTaskBar +class CArchTaskBarXWindows : public IArchTaskBar { +public: + CArchTaskBarXWindows(void*); + virtual ~CArchTaskBarXWindows(); + + // IArchTaskBar overrides + virtual void addReceiver(IArchTaskBarReceiver*); + virtual void removeReceiver(IArchTaskBarReceiver*); + virtual void updateReceiver(IArchTaskBarReceiver*); +}; + +#endif diff --git a/lib/arch/CArchTimeUnix.cpp b/lib/arch/CArchTimeUnix.cpp new file mode 100644 index 00000000..49506bad --- /dev/null +++ b/lib/arch/CArchTimeUnix.cpp @@ -0,0 +1,47 @@ +/* + * 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 "CArchTimeUnix.h" +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +// +// CArchTimeUnix +// + +CArchTimeUnix::CArchTimeUnix() +{ + // do nothing +} + +CArchTimeUnix::~CArchTimeUnix() +{ + // do nothing +} + +double +CArchTimeUnix::time() +{ + struct timeval t; + gettimeofday(&t, NULL); + return (double)t.tv_sec + 1.0e-6 * (double)t.tv_usec; +} diff --git a/lib/arch/CArchTimeUnix.h b/lib/arch/CArchTimeUnix.h new file mode 100644 index 00000000..78c6ff6f --- /dev/null +++ b/lib/arch/CArchTimeUnix.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHTIMEUNIX_H +#define CARCHTIMEUNIX_H + +#include "IArchTime.h" + +#define ARCH_TIME CArchTimeUnix + +//! Generic Unix implementation of IArchTime +class CArchTimeUnix : public IArchTime { +public: + CArchTimeUnix(); + virtual ~CArchTimeUnix(); + + // IArchTime overrides + virtual double time(); +}; + +#endif diff --git a/lib/arch/CArchTimeWindows.cpp b/lib/arch/CArchTimeWindows.cpp new file mode 100644 index 00000000..57aee290 --- /dev/null +++ b/lib/arch/CArchTimeWindows.cpp @@ -0,0 +1,86 @@ +/* + * 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. + */ + +// avoid getting a lot a crap from mmsystem.h that we don't need +#define MMNODRV // Installable driver support +#define MMNOSOUND // Sound support +#define MMNOWAVE // Waveform support +#define MMNOMIDI // MIDI support +#define MMNOAUX // Auxiliary audio support +#define MMNOMIXER // Mixer support +#define MMNOJOY // Joystick support +#define MMNOMCI // MCI support +#define MMNOMMIO // Multimedia file I/O support +#define MMNOMMSYSTEM // General MMSYSTEM functions + +#define WIN32_LEAN_AND_MEAN + +#include "CArchTimeWindows.h" +#include +#include + +typedef WINMMAPI DWORD (WINAPI *PTimeGetTime)(void); + +static double s_freq = 0.0; +static HINSTANCE s_mmInstance = NULL; +static PTimeGetTime s_tgt = NULL; + + +// +// CArchTimeWindows +// + +CArchTimeWindows::CArchTimeWindows() +{ + assert(s_freq == 0.0 || s_mmInstance == NULL); + + LARGE_INTEGER freq; + if (QueryPerformanceFrequency(&freq) && freq.QuadPart != 0) { + s_freq = 1.0 / static_cast(freq.QuadPart); + } + else { + // load winmm.dll and get timeGetTime + s_mmInstance = LoadLibrary("winmm"); + if (s_mmInstance != NULL) { + s_tgt = (PTimeGetTime)GetProcAddress(s_mmInstance, "timeGetTime"); + } + } +} + +CArchTimeWindows::~CArchTimeWindows() +{ + s_freq = 0.0; + if (s_mmInstance == NULL) { + FreeLibrary(reinterpret_cast(s_mmInstance)); + s_tgt = NULL; + s_mmInstance = NULL; + } +} + +double +CArchTimeWindows::time() +{ + // get time. we try three ways, in order of descending precision + if (s_freq != 0.0) { + LARGE_INTEGER c; + QueryPerformanceCounter(&c); + return s_freq * static_cast(c.QuadPart); + } + else if (s_tgt != NULL) { + return 0.001 * static_cast(s_tgt()); + } + else { + return 0.001 * static_cast(GetTickCount()); + } +} diff --git a/lib/arch/CArchTimeWindows.h b/lib/arch/CArchTimeWindows.h new file mode 100644 index 00000000..fb9b1e9f --- /dev/null +++ b/lib/arch/CArchTimeWindows.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHTIMEWINDOWS_H +#define CARCHTIMEWINDOWS_H + +#include "IArchTime.h" + +#define ARCH_TIME CArchTimeWindows + +//! Win32 implementation of IArchTime +class CArchTimeWindows : public IArchTime { +public: + CArchTimeWindows(); + virtual ~CArchTimeWindows(); + + // IArchTime overrides + virtual double time(); +}; + +#endif diff --git a/lib/arch/CMultibyte.cpp b/lib/arch/CMultibyte.cpp new file mode 100644 index 00000000..517d72d6 --- /dev/null +++ b/lib/arch/CMultibyte.cpp @@ -0,0 +1,219 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMULTIBYTE_H +#define CMULTIBYTE_H + +#include "common.h" +#include "CArch.h" +#include +#include +#if HAVE_LOCALE_H +# include +#endif +#if HAVE_WCHAR_H || defined(_MSC_VER) +# include +#elif __APPLE__ + // wtf? Darwin puts mbtowc() et al. in stdlib +# include +#else + // platform apparently has no wchar_t support. provide dummy + // implementations. hopefully at least the C++ compiler has + // a built-in wchar_t type. + +static inline +int +mbtowc(wchar_t* dst, const char* src, int n) +{ + *dst = static_cast(*src); + return 1; +} + +static inline +int +wctomb(char* dst, wchar_t src) +{ + *dst = static_cast(src); + return 1; +} + +#endif + +// +// use C library non-reentrant multibyte conversion with mutex +// + +static CArchMutex s_mutex = NULL; + +ARCH_STRING::ARCH_STRING() +{ + s_mutex = ARCH->newMutex(); + +#if HAVE_LOCALE_H + // see if we can convert a Latin-1 character + char mb[MB_LEN_MAX]; + if (wctomb(mb, 0xe3) == -1) { + // can't convert. try another locale so we can convert latin-1. + setlocale(LC_CTYPE, "en_US"); + } +#endif +} + +ARCH_STRING::~ARCH_STRING() +{ + ARCH->closeMutex(s_mutex); + s_mutex = NULL; +} + +int +ARCH_STRING::convStringWCToMB(char* dst, + const wchar_t* src, UInt32 n, bool* errors) +{ + int len = 0; + + bool dummyErrors; + if (errors == NULL) { + errors = &dummyErrors; + } + + ARCH->lockMutex(s_mutex); + if (dst == NULL) { + char dummy[MB_LEN_MAX]; + for (const wchar_t* scan = src; n > 0; ++scan, --n) { + int mblen = wctomb(dummy, *scan); + if (mblen == -1) { + *errors = true; + mblen = 1; + } + len += mblen; + } + int mblen = wctomb(dummy, L'\0'); + if (mblen != -1) { + len += mblen - 1; + } + } + else { + char* dst0 = dst; + for (const wchar_t* scan = src; n > 0; ++scan, --n) { + int mblen = wctomb(dst, *scan); + if (mblen == -1) { + *errors = true; + *dst++ = '?'; + } + else { + dst += mblen; + } + } + int mblen = wctomb(dst, L'\0'); + if (mblen != -1) { + // don't include nul terminator + dst += mblen - 1; + } + len = (int)(dst - dst0); + } + ARCH->unlockMutex(s_mutex); + + return len; +} + +int +ARCH_STRING::convStringMBToWC(wchar_t* dst, + const char* src, UInt32 n, bool* errors) +{ + int len = 0; + wchar_t dummy; + + bool dummyErrors; + if (errors == NULL) { + errors = &dummyErrors; + } + + ARCH->lockMutex(s_mutex); + if (dst == NULL) { + for (const char* scan = src; n > 0; ) { + int mblen = mbtowc(&dummy, scan, n); + switch (mblen) { + case -2: + // incomplete last character. convert to unknown character. + *errors = true; + len += 1; + n = 0; + break; + + case -1: + // invalid character. count one unknown character and + // start at the next byte. + *errors = true; + len += 1; + scan += 1; + n -= 1; + break; + + case 0: + len += 1; + scan += 1; + n -= 1; + break; + + default: + // normal character + len += 1; + scan += mblen; + n -= mblen; + break; + } + } + } + else { + wchar_t* dst0 = dst; + for (const char* scan = src; n > 0; ++dst) { + int mblen = mbtowc(dst, scan, n); + switch (mblen) { + case -2: + // incomplete character. convert to unknown character. + *errors = true; + *dst = (wchar_t)0xfffd; + n = 0; + break; + + case -1: + // invalid character. count one unknown character and + // start at the next byte. + *errors = true; + *dst = (wchar_t)0xfffd; + scan += 1; + n -= 1; + break; + + case 0: + *dst = (wchar_t)0x0000; + scan += 1; + n -= 1; + break; + + default: + // normal character + scan += mblen; + n -= mblen; + break; + } + } + len = (int)(dst - dst0); + } + ARCH->unlockMutex(s_mutex); + + return len; +} + +#endif diff --git a/lib/arch/IArchConsole.h b/lib/arch/IArchConsole.h new file mode 100644 index 00000000..2befb196 --- /dev/null +++ b/lib/arch/IArchConsole.h @@ -0,0 +1,71 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHCONSOLE_H +#define IARCHCONSOLE_H + +#include "IInterface.h" + +//! Interface for architecture dependent console output +/*! +This interface defines the console operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchConsole : public IInterface { +public: + //! @name manipulators + //@{ + + //! Open the console + /*! + Opens the console for writing. The console is opened automatically + on the first write so calling this method is optional. Uses \c title + for the console's title if appropriate for the architecture. Calling + this method on an already open console must have no effect. + */ + virtual void openConsole(const char* title) = 0; + + //! Close the console + /*! + Close the console. Calling this method on an already closed console + must have no effect. + */ + virtual void closeConsole() = 0; + + //! Show the console + /*! + Causes the console to become visible. This generally only makes sense + for a console in a graphical user interface. Other implementations + will do nothing. Iff \p showIfEmpty is \c false then the implementation + may optionally only show the console if it's not empty. + */ + virtual void showConsole(bool showIfEmpty) = 0; + + //! Write to the console + /*! + Writes the given string to the console, opening it if necessary. + */ + 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; + + //@} +}; + +#endif diff --git a/lib/arch/IArchDaemon.h b/lib/arch/IArchDaemon.h new file mode 100644 index 00000000..ba6b049b --- /dev/null +++ b/lib/arch/IArchDaemon.h @@ -0,0 +1,107 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHDAEMON_H +#define IARCHDAEMON_H + +#include "IInterface.h" + +//! Interface for architecture dependent daemonizing +/*! +This interface defines the operations required by synergy for installing +uninstalling daeamons and daemonizing a process. Each architecture must +implement this interface. +*/ +class IArchDaemon : public IInterface { +public: + typedef int (*DaemonFunc)(int argc, const char** argv); + + //! @name manipulators + //@{ + + //! Install daemon + /*! + Install a daemon. \c name is the name of the daemon passed to the + system and \c description is a short human readable description of + the daemon. \c pathname is the path to the daemon executable. + \c commandLine should \b not include the name of program as the + first argument. If \c allUsers is true then the daemon will be + installed to start at boot time, otherwise it will be installed to + start when the current user logs in. If \p dependencies is not NULL + then it's a concatenation of NUL terminated other daemon names + followed by a NUL; the daemon will be configured to startup after + the listed daemons. Throws an \c XArchDaemon exception on failure. + */ + virtual void installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers) = 0; + + //! Uninstall daemon + /*! + Uninstall a daemon. Throws an \c XArchDaemon on failure. + */ + virtual void uninstallDaemon(const char* name, bool allUsers) = 0; + + //! Daemonize the process + /*! + Daemonize. Throw XArchDaemonFailed on error. \c name is the name + of the daemon. Once daemonized, \c func is invoked and daemonize + returns when and what it does. + + Exactly what happens when daemonizing depends on the platform. +
      +
    • unix: + Detaches from terminal. \c func gets passed one argument, the + name passed to daemonize(). +
    • win32: + Becomes a service. Argument 0 is the name of the service + and the rest are the arguments passed to StartService(). + \c func is only called when the service is actually started. + \c func must call \c CArchMiscWindows::runDaemon() to finally + becoming a service. The \c runFunc function passed to \c runDaemon() + must call \c CArchMiscWindows::daemonRunning(true) when it + enters the main loop (i.e. after initialization) and + \c CArchMiscWindows::daemonRunning(false) when it leaves + the main loop. The \c stopFunc function passed to \c runDaemon() + is called when the daemon must exit the main loop and it must cause + \c runFunc to return. \c func should return what \c runDaemon() + returns. \c func or \c runFunc can call + \c CArchMiscWindows::daemonFailed() to indicate startup failure. +
    + */ + virtual int daemonize(const char* name, DaemonFunc func) = 0; + + //! Check if user has permission to install the daemon + /*! + Returns true iff the caller has permission to install or + uninstall the daemon. Note that even if this method returns + true it's possible that installing/uninstalling the service + may still fail. This method ignores whether or not the + service is already installed. + */ + virtual bool canInstallDaemon(const char* name, bool allUsers) = 0; + + //! Check if the daemon is installed + /*! + Returns true iff the daemon is installed. + */ + virtual bool isDaemonInstalled(const char* name, bool allUsers) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchFile.h b/lib/arch/IArchFile.h new file mode 100644 index 00000000..8594053d --- /dev/null +++ b/lib/arch/IArchFile.h @@ -0,0 +1,64 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHFILE_H +#define IARCHFILE_H + +#include "IInterface.h" +#include "stdstring.h" + +//! Interface for architecture dependent file system operations +/*! +This interface defines the file system operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchFile : public IInterface { +public: + //! @name manipulators + //@{ + + //! Extract base name + /*! + Find the base name in the given \c pathname. + */ + virtual const char* getBasename(const char* pathname) = 0; + + //! Get user's home directory + /*! + Returns the user's home directory. Returns the empty string if + this cannot be determined. + */ + virtual std::string getUserDirectory() = 0; + + //! Get system directory + /*! + Returns the ussystem configuration file directory. + */ + virtual std::string getSystemDirectory() = 0; + + //! Concatenate path components + /*! + Concatenate pathname components with a directory separator + between them. This should not check if the resulting path + is longer than allowed by the system; we'll rely on the + system calls to tell us that. + */ + virtual std::string concatPath( + const std::string& prefix, + const std::string& suffix) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchLog.h b/lib/arch/IArchLog.h new file mode 100644 index 00000000..7655ff95 --- /dev/null +++ b/lib/arch/IArchLog.h @@ -0,0 +1,73 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHLOG_H +#define IARCHLOG_H + +#include "IInterface.h" + +//! Interface for architecture dependent logging +/*! +This interface defines the logging operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchLog : public IInterface { +public: + //! Log levels + /*! + The logging priority levels in order of highest to lowest priority. + */ + enum ELevel { + kERROR, //!< For serious or fatal errors + kWARNING, //!< For minor errors and warnings + kNOTE, //!< For messages about notable events + kINFO, //!< For informational messages + kDEBUG //!< For debugging messages + }; + + //! @name manipulators + //@{ + + //! Open the log + /*! + Opens the log for writing. The log must be opened before being + written to. + */ + virtual void openLog(const char* name) = 0; + + //! Close the log + /*! + Close the log. + */ + virtual void closeLog() = 0; + + //! Show the log + /*! + Causes the log to become visible. This generally only makes sense + for a log in a graphical user interface. Other implementations + will do nothing. Iff \p showIfEmpty is \c false then the implementation + may optionally only show the log if it's not empty. + */ + virtual void showLog(bool showIfEmpty) = 0; + + //! Write to the log + /*! + Writes the given string to the log with the given level. + */ + virtual void writeLog(ELevel, const char*) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchMultithread.h b/lib/arch/IArchMultithread.h new file mode 100644 index 00000000..b7b72293 --- /dev/null +++ b/lib/arch/IArchMultithread.h @@ -0,0 +1,272 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHMULTITHREAD_H +#define IARCHMULTITHREAD_H + +#include "IInterface.h" + +/*! +\class CArchCondImpl +\brief Internal condition variable data. +An architecture dependent type holding the necessary data for a +condition variable. +*/ +class CArchCondImpl; + +/*! +\var CArchCond +\brief Opaque condition variable type. +An opaque type representing a condition variable. +*/ +typedef CArchCondImpl* CArchCond; + +/*! +\class CArchMutexImpl +\brief Internal mutex data. +An architecture dependent type holding the necessary data for a mutex. +*/ +class CArchMutexImpl; + +/*! +\var CArchMutex +\brief Opaque mutex type. +An opaque type representing a mutex. +*/ +typedef CArchMutexImpl* CArchMutex; + +/*! +\class CArchThreadImpl +\brief Internal thread data. +An architecture dependent type holding the necessary data for a thread. +*/ +class CArchThreadImpl; + +/*! +\var CArchThread +\brief Opaque thread type. +An opaque type representing a thread. +*/ +typedef CArchThreadImpl* CArchThread; + +//! Interface for architecture dependent multithreading +/*! +This interface defines the multithreading operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchMultithread : public IInterface { +public: + //! Type of thread entry point + typedef void* (*ThreadFunc)(void*); + //! Type of thread identifier + typedef unsigned int ThreadID; + //! Types of signals + /*! + Not all platforms support all signals. Unsupported signals are + ignored. + */ + enum ESignal { + kINTERRUPT, //!< Interrupt (e.g. Ctrl+C) + kTERMINATE, //!< Terminate (e.g. Ctrl+Break) + kHANGUP, //!< Hangup (SIGHUP) + kUSER, //!< User (SIGUSR2) + kNUM_SIGNALS + }; + //! Type of signal handler function + typedef void (*SignalFunc)(ESignal, void* userData); + + //! @name manipulators + //@{ + + // + // condition variable methods + // + + //! Create a condition variable + /*! + The condition variable is an opaque data type. + */ + virtual CArchCond newCondVar() = 0; + + //! Destroy a condition variable + virtual void closeCondVar(CArchCond) = 0; + + //! Signal a condition variable + /*! + Signalling a condition variable releases one waiting thread. + */ + virtual void signalCondVar(CArchCond) = 0; + + //! Broadcast a condition variable + /*! + Broadcasting a condition variable releases all waiting threads. + */ + virtual void broadcastCondVar(CArchCond) = 0; + + //! Wait on a condition variable + /*! + Wait on a conditation variable for up to \c timeout seconds. + If \c timeout is < 0 then there is no timeout. The mutex must + be locked when this method is called. The mutex is unlocked + during the wait and locked again before returning. Returns + true if the condition variable was signalled and false on + timeout. + + (Cancellation point) + */ + virtual bool waitCondVar(CArchCond, CArchMutex, double timeout) = 0; + + // + // mutex methods + // + + //! Create a recursive mutex + /*! + Creates a recursive mutex. A thread may lock a recursive mutex + when it already holds a lock on that mutex. The mutex is an + opaque data type. + */ + virtual CArchMutex newMutex() = 0; + + //! Destroy a mutex + virtual void closeMutex(CArchMutex) = 0; + + //! Lock a mutex + virtual void lockMutex(CArchMutex) = 0; + + //! Unlock a mutex + virtual void unlockMutex(CArchMutex) = 0; + + // + // thread methods + // + + //! Start a new thread + /*! + Creates and starts a new thread, using \c func as the entry point + and passing it \c userData. The thread is an opaque data type. + */ + virtual CArchThread newThread(ThreadFunc func, void* userData) = 0; + + //! Get a reference to the calling thread + /*! + Returns a thread representing the current (i.e. calling) thread. + */ + virtual CArchThread newCurrentThread() = 0; + + //! Copy a thread object + /*! + Returns a reference to to thread referred to by \c thread. + */ + virtual CArchThread copyThread(CArchThread thread) = 0; + + //! Release a thread reference + /*! + Deletes the given thread object. This does not destroy the thread + the object referred to, even if there are no remaining references. + Use cancelThread() and waitThread() to stop a thread and wait for + it to exit. + */ + virtual void closeThread(CArchThread) = 0; + + //! Force a thread to exit + /*! + Causes \c thread to exit when it next calls a cancellation point. + A thread avoids cancellation as long as it nevers calls a + cancellation point. Once it begins the cancellation process it + must always let cancellation go to completion but may take as + long as necessary to clean up. + */ + virtual void cancelThread(CArchThread thread) = 0; + + //! Change thread priority + /*! + Changes the priority of \c thread by \c n. If \c n is positive + the thread has a lower priority and if negative a higher priority. + Some architectures may not support either or both directions. + */ + virtual void setPriorityOfThread(CArchThread, int n) = 0; + + //! Cancellation point + /*! + This method does nothing but is a cancellation point. Clients + can make their own functions cancellation points by calling this + method at appropriate times. + + (Cancellation point) + */ + virtual void testCancelThread() = 0; + + //! Wait for a thread to exit + /*! + Waits for up to \c timeout seconds for \c thread to exit (normally + or by cancellation). Waits forever if \c timeout < 0. Returns + true if the thread exited, false otherwise. Waiting on the current + thread returns immediately with false. + + (Cancellation point) + */ + virtual bool wait(CArchThread thread, double timeout) = 0; + + //! Compare threads + /*! + Returns true iff two thread objects refer to the same thread. + Note that comparing thread objects directly is meaningless. + */ + virtual bool isSameThread(CArchThread, CArchThread) = 0; + + //! Test if thread exited + /*! + Returns true iff \c thread has exited. + */ + virtual bool isExitedThread(CArchThread thread) = 0; + + //! Returns the exit code of a thread + /*! + Waits indefinitely for \c thread to exit (if it hasn't yet) then + returns the thread's exit code. + + (Cancellation point) + */ + virtual void* getResultOfThread(CArchThread thread) = 0; + + //! Returns an ID for a thread + /*! + Returns some ID number for \c thread. This is for logging purposes. + All thread objects referring to the same thread return the same ID. + However, clients should us isSameThread() to compare thread objects + instead of comparing IDs. + */ + virtual ThreadID getIDOfThread(CArchThread thread) = 0; + + //! Set the interrupt handler + /*! + Sets the function to call on receipt of an external interrupt. + By default and when \p func is NULL, the main thread is cancelled. + */ + virtual void setSignalHandler(ESignal, SignalFunc func, + void* userData) = 0; + + //! Invoke the signal handler + /*! + Invokes the signal handler for \p signal, if any. If no handler + cancels the main thread for \c kINTERRUPT and \c kTERMINATE and + ignores the call otherwise. + */ + virtual void raiseSignal(ESignal signal) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchNetwork.h b/lib/arch/IArchNetwork.h new file mode 100644 index 00000000..007fb442 --- /dev/null +++ b/lib/arch/IArchNetwork.h @@ -0,0 +1,279 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHNETWORK_H +#define IARCHNETWORK_H + +#include "IInterface.h" +#include "stdstring.h" + +class CArchThreadImpl; +typedef CArchThreadImpl* CArchThread; + +/*! +\class CArchSocketImpl +\brief Internal socket data. +An architecture dependent type holding the necessary data for a socket. +*/ +class CArchSocketImpl; + +/*! +\var CArchSocket +\brief Opaque socket type. +An opaque type representing a socket. +*/ +typedef CArchSocketImpl* CArchSocket; + +/*! +\class CArchNetAddressImpl +\brief Internal network address data. +An architecture dependent type holding the necessary data for a network +address. +*/ +class CArchNetAddressImpl; + +/*! +\var CArchNetAddress +\brief Opaque network address type. +An opaque type representing a network address. +*/ +typedef CArchNetAddressImpl* CArchNetAddress; + +//! Interface for architecture dependent networking +/*! +This interface defines the networking operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchNetwork : public IInterface { +public: + //! Supported address families + enum EAddressFamily { + kUNKNOWN, + kINET, + }; + + //! Supported socket types + enum ESocketType { + kDGRAM, + kSTREAM + }; + + //! Events for \c poll() + /*! + Events for \c poll() are bitmasks and can be combined using the + bitwise operators. + */ + enum { + kPOLLIN = 1, //!< Socket is readable + kPOLLOUT = 2, //!< Socket is writable + kPOLLERR = 4, //!< The socket is in an error state + kPOLLNVAL = 8 //!< The socket is invalid + }; + + //! A socket query for \c poll() + class CPollEntry { + public: + //! The socket to query + CArchSocket m_socket; + + //! The events to query for + /*! + The events to query for can be any combination of kPOLLIN and + kPOLLOUT. + */ + unsigned short m_events; + + //! The result events + unsigned short m_revents; + }; + + //! @name manipulators + //@{ + + //! Create a new socket + /*! + The socket is an opaque data type. + */ + virtual CArchSocket newSocket(EAddressFamily, ESocketType) = 0; + + //! Copy a socket object + /*! + Returns a reference to to socket referred to by \c s. + */ + virtual CArchSocket copySocket(CArchSocket s) = 0; + + //! Release a socket reference + /*! + Deletes the given socket object. This does not destroy the socket + the object referred to until there are no remaining references for + the socket. + */ + virtual void closeSocket(CArchSocket s) = 0; + + //! Close socket for further reads + /*! + Calling this disallows future reads on socket \c s. + */ + virtual void closeSocketForRead(CArchSocket s) = 0; + + //! Close socket for further writes + /*! + Calling this disallows future writes on socket \c s. + */ + virtual void closeSocketForWrite(CArchSocket s) = 0; + + //! Bind socket to address + /*! + Binds socket \c s to the address \c addr. + */ + virtual void bindSocket(CArchSocket s, CArchNetAddress addr) = 0; + + //! Listen for connections on socket + /*! + Causes the socket \c s to begin listening for incoming connections. + */ + virtual void listenOnSocket(CArchSocket s) = 0; + + //! Accept connection on socket + /*! + Accepts a connection on socket \c s, returning a new socket for the + connection and filling in \c addr with the address of the remote + end. \c addr may be NULL if the remote address isn't required. + The original socket \c s is unaffected and remains in the listening + state. The new socket shares most of the properties of \c s except + it's not in the listening state and it's connected. Returns NULL + if there are no pending connection requests. + */ + virtual CArchSocket acceptSocket(CArchSocket s, CArchNetAddress* addr) = 0; + + //! Connect socket + /*! + Connects the socket \c s to the remote address \c addr. Returns + true if the connection succeed immediately, false if the connection + is in progress, and throws if the connection failed immediately. + If it returns false, \c pollSocket() can be used to wait on the + socket for writing to detect when the connection finally succeeds + or fails. + */ + virtual bool connectSocket(CArchSocket s, CArchNetAddress addr) = 0; + + //! Check socket state + /*! + Tests the state of \c num sockets for readability and/or writability. + Waits up to \c timeout seconds for some socket to become readable + and/or writable (or indefinitely if \c timeout < 0). Returns the + number of sockets that were readable (if readability was being + queried) or writable (if writablility was being queried) and sets + the \c m_revents members of the entries. \c kPOLLERR and \c kPOLLNVAL + are set in \c m_revents as appropriate. If a socket indicates + \c kPOLLERR then \c throwErrorOnSocket() can be used to determine + the type of error. Returns 0 immediately regardless of the \c timeout + if no valid sockets are selected for testing. + + (Cancellation point) + */ + virtual int pollSocket(CPollEntry[], int num, double timeout) = 0; + + //! Unblock thread in pollSocket() + /*! + Cause a thread that's in a pollSocket() call to return. This + call may return before the thread is unblocked. If the thread is + not in a pollSocket() call this call has no effect. + */ + virtual void unblockPollSocket(CArchThread thread) = 0; + + //! Read data from socket + /*! + Read up to \c len bytes from socket \c s in \c buf and return the + number of bytes read. The number of bytes can be less than \c len + if not enough data is available. Returns 0 if the remote end has + disconnected and/or there is no more queued received data. + */ + virtual size_t readSocket(CArchSocket s, void* buf, size_t len) = 0; + + //! Write data from socket + /*! + Write up to \c len bytes to socket \c s from \c buf and return the + number of bytes written. The number of bytes can be less than + \c len if the remote end disconnected or the internal buffers fill + up. + */ + virtual size_t writeSocket(CArchSocket s, + const void* buf, size_t len) = 0; + + //! Check error on socket + /*! + If the socket \c s is in an error state then throws an appropriate + XArchNetwork exception. + */ + virtual void throwErrorOnSocket(CArchSocket s) = 0; + + //! Turn Nagle algorithm on or off on socket + /*! + Set socket to send messages immediately (true) or to collect small + messages into one packet (false). Returns the previous state. + */ + virtual bool setNoDelayOnSocket(CArchSocket, bool noDelay) = 0; + + //! Turn address reuse on or off on socket + /*! + Allows the address this socket is bound to to be reused while in the + TIME_WAIT state. Returns the previous state. + */ + virtual bool setReuseAddrOnSocket(CArchSocket, bool reuse) = 0; + + //! Return local host's name + virtual std::string getHostName() = 0; + + //! Create an "any" network address + virtual CArchNetAddress newAnyAddr(EAddressFamily) = 0; + + //! Copy a network address + virtual CArchNetAddress copyAddr(CArchNetAddress) = 0; + + //! Convert a name to a network address + virtual CArchNetAddress nameToAddr(const std::string&) = 0; + + //! Destroy a network address + virtual void closeAddr(CArchNetAddress) = 0; + + //! Convert an address to a host name + virtual std::string addrToName(CArchNetAddress) = 0; + + //! Convert an address to a string + virtual std::string addrToString(CArchNetAddress) = 0; + + //! Get an address's family + virtual EAddressFamily getAddrFamily(CArchNetAddress) = 0; + + //! Set the port of an address + virtual void setAddrPort(CArchNetAddress, int port) = 0; + + //! Get the port of an address + virtual int getAddrPort(CArchNetAddress) = 0; + + //! Test addresses for equality + virtual bool isEqualAddr(CArchNetAddress, CArchNetAddress) = 0; + + //! Test for the "any" address + /*! + Returns true if \c addr is the "any" address. \c newAnyAddr() + returns an "any" address. + */ + virtual bool isAnyAddr(CArchNetAddress addr) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchSleep.h b/lib/arch/IArchSleep.h new file mode 100644 index 00000000..95a2852c --- /dev/null +++ b/lib/arch/IArchSleep.h @@ -0,0 +1,43 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHSLEEP_H +#define IARCHSLEEP_H + +#include "IInterface.h" + +//! Interface for architecture dependent sleeping +/*! +This interface defines the sleep operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchSleep : public IInterface { +public: + //! @name manipulators + //@{ + + //! Sleep + /*! + Blocks the calling thread for \c timeout seconds. If + \c timeout < 0.0 then the call returns immediately. If \c timeout + == 0.0 then the calling thread yields the CPU. + + (cancellation point) + */ + virtual void sleep(double timeout) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchString.h b/lib/arch/IArchString.h new file mode 100644 index 00000000..703d64b1 --- /dev/null +++ b/lib/arch/IArchString.h @@ -0,0 +1,68 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHSTRING_H +#define IARCHSTRING_H + +#include "IInterface.h" +#include "BasicTypes.h" +#include + +//! Interface for architecture dependent string operations +/*! +This interface defines the string operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchString : public IInterface { +public: + //! Wide character encodings + /*! + The known wide character encodings + */ + enum EWideCharEncoding { + kUCS2, //!< The UCS-2 encoding + kUCS4, //!< The UCS-4 encoding + kUTF16, //!< The UTF-16 encoding + kUTF32 //!< The UTF-32 encoding + }; + + //! @name manipulators + //@{ + + //! printf() to limited size buffer with va_list + /*! + This method is equivalent to vsprintf() except it will not write + more than \c n bytes to the buffer, returning -1 if the output + was truncated and the number of bytes written not including the + trailing NUL otherwise. + */ + virtual int vsnprintf(char* str, + int size, const char* fmt, va_list ap) = 0; + + //! Convert multibyte string to wide character string + virtual int convStringMBToWC(wchar_t*, + const char*, UInt32 n, bool* errors) = 0; + + //! Convert wide character string to multibyte string + virtual int convStringWCToMB(char*, + const wchar_t*, UInt32 n, bool* errors) = 0; + + //! Return the architecture's native wide character encoding + virtual EWideCharEncoding + getWideCharEncoding() = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchSystem.h b/lib/arch/IArchSystem.h new file mode 100644 index 00000000..7a6c941b --- /dev/null +++ b/lib/arch/IArchSystem.h @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHSYSTEM_H +#define IARCHSYSTEM_H + +#include "IInterface.h" +#include "stdstring.h" + +//! Interface for architecture dependent system queries +/*! +This interface defines operations for querying system info. +*/ +class IArchSystem : public IInterface { +public: + //! @name accessors + //@{ + + //! Identify the OS + /*! + Returns a string identifying the operating system. + */ + virtual std::string getOSName() const = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchTaskBar.h b/lib/arch/IArchTaskBar.h new file mode 100644 index 00000000..e9471566 --- /dev/null +++ b/lib/arch/IArchTaskBar.h @@ -0,0 +1,60 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHTASKBAR_H +#define IARCHTASKBAR_H + +#include "IInterface.h" + +class IArchTaskBarReceiver; + +//! Interface for architecture dependent task bar control +/*! +This interface defines the task bar icon operations required +by synergy. Each architecture must implement this interface +though each operation can be a no-op. +*/ +class IArchTaskBar : public IInterface { +public: + //! @name manipulators + //@{ + + //! Add a receiver + /*! + Add a receiver object to be notified of user and application + events. This should be called before other methods. When + the receiver is added to the task bar, its icon appears on + the task bar. + */ + virtual void addReceiver(IArchTaskBarReceiver*) = 0; + + //! Remove a receiver + /*! + Remove a receiver object from the task bar. This removes the + icon from the task bar. + */ + virtual void removeReceiver(IArchTaskBarReceiver*) = 0; + + //! Update a receiver + /*! + Updates the display of the receiver on the task bar. This + should be called when the receiver appearance may have changed + (e.g. it's icon or tool tip has changed). + */ + virtual void updateReceiver(IArchTaskBarReceiver*) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchTaskBarReceiver.h b/lib/arch/IArchTaskBarReceiver.h new file mode 100644 index 00000000..917f2fbf --- /dev/null +++ b/lib/arch/IArchTaskBarReceiver.h @@ -0,0 +1,90 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHTASKBARRECEIVER_H +#define IARCHTASKBARRECEIVER_H + +#include "IInterface.h" +#include "stdstring.h" + +//! Interface for architecture dependent task bar event handling +/*! +This interface defines the task bar icon event handlers required +by synergy. Each architecture must implement this interface +though each operation can be a no-op. +*/ +class IArchTaskBarReceiver : public IInterface { +public: + // Icon data is architecture dependent + typedef void* Icon; + + //! @name manipulators + //@{ + + //! Show status window + /*! + Open a window displaying current status. This should return + immediately without waiting for the window to be closed. + */ + virtual void showStatus() = 0; + + //! Popup menu + /*! + Popup a menu of operations at or around \c x,y and perform the + chosen operation. + */ + virtual void runMenu(int x, int y) = 0; + + //! Perform primary action + /*! + Perform the primary (default) action. + */ + virtual void primaryAction() = 0; + + //@} + //! @name accessors + //@{ + + //! Lock receiver + /*! + Locks the receiver from changing state. The receiver should be + locked when querying it's state to ensure consistent results. + Each call to \c lock() must have a matching \c unlock() and + locks cannot be nested. + */ + virtual void lock() const = 0; + + //! Unlock receiver + virtual void unlock() const = 0; + + //! Get icon + /*! + Returns the icon to display in the task bar. The interface + to set the icon is left to subclasses. Getting and setting + the icon must be thread safe. + */ + virtual const Icon getIcon() const = 0; + + //! Get tooltip + /*! + Returns the tool tip to display in the task bar. The interface + to set the tooltip is left to sublclasses. Getting and setting + the icon must be thread safe. + */ + virtual std::string getToolTip() const = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchTime.h b/lib/arch/IArchTime.h new file mode 100644 index 00000000..dade64bb --- /dev/null +++ b/lib/arch/IArchTime.h @@ -0,0 +1,40 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHTIME_H +#define IARCHTIME_H + +#include "IInterface.h" + +//! Interface for architecture dependent time operations +/*! +This interface defines the time operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchTime : public IInterface { +public: + //! @name manipulators + //@{ + + //! Get the current time + /*! + Returns the number of seconds since some arbitrary starting time. + This should return as high a precision as reasonable. + */ + virtual double time() = 0; + + //@} +}; + +#endif diff --git a/lib/arch/Makefile.am b/lib/arch/Makefile.am new file mode 100644 index 00000000..8a1749ab --- /dev/null +++ b/lib/arch/Makefile.am @@ -0,0 +1,118 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +COMMON_SOURCE_FILES = \ + CArch.cpp \ + CArchDaemonNone.cpp \ + CArchDaemonNone.h \ + XArch.cpp \ + CArch.h \ + IArchConsole.h \ + IArchDaemon.h \ + IArchFile.h \ + IArchLog.h \ + IArchMultithread.h \ + IArchNetwork.h \ + IArchSleep.h \ + IArchString.h \ + IArchSystem.h \ + IArchTaskBar.h \ + IArchTaskBarReceiver.h \ + IArchTime.h \ + XArch.h \ + $(NULL) +UNIX_SOURCE_FILES = \ + CArchConsoleUnix.cpp \ + CArchDaemonUnix.cpp \ + CArchFileUnix.cpp \ + CArchLogUnix.cpp \ + CArchMultithreadPosix.cpp \ + CArchNetworkBSD.cpp \ + CArchSleepUnix.cpp \ + CArchStringUnix.cpp \ + CArchSystemUnix.cpp \ + CArchTaskBarXWindows.cpp \ + CArchTimeUnix.cpp \ + XArchUnix.cpp \ + CArchConsoleUnix.h \ + CArchDaemonUnix.h \ + CArchFileUnix.h \ + CArchLogUnix.h \ + CArchMultithreadPosix.h \ + CArchNetworkBSD.h \ + CArchSleepUnix.h \ + CArchStringUnix.h \ + CArchSystemUnix.h \ + CArchTaskBarXWindows.h \ + CArchTimeUnix.h \ + XArchUnix.h \ + $(NULL) +WIN32_SOURCE_FILES = \ + CArchConsoleWindows.cpp \ + CArchDaemonWindows.cpp \ + CArchFileWindows.cpp \ + CArchLogWindows.cpp \ + CArchMiscWindows.cpp \ + CArchMultithreadWindows.cpp \ + CArchNetworkWinsock.cpp \ + CArchSleepWindows.cpp \ + CArchStringWindows.cpp \ + CArchSystemWindows.cpp \ + CArchTaskBarWindows.cpp \ + CArchTimeWindows.cpp \ + XArchWindows.cpp \ + CArchConsoleWindows.h \ + CArchDaemonWindows.h \ + CArchFileWindows.h \ + CArchLogWindows.h \ + CArchMiscWindows.h \ + CArchMultithreadWindows.h \ + CArchNetworkWinsock.h \ + CArchSleepWindows.h \ + CArchStringWindows.h \ + CArchSystemWindows.h \ + CArchTaskBarWindows.h \ + CArchTimeWindows.h \ + XArchWindows.h \ + $(NULL) + +EXTRA_DIST = \ + CMultibyte.cpp \ + Makefile.win \ + vsnprintf.cpp \ + $(UNIX_SOURCE_FILES) \ + $(WIN32_SOURCE_FILES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libarch.a +if UNIX +libarch_a_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(UNIX_SOURCE_FILES) \ + $(NULL) +endif +if WIN32 +libarch_a_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(WIN32_SOURCE_FILES) \ + $(NULL) +endif +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + $(NULL) diff --git a/lib/arch/Makefile.win b/lib/arch/Makefile.win new file mode 100644 index 00000000..4e151976 --- /dev/null +++ b/lib/arch/Makefile.win @@ -0,0 +1,84 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +LIB_ARCH_SRC = lib\arch +LIB_ARCH_DST = $(BUILD_DST)\$(LIB_ARCH_SRC) +LIB_ARCH_LIB = "$(LIB_ARCH_DST)\arch.lib" +LIB_ARCH_CPP = \ + "CArch.cpp" \ + "CArchDaemonNone.cpp" \ + "XArch.cpp" \ + "CArchConsoleWindows.cpp" \ + "CArchDaemonWindows.cpp" \ + "CArchFileWindows.cpp" \ + "CArchLogWindows.cpp" \ + "CArchMiscWindows.cpp" \ + "CArchMultithreadWindows.cpp" \ + "CArchNetworkWinsock.cpp" \ + "CArchSleepWindows.cpp" \ + "CArchStringWindows.cpp" \ + "CArchSystemWindows.cpp" \ + "CArchTaskBarWindows.cpp" \ + "CArchTimeWindows.cpp" \ + "XArchWindows.cpp" \ + $(NULL) +LIB_ARCH_OBJ = \ + "$(LIB_ARCH_DST)\CArch.obj" \ + "$(LIB_ARCH_DST)\CArchDaemonNone.obj" \ + "$(LIB_ARCH_DST)\XArch.obj" \ + "$(LIB_ARCH_DST)\CArchConsoleWindows.obj" \ + "$(LIB_ARCH_DST)\CArchDaemonWindows.obj" \ + "$(LIB_ARCH_DST)\CArchFileWindows.obj" \ + "$(LIB_ARCH_DST)\CArchLogWindows.obj" \ + "$(LIB_ARCH_DST)\CArchMiscWindows.obj" \ + "$(LIB_ARCH_DST)\CArchMultithreadWindows.obj" \ + "$(LIB_ARCH_DST)\CArchNetworkWinsock.obj" \ + "$(LIB_ARCH_DST)\CArchSleepWindows.obj" \ + "$(LIB_ARCH_DST)\CArchStringWindows.obj" \ + "$(LIB_ARCH_DST)\CArchSystemWindows.obj" \ + "$(LIB_ARCH_DST)\CArchTaskBarWindows.obj" \ + "$(LIB_ARCH_DST)\CArchTimeWindows.obj" \ + "$(LIB_ARCH_DST)\XArchWindows.obj" \ + $(NULL) +LIB_ARCH_INC = \ + /I"lib\common" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_ARCH_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_ARCH_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_ARCH_LIB) + +# Dependency rules +$(LIB_ARCH_OBJ): $(AUTODEP) +!if EXIST($(LIB_ARCH_DST)\deps.mak) +!include $(LIB_ARCH_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_ARCH_SRC)\}.cpp{$(LIB_ARCH_DST)\}.obj:: +!else +{$(LIB_ARCH_SRC)\}.cpp{$(LIB_ARCH_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_ARCH_SRC) + -@$(MKDIR) $(LIB_ARCH_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_ARCH_INC) \ + /Fo$(LIB_ARCH_DST)\ \ + /Fd$(LIB_ARCH_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_ARCH_SRC) $(LIB_ARCH_DST) +$(LIB_ARCH_LIB): $(LIB_ARCH_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_ARCH_SRC) $(LIB_ARCH_DST) $(**:.obj=.d) diff --git a/lib/arch/XArch.cpp b/lib/arch/XArch.cpp new file mode 100644 index 00000000..9dce5283 --- /dev/null +++ b/lib/arch/XArch.cpp @@ -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. + */ + +#include "XArch.h" + +// +// XArch +// + +std::string +XArch::what() const throw() +{ + try { + if (m_what.empty() && m_eval != NULL) { + m_what = m_eval->eval(); + } + } + catch (...) { + // ignore + } + return m_what; +} diff --git a/lib/arch/XArch.h b/lib/arch/XArch.h new file mode 100644 index 00000000..75083649 --- /dev/null +++ b/lib/arch/XArch.h @@ -0,0 +1,170 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XARCH_H +#define XARCH_H + +#include "common.h" +#include "stdstring.h" + +//! Generic thread exception +/*! +Exceptions derived from this class are used by the multithreading +library to perform stack unwinding when a thread terminates. These +exceptions must always be rethrown by clients when caught. +*/ +class XThread { }; + +//! Thread exception to cancel +/*! +Thrown to cancel a thread. Clients must not throw this type, but +must rethrow it if caught (by XThreadCancel, XThread, or ...). +*/ +class XThreadCancel : public XThread { }; + +/*! +\def RETHROW_XTHREAD +Convenience macro to rethrow an XThread exception but ignore other +exceptions. Put this in your catch (...) handler after necessary +cleanup but before leaving or returning from the handler. +*/ +#define RETHROW_XTHREAD \ + try { throw; } catch (XThread&) { throw; } catch (...) { } + +//! Lazy error message string evaluation +/*! +This class encapsulates platform dependent error string lookup. +Platforms subclass this type, taking an appropriate error code +type in the c'tor and overriding eval() to return the error +string for that error code. +*/ +class XArchEval { +public: + XArchEval() { } + virtual ~XArchEval() { } + + virtual XArchEval* clone() const throw() = 0; + + virtual std::string eval() const throw() = 0; +}; + +//! Generic exception architecture dependent library +class XArch { +public: + XArch(XArchEval* adoptedEvaluator) : m_eval(adoptedEvaluator) { } + XArch(const std::string& msg) : m_eval(NULL), m_what(msg) { } + XArch(const XArch& e) : m_eval(e.m_eval != NULL ? e.m_eval->clone() : NULL), + m_what(e.m_what) { } + ~XArch() { delete m_eval; } + + std::string what() const throw(); + +private: + XArchEval* m_eval; + mutable std::string m_what; +}; + +// Macro to declare XArch derived types +#define XARCH_SUBCLASS(name_, super_) \ +class name_ : public super_ { \ +public: \ + name_(XArchEval* adoptedEvaluator) : super_(adoptedEvaluator) { } \ + name_(const std::string& msg) : super_(msg) { } \ +} + +//! Generic network exception +/*! +Exceptions derived from this class are used by the networking +library to indicate various errors. +*/ +XARCH_SUBCLASS(XArchNetwork, XArch); + +//! Operation was interrupted +XARCH_SUBCLASS(XArchNetworkInterrupted, XArchNetwork); + +//! Network insufficient permission +XARCH_SUBCLASS(XArchNetworkAccess, XArchNetwork); + +//! Network insufficient resources +XARCH_SUBCLASS(XArchNetworkResource, XArchNetwork); + +//! No support for requested network resource/service +XARCH_SUBCLASS(XArchNetworkSupport, XArchNetwork); + +//! Network I/O error +XARCH_SUBCLASS(XArchNetworkIO, XArchNetwork); + +//! Network address is unavailable or not local +XARCH_SUBCLASS(XArchNetworkNoAddress, XArchNetwork); + +//! Network address in use +XARCH_SUBCLASS(XArchNetworkAddressInUse, XArchNetwork); + +//! No route to address +XARCH_SUBCLASS(XArchNetworkNoRoute, XArchNetwork); + +//! Socket not connected +XARCH_SUBCLASS(XArchNetworkNotConnected, XArchNetwork); + +//! Remote read end of socket has closed +XARCH_SUBCLASS(XArchNetworkShutdown, XArchNetwork); + +//! Remote end of socket has disconnected +XARCH_SUBCLASS(XArchNetworkDisconnected, XArchNetwork); + +//! Remote end of socket refused connection +XARCH_SUBCLASS(XArchNetworkConnectionRefused, XArchNetwork); + +//! Remote end of socket is not responding +XARCH_SUBCLASS(XArchNetworkTimedOut, XArchNetwork); + +//! Generic network name lookup erros +XARCH_SUBCLASS(XArchNetworkName, XArchNetwork); + +//! The named host is unknown +XARCH_SUBCLASS(XArchNetworkNameUnknown, XArchNetworkName); + +//! The named host is known but has no address +XARCH_SUBCLASS(XArchNetworkNameNoAddress, XArchNetworkName); + +//! Non-recoverable name server error +XARCH_SUBCLASS(XArchNetworkNameFailure, XArchNetworkName); + +//! Temporary name server error +XARCH_SUBCLASS(XArchNetworkNameUnavailable, XArchNetworkName); + +//! The named host is known but no supported address +XARCH_SUBCLASS(XArchNetworkNameUnsupported, XArchNetworkName); + +//! Generic daemon exception +/*! +Exceptions derived from this class are used by the daemon +library to indicate various errors. +*/ +XARCH_SUBCLASS(XArchDaemon, XArch); + +//! Could not daemonize +XARCH_SUBCLASS(XArchDaemonFailed, XArchDaemon); + +//! Could not install daemon +XARCH_SUBCLASS(XArchDaemonInstallFailed, XArchDaemon); + +//! Could not uninstall daemon +XARCH_SUBCLASS(XArchDaemonUninstallFailed, XArchDaemon); + +//! Attempted to uninstall a daemon that was not installed +XARCH_SUBCLASS(XArchDaemonUninstallNotInstalled, XArchDaemonUninstallFailed); + + +#endif diff --git a/lib/arch/XArchUnix.cpp b/lib/arch/XArchUnix.cpp new file mode 100644 index 00000000..6f4047d5 --- /dev/null +++ b/lib/arch/XArchUnix.cpp @@ -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. + */ + +#include "XArchUnix.h" +#include + +// +// XArchEvalUnix +// + +XArchEval* +XArchEvalUnix::clone() const throw() +{ + return new XArchEvalUnix(m_errno); +} + +std::string +XArchEvalUnix::eval() const throw() +{ + // FIXME -- not thread safe + return strerror(m_errno); +} diff --git a/lib/arch/XArchUnix.h b/lib/arch/XArchUnix.h new file mode 100644 index 00000000..19e0df4c --- /dev/null +++ b/lib/arch/XArchUnix.h @@ -0,0 +1,34 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XARCHUNIX_H +#define XARCHUNIX_H + +#include "XArch.h" + +//! Lazy error message string evaluation for unix +class XArchEvalUnix : public XArchEval { +public: + XArchEvalUnix(int err) : m_errno(err) { } + virtual ~XArchEvalUnix() { } + + // XArchEval overrides + virtual XArchEval* clone() const throw(); + virtual std::string eval() const throw(); + +private: + int m_errno; +}; + +#endif diff --git a/lib/arch/XArchWindows.cpp b/lib/arch/XArchWindows.cpp new file mode 100644 index 00000000..eebd6449 --- /dev/null +++ b/lib/arch/XArchWindows.cpp @@ -0,0 +1,127 @@ +/* + * 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 "XArchWindows.h" +#include "CArchNetworkWinsock.h" + +// +// XArchEvalWindows +// + +XArchEval* +XArchEvalWindows::clone() const throw() +{ + return new XArchEvalWindows(m_errno); +} + +std::string +XArchEvalWindows::eval() const throw() +{ + char* cmsg; + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM, + 0, + m_errno, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&cmsg, + 0, + NULL) == 0) { + cmsg = NULL; + return "Unknown error"; + } + std::string smsg(cmsg); + LocalFree(cmsg); + return smsg; +} + + +// +// XArchEvalWinsock +// + +XArchEval* +XArchEvalWinsock::clone() const throw() +{ + return new XArchEvalWinsock(m_errno); +} + +std::string +XArchEvalWinsock::eval() const throw() +{ + // built-in windows function for looking up error message strings + // may not look up network error messages correctly. we'll have + // to do it ourself. + static const struct { int m_code; const char* m_msg; } s_netErrorCodes[] = { + /* 10004 */{WSAEINTR, "The (blocking) call was canceled via WSACancelBlockingCall"}, + /* 10009 */{WSAEBADF, "Bad file handle"}, + /* 10013 */{WSAEACCES, "The requested address is a broadcast address, but the appropriate flag was not set"}, + /* 10014 */{WSAEFAULT, "WSAEFAULT"}, + /* 10022 */{WSAEINVAL, "WSAEINVAL"}, + /* 10024 */{WSAEMFILE, "No more file descriptors available"}, + /* 10035 */{WSAEWOULDBLOCK, "Socket is marked as non-blocking and no connections are present or the receive operation would block"}, + /* 10036 */{WSAEINPROGRESS, "A blocking Windows Sockets operation is in progress"}, + /* 10037 */{WSAEALREADY, "The asynchronous routine being canceled has already completed"}, + /* 10038 */{WSAENOTSOCK, "At least on descriptor is not a socket"}, + /* 10039 */{WSAEDESTADDRREQ, "A destination address is required"}, + /* 10040 */{WSAEMSGSIZE, "The datagram was too large to fit into the specified buffer and was truncated"}, + /* 10041 */{WSAEPROTOTYPE, "The specified protocol is the wrong type for this socket"}, + /* 10042 */{WSAENOPROTOOPT, "The option is unknown or unsupported"}, + /* 10043 */{WSAEPROTONOSUPPORT,"The specified protocol is not supported"}, + /* 10044 */{WSAESOCKTNOSUPPORT,"The specified socket type is not supported by this address family"}, + /* 10045 */{WSAEOPNOTSUPP, "The referenced socket is not a type that supports that operation"}, + /* 10046 */{WSAEPFNOSUPPORT, "BSD: Protocol family not supported"}, + /* 10047 */{WSAEAFNOSUPPORT, "The specified address family is not supported"}, + /* 10048 */{WSAEADDRINUSE, "The specified address is already in use"}, + /* 10049 */{WSAEADDRNOTAVAIL, "The specified address is not available from the local machine"}, + /* 10050 */{WSAENETDOWN, "The Windows Sockets implementation has detected that the network subsystem has failed"}, + /* 10051 */{WSAENETUNREACH, "The network can't be reached from this hos at this time"}, + /* 10052 */{WSAENETRESET, "The connection must be reset because the Windows Sockets implementation dropped it"}, + /* 10053 */{WSAECONNABORTED, "The virtual circuit was aborted due to timeout or other failure"}, + /* 10054 */{WSAECONNRESET, "The virtual circuit was reset by the remote side"}, + /* 10055 */{WSAENOBUFS, "No buffer space is available or a buffer deadlock has occured. The socket cannot be created"}, + /* 10056 */{WSAEISCONN, "The socket is already connected"}, + /* 10057 */{WSAENOTCONN, "The socket is not connected"}, + /* 10058 */{WSAESHUTDOWN, "The socket has been shutdown"}, + /* 10059 */{WSAETOOMANYREFS, "BSD: Too many references"}, + /* 10060 */{WSAETIMEDOUT, "Attempt to connect timed out without establishing a connection"}, + /* 10061 */{WSAECONNREFUSED, "The attempt to connect was forcefully rejected"}, + /* 10062 */{WSAELOOP, "Undocumented WinSock error code used in BSD"}, + /* 10063 */{WSAENAMETOOLONG, "Undocumented WinSock error code used in BSD"}, + /* 10064 */{WSAEHOSTDOWN, "Undocumented WinSock error code used in BSD"}, + /* 10065 */{WSAEHOSTUNREACH, "No route to host"}, + /* 10066 */{WSAENOTEMPTY, "Undocumented WinSock error code"}, + /* 10067 */{WSAEPROCLIM, "Undocumented WinSock error code"}, + /* 10068 */{WSAEUSERS, "Undocumented WinSock error code"}, + /* 10069 */{WSAEDQUOT, "Undocumented WinSock error code"}, + /* 10070 */{WSAESTALE, "Undocumented WinSock error code"}, + /* 10071 */{WSAEREMOTE, "Undocumented WinSock error code"}, + /* 10091 */{WSASYSNOTREADY, "Underlying network subsytem is not ready for network communication"}, + /* 10092 */{WSAVERNOTSUPPORTED, "The version of WinSock API support requested is not provided in this implementation"}, + /* 10093 */{WSANOTINITIALISED, "WinSock subsystem not properly initialized"}, + /* 10101 */{WSAEDISCON, "Virtual circuit has gracefully terminated connection"}, + /* 11001 */{WSAHOST_NOT_FOUND, "The specified host is unknown"}, + /* 11002 */{WSATRY_AGAIN, "A temporary error occurred on an authoritative name server"}, + /* 11003 */{WSANO_RECOVERY, "A non-recoverable name server error occurred"}, + /* 11004 */{WSANO_DATA, "The requested name is valid but does not have an IP address"}, + /* end */{0, NULL} + }; + + for (unsigned int i = 0; s_netErrorCodes[i].m_code != 0; ++i) { + if (s_netErrorCodes[i].m_code == m_errno) { + return s_netErrorCodes[i].m_msg; + } + } + return "Unknown error"; +} diff --git a/lib/arch/XArchWindows.h b/lib/arch/XArchWindows.h new file mode 100644 index 00000000..56c24007 --- /dev/null +++ b/lib/arch/XArchWindows.h @@ -0,0 +1,52 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XARCHWINDOWS_H +#define XARCHWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "XArch.h" +#include + +//! Lazy error message string evaluation for windows +class XArchEvalWindows : public XArchEval { +public: + XArchEvalWindows() : m_errno(GetLastError()) { } + XArchEvalWindows(DWORD err) : m_errno(err) { } + virtual ~XArchEvalWindows() { } + + // XArchEval overrides + virtual XArchEval* clone() const throw(); + virtual std::string eval() const throw(); + +private: + DWORD m_errno; +}; + +//! Lazy error message string evaluation for winsock +class XArchEvalWinsock : public XArchEval { +public: + XArchEvalWinsock(int err) : m_errno(err) { } + virtual ~XArchEvalWinsock() { } + + // XArchEval overrides + virtual XArchEval* clone() const throw(); + virtual std::string eval() const throw(); + +private: + int m_errno; +}; + +#endif diff --git a/lib/arch/vsnprintf.cpp b/lib/arch/vsnprintf.cpp new file mode 100644 index 00000000..10800ec7 --- /dev/null +++ b/lib/arch/vsnprintf.cpp @@ -0,0 +1,61 @@ +/* + * 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. + */ + +#if HAVE_VSNPRINTF + +#if !defined(ARCH_VSNPRINTF) +# define ARCH_VSNPRINTF vsnprintf +#endif + +int +ARCH_STRING::vsnprintf(char* str, int size, const char* fmt, va_list ap) +{ + int n = ::ARCH_VSNPRINTF(str, size, fmt, ap); + if (n > size) { + n = -1; + } + return n; +} + +#elif SYSAPI_UNIX // !HAVE_VSNPRINTF + +#include + +int +ARCH_STRING::vsnprintf(char* str, int size, const char* fmt, va_list ap) +{ + static FILE* bitbucket = fopen("/dev/null", "w"); + if (bitbucket == NULL) { + // uh oh + if (size > 0) { + str[0] = '\0'; + } + return 0; + } + else { + // count the characters using the bitbucket + int n = vfprintf(bitbucket, fmt, ap); + if (n + 1 <= size) { + // it'll fit so print it into str + vsprintf(str, fmt, ap); + } + return n; + } +} + +#else // !HAVE_VSNPRINTF && !SYSAPI_UNIX + +#error vsnprintf not implemented + +#endif // !HAVE_VSNPRINTF diff --git a/lib/base/CEvent.cpp b/lib/base/CEvent.cpp new file mode 100644 index 00000000..bfdf88ed --- /dev/null +++ b/lib/base/CEvent.cpp @@ -0,0 +1,98 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CEvent.h" +#include "CEventQueue.h" + +// +// CEvent +// + +CEvent::CEvent() : + m_type(kUnknown), + m_target(NULL), + m_data(NULL), + m_flags(0) +{ + // do nothing +} + +CEvent::CEvent(Type type, void* target, void* data, Flags flags) : + m_type(type), + m_target(target), + m_data(data), + m_flags(flags) +{ + // do nothing +} + +CEvent::Type +CEvent::getType() const +{ + return m_type; +} + +void* +CEvent::getTarget() const +{ + return m_target; +} + +void* +CEvent::getData() const +{ + return m_data; +} + +CEvent::Flags +CEvent::getFlags() const +{ + return m_flags; +} + +CEvent::Type +CEvent::registerType(const char* name) +{ + return EVENTQUEUE->registerType(name); +} + +CEvent::Type +CEvent::registerTypeOnce(Type& type, const char* name) +{ + return EVENTQUEUE->registerTypeOnce(type, name); +} + +const char* +CEvent::getTypeName(Type type) +{ + return EVENTQUEUE->getTypeName(type); +} + +void +CEvent::deleteData(const CEvent& event) +{ + switch (event.getType()) { + case kUnknown: + case kQuit: + case kSystem: + case kTimer: + break; + + default: + if ((event.getFlags() & kDontFreeData) == 0) { + free(event.getData()); + } + break; + } +} diff --git a/lib/base/CEvent.h b/lib/base/CEvent.h new file mode 100644 index 00000000..8b637cef --- /dev/null +++ b/lib/base/CEvent.h @@ -0,0 +1,123 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CEVENT_H +#define CEVENT_H + +#include "BasicTypes.h" +#include "stdmap.h" + +//! Event +/*! +A \c CEvent holds an event type and a pointer to event data. +*/ +class CEvent { +public: + typedef UInt32 Type; + enum { + kUnknown, //!< The event type is unknown + kQuit, //!< The quit event + kSystem, //!< The data points to a system event type + kTimer, //!< The data points to timer info + kLast //!< Must be last + }; + + typedef UInt32 Flags; + enum { + kNone = 0x00, //!< No flags + kDeliverImmediately = 0x01, //!< Dispatch and free event immediately + kDontFreeData = 0x02 //!< Don't free data in deleteData + }; + + CEvent(); + + //! Create \c CEvent with data + /*! + The \p type must have been registered using \c registerType(). + The \p data must be POD (plain old data) allocated by malloc(), + which means it cannot have a constructor, destructor or be + composed of any types that do. \p target is the intended + recipient of the event. \p flags is any combination of \c Flags. + */ + CEvent(Type type, void* target = NULL, void* data = NULL, + UInt32 flags = kNone); + + //! @name manipulators + //@{ + + //! Creates a new event type + /*! + Returns a unique event type id. + */ + static Type registerType(const char* name); + + //! Creates a new event type + /*! + If \p type contains \c kUnknown then it is set to a unique event + type id otherwise it is left alone. The final value of \p type + is returned. + */ + static Type registerTypeOnce(Type& type, const char* name); + + //! Get name for event + /*! + Returns the name for the event \p type. This is primarily for + debugging. + */ + static const char* getTypeName(Type type); + + //! Release event data + /*! + Deletes event data for the given event (using free()). + */ + static void deleteData(const CEvent&); + + //@} + //! @name accessors + //@{ + + //! Get event type + /*! + Returns the event type. + */ + Type getType() const; + + //! Get the event target + /*! + Returns the event target. + */ + void* getTarget() const; + + //! Get the event data + /*! + Returns the event data. + */ + void* getData() const; + + //! Get event flags + /*! + Returns the event flags. + */ + Flags getFlags() const; + + //@} + +private: + Type m_type; + void* m_target; + void* m_data; + Flags m_flags; +}; + +#endif diff --git a/lib/base/CEventQueue.cpp b/lib/base/CEventQueue.cpp new file mode 100644 index 00000000..d0a93391 --- /dev/null +++ b/lib/base/CEventQueue.cpp @@ -0,0 +1,526 @@ +;/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CEventQueue.h" +#include "CLog.h" +#include "CSimpleEventQueueBuffer.h" +#include "CStopwatch.h" +#include "IEventJob.h" +#include "CArch.h" + +// interrupt handler. this just adds a quit event to the queue. +static +void +interrupt(CArch::ESignal, void*) +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + + +// +// CEventQueue +// + +CEventQueue::CEventQueue() : + m_nextType(CEvent::kLast) +{ + setInstance(this); + m_mutex = ARCH->newMutex(); + ARCH->setSignalHandler(CArch::kINTERRUPT, &interrupt, NULL); + ARCH->setSignalHandler(CArch::kTERMINATE, &interrupt, NULL); + m_buffer = new CSimpleEventQueueBuffer; +} + +CEventQueue::~CEventQueue() +{ + delete m_buffer; + ARCH->setSignalHandler(CArch::kINTERRUPT, NULL, NULL); + ARCH->setSignalHandler(CArch::kTERMINATE, NULL, NULL); + ARCH->closeMutex(m_mutex); + setInstance(NULL); +} + +CEvent::Type +CEventQueue::registerType(const char* name) +{ + CArchMutexLock lock(m_mutex); + m_typeMap.insert(std::make_pair(m_nextType, name)); + LOG((CLOG_DEBUG1 "registered event type %s as %d", name, m_nextType)); + return m_nextType++; +} + +CEvent::Type +CEventQueue::registerTypeOnce(CEvent::Type& type, const char* name) +{ + CArchMutexLock lock(m_mutex); + if (type == CEvent::kUnknown) { + m_typeMap.insert(std::make_pair(m_nextType, name)); + LOG((CLOG_DEBUG1 "registered event type %s as %d", name, m_nextType)); + type = m_nextType++; + } + return type; +} + +const char* +CEventQueue::getTypeName(CEvent::Type type) +{ + switch (type) { + case CEvent::kUnknown: + return "nil"; + + case CEvent::kQuit: + return "quit"; + + case CEvent::kSystem: + return "system"; + + case CEvent::kTimer: + return "timer"; + + default: + CTypeMap::const_iterator i = m_typeMap.find(type); + if (i == m_typeMap.end()) { + return ""; + } + else { + return i->second; + } + } +} + +void +CEventQueue::adoptBuffer(IEventQueueBuffer* buffer) +{ + CArchMutexLock lock(m_mutex); + + // discard old buffer and old events + delete m_buffer; + for (CEventTable::iterator i = m_events.begin(); i != m_events.end(); ++i) { + CEvent::deleteData(i->second); + } + m_events.clear(); + m_oldEventIDs.clear(); + + // use new buffer + m_buffer = buffer; + if (m_buffer == NULL) { + m_buffer = new CSimpleEventQueueBuffer; + } +} + +bool +CEventQueue::getEvent(CEvent& event, double timeout) +{ + CStopwatch timer(true); +retry: + // if no events are waiting then handle timers and then wait + while (m_buffer->isEmpty()) { + // handle timers first + if (hasTimerExpired(event)) { + return true; + } + + // get time remaining in timeout + double timeLeft = timeout - timer.getTime(); + if (timeout >= 0.0 && timeLeft <= 0.0) { + return false; + } + + // get time until next timer expires. if there is a timer + // and it'll expire before the client's timeout then use + // that duration for our timeout instead. + double timerTimeout = getNextTimerTimeout(); + if (timeout < 0.0 || (timerTimeout >= 0.0 && timerTimeout < timeLeft)) { + timeLeft = timerTimeout; + } + + // wait for an event + m_buffer->waitForEvent(timeLeft); + } + + // get the event + UInt32 dataID; + IEventQueueBuffer::Type type = m_buffer->getEvent(event, dataID); + switch (type) { + case IEventQueueBuffer::kNone: + if (timeout < 0.0 || timeout <= timer.getTime()) { + // don't want to fail if client isn't expecting that + // so if getEvent() fails with an infinite timeout + // then just try getting another event. + goto retry; + } + return false; + + case IEventQueueBuffer::kSystem: + return true; + + case IEventQueueBuffer::kUser: + { + CArchMutexLock lock(m_mutex); + event = removeEvent(dataID); + return true; + } + + default: + assert(0 && "invalid event type"); + return false; + } +} + +bool +CEventQueue::dispatchEvent(const CEvent& event) +{ + void* target = event.getTarget(); + IEventJob* job = getHandler(event.getType(), target); + if (job == NULL) { + job = getHandler(CEvent::kUnknown, target); + } + if (job != NULL) { + job->run(event); + return true; + } + return false; +} + +void +CEventQueue::addEvent(const CEvent& event) +{ + // discard bogus event types + switch (event.getType()) { + case CEvent::kUnknown: + case CEvent::kSystem: + case CEvent::kTimer: + return; + + default: + break; + } + + if ((event.getFlags() & CEvent::kDeliverImmediately) != 0) { + dispatchEvent(event); + CEvent::deleteData(event); + } + else { + CArchMutexLock lock(m_mutex); + + // store the event's data locally + UInt32 eventID = saveEvent(event); + + // add it + if (!m_buffer->addEvent(eventID)) { + // failed to send event + removeEvent(eventID); + CEvent::deleteData(event); + } + } +} + +CEventQueueTimer* +CEventQueue::newTimer(double duration, void* target) +{ + assert(duration > 0.0); + + CEventQueueTimer* timer = m_buffer->newTimer(duration, false); + if (target == NULL) { + target = timer; + } + CArchMutexLock lock(m_mutex); + m_timers.insert(timer); + // initial duration is requested duration plus whatever's on + // the clock currently because the latter will be subtracted + // the next time we check for timers. + m_timerQueue.push(CTimer(timer, duration, + duration + m_time.getTime(), target, false)); + return timer; +} + +CEventQueueTimer* +CEventQueue::newOneShotTimer(double duration, void* target) +{ + assert(duration > 0.0); + + CEventQueueTimer* timer = m_buffer->newTimer(duration, true); + if (target == NULL) { + target = timer; + } + CArchMutexLock lock(m_mutex); + m_timers.insert(timer); + // initial duration is requested duration plus whatever's on + // the clock currently because the latter will be subtracted + // the next time we check for timers. + m_timerQueue.push(CTimer(timer, duration, + duration + m_time.getTime(), target, true)); + return timer; +} + +void +CEventQueue::deleteTimer(CEventQueueTimer* timer) +{ + CArchMutexLock lock(m_mutex); + for (CTimerQueue::iterator index = m_timerQueue.begin(); + index != m_timerQueue.end(); ++index) { + if (index->getTimer() == timer) { + m_timerQueue.erase(index); + break; + } + } + CTimers::iterator index = m_timers.find(timer); + if (index != m_timers.end()) { + m_timers.erase(index); + } + m_buffer->deleteTimer(timer); +} + +void +CEventQueue::adoptHandler(CEvent::Type type, void* target, IEventJob* handler) +{ + CArchMutexLock lock(m_mutex); + IEventJob*& job = m_handlers[target][type]; + delete job; + job = handler; +} + +void +CEventQueue::removeHandler(CEvent::Type type, void* target) +{ + IEventJob* handler = NULL; + { + CArchMutexLock lock(m_mutex); + CHandlerTable::iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + CTypeHandlerTable& typeHandlers = index->second; + CTypeHandlerTable::iterator index2 = typeHandlers.find(type); + if (index2 != typeHandlers.end()) { + handler = index2->second; + typeHandlers.erase(index2); + } + } + } + delete handler; +} + +void +CEventQueue::removeHandlers(void* target) +{ + std::vector handlers; + { + CArchMutexLock lock(m_mutex); + CHandlerTable::iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + // copy to handlers array and clear table for target + CTypeHandlerTable& typeHandlers = index->second; + for (CTypeHandlerTable::iterator index2 = typeHandlers.begin(); + index2 != typeHandlers.end(); ++index2) { + handlers.push_back(index2->second); + } + typeHandlers.clear(); + } + } + + // delete handlers + for (std::vector::iterator index = handlers.begin(); + index != handlers.end(); ++index) { + delete *index; + } +} + +bool +CEventQueue::isEmpty() const +{ + return (m_buffer->isEmpty() && getNextTimerTimeout() != 0.0); +} + +IEventJob* +CEventQueue::getHandler(CEvent::Type type, void* target) const +{ + CArchMutexLock lock(m_mutex); + CHandlerTable::const_iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + const CTypeHandlerTable& typeHandlers = index->second; + CTypeHandlerTable::const_iterator index2 = typeHandlers.find(type); + if (index2 != typeHandlers.end()) { + return index2->second; + } + } + return NULL; +} + +UInt32 +CEventQueue::saveEvent(const CEvent& event) +{ + // choose id + UInt32 id; + if (!m_oldEventIDs.empty()) { + // reuse an id + id = m_oldEventIDs.back(); + m_oldEventIDs.pop_back(); + } + else { + // make a new id + id = static_cast(m_events.size()); + } + + // save data + m_events[id] = event; + return id; +} + +CEvent +CEventQueue::removeEvent(UInt32 eventID) +{ + // look up id + CEventTable::iterator index = m_events.find(eventID); + if (index == m_events.end()) { + return CEvent(); + } + + // get data + CEvent event = index->second; + m_events.erase(index); + + // save old id for reuse + m_oldEventIDs.push_back(eventID); + + return event; +} + +bool +CEventQueue::hasTimerExpired(CEvent& event) +{ + // return true if there's a timer in the timer priority queue that + // has expired. if returning true then fill in event appropriately + // and reset and reinsert the timer. + if (m_timerQueue.empty()) { + return false; + } + + // get time elapsed since last check + const double time = m_time.getTime(); + m_time.reset(); + + // countdown elapsed time + for (CTimerQueue::iterator index = m_timerQueue.begin(); + index != m_timerQueue.end(); ++index) { + (*index) -= time; + } + + // done if no timers are expired + if (m_timerQueue.top() > 0.0) { + return false; + } + + // remove timer from queue + CTimer timer = m_timerQueue.top(); + m_timerQueue.pop(); + + // prepare event and reset the timer's clock + timer.fillEvent(m_timerEvent); + event = CEvent(CEvent::kTimer, timer.getTarget(), &m_timerEvent); + timer.reset(); + + // reinsert timer into queue if it's not a one-shot + if (!timer.isOneShot()) { + m_timerQueue.push(timer); + } + + return true; +} + +double +CEventQueue::getNextTimerTimeout() const +{ + // return -1 if no timers, 0 if the top timer has expired, otherwise + // the time until the top timer in the timer priority queue will + // expire. + if (m_timerQueue.empty()) { + return -1.0; + } + if (m_timerQueue.top() <= 0.0) { + return 0.0; + } + return m_timerQueue.top(); +} + + +// +// CEventQueue::CTimer +// + +CEventQueue::CTimer::CTimer(CEventQueueTimer* timer, double timeout, + double initialTime, void* target, bool oneShot) : + m_timer(timer), + m_timeout(timeout), + m_target(target), + m_oneShot(oneShot), + m_time(initialTime) +{ + assert(m_timeout > 0.0); +} + +CEventQueue::CTimer::~CTimer() +{ + // do nothing +} + +void +CEventQueue::CTimer::reset() +{ + m_time = m_timeout; +} + +CEventQueue::CTimer& +CEventQueue::CTimer::operator-=(double dt) +{ + m_time -= dt; + return *this; +} + +CEventQueue::CTimer::operator double() const +{ + return m_time; +} + +bool +CEventQueue::CTimer::isOneShot() const +{ + return m_oneShot; +} + +CEventQueueTimer* +CEventQueue::CTimer::getTimer() const +{ + return m_timer; +} + +void* +CEventQueue::CTimer::getTarget() const +{ + return m_target; +} + +void +CEventQueue::CTimer::fillEvent(CTimerEvent& event) const +{ + event.m_timer = m_timer; + event.m_count = 0; + if (m_time <= 0.0) { + event.m_count = static_cast((m_timeout - m_time) / m_timeout); + } +} + +bool +CEventQueue::CTimer::operator<(const CTimer& t) const +{ + return m_time < t.m_time; +} diff --git a/lib/base/CEventQueue.h b/lib/base/CEventQueue.h new file mode 100644 index 00000000..a63c7b16 --- /dev/null +++ b/lib/base/CEventQueue.h @@ -0,0 +1,123 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CEVENTQUEUE_H +#define CEVENTQUEUE_H + +#include "IEventQueue.h" +#include "CEvent.h" +#include "CPriorityQueue.h" +#include "CStopwatch.h" +#include "IArchMultithread.h" +#include "stdmap.h" +#include "stdset.h" + +//! Event queue +/*! +An event queue that implements the platform independent parts and +delegates the platform dependent parts to a subclass. +*/ +class CEventQueue : public IEventQueue { +public: + CEventQueue(); + virtual ~CEventQueue(); + + // IEventQueue overrides + virtual void adoptBuffer(IEventQueueBuffer*); + virtual bool getEvent(CEvent& event, double timeout = -1.0); + virtual bool dispatchEvent(const CEvent& event); + virtual void addEvent(const CEvent& event); + virtual CEventQueueTimer* + newTimer(double duration, void* target); + virtual CEventQueueTimer* + newOneShotTimer(double duration, void* target); + virtual void deleteTimer(CEventQueueTimer*); + virtual void adoptHandler(CEvent::Type type, + void* target, IEventJob* handler); + virtual void removeHandler(CEvent::Type type, void* target); + virtual void removeHandlers(void* target); + virtual CEvent::Type + registerType(const char* name); + virtual CEvent::Type + registerTypeOnce(CEvent::Type& type, const char* name); + virtual bool isEmpty() const; + virtual IEventJob* getHandler(CEvent::Type type, void* target) const; + virtual const char* getTypeName(CEvent::Type type); + +private: + UInt32 saveEvent(const CEvent& event); + CEvent removeEvent(UInt32 eventID); + bool hasTimerExpired(CEvent& event); + double getNextTimerTimeout() const; + +private: + class CTimer { + public: + CTimer(CEventQueueTimer*, double timeout, double initialTime, + void* target, bool oneShot); + ~CTimer(); + + void reset(); + + CTimer& operator-=(double); + + operator double() const; + + bool isOneShot() const; + CEventQueueTimer* + getTimer() const; + void* getTarget() const; + void fillEvent(CTimerEvent&) const; + + bool operator<(const CTimer&) const; + + private: + CEventQueueTimer* m_timer; + double m_timeout; + void* m_target; + bool m_oneShot; + double m_time; + }; + typedef std::set CTimers; + typedef CPriorityQueue CTimerQueue; + typedef std::map CEventTable; + typedef std::vector CEventIDList; + typedef std::map CTypeMap; + typedef std::map CTypeHandlerTable; + typedef std::map CHandlerTable; + + CArchMutex m_mutex; + + // registered events + CEvent::Type m_nextType; + CTypeMap m_typeMap; + + // buffer of events + IEventQueueBuffer* m_buffer; + + // saved events + CEventTable m_events; + CEventIDList m_oldEventIDs; + + // timers + CStopwatch m_time; + CTimers m_timers; + CTimerQueue m_timerQueue; + CTimerEvent m_timerEvent; + + // event handlers + CHandlerTable m_handlers; +}; + +#endif diff --git a/lib/base/CFunctionEventJob.cpp b/lib/base/CFunctionEventJob.cpp new file mode 100644 index 00000000..5afdc988 --- /dev/null +++ b/lib/base/CFunctionEventJob.cpp @@ -0,0 +1,40 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CFunctionEventJob.h" + +// +// CFunctionEventJob +// + +CFunctionEventJob::CFunctionEventJob( + void (*func)(const CEvent&, void*), void* arg) : + m_func(func), + m_arg(arg) +{ + // do nothing +} + +CFunctionEventJob::~CFunctionEventJob() +{ + // do nothing +} + +void +CFunctionEventJob::run(const CEvent& event) +{ + if (m_func != NULL) { + m_func(event, m_arg); + } +} diff --git a/lib/base/CFunctionEventJob.h b/lib/base/CFunctionEventJob.h new file mode 100644 index 00000000..517b9c45 --- /dev/null +++ b/lib/base/CFunctionEventJob.h @@ -0,0 +1,38 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CFUNCTIONEVENTJOB_H +#define CFUNCTIONEVENTJOB_H + +#include "IEventJob.h" + +//! Use a function as an event job +/*! +An event job class that invokes a function. +*/ +class CFunctionEventJob : public IEventJob { +public: + //! run() invokes \c func(arg) + CFunctionEventJob(void (*func)(const CEvent&, void*), void* arg = NULL); + virtual ~CFunctionEventJob(); + + // IEventJob overrides + virtual void run(const CEvent&); + +private: + void (*m_func)(const CEvent&, void*); + void* m_arg; +}; + +#endif diff --git a/lib/base/CFunctionJob.cpp b/lib/base/CFunctionJob.cpp new file mode 100644 index 00000000..bc16c509 --- /dev/null +++ b/lib/base/CFunctionJob.cpp @@ -0,0 +1,39 @@ +/* + * 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 "CFunctionJob.h" + +// +// CFunctionJob +// + +CFunctionJob::CFunctionJob(void (*func)(void*), void* arg) : + m_func(func), + m_arg(arg) +{ + // do nothing +} + +CFunctionJob::~CFunctionJob() +{ + // do nothing +} + +void +CFunctionJob::run() +{ + if (m_func != NULL) { + m_func(m_arg); + } +} diff --git a/lib/base/CFunctionJob.h b/lib/base/CFunctionJob.h new file mode 100644 index 00000000..e30bbfa2 --- /dev/null +++ b/lib/base/CFunctionJob.h @@ -0,0 +1,38 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CFUNCTIONJOB_H +#define CFUNCTIONJOB_H + +#include "IJob.h" + +//! Use a function as a job +/*! +A job class that invokes a function. +*/ +class CFunctionJob : public IJob { +public: + //! run() invokes \c func(arg) + CFunctionJob(void (*func)(void*), void* arg = NULL); + virtual ~CFunctionJob(); + + // IJob overrides + virtual void run(); + +private: + void (*m_func)(void*); + void* m_arg; +}; + +#endif diff --git a/lib/base/CLog.cpp b/lib/base/CLog.cpp new file mode 100644 index 00000000..7c73ac87 --- /dev/null +++ b/lib/base/CLog.cpp @@ -0,0 +1,293 @@ +/* + * 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 + +// 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]; + int pPad = g_priorityPad; + if (file != NULL) { + sprintf(stack, "%d", line); + pPad += strlen(file) + 1 /* comma */ + + strlen(stack) + 1 /* colon */ + 1 /* space */; + } + + // 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 + pPad, len - pPad - 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. + char* message = buffer; + if (file != NULL) { + sprintf(buffer + g_priorityPad, "%s,%d:", file, line); + buffer[pPad - 1] = ' '; + + // discard file and line if priority < 0 + if (priority < 0) { + message += pPad - g_priorityPad; + } + } + + // output buffer + output(priority, message); + + // 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 = strlen(outputter->getNewline()); + if (newlineLength > m_maxNewlineLength) { + m_maxNewlineLength = newlineLength; + } + outputter->open(kAppVersion); + 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); + + // 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), + msg + 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), + msg + g_maxPriorityLength - n)) { + break; + } + } +} diff --git a/lib/base/CLog.h b/lib/base/CLog.h new file mode 100644 index 00000000..391480e2 --- /dev/null +++ b/lib/base/CLog.h @@ -0,0 +1,202 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CLOG_H +#define CLOG_H + +#include "common.h" +#include "IArchMultithread.h" +#include "stdlist.h" +#include + +#define CLOG (CLog::getInstance()) + +class ILogOutputter; + +//! Logging facility +/*! +The logging class; all console output should go through this class. +It supports multithread safe operation, several message priority levels, +filtering by priority, and output redirection. The macros LOG() and +LOGC() provide convenient access. +*/ +class CLog { +public: + //! Log levels + /*! + The logging priority levels in order of highest to lowest priority. + */ + enum ELevel { + kFATAL, //!< For fatal errors + kERROR, //!< For serious errors + kWARNING, //!< For minor errors and warnings + 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 + }; + + ~CLog(); + + //! @name manipulators + //@{ + + //! Add an outputter to the head of the list + /*! + Inserts an outputter to the head of the outputter list. When the + logger writes a message, it goes to the outputter at the head of + the outputter list. If that outputter's \c write() method returns + true then it also goes to the next outputter, as so on until an + outputter returns false or there are no more outputters. Outputters + still in the outputter list when the log is destroyed will be + deleted. If \c alwaysAtHead is true then the outputter is always + called before all outputters with \c alwaysAtHead false and the + return value of the outputter is ignored. + + By default, the logger has one outputter installed which writes to + the console. + */ + void insert(ILogOutputter* adopted, + bool alwaysAtHead = false); + + //! Remove an outputter from the list + /*! + Removes the first occurrence of the given outputter from the + outputter list. It does nothing if the outputter is not in the + list. The outputter is not deleted. + */ + void remove(ILogOutputter* orphaned); + + //! Remove the outputter from the head of the list + /*! + Removes and deletes the outputter at the head of the outputter list. + This does nothing if the outputter list is empty. Only removes + outputters that were inserted with the matching \c alwaysAtHead. + */ + void pop_front(bool alwaysAtHead = false); + + //! Set the minimum priority filter. + /*! + Set the filter. Messages below this priority are discarded. + The default priority is 4 (INFO) (unless built without NDEBUG + in which case it's 5 (DEBUG)). setFilter(const char*) returns + true if the priority \c name was recognized; if \c name is NULL + then it simply returns true. + */ + bool setFilter(const char* name); + void setFilter(int); + + //@} + //! @name accessors + //@{ + + //! Print a log message + /*! + Print a log message using the printf-like \c format and arguments + preceded by the filename and line number. If \c file is NULL then + neither the file nor the line are printed. + */ + void print(const char* file, int line, + const char* format, ...) const; + + //! Get the minimum priority level. + int getFilter() const; + + //! Get the singleton instance of the log + static CLog* getInstance(); + + //@} + +private: + CLog(); + + void output(int priority, char* msg) const; + +private: + typedef std::list COutputterList; + + static CLog* s_log; + + CArchMutex m_mutex; + COutputterList m_outputters; + COutputterList m_alwaysOutputters; + int m_maxNewlineLength; + int m_maxPriority; +}; + +/*! +\def LOG(arg) +Write to the log. Because macros cannot accept variable arguments, this +should be invoked like so: +\code +LOG((CLOG_XXX "%d and %d are %s", x, y, x == y ? "equal" : "not equal")); +\endcode +In particular, notice the double open and close parentheses. Also note +that there is no comma after the \c CLOG_XXX. The \c XXX should be +replaced by one of enumerants in \c CLog::ELevel without the leading +\c k. For example, \c CLOG_INFO. The special \c CLOG_PRINT level will +not be filtered and is never prefixed by the filename and line number. + +If \c NOLOGGING is defined during the build then this macro expands to +nothing. If \c NDEBUG is defined during the build then it expands to a +call to CLog::print. Otherwise it expands to a call to CLog::printt, +which includes the filename and line number. +*/ + +/*! +\def LOGC(expr, arg) +Write to the log if and only if expr is true. Because macros cannot accept +variable arguments, this should be invoked like so: +\code +LOGC(x == y, (CLOG_XXX "%d and %d are equal", x, y)); +\endcode +In particular, notice the parentheses around everything after the boolean +expression. Also note that there is no comma after the \c CLOG_XXX. +The \c XXX should be replaced by one of enumerants in \c CLog::ELevel +without the leading \c k. For example, \c CLOG_INFO. The special +\c CLOG_PRINT level will not be filtered and is never prefixed by the +filename and line number. + +If \c NOLOGGING is defined during the build then this macro expands to +nothing. If \c NDEBUG is not defined during the build then it expands +to a call to CLog::print that prints the filename and line number, +otherwise it expands to a call that doesn't. +*/ + +#if defined(NOLOGGING) +#define LOG(_a1) +#define LOGC(_a1, _a2) +#define CLOG_TRACE +#elif defined(NDEBUG) +#define LOG(_a1) CLOG->print _a1 +#define LOGC(_a1, _a2) if (_a1) CLOG->print _a2 +#define CLOG_TRACE NULL, 0, +#else +#define LOG(_a1) CLOG->print _a1 +#define LOGC(_a1, _a2) if (_a1) CLOG->print _a2 +#define CLOG_TRACE __FILE__, __LINE__, +#endif + +#define CLOG_PRINT CLOG_TRACE "%z\057" +#define CLOG_CRIT CLOG_TRACE "%z\060" +#define CLOG_ERR CLOG_TRACE "%z\061" +#define CLOG_WARN CLOG_TRACE "%z\062" +#define CLOG_NOTE CLOG_TRACE "%z\063" +#define CLOG_INFO CLOG_TRACE "%z\064" +#define CLOG_DEBUG CLOG_TRACE "%z\065" +#define CLOG_DEBUG1 CLOG_TRACE "%z\066" +#define CLOG_DEBUG2 CLOG_TRACE "%z\067" + +#endif diff --git a/lib/base/CPriorityQueue.h b/lib/base/CPriorityQueue.h new file mode 100644 index 00000000..29129e31 --- /dev/null +++ b/lib/base/CPriorityQueue.h @@ -0,0 +1,136 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CPRIORITYQUEUE_H +#define CPRIORITYQUEUE_H + +#include "stdvector.h" +#include +#include + +//! A priority queue with an iterator +/*! +This priority queue is the same as a standard priority queue except: +it sorts by std::greater, it has a forward iterator through the elements +(which can appear in any order), and its contents can be swapped. +*/ +template , +#if defined(_MSC_VER) + class Compare = std::greater > +#else + class Compare = std::greater > +#endif +class CPriorityQueue { +public: + typedef typename Container::value_type value_type; + typedef typename Container::size_type size_type; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + typedef Container container_type; + + CPriorityQueue() { } + CPriorityQueue(Container& swappedIn) { swap(swappedIn); } + ~CPriorityQueue() { } + + //! @name manipulators + //@{ + + //! Add element + void push(const value_type& v) + { + c.push_back(v); + std::push_heap(c.begin(), c.end(), comp); + } + + //! Remove head element + void pop() + { + std::pop_heap(c.begin(), c.end(), comp); + c.pop_back(); + } + + //! Erase element + void erase(iterator i) + { + c.erase(i); + std::make_heap(c.begin(), c.end(), comp); + } + + //! Get start iterator + iterator begin() + { + return c.begin(); + } + + //! Get end iterator + iterator end() + { + return c.end(); + } + + //! Swap contents with another priority queue + void swap(CPriorityQueue& q) + { + c.swap(q.c); + } + + //! Swap contents with another container + void swap(Container& c2) + { + c.swap(c2); + std::make_heap(c.begin(), c.end(), comp); + } + + //@} + //! @name accessors + //@{ + + //! Returns true if there are no elements + bool empty() const + { + return c.empty(); + } + + //! Returns the number of elements + size_type size() const + { + return c.size(); + } + + //! Returns the head element + const value_type& top() const + { + return c.front(); + } + + //! Get start iterator + const_iterator begin() const + { + return c.begin(); + } + + //! Get end iterator + const_iterator end() const + { + return c.end(); + } + + //@} + +private: + Container c; + Compare comp; +}; + +#endif diff --git a/lib/base/CSimpleEventQueueBuffer.cpp b/lib/base/CSimpleEventQueueBuffer.cpp new file mode 100644 index 00000000..8f2dbd14 --- /dev/null +++ b/lib/base/CSimpleEventQueueBuffer.cpp @@ -0,0 +1,97 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CSimpleEventQueueBuffer.h" +#include "CStopwatch.h" +#include "CArch.h" + +class CEventQueueTimer { }; + +// +// CSimpleEventQueueBuffer +// + +CSimpleEventQueueBuffer::CSimpleEventQueueBuffer() +{ + m_queueMutex = ARCH->newMutex(); + m_queueReadyCond = ARCH->newCondVar(); + m_queueReady = false; +} + +CSimpleEventQueueBuffer::~CSimpleEventQueueBuffer() +{ + ARCH->closeCondVar(m_queueReadyCond); + ARCH->closeMutex(m_queueMutex); +} + +void +CSimpleEventQueueBuffer::waitForEvent(double timeout) +{ + CArchMutexLock lock(m_queueMutex); + CStopwatch timer(true); + while (!m_queueReady) { + double timeLeft = timeout; + if (timeLeft >= 0.0) { + timeLeft -= timer.getTime(); + if (timeLeft < 0.0) { + return; + } + } + ARCH->waitCondVar(m_queueReadyCond, m_queueMutex, timeLeft); + } +} + +IEventQueueBuffer::Type +CSimpleEventQueueBuffer::getEvent(CEvent&, UInt32& dataID) +{ + CArchMutexLock lock(m_queueMutex); + if (!m_queueReady) { + return kNone; + } + dataID = m_queue.back(); + m_queue.pop_back(); + m_queueReady = !m_queue.empty(); + return kUser; +} + +bool +CSimpleEventQueueBuffer::addEvent(UInt32 dataID) +{ + CArchMutexLock lock(m_queueMutex); + m_queue.push_front(dataID); + if (!m_queueReady) { + m_queueReady = true; + ARCH->broadcastCondVar(m_queueReadyCond); + } + return true; +} + +bool +CSimpleEventQueueBuffer::isEmpty() const +{ + CArchMutexLock lock(m_queueMutex); + return !m_queueReady; +} + +CEventQueueTimer* +CSimpleEventQueueBuffer::newTimer(double, bool) const +{ + return new CEventQueueTimer; +} + +void +CSimpleEventQueueBuffer::deleteTimer(CEventQueueTimer* timer) const +{ + delete timer; +} diff --git a/lib/base/CSimpleEventQueueBuffer.h b/lib/base/CSimpleEventQueueBuffer.h new file mode 100644 index 00000000..c395fabd --- /dev/null +++ b/lib/base/CSimpleEventQueueBuffer.h @@ -0,0 +1,49 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSIMPLEEVENTQUEUEBUFFER_H +#define CSIMPLEEVENTQUEUEBUFFER_H + +#include "IEventQueueBuffer.h" +#include "IArchMultithread.h" +#include "stddeque.h" + +//! In-memory event queue buffer +/*! +An event queue buffer provides a queue of events for an IEventQueue. +*/ +class CSimpleEventQueueBuffer : public IEventQueueBuffer { +public: + CSimpleEventQueueBuffer(); + ~CSimpleEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void waitForEvent(double timeout); + virtual Type getEvent(CEvent& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(CEventQueueTimer*) const; + +private: + typedef std::deque CEventDeque; + + CArchMutex m_queueMutex; + CArchCond m_queueReadyCond; + bool m_queueReady; + CEventDeque m_queue; +}; + +#endif diff --git a/lib/base/CStopwatch.cpp b/lib/base/CStopwatch.cpp new file mode 100644 index 00000000..89edce2b --- /dev/null +++ b/lib/base/CStopwatch.cpp @@ -0,0 +1,126 @@ +/* + * 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 "CStopwatch.h" +#include "CArch.h" + +// +// CStopwatch +// + +CStopwatch::CStopwatch(bool triggered) : + m_mark(0.0), + m_triggered(triggered), + m_stopped(triggered) +{ + if (!triggered) { + m_mark = ARCH->time(); + } +} + +CStopwatch::~CStopwatch() +{ + // do nothing +} + +double +CStopwatch::reset() +{ + if (m_stopped) { + const double dt = m_mark; + m_mark = 0.0; + return dt; + } + else { + const double t = ARCH->time(); + const double dt = t - m_mark; + m_mark = t; + return dt; + } +} + +void +CStopwatch::stop() +{ + if (m_stopped) { + return; + } + + // save the elapsed time + m_mark = ARCH->time() - m_mark; + m_stopped = true; +} + +void +CStopwatch::start() +{ + m_triggered = false; + if (!m_stopped) { + return; + } + + // set the mark such that it reports the time elapsed at stop() + m_mark = ARCH->time() - m_mark; + m_stopped = false; +} + +void +CStopwatch::setTrigger() +{ + stop(); + m_triggered = true; +} + +double +CStopwatch::getTime() +{ + if (m_triggered) { + const double dt = m_mark; + start(); + return dt; + } + else if (m_stopped) { + return m_mark; + } + else { + return ARCH->time() - m_mark; + } +} + +CStopwatch::operator double() +{ + return getTime(); +} + +bool +CStopwatch::isStopped() const +{ + return m_stopped; +} + +double +CStopwatch::getTime() const +{ + if (m_stopped) { + return m_mark; + } + else { + return ARCH->time() - m_mark; + } +} + +CStopwatch::operator double() const +{ + return getTime(); +} diff --git a/lib/base/CStopwatch.h b/lib/base/CStopwatch.h new file mode 100644 index 00000000..e6a34719 --- /dev/null +++ b/lib/base/CStopwatch.h @@ -0,0 +1,108 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSTOPWATCH_H +#define CSTOPWATCH_H + +#include "common.h" + +//! A timer class +/*! +This class measures time intervals. All time interval measurement +should use this class. +*/ +class CStopwatch { +public: + /*! + The default constructor does an implicit reset() or setTrigger(). + If triggered == false then the clock starts ticking. + */ + CStopwatch(bool triggered = false); + ~CStopwatch(); + + //! @name manipulators + //@{ + + //! Reset the timer to zero + /*! + Set the start time to the current time, returning the time since + the last reset. This does not remove the trigger if it's set nor + does it start a stopped clock. If the clock is stopped then + subsequent reset()'s will return 0. + */ + double reset(); + + //! Stop the timer + /*! + Stop the stopwatch. The time interval while stopped is not + counted by the stopwatch. stop() does not remove the trigger. + Has no effect if already stopped. + */ + void stop(); + + //! Start the timer + /*! + Start the stopwatch. start() removes the trigger, even if the + stopwatch was already started. + */ + void start(); + + //! Stop the timer and set the trigger + /*! + setTrigger() stops the clock like stop() except there's an + implicit start() the next time (non-const) getTime() is called. + This is useful when you want the clock to start the first time + you check it. + */ + void setTrigger(); + + //! Get elapsed time + /*! + Returns the time since the last reset() (or calls reset() and + returns zero if the trigger is set). + */ + double getTime(); + //! Same as getTime() + operator double(); + //@} + //! @name accessors + //@{ + + //! Check if timer is stopped + /*! + Returns true if the stopwatch is stopped. + */ + bool isStopped() const; + + // return the time since the last reset(). + //! Get elapsed time + /*! + Returns the time since the last reset(). This cannot trigger the + stopwatch to start and will not clear the trigger. + */ + double getTime() const; + //! Same as getTime() const + operator double() const; + //@} + +private: + double getClock() const; + +private: + double m_mark; + bool m_triggered; + bool m_stopped; +}; + +#endif diff --git a/lib/base/CString.h b/lib/base/CString.h new file mode 100644 index 00000000..bc905009 --- /dev/null +++ b/lib/base/CString.h @@ -0,0 +1,25 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSTRING_H +#define CSTRING_H + +#include "common.h" +#include "stdstring.h" + +// use standard C++ string class for our string class +typedef std::string CString; + +#endif + diff --git a/lib/base/CStringUtil.cpp b/lib/base/CStringUtil.cpp new file mode 100644 index 00000000..46361932 --- /dev/null +++ b/lib/base/CStringUtil.cpp @@ -0,0 +1,194 @@ +/* + * 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 "CStringUtil.h" +#include "CArch.h" +#include "common.h" +#include "stdvector.h" +#include +#include +#include +#include + +// +// CStringUtil +// + +CString +CStringUtil::format(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + CString result = vformat(fmt, args); + va_end(args); + return result; +} + +CString +CStringUtil::vformat(const char* fmt, va_list args) +{ + // find highest indexed substitution and the locations of substitutions + std::vector pos; + std::vector width; + std::vector index; + int maxIndex = 0; + for (const char* scan = fmt; *scan != '\0'; ++scan) { + if (*scan == '%') { + ++scan; + if (*scan == '\0') { + break; + } + else if (*scan == '%') { + // literal + index.push_back(0); + pos.push_back(static_cast(scan - 1 - fmt)); + width.push_back(2); + } + else if (*scan == '{') { + // get argument index + char* end; + int i = static_cast(strtol(scan + 1, &end, 10)); + if (*end != '}') { + // invalid index -- ignore + scan = end - 1; + } + else { + index.push_back(i); + pos.push_back(static_cast(scan - 1 - fmt)); + width.push_back(static_cast(end - scan + 2)); + if (i > maxIndex) { + maxIndex = i; + } + scan = end; + } + } + else { + // improper escape -- ignore + } + } + } + + // get args + std::vector value; + std::vector length; + value.push_back("%"); + length.push_back(1); + for (int i = 0; i < maxIndex; ++i) { + const char* arg = va_arg(args, const char*); + size_t len = strlen(arg); + value.push_back(arg); + length.push_back(len); + } + + // compute final length + size_t resultLength = strlen(fmt); + const int n = static_cast(pos.size()); + for (int i = 0; i < n; ++i) { + resultLength -= width[i]; + resultLength += length[index[i]]; + } + + // substitute + CString result; + result.reserve(resultLength); + size_t src = 0; + for (int i = 0; i < n; ++i) { + result.append(fmt + src, pos[i] - src); + result.append(value[index[i]]); + src = pos[i] + width[i]; + } + result.append(fmt + src); + + return result; +} + +CString +CStringUtil::print(const char* fmt, ...) +{ + char tmp[1024]; + char* buffer = tmp; + int len = (int)(sizeof(tmp) / sizeof(tmp[0])); + CString result; + while (buffer != NULL) { + // try printing into the buffer + va_list args; + va_start(args, fmt); + int n = ARCH->vsnprintf(buffer, len, fmt, args); + va_end(args); + + // if the buffer wasn't big enough then make it bigger and try again + if (n < 0 || n > len) { + if (buffer != tmp) { + delete[] buffer; + } + len *= 2; + buffer = new char[len]; + } + + // if it was big enough then save the string and don't try again + else { + result = buffer; + if (buffer != tmp) { + delete[] buffer; + } + buffer = NULL; + } + } + + return result; +} + + +// +// CStringUtil::CaselessCmp +// + +bool +CStringUtil::CaselessCmp::cmpEqual( + const CString::value_type& a, + const CString::value_type& b) +{ + // should use std::tolower but not in all versions of libstdc++ have it + return tolower(a) == tolower(b); +} + +bool +CStringUtil::CaselessCmp::cmpLess( + const CString::value_type& a, + const CString::value_type& b) +{ + // should use std::tolower but not in all versions of libstdc++ have it + return tolower(a) < tolower(b); +} + +bool +CStringUtil::CaselessCmp::less(const CString& a, const CString& b) +{ + return std::lexicographical_compare( + a.begin(), a.end(), + b.begin(), b.end(), + &CStringUtil::CaselessCmp::cmpLess); +} + +bool +CStringUtil::CaselessCmp::equal(const CString& a, const CString& b) +{ + return !(less(a, b) || less(b, a)); +} + +bool +CStringUtil::CaselessCmp::operator()(const CString& a, const CString& b) const +{ + return less(a, b); +} diff --git a/lib/base/CStringUtil.h b/lib/base/CStringUtil.h new file mode 100644 index 00000000..8ee86647 --- /dev/null +++ b/lib/base/CStringUtil.h @@ -0,0 +1,77 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSTRINGUTIL_H +#define CSTRINGUTIL_H + +#include "CString.h" +#include + +//! String utilities +/*! +This class provides various functions for string manipulation. +*/ +class CStringUtil { +public: + //! Format positional arguments + /*! + Format a string using positional arguments. fmt has literal + characters and conversion specifications introduced by `\%': + - \c\%\% -- literal `\%' + - \c\%{n} -- positional element n, n a positive integer, {} are literal + + All arguments in the variable list are const char*. Positional + elements are indexed from 1. + */ + static CString format(const char* fmt, ...); + + //! Format positional arguments + /*! + Same as format() except takes va_list. + */ + static CString vformat(const char* fmt, va_list); + + //! Print a string using printf-style formatting + /*! + Equivalent to printf() except the result is returned as a CString. + */ + static CString print(const char* fmt, ...); + + //! Case-insensitive comparisons + /*! + This class provides case-insensitve comparison functions. + */ + class CaselessCmp { + public: + //! Same as less() + bool operator()(const CString& a, const CString& b) const; + + //! Returns true iff \c a is lexicographically less than \c b + static bool less(const CString& a, const CString& b); + + //! Returns true iff \c a is lexicographically equal to \c b + static bool equal(const CString& a, const CString& b); + + //! Returns true iff \c a is lexicographically less than \c b + static bool cmpLess(const CString::value_type& a, + const CString::value_type& b); + + //! Returns true iff \c a is lexicographically equal to \c b + static bool cmpEqual(const CString::value_type& a, + const CString::value_type& b); + }; +}; + +#endif + diff --git a/lib/base/CUnicode.cpp b/lib/base/CUnicode.cpp new file mode 100644 index 00000000..4dcfcd6f --- /dev/null +++ b/lib/base/CUnicode.cpp @@ -0,0 +1,779 @@ +/* + * 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 "CUnicode.h" +#include "CArch.h" +#include + +// +// local utility functions +// + +inline +static +UInt16 +decode16(const UInt8* n, bool byteSwapped) +{ + union x16 { + UInt8 n8[2]; + UInt16 n16; + } c; + if (byteSwapped) { + c.n8[0] = n[1]; + c.n8[1] = n[0]; + } + else { + c.n8[0] = n[0]; + c.n8[1] = n[1]; + } + return c.n16; +} + +inline +static +UInt32 +decode32(const UInt8* n, bool byteSwapped) +{ + union x32 { + UInt8 n8[4]; + UInt32 n32; + } c; + if (byteSwapped) { + c.n8[0] = n[3]; + c.n8[1] = n[2]; + c.n8[2] = n[1]; + c.n8[3] = n[0]; + } + else { + c.n8[0] = n[0]; + c.n8[1] = n[1]; + c.n8[2] = n[2]; + c.n8[3] = n[3]; + } + return c.n32; +} + +inline +static +void +resetError(bool* errors) +{ + if (errors != NULL) { + *errors = false; + } +} + +inline +static +void +setError(bool* errors) +{ + if (errors != NULL) { + *errors = true; + } +} + + +// +// CUnicode +// + +UInt32 CUnicode::s_invalid = 0x0000ffff; +UInt32 CUnicode::s_replacement = 0x0000fffd; + +bool +CUnicode::isUTF8(const CString& src) +{ + // convert and test each character + const UInt8* data = reinterpret_cast(src.c_str()); + for (UInt32 n = src.size(); n > 0; ) { + if (fromUTF8(data, n) == s_invalid) { + return false; + } + } + return true; +} + +CString +CUnicode::UTF8ToUCS2(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = src.size(); + CString dst; + dst.reserve(2 * n); + + // convert each character + const UInt8* data = reinterpret_cast(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00010000) { + setError(errors); + c = s_replacement; + } + UInt16 ucs2 = static_cast(c); + dst.append(reinterpret_cast(&ucs2), 2); + } + + return dst; +} + +CString +CUnicode::UTF8ToUCS4(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = src.size(); + CString dst; + dst.reserve(4 * n); + + // convert each character + const UInt8* data = reinterpret_cast(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + dst.append(reinterpret_cast(&c), 4); + } + + return dst; +} + +CString +CUnicode::UTF8ToUTF16(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = src.size(); + CString dst; + dst.reserve(2 * n); + + // convert each character + const UInt8* data = reinterpret_cast(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + if (c < 0x00010000) { + UInt16 ucs2 = static_cast(c); + dst.append(reinterpret_cast(&ucs2), 2); + } + else { + c -= 0x00010000; + UInt16 utf16h = static_cast((c >> 10) + 0xd800); + UInt16 utf16l = static_cast((c & 0x03ff) + 0xdc00); + dst.append(reinterpret_cast(&utf16h), 2); + dst.append(reinterpret_cast(&utf16l), 2); + } + } + + return dst; +} + +CString +CUnicode::UTF8ToUTF32(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = src.size(); + CString dst; + dst.reserve(4 * n); + + // convert each character + const UInt8* data = reinterpret_cast(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + dst.append(reinterpret_cast(&c), 4); + } + + return dst; +} + +CString +CUnicode::UTF8ToText(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert to wide char + UInt32 size; + wchar_t* tmp = UTF8ToWideChar(src, size, errors); + + // convert string to multibyte + int len = ARCH->convStringWCToMB(NULL, tmp, size, errors); + char* mbs = new char[len + 1]; + ARCH->convStringWCToMB(mbs, tmp, size, errors); + CString text(mbs, len); + + // clean up + delete[] mbs; + delete[] tmp; + + return text; +} + +CString +CUnicode::UCS2ToUTF8(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = src.size() >> 1; + return doUCS2ToUTF8(reinterpret_cast(src.data()), n, errors); +} + +CString +CUnicode::UCS4ToUTF8(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = src.size() >> 2; + return doUCS4ToUTF8(reinterpret_cast(src.data()), n, errors); +} + +CString +CUnicode::UTF16ToUTF8(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = src.size() >> 1; + return doUTF16ToUTF8(reinterpret_cast(src.data()), n, errors); +} + +CString +CUnicode::UTF32ToUTF8(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = src.size() >> 2; + return doUTF32ToUTF8(reinterpret_cast(src.data()), n, errors); +} + +CString +CUnicode::textToUTF8(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert string to wide characters + UInt32 n = src.size(); + int len = ARCH->convStringMBToWC(NULL, src.c_str(), n, errors); + wchar_t* wcs = new wchar_t[len + 1]; + ARCH->convStringMBToWC(wcs, src.c_str(), n, errors); + + // convert to UTF8 + CString utf8 = wideCharToUTF8(wcs, len, errors); + + // clean up + delete[] wcs; + + return utf8; +} + +wchar_t* +CUnicode::UTF8ToWideChar(const CString& src, UInt32& size, bool* errors) +{ + // convert to platform's wide character encoding + CString tmp; + switch (ARCH->getWideCharEncoding()) { + case IArchString::kUCS2: + tmp = UTF8ToUCS2(src, errors); + size = tmp.size() >> 1; + break; + + case IArchString::kUCS4: + tmp = UTF8ToUCS4(src, errors); + size = tmp.size() >> 2; + break; + + case IArchString::kUTF16: + tmp = UTF8ToUTF16(src, errors); + size = tmp.size() >> 1; + break; + + case IArchString::kUTF32: + tmp = UTF8ToUTF32(src, errors); + size = tmp.size() >> 2; + break; + + default: + assert(0 && "unknown wide character encoding"); + } + + // copy to a wchar_t array + wchar_t* dst = new wchar_t[size]; + ::memcpy(dst, tmp.data(), sizeof(wchar_t) * size); + return dst; +} + +CString +CUnicode::wideCharToUTF8(const wchar_t* src, UInt32 size, bool* errors) +{ + // convert from platform's wide character encoding. + // note -- this must include a wide nul character (independent of + // the CString's nul character). + switch (ARCH->getWideCharEncoding()) { + case IArchString::kUCS2: + return doUCS2ToUTF8(reinterpret_cast(src), size, errors); + + case IArchString::kUCS4: + return doUCS4ToUTF8(reinterpret_cast(src), size, errors); + + case IArchString::kUTF16: + return doUTF16ToUTF8(reinterpret_cast(src), size, errors); + + case IArchString::kUTF32: + return doUTF32ToUTF8(reinterpret_cast(src), size, errors); + + default: + assert(0 && "unknown wide character encoding"); + return CString(); + } +} + +CString +CUnicode::doUCS2ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + CString dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode16(data, false)) { + case 0x0000feff: + data += 2; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 2; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 2, --n) { + UInt32 c = decode16(data, byteSwapped); + toUTF8(dst, c, errors); + } + + return dst; +} + +CString +CUnicode::doUCS4ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + CString dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode32(data, false)) { + case 0x0000feff: + data += 4; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 4; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 4, --n) { + UInt32 c = decode32(data, byteSwapped); + toUTF8(dst, c, errors); + } + + return dst; +} + +CString +CUnicode::doUTF16ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + CString dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode16(data, false)) { + case 0x0000feff: + data += 2; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 2; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 2, --n) { + UInt32 c = decode16(data, byteSwapped); + if (c < 0x0000d800 || c > 0x0000dfff) { + toUTF8(dst, c, errors); + } + else if (n == 1) { + // error -- missing second word + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + else if (c >= 0x0000d800 && c <= 0x0000dbff) { + UInt32 c2 = decode16(data, byteSwapped); + data += 2; + --n; + if (c2 < 0x0000dc00 || c2 > 0x0000dfff) { + // error -- [d800,dbff] not followed by [dc00,dfff] + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + else { + c = (((c - 0x0000d800) << 10) | (c2 - 0x0000dc00)) + 0x00010000; + toUTF8(dst, c, errors); + } + } + else { + // error -- [dc00,dfff] without leading [d800,dbff] + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + } + + return dst; +} + +CString +CUnicode::doUTF32ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + CString dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode32(data, false)) { + case 0x0000feff: + data += 4; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 4; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 4, --n) { + UInt32 c = decode32(data, byteSwapped); + if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + toUTF8(dst, c, errors); + } + + return dst; +} + +UInt32 +CUnicode::fromUTF8(const UInt8*& data, UInt32& n) +{ + assert(data != NULL); + assert(n != 0); + + // compute character encoding length, checking for overlong + // sequences (i.e. characters that don't use the shortest + // possible encoding). + UInt32 size; + if (data[0] < 0x80) { + // 0xxxxxxx + size = 1; + } + else if (data[0] < 0xc0) { + // 10xxxxxx -- in the middle of a multibyte character. counts + // as one invalid character. + --n; + ++data; + return s_invalid; + } + else if (data[0] < 0xe0) { + // 110xxxxx + size = 2; + } + else if (data[0] < 0xf0) { + // 1110xxxx + size = 3; + } + else if (data[0] < 0xf8) { + // 11110xxx + size = 4; + } + else if (data[0] < 0xfc) { + // 111110xx + size = 5; + } + else if (data[0] < 0xfe) { + // 1111110x + size = 6; + } + else { + // invalid sequence. dunno how many bytes to skip so skip one. + --n; + ++data; + return s_invalid; + } + + // make sure we have enough data + if (size > n) { + data += n; + n = 0; + return s_invalid; + } + + // extract character + UInt32 c; + switch (size) { + case 1: + c = static_cast(data[0]); + break; + + case 2: + c = ((static_cast(data[0]) & 0x1f) << 6) | + ((static_cast(data[1]) & 0x3f) ); + break; + + case 3: + c = ((static_cast(data[0]) & 0x0f) << 12) | + ((static_cast(data[1]) & 0x3f) << 6) | + ((static_cast(data[2]) & 0x3f) ); + break; + + case 4: + c = ((static_cast(data[0]) & 0x07) << 18) | + ((static_cast(data[1]) & 0x3f) << 12) | + ((static_cast(data[1]) & 0x3f) << 6) | + ((static_cast(data[1]) & 0x3f) ); + break; + + case 5: + c = ((static_cast(data[0]) & 0x03) << 24) | + ((static_cast(data[1]) & 0x3f) << 18) | + ((static_cast(data[1]) & 0x3f) << 12) | + ((static_cast(data[1]) & 0x3f) << 6) | + ((static_cast(data[1]) & 0x3f) ); + break; + + case 6: + c = ((static_cast(data[0]) & 0x01) << 30) | + ((static_cast(data[1]) & 0x3f) << 24) | + ((static_cast(data[1]) & 0x3f) << 18) | + ((static_cast(data[1]) & 0x3f) << 12) | + ((static_cast(data[1]) & 0x3f) << 6) | + ((static_cast(data[1]) & 0x3f) ); + break; + + default: + assert(0 && "invalid size"); + return s_invalid; + } + + // check that all bytes after the first have the pattern 10xxxxxx. + // truncated sequences are treated as a single malformed character. + bool truncated = false; + switch (size) { + case 6: + if ((data[5] & 0xc0) != 0x80) { + truncated = true; + size = 5; + } + // fall through + + case 5: + if ((data[4] & 0xc0) != 0x80) { + truncated = true; + size = 4; + } + // fall through + + case 4: + if ((data[3] & 0xc0) != 0x80) { + truncated = true; + size = 3; + } + // fall through + + case 3: + if ((data[2] & 0xc0) != 0x80) { + truncated = true; + size = 2; + } + // fall through + + case 2: + if ((data[1] & 0xc0) != 0x80) { + truncated = true; + size = 1; + } + } + + // update parameters + data += size; + n -= size; + + // invalid if sequence was truncated + if (truncated) { + return s_invalid; + } + + // check for characters that didn't use the smallest possible encoding + static UInt32 s_minChar[] = { + 0, + 0x00000000, + 0x00000080, + 0x00000800, + 0x00010000, + 0x00200000, + 0x04000000 + }; + if (c < s_minChar[size]) { + return s_invalid; + } + + // check for characters not in ISO-10646 + if (c >= 0x0000d800 && c <= 0x0000dfff) { + return s_invalid; + } + if (c >= 0x0000fffe && c <= 0x0000ffff) { + return s_invalid; + } + + return c; +} + +void +CUnicode::toUTF8(CString& dst, UInt32 c, bool* errors) +{ + UInt8 data[6]; + + // handle characters outside the valid range + if ((c >= 0x0000d800 && c <= 0x0000dfff) || c >= 0x80000000) { + setError(errors); + c = s_replacement; + } + + // convert to UTF-8 + if (c < 0x00000080) { + data[0] = static_cast(c); + dst.append(reinterpret_cast(data), 1); + } + else if (c < 0x00000800) { + data[0] = static_cast(((c >> 6) & 0x0000001f) + 0xc0); + data[1] = static_cast((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast(data), 2); + } + else if (c < 0x00010000) { + data[0] = static_cast(((c >> 12) & 0x0000000f) + 0xe0); + data[1] = static_cast(((c >> 6) & 0x0000003f) + 0x80); + data[2] = static_cast((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast(data), 3); + } + else if (c < 0x00200000) { + data[0] = static_cast(((c >> 18) & 0x00000007) + 0xf0); + data[1] = static_cast(((c >> 12) & 0x0000003f) + 0x80); + data[2] = static_cast(((c >> 6) & 0x0000003f) + 0x80); + data[3] = static_cast((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast(data), 4); + } + else if (c < 0x04000000) { + data[0] = static_cast(((c >> 24) & 0x00000003) + 0xf8); + data[1] = static_cast(((c >> 18) & 0x0000003f) + 0x80); + data[2] = static_cast(((c >> 12) & 0x0000003f) + 0x80); + data[3] = static_cast(((c >> 6) & 0x0000003f) + 0x80); + data[4] = static_cast((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast(data), 5); + } + else if (c < 0x80000000) { + data[0] = static_cast(((c >> 30) & 0x00000001) + 0xfc); + data[1] = static_cast(((c >> 24) & 0x0000003f) + 0x80); + data[2] = static_cast(((c >> 18) & 0x0000003f) + 0x80); + data[3] = static_cast(((c >> 12) & 0x0000003f) + 0x80); + data[4] = static_cast(((c >> 6) & 0x0000003f) + 0x80); + data[5] = static_cast((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast(data), 6); + } + else { + assert(0 && "character out of range"); + } +} diff --git a/lib/base/CUnicode.h b/lib/base/CUnicode.h new file mode 100644 index 00000000..85afbfd9 --- /dev/null +++ b/lib/base/CUnicode.h @@ -0,0 +1,143 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CUNICODE_H +#define CUNICODE_H + +#include "CString.h" +#include "BasicTypes.h" + +//! Unicode utility functions +/*! +This class provides functions for converting between various Unicode +encodings and the current locale encoding. +*/ +class CUnicode { +public: + //! @name accessors + //@{ + + //! Test UTF-8 string for validity + /*! + Returns true iff the string contains a valid sequence of UTF-8 + encoded characters. + */ + static bool isUTF8(const CString&); + + //! Convert from UTF-8 to UCS-2 encoding + /*! + Convert from UTF-8 to UCS-2. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UCS-2. + Decoding errors do not set *errors. + */ + static CString UTF8ToUCS2(const CString&, bool* errors = NULL); + + //! Convert from UTF-8 to UCS-4 encoding + /*! + Convert from UTF-8 to UCS-4. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UCS-4. + Decoding errors do not set *errors. + */ + static CString UTF8ToUCS4(const CString&, bool* errors = NULL); + + //! Convert from UTF-8 to UTF-16 encoding + /*! + Convert from UTF-8 to UTF-16. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UTF-16. + Decoding errors do not set *errors. + */ + static CString UTF8ToUTF16(const CString&, bool* errors = NULL); + + //! Convert from UTF-8 to UTF-32 encoding + /*! + Convert from UTF-8 to UTF-32. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UTF-32. + Decoding errors do not set *errors. + */ + static CString UTF8ToUTF32(const CString&, bool* errors = NULL); + + //! Convert from UTF-8 to the current locale encoding + /*! + Convert from UTF-8 to the current locale encoding. If errors is not + NULL then *errors is set to true iff any character could not be encoded. + Decoding errors do not set *errors. + */ + static CString UTF8ToText(const CString&, bool* errors = NULL); + + //! Convert from UCS-2 to UTF-8 + /*! + Convert from UCS-2 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static CString UCS2ToUTF8(const CString&, bool* errors = NULL); + + //! Convert from UCS-4 to UTF-8 + /*! + Convert from UCS-4 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static CString UCS4ToUTF8(const CString&, bool* errors = NULL); + + //! Convert from UTF-16 to UTF-8 + /*! + Convert from UTF-16 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static CString UTF16ToUTF8(const CString&, bool* errors = NULL); + + //! Convert from UTF-32 to UTF-8 + /*! + Convert from UTF-32 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static CString UTF32ToUTF8(const CString&, bool* errors = NULL); + + //! Convert from the current locale encoding to UTF-8 + /*! + Convert from the current locale encoding to UTF-8. If errors is not + NULL then *errors is set to true iff any character could not be decoded. + */ + static CString textToUTF8(const CString&, bool* errors = NULL); + + //@} + +private: + // convert UTF8 to wchar_t string (using whatever encoding is native + // to the platform). caller must delete[] the returned string. the + // string is *not* nul terminated; the length (in characters) is + // returned in size. + static wchar_t* UTF8ToWideChar(const CString&, + UInt32& size, bool* errors); + + // convert nul terminated wchar_t string (in platform's native + // encoding) to UTF8. + static CString wideCharToUTF8(const wchar_t*, + UInt32 size, bool* errors); + + // internal conversion to UTF8 + static CString doUCS2ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static CString doUCS4ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static CString doUTF16ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static CString doUTF32ToUTF8(const UInt8* src, UInt32 n, bool* errors); + + // convert characters to/from UTF8 + static UInt32 fromUTF8(const UInt8*& src, UInt32& size); + static void toUTF8(CString& dst, UInt32 c, bool* errors); + +private: + static UInt32 s_invalid; + static UInt32 s_replacement; +}; + +#endif diff --git a/lib/base/IEventJob.h b/lib/base/IEventJob.h new file mode 100644 index 00000000..01ef9a96 --- /dev/null +++ b/lib/base/IEventJob.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IEVENTJOB_H +#define IEVENTJOB_H + +#include "IInterface.h" + +class CEvent; + +//! Event handler interface +/*! +An event job is an interface for executing a event handler. +*/ +class IEventJob : public IInterface { +public: + //! Run the job + virtual void run(const CEvent&) = 0; +}; + +#endif diff --git a/lib/base/IEventQueue.cpp b/lib/base/IEventQueue.cpp new file mode 100644 index 00000000..a10621f2 --- /dev/null +++ b/lib/base/IEventQueue.cpp @@ -0,0 +1,43 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "IEventQueue.h" + +// +// IEventQueue +// + +static int g_systemTarget = 0; +IEventQueue* IEventQueue::s_instance = NULL; + +void* +IEventQueue::getSystemTarget() +{ + // any unique arbitrary pointer will do + return &g_systemTarget; +} + +IEventQueue* +IEventQueue::getInstance() +{ + assert(s_instance != NULL); + return s_instance; +} + +void +IEventQueue::setInstance(IEventQueue* instance) +{ + assert(s_instance == NULL || instance == NULL); + s_instance = instance; +} diff --git a/lib/base/IEventQueue.h b/lib/base/IEventQueue.h new file mode 100644 index 00000000..6f48f25c --- /dev/null +++ b/lib/base/IEventQueue.h @@ -0,0 +1,213 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IEVENTQUEUE_H +#define IEVENTQUEUE_H + +#include "IInterface.h" +#include "CEvent.h" + +#define EVENTQUEUE IEventQueue::getInstance() + +class IEventJob; +class IEventQueueBuffer; + +// Opaque type for timer info. This is defined by subclasses of +// IEventQueueBuffer. +class CEventQueueTimer; + +//! Event queue interface +/*! +An event queue provides a queue of CEvents. Clients can block waiting +on any event becoming available at the head of the queue and can place +new events at the end of the queue. Clients can also add and remove +timers which generate events periodically. +*/ +class IEventQueue : public IInterface { +public: + class CTimerEvent { + public: + CEventQueueTimer* m_timer; //!< The timer + UInt32 m_count; //!< Number of repeats + }; + + //! @name manipulators + //@{ + + //! Set the buffer + /*! + Replace the current event queue buffer. Any queued events are + discarded. The queue takes ownership of the buffer. + */ + virtual void adoptBuffer(IEventQueueBuffer*) = 0; + + //! Remove event from queue + /*! + Returns the next event on the queue into \p event. If no event is + available then blocks for up to \p timeout seconds, or forever if + \p timeout is negative. Returns true iff an event was available. + */ + virtual bool getEvent(CEvent& event, double timeout = -1.0) = 0; + + //! Dispatch an event + /*! + Looks up the dispatcher for the event's target and invokes it. + Returns true iff a dispatcher exists for the target. + */ + virtual bool dispatchEvent(const CEvent& event) = 0; + + //! Add event to queue + /*! + Adds \p event to the end of the queue. + */ + virtual void addEvent(const CEvent& event) = 0; + + //! Create a recurring timer + /*! + Creates and returns a timer. An event is returned after \p duration + seconds and the timer is reset to countdown again. When a timer event + is returned the data points to a \c CTimerEvent. The client must pass + the returned timer to \c deleteTimer() (whether or not the timer has + expired) to release the timer. The returned timer event uses the + given \p target. If \p target is NULL it uses the returned timer as + the target. + + Events for a single timer don't accumulate in the queue, even if the + client reading events can't keep up. Instead, the \c m_count member + of the \c CTimerEvent indicates how many events for the timer would + have been put on the queue since the last event for the timer was + removed (or since the timer was added). + */ + virtual CEventQueueTimer* + newTimer(double duration, void* target) = 0; + + //! Create a one-shot timer + /*! + Creates and returns a one-shot timer. An event is returned when + the timer expires and the timer is removed from further handling. + When a timer event is returned the data points to a \c CTimerEvent. + The \m c_count member of the \c CTimerEvent is always 1. The client + must pass the returned timer to \c deleteTimer() (whether or not the + timer has expired) to release the timer. The returned timer event + uses the given \p target. If \p target is NULL it uses the returned + timer as the target. + */ + virtual CEventQueueTimer* + newOneShotTimer(double duration, + void* target) = 0; + + //! Destroy a timer + /*! + Destroys a previously created timer. The timer is removed from the + queue and will not generate event, even if the timer has expired. + */ + virtual void deleteTimer(CEventQueueTimer*) = 0; + + //! Register an event handler for an event type + /*! + Registers an event handler for \p type and \p target. The \p handler + is adopted. Any existing handler for the type,target pair is deleted. + \c dispatchEvent() will invoke \p handler for any event for \p target + of type \p type. If no such handler exists it will use the handler + for \p target and type \p kUnknown if it exists. + */ + virtual void adoptHandler(CEvent::Type type, + void* target, IEventJob* handler) = 0; + + //! Unregister an event handler for an event type + /*! + Unregisters an event handler for the \p type, \p target pair and + deletes it. + */ + virtual void removeHandler(CEvent::Type type, void* target) = 0; + + //! Unregister all event handlers for an event target + /*! + Unregisters all event handlers for the \p target and deletes them. + */ + virtual void removeHandlers(void* target) = 0; + + //! Creates a new event type + /*! + Returns a unique event type id. + */ + virtual CEvent::Type + registerType(const char* name) = 0; + + //! Creates a new event type + /*! + If \p type contains \c kUnknown then it is set to a unique event + type id otherwise it is left alone. The final value of \p type + is returned. + */ + virtual CEvent::Type + registerTypeOnce(CEvent::Type& type, + const char* name) = 0; + + //@} + //! @name accessors + //@{ + + //! Test if queue is empty + /*! + Returns true iff the queue has no events in it, including timer + events. + */ + virtual bool isEmpty() const = 0; + + //! Get an event handler + /*! + Finds and returns the event handler for the \p type, \p target pair + if it exists, otherwise it returns NULL. + */ + virtual IEventJob* getHandler(CEvent::Type type, void* target) const = 0; + + //! Get name for event + /*! + Returns the name for the event \p type. This is primarily for + debugging. + */ + virtual const char* getTypeName(CEvent::Type type) = 0; + + //! Get the system event type target + /*! + Returns the target to use for dispatching \c CEvent::kSystem events. + */ + static void* getSystemTarget(); + + //! Get the singleton instance + /*! + Returns the singleton instance of the event queue + */ + static IEventQueue* getInstance(); + + //@} + +protected: + //! @name manipulators + //@{ + + //! Set the singleton instance + /*! + Sets the singleton instance of the event queue + */ + static void setInstance(IEventQueue*); + + //@} + +private: + static IEventQueue* s_instance; +}; + +#endif diff --git a/lib/base/IEventQueueBuffer.h b/lib/base/IEventQueueBuffer.h new file mode 100644 index 00000000..1aff51a6 --- /dev/null +++ b/lib/base/IEventQueueBuffer.h @@ -0,0 +1,94 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IEVENTQUEUEBUFFER_H +#define IEVENTQUEUEBUFFER_H + +#include "IInterface.h" +#include "BasicTypes.h" + +class CEvent; +class CEventQueueTimer; + +//! Event queue buffer interface +/*! +An event queue buffer provides a queue of events for an IEventQueue. +*/ +class IEventQueueBuffer : public IInterface { +public: + enum Type { + kNone, //!< No event is available + kSystem, //!< Event is a system event + kUser //!< Event is a user event + }; + + //! @name manipulators + //@{ + + //! Block waiting for an event + /*! + Wait for an event in the event queue buffer for up to \p timeout + seconds. + */ + virtual void waitForEvent(double timeout) = 0; + + //! Get the next event + /*! + Get the next event from the buffer. Return kNone if no event is + available. If a system event is next, return kSystem and fill in + event. The event data in a system event can point to a static + buffer (because CEvent::deleteData() will not attempt to delete + data in a kSystem event). Otherwise, return kUser and fill in + \p dataID with the value passed to \c addEvent(). + */ + virtual Type getEvent(CEvent& event, UInt32& dataID) = 0; + + //! Post an event + /*! + Add the given event to the end of the queue buffer. This is a user + event and \c getEvent() must be able to identify it as such and + return \p dataID. This method must cause \c waitForEvent() to + return at some future time if it's blocked waiting on an event. + */ + virtual bool addEvent(UInt32 dataID) = 0; + + //@} + //! @name accessors + //@{ + + //! Check if event queue buffer is empty + /*! + Return true iff the event queue buffer is empty. + */ + virtual bool isEmpty() const = 0; + + //! Create a timer object + /*! + Create and return a timer object. The object is opaque and is + used only by the buffer but it must be a valid object (i.e. + not NULL). + */ + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const = 0; + + //! Destroy a timer object + /*! + Destroy a timer object previously returned by \c newTimer(). + */ + virtual void deleteTimer(CEventQueueTimer*) const = 0; + + //@} +}; + +#endif diff --git a/lib/base/IJob.h b/lib/base/IJob.h new file mode 100644 index 00000000..30ef5f5a --- /dev/null +++ b/lib/base/IJob.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. + */ + +#ifndef IJOB_H +#define IJOB_H + +#include "IInterface.h" + +//! Job interface +/*! +A job is an interface for executing some function. +*/ +class IJob : public IInterface { +public: + //! Run the job + virtual void run() = 0; +}; + +#endif diff --git a/lib/base/ILogOutputter.h b/lib/base/ILogOutputter.h new file mode 100644 index 00000000..2be4dcc9 --- /dev/null +++ b/lib/base/ILogOutputter.h @@ -0,0 +1,81 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ILOGOUTPUTTER_H +#define ILOGOUTPUTTER_H + +#include "IInterface.h" +#include "CLog.h" + +//! Outputter interface +/*! +Type of outputter interface. The logger performs all output through +outputters. ILogOutputter overrides must not call any log functions +directly or indirectly. +*/ +class ILogOutputter : public IInterface { +public: + typedef CLog::ELevel ELevel; + + //! @name manipulators + //@{ + + //! Open the outputter + /*! + Opens the outputter for writing. Calling this method on an + already open outputter must have no effect. + */ + virtual void open(const char* title) = 0; + + //! Close the outputter + /*! + Close the outputter. Calling this method on an already closed + outputter must have no effect. + */ + virtual void close() = 0; + + //! Show the outputter + /*! + Causes the output to become visible. This generally only makes sense + for a logger in a graphical user interface. Other implementations + will do nothing. Iff \p showIfEmpty is \c false then the implementation + may optionally only show the log if it's not empty. + */ + virtual void show(bool showIfEmpty) = 0; + + //! Write a message with level + /*! + Writes \c message, which has the given \c level, to a log. + If this method returns true then CLog will stop passing the + message to all outputters in the outputter chain, otherwise + it continues. Most implementations should return true. + */ + 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; + + //@} +}; + +#endif diff --git a/lib/base/LogOutputters.cpp b/lib/base/LogOutputters.cpp new file mode 100644 index 00000000..f556d53f --- /dev/null +++ b/lib/base/LogOutputters.cpp @@ -0,0 +1,267 @@ +/* + * 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 "LogOutputters.h" +#include "CArch.h" + +// +// CStopLogOutputter +// + +CStopLogOutputter::CStopLogOutputter() +{ + // do nothing +} + +CStopLogOutputter::~CStopLogOutputter() +{ + // do nothing +} + +void +CStopLogOutputter::open(const char*) +{ + // do nothing +} + +void +CStopLogOutputter::close() +{ + // do nothing +} + +void +CStopLogOutputter::show(bool) +{ + // do nothing +} + +bool +CStopLogOutputter::write(ELevel, const char*) +{ + return false; +} + +const char* +CStopLogOutputter::getNewline() const +{ + return ""; +} + + +// +// CConsoleLogOutputter +// + +CConsoleLogOutputter::CConsoleLogOutputter() +{ + // do nothing +} + +CConsoleLogOutputter::~CConsoleLogOutputter() +{ + // do nothing +} + +void +CConsoleLogOutputter::open(const char* title) +{ + ARCH->openConsole(title); +} + +void +CConsoleLogOutputter::close() +{ + ARCH->closeConsole(); +} + +void +CConsoleLogOutputter::show(bool showIfEmpty) +{ + ARCH->showConsole(showIfEmpty); +} + +bool +CConsoleLogOutputter::write(ELevel, const char* msg) +{ + ARCH->writeConsole(msg); + return true; +} + +const char* +CConsoleLogOutputter::getNewline() const +{ + return ARCH->getNewlineForConsole(); +} + + +// +// CSystemLogOutputter +// + +CSystemLogOutputter::CSystemLogOutputter() +{ + // do nothing +} + +CSystemLogOutputter::~CSystemLogOutputter() +{ + // do nothing +} + +void +CSystemLogOutputter::open(const char* title) +{ + ARCH->openLog(title); +} + +void +CSystemLogOutputter::close() +{ + ARCH->closeLog(); +} + +void +CSystemLogOutputter::show(bool showIfEmpty) +{ + ARCH->showLog(showIfEmpty); +} + +bool +CSystemLogOutputter::write(ELevel level, const char* msg) +{ + IArchLog::ELevel archLogLevel; + switch (level) { + case CLog::kFATAL: + case CLog::kERROR: + archLogLevel = IArchLog::kERROR; + break; + + case CLog::kWARNING: + archLogLevel = IArchLog::kWARNING; + break; + + case CLog::kNOTE: + archLogLevel = IArchLog::kNOTE; + break; + + case CLog::kINFO: + archLogLevel = IArchLog::kINFO; + break; + + default: + archLogLevel = IArchLog::kDEBUG; + break; + + }; + ARCH->writeLog(archLogLevel, msg); + return true; +} + +const char* +CSystemLogOutputter::getNewline() const +{ + return ""; +} + + +// +// CSystemLogger +// + +CSystemLogger::CSystemLogger(const char* title, bool blockConsole) : + m_stop(NULL) +{ + // redirect log messages + if (blockConsole) { + m_stop = new CStopLogOutputter; + CLOG->insert(m_stop); + } + m_syslog = new CSystemLogOutputter; + m_syslog->open(title); + CLOG->insert(m_syslog); +} + +CSystemLogger::~CSystemLogger() +{ + CLOG->remove(m_syslog); + delete m_syslog; + if (m_stop != NULL) { + CLOG->remove(m_stop); + delete m_stop; + } +} + + +// +// CBufferedLogOutputter +// + +CBufferedLogOutputter::CBufferedLogOutputter(UInt32 maxBufferSize) : + m_maxBufferSize(maxBufferSize) +{ + // do nothing +} + +CBufferedLogOutputter::~CBufferedLogOutputter() +{ + // do nothing +} + +CBufferedLogOutputter::const_iterator +CBufferedLogOutputter::begin() const +{ + return m_buffer.begin(); +} + +CBufferedLogOutputter::const_iterator +CBufferedLogOutputter::end() const +{ + return m_buffer.end(); +} + +void +CBufferedLogOutputter::open(const char*) +{ + // do nothing +} + +void +CBufferedLogOutputter::close() +{ + // remove all elements from the buffer + m_buffer.clear(); +} + +void +CBufferedLogOutputter::show(bool) +{ + // do nothing +} + +bool +CBufferedLogOutputter::write(ELevel, const char* message) +{ + while (m_buffer.size() >= m_maxBufferSize) { + m_buffer.pop_front(); + } + m_buffer.push_back(CString(message)); + return true; +} + +const char* +CBufferedLogOutputter::getNewline() const +{ + return ""; +} diff --git a/lib/base/LogOutputters.h b/lib/base/LogOutputters.h new file mode 100644 index 00000000..a8087593 --- /dev/null +++ b/lib/base/LogOutputters.h @@ -0,0 +1,132 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef LOGOUTPUTTERS_H +#define LOGOUTPUTTERS_H + +#include "BasicTypes.h" +#include "ILogOutputter.h" +#include "CString.h" +#include "stddeque.h" + +//! Stop traversing log chain outputter +/*! +This outputter performs no output and returns false from \c write(), +causing the logger to stop traversing the outputter chain. Insert +this to prevent already inserted outputters from writing. +*/ +class CStopLogOutputter : public ILogOutputter { +public: + CStopLogOutputter(); + virtual ~CStopLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + 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 +/*! +This outputter writes output to the console. The level for each +message is ignored. +*/ +class CConsoleLogOutputter : public ILogOutputter { +public: + CConsoleLogOutputter(); + virtual ~CConsoleLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + 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 +/*! +This outputter writes output to the system log. +*/ +class CSystemLogOutputter : public ILogOutputter { +public: + CSystemLogOutputter(); + virtual ~CSystemLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + 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 +/*! +Creating an object of this type inserts a CStopLogOutputter followed +by a CSystemLogOutputter into CLog. The destructor removes those +outputters. Add one of these to any scope that needs to write to +the system log (only) and restore the old outputters when exiting +the scope. +*/ +class CSystemLogger { +public: + CSystemLogger(const char* title, bool blockConsole); + ~CSystemLogger(); + +private: + ILogOutputter* m_syslog; + ILogOutputter* m_stop; +}; + +//! Save log history +/*! +This outputter records the last N log messages. +*/ +class CBufferedLogOutputter : public ILogOutputter { +private: + typedef std::deque CBuffer; + +public: + typedef CBuffer::const_iterator const_iterator; + + CBufferedLogOutputter(UInt32 maxBufferSize); + virtual ~CBufferedLogOutputter(); + + //! @name accessors + //@{ + + //! Get start of buffer + const_iterator begin() const; + + //! Get end of buffer + const_iterator end() const; + + //@} + + // ILogOutputter overrides + virtual void open(const char* title); + 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; +}; + +#endif diff --git a/lib/base/Makefile.am b/lib/base/Makefile.am new file mode 100644 index 00000000..a53c8b33 --- /dev/null +++ b/lib/base/Makefile.am @@ -0,0 +1,62 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libbase.a +libbase_a_SOURCES = \ + CEvent.cpp \ + CEventQueue.cpp \ + CFunctionEventJob.cpp \ + CFunctionJob.cpp \ + CLog.cpp \ + CSimpleEventQueueBuffer.cpp \ + CStopwatch.cpp \ + CStringUtil.cpp \ + CUnicode.cpp \ + IEventQueue.cpp \ + LogOutputters.cpp \ + XBase.cpp \ + CEvent.h \ + CEventQueue.h \ + CFunctionEventJob.h \ + CFunctionJob.h \ + CLog.h \ + CPriorityQueue.h \ + CSimpleEventQueueBuffer.h \ + CStopwatch.h \ + CString.h \ + CStringUtil.h \ + CUnicode.h \ + IEventJob.h \ + IEventQueue.h \ + IEventQueueBuffer.h \ + IJob.h \ + ILogOutputter.h \ + LogOutputters.h \ + TMethodEventJob.h \ + TMethodJob.h \ + XBase.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + $(NULL) diff --git a/lib/base/Makefile.win b/lib/base/Makefile.win new file mode 100644 index 00000000..a386da17 --- /dev/null +++ b/lib/base/Makefile.win @@ -0,0 +1,77 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +LIB_BASE_SRC = lib\base +LIB_BASE_DST = $(BUILD_DST)\$(LIB_BASE_SRC) +LIB_BASE_LIB = "$(LIB_BASE_DST)\base.lib" +LIB_BASE_CPP = \ + "CEvent.cpp" \ + "CEventQueue.cpp" \ + "CFunctionEventJob.cpp" \ + "CFunctionJob.cpp" \ + "CLog.cpp" \ + "CSimpleEventQueueBuffer.cpp" \ + "CStopwatch.cpp" \ + "CStringUtil.cpp" \ + "CUnicode.cpp" \ + "IEventQueue.cpp" \ + "LogOutputters.cpp" \ + "XBase.cpp" \ + $(NULL) +LIB_BASE_OBJ = \ + "$(LIB_BASE_DST)\CEvent.obj" \ + "$(LIB_BASE_DST)\CEventQueue.obj" \ + "$(LIB_BASE_DST)\CFunctionEventJob.obj" \ + "$(LIB_BASE_DST)\CFunctionJob.obj" \ + "$(LIB_BASE_DST)\CLog.obj" \ + "$(LIB_BASE_DST)\CSimpleEventQueueBuffer.obj" \ + "$(LIB_BASE_DST)\CStopwatch.obj" \ + "$(LIB_BASE_DST)\CStringUtil.obj" \ + "$(LIB_BASE_DST)\CUnicode.obj" \ + "$(LIB_BASE_DST)\IEventQueue.obj" \ + "$(LIB_BASE_DST)\LogOutputters.obj" \ + "$(LIB_BASE_DST)\XBase.obj" \ + $(NULL) +LIB_BASE_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_BASE_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_BASE_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_BASE_LIB) + +# Dependency rules +$(LIB_BASE_OBJ): $(AUTODEP) +!if EXIST($(LIB_BASE_DST)\deps.mak) +!include $(LIB_BASE_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_BASE_SRC)\}.cpp{$(LIB_BASE_DST)\}.obj:: +!else +{$(LIB_BASE_SRC)\}.cpp{$(LIB_BASE_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_BASE_SRC) + -@$(MKDIR) $(LIB_BASE_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_BASE_INC) \ + /Fo$(LIB_BASE_DST)\ \ + /Fd$(LIB_BASE_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_BASE_SRC) $(LIB_BASE_DST) +$(LIB_BASE_LIB): $(LIB_BASE_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_BASE_SRC) $(LIB_BASE_DST) $(**:.obj=.d) diff --git a/lib/base/TMethodEventJob.h b/lib/base/TMethodEventJob.h new file mode 100644 index 00000000..15826be0 --- /dev/null +++ b/lib/base/TMethodEventJob.h @@ -0,0 +1,70 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMETHODEVENTJOB_H +#define CMETHODEVENTJOB_H + +#include "IEventJob.h" + +//! Use a member function as an event job +/*! +An event job class that invokes a member function. +*/ +template +class TMethodEventJob : public IEventJob { +public: + //! run(event) invokes \c object->method(event, arg) + TMethodEventJob(T* object, + void (T::*method)(const CEvent&, void*), + void* arg = NULL); + virtual ~TMethodEventJob(); + + // IJob overrides + virtual void run(const CEvent&); + +private: + T* m_object; + void (T::*m_method)(const CEvent&, void*); + void* m_arg; +}; + +template +inline +TMethodEventJob::TMethodEventJob(T* object, + void (T::*method)(const CEvent&, void*), void* arg) : + m_object(object), + m_method(method), + m_arg(arg) +{ + // do nothing +} + +template +inline +TMethodEventJob::~TMethodEventJob() +{ + // do nothing +} + +template +inline +void +TMethodEventJob::run(const CEvent& event) +{ + if (m_object != NULL) { + (m_object->*m_method)(event, m_arg); + } +} + +#endif diff --git a/lib/base/TMethodJob.h b/lib/base/TMethodJob.h new file mode 100644 index 00000000..54cd936b --- /dev/null +++ b/lib/base/TMethodJob.h @@ -0,0 +1,67 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMETHODJOB_H +#define CMETHODJOB_H + +#include "IJob.h" + +//! Use a function as a job +/*! +A job class that invokes a member function. +*/ +template +class TMethodJob : public IJob { +public: + //! run() invokes \c object->method(arg) + TMethodJob(T* object, void (T::*method)(void*), void* arg = NULL); + virtual ~TMethodJob(); + + // IJob overrides + virtual void run(); + +private: + T* m_object; + void (T::*m_method)(void*); + void* m_arg; +}; + +template +inline +TMethodJob::TMethodJob(T* object, void (T::*method)(void*), void* arg) : + m_object(object), + m_method(method), + m_arg(arg) +{ + // do nothing +} + +template +inline +TMethodJob::~TMethodJob() +{ + // do nothing +} + +template +inline +void +TMethodJob::run() +{ + if (m_object != NULL) { + (m_object->*m_method)(m_arg); + } +} + +#endif diff --git a/lib/base/XBase.cpp b/lib/base/XBase.cpp new file mode 100644 index 00000000..022be306 --- /dev/null +++ b/lib/base/XBase.cpp @@ -0,0 +1,69 @@ +/* + * 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 "XBase.h" +#include "CStringUtil.h" +#include +#include + +// +// XBase +// + +XBase::XBase() : + m_what() +{ + // do nothing +} + +XBase::XBase(const CString& msg) : + m_what(msg) +{ + // do nothing +} + +XBase::~XBase() +{ + // do nothing +} + +const char* +XBase::what() const +{ + if (m_what.empty()) { + m_what = getWhat(); + } + return m_what.c_str(); +} + +CString +XBase::format(const char* /*id*/, const char* fmt, ...) const throw() +{ + // FIXME -- lookup message string using id as an index. set + // fmt to that string if it exists. + + // format + CString result; + va_list args; + va_start(args, fmt); + try { + result = CStringUtil::vformat(fmt, args); + } + catch (...) { + // ignore + } + va_end(args); + + return result; +} diff --git a/lib/base/XBase.h b/lib/base/XBase.h new file mode 100644 index 00000000..e9161d70 --- /dev/null +++ b/lib/base/XBase.h @@ -0,0 +1,121 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XBASE_H +#define XBASE_H + +#include "CString.h" + +//! Exception base class +/*! +This is the base class of most exception types. +*/ +class XBase { +public: + //! Use getWhat() as the result of what() + XBase(); + //! Use \c msg as the result of what() + XBase(const CString& msg); + virtual ~XBase(); + + //! Reason for exception + virtual const char* what() const; + +protected: + //! Get a human readable string describing the exception + virtual CString getWhat() const throw() = 0; + + //! Format a string + /*! + Looks up a message format using \c id, using \c defaultFormat if + no format can be found, then replaces positional parameters in + the format string and returns the result. + */ + virtual CString format(const char* id, + const char* defaultFormat, ...) const throw(); + +private: + mutable CString m_what; +}; + +/*! +\def XBASE_SUBCLASS +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const CString&. getWhat() is not +declared. +*/ +#define XBASE_SUBCLASS(name_, super_) \ +class name_ : public super_ { \ +public: \ + name_() : super_() { } \ + name_(const CString& msg) : super_(msg) { } \ +} + +/*! +\def XBASE_SUBCLASS +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const CString&. getWhat() must be +implemented. +*/ +#define XBASE_SUBCLASS_WHAT(name_, super_) \ +class name_ : public super_ { \ +public: \ + name_() : super_() { } \ + name_(const CString& msg) : super_(msg) { } \ + \ +protected: \ + virtual CString getWhat() const throw(); \ +} + +/*! +\def XBASE_SUBCLASS_FORMAT +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const CString&. what() is overridden +to call getWhat() when first called; getWhat() can format the +error message and can call what() to get the message passed to the +c'tor. +*/ +#define XBASE_SUBCLASS_FORMAT(name_, super_) \ +class name_ : public super_ { \ +private: \ + enum EState { kFirst, kFormat, kDone }; \ + \ +public: \ + name_() : super_(), m_state(kDone) { } \ + name_(const CString& msg) : super_(msg), m_state(kFirst) { } \ + \ + virtual const char* what() const \ + { \ + if (m_state == kFirst) { \ + m_state = kFormat; \ + m_formatted = getWhat(); \ + m_state = kDone; \ + } \ + if (m_state == kDone) { \ + return m_formatted.c_str(); \ + } \ + else { \ + return super_::what(); \ + } \ + } \ + \ +protected: \ + virtual CString getWhat() const throw(); \ + \ +private: \ + mutable EState m_state; \ + mutable std::string m_formatted; \ +} + +#endif diff --git a/lib/client/CClient.cpp b/lib/client/CClient.cpp new file mode 100644 index 00000000..1e450a17 --- /dev/null +++ b/lib/client/CClient.cpp @@ -0,0 +1,662 @@ +/* + * 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 "CClient.h" +#include "CServerProxy.h" +#include "CScreen.h" +#include "CClipboard.h" +#include "CPacketStreamFilter.h" +#include "CProtocolUtil.h" +#include "ProtocolTypes.h" +#include "XSynergy.h" +#include "IDataSocket.h" +#include "ISocketFactory.h" +#include "IStreamFilterFactory.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CClient +// + +CEvent::Type CClient::s_connectedEvent = CEvent::kUnknown; +CEvent::Type CClient::s_connectionFailedEvent = CEvent::kUnknown; +CEvent::Type CClient::s_disconnectedEvent = CEvent::kUnknown; + +CClient::CClient(const CString& name, const CNetworkAddress& address, + ISocketFactory* socketFactory, + IStreamFilterFactory* streamFilterFactory, + CScreen* screen) : + m_name(name), + m_serverAddress(address), + m_socketFactory(socketFactory), + m_streamFilterFactory(streamFilterFactory), + m_screen(screen), + m_stream(NULL), + m_timer(NULL), + m_server(NULL), + m_ready(false), + m_active(false), + m_suspended(false), + m_connectOnResume(false) +{ + assert(m_socketFactory != NULL); + assert(m_screen != NULL); + + // register suspend/resume event handlers + EVENTQUEUE->adoptHandler(IScreen::getSuspendEvent(), + getEventTarget(), + new TMethodEventJob(this, + &CClient::handleSuspend)); + EVENTQUEUE->adoptHandler(IScreen::getResumeEvent(), + getEventTarget(), + new TMethodEventJob(this, + &CClient::handleResume)); +} + +CClient::~CClient() +{ + EVENTQUEUE->removeHandler(IScreen::getSuspendEvent(), + getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getResumeEvent(), + getEventTarget()); + + cleanupTimer(); + cleanupScreen(); + cleanupConnecting(); + cleanupConnection(); + delete m_socketFactory; + delete m_streamFilterFactory; +} + +void +CClient::connect() +{ + if (m_stream != NULL) { + return; + } + if (m_suspended) { + m_connectOnResume = true; + return; + } + + try { + // resolve the server hostname. do this every time we connect + // in case we couldn't resolve the address earlier or the address + // has changed (which can happen frequently if this is a laptop + // being shuttled between various networks). patch by Brent + // Priddy. + m_serverAddress.resolve(); + + // create the socket + IDataSocket* socket = m_socketFactory->create(); + + // filter socket messages, including a packetizing filter + m_stream = socket; + if (m_streamFilterFactory != NULL) { + m_stream = m_streamFilterFactory->create(m_stream, true); + } + m_stream = new CPacketStreamFilter(m_stream, true); + + // connect + LOG((CLOG_DEBUG1 "connecting to server")); + setupConnecting(); + setupTimer(); + socket->connect(m_serverAddress); + } + catch (XBase& e) { + cleanupTimer(); + cleanupConnecting(); + delete m_stream; + m_stream = NULL; + LOG((CLOG_DEBUG1 "connection failed")); + sendConnectionFailedEvent(e.what()); + return; + } +} + +void +CClient::disconnect(const char* msg) +{ + m_connectOnResume = false; + cleanupTimer(); + cleanupScreen(); + cleanupConnecting(); + cleanupConnection(); + if (msg != NULL) { + sendConnectionFailedEvent(msg); + } + else { + sendEvent(getDisconnectedEvent(), NULL); + } +} + +void +CClient::handshakeComplete() +{ + m_ready = true; + m_screen->enable(); + sendEvent(getConnectedEvent(), NULL); +} + +bool +CClient::isConnected() const +{ + return (m_server != NULL); +} + +bool +CClient::isConnecting() const +{ + return (m_timer != NULL); +} + +CNetworkAddress +CClient::getServerAddress() const +{ + return m_serverAddress; +} + +CEvent::Type +CClient::getConnectedEvent() +{ + return CEvent::registerTypeOnce(s_connectedEvent, + "CClient::connected"); +} + +CEvent::Type +CClient::getConnectionFailedEvent() +{ + return CEvent::registerTypeOnce(s_connectionFailedEvent, + "CClient::failed"); +} + +CEvent::Type +CClient::getDisconnectedEvent() +{ + return CEvent::registerTypeOnce(s_disconnectedEvent, + "CClient::disconnected"); +} + +void* +CClient::getEventTarget() const +{ + return m_screen->getEventTarget(); +} + +bool +CClient::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + return m_screen->getClipboard(id, clipboard); +} + +void +CClient::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + m_screen->getShape(x, y, w, h); +} + +void +CClient::getCursorPos(SInt32& x, SInt32& y) const +{ + m_screen->getCursorPos(x, y); +} + +void +CClient::enter(SInt32 xAbs, SInt32 yAbs, UInt32, KeyModifierMask mask, bool) +{ + m_active = true; + m_screen->mouseMove(xAbs, yAbs); + m_screen->enter(mask); +} + +bool +CClient::leave() +{ + m_screen->leave(); + + m_active = false; + + // send clipboards that we own and that have changed + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_ownClipboard[id]) { + sendClipboard(id); + } + } + + return true; +} + +void +CClient::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + m_screen->setClipboard(id, clipboard); + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; +} + +void +CClient::grabClipboard(ClipboardID id) +{ + m_screen->grabClipboard(id); + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; +} + +void +CClient::setClipboardDirty(ClipboardID, bool) +{ + assert(0 && "shouldn't be called"); +} + +void +CClient::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) +{ + m_screen->keyDown(id, mask, button); +} + +void +CClient::keyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + m_screen->keyRepeat(id, mask, count, button); +} + +void +CClient::keyUp(KeyID id, KeyModifierMask mask, KeyButton button) +{ + m_screen->keyUp(id, mask, button); +} + +void +CClient::mouseDown(ButtonID id) +{ + m_screen->mouseDown(id); +} + +void +CClient::mouseUp(ButtonID id) +{ + m_screen->mouseUp(id); +} + +void +CClient::mouseMove(SInt32 x, SInt32 y) +{ + m_screen->mouseMove(x, y); +} + +void +CClient::mouseRelativeMove(SInt32 dx, SInt32 dy) +{ + m_screen->mouseRelativeMove(dx, dy); +} + +void +CClient::mouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + m_screen->mouseWheel(xDelta, yDelta); +} + +void +CClient::screensaver(bool activate) +{ + m_screen->screensaver(activate); +} + +void +CClient::resetOptions() +{ + m_screen->resetOptions(); +} + +void +CClient::setOptions(const COptionsList& options) +{ + m_screen->setOptions(options); +} + +CString +CClient::getName() const +{ + return m_name; +} + +void +CClient::sendClipboard(ClipboardID id) +{ + // note -- m_mutex must be locked on entry + assert(m_screen != NULL); + assert(m_server != NULL); + + // get clipboard data. set the clipboard time to the last + // clipboard time before getting the data from the screen + // as the screen may detect an unchanged clipboard and + // avoid copying the data. + CClipboard clipboard; + if (clipboard.open(m_timeClipboard[id])) { + clipboard.close(); + } + m_screen->getClipboard(id, &clipboard); + + // check time + if (m_timeClipboard[id] == 0 || + clipboard.getTime() != m_timeClipboard[id]) { + // save new time + m_timeClipboard[id] = clipboard.getTime(); + + // marshall the data + CString data = clipboard.marshall(); + + // save and send data if different or not yet sent + if (!m_sentClipboard[id] || data != m_dataClipboard[id]) { + m_sentClipboard[id] = true; + m_dataClipboard[id] = data; + m_server->onClipboardChanged(id, &clipboard); + } + } +} + +void +CClient::sendEvent(CEvent::Type type, void* data) +{ + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), 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); +} + +void +CClient::setupConnecting() +{ + assert(m_stream != NULL); + + EVENTQUEUE->adoptHandler(IDataSocket::getConnectedEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleConnected)); + EVENTQUEUE->adoptHandler(IDataSocket::getConnectionFailedEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleConnectionFailed)); +} + +void +CClient::setupConnection() +{ + assert(m_stream != NULL); + + EVENTQUEUE->adoptHandler(ISocket::getDisconnectedEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleDisconnected)); + EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleHello)); + EVENTQUEUE->adoptHandler(IStream::getOutputErrorEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleOutputError)); + EVENTQUEUE->adoptHandler(IStream::getInputShutdownEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleDisconnected)); + EVENTQUEUE->adoptHandler(IStream::getOutputShutdownEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleDisconnected)); +} + +void +CClient::setupScreen() +{ + assert(m_server == NULL); + + m_ready = false; + m_server = new CServerProxy(this, m_stream); + EVENTQUEUE->adoptHandler(IScreen::getShapeChangedEvent(), + getEventTarget(), + new TMethodEventJob(this, + &CClient::handleShapeChanged)); + EVENTQUEUE->adoptHandler(IScreen::getClipboardGrabbedEvent(), + getEventTarget(), + new TMethodEventJob(this, + &CClient::handleClipboardGrabbed)); +} + +void +CClient::setupTimer() +{ + assert(m_timer == NULL); + + m_timer = EVENTQUEUE->newOneShotTimer(15.0, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_timer, + new TMethodEventJob(this, + &CClient::handleConnectTimeout)); +} + +void +CClient::cleanupConnecting() +{ + if (m_stream != NULL) { + EVENTQUEUE->removeHandler(IDataSocket::getConnectedEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IDataSocket::getConnectionFailedEvent(), + m_stream->getEventTarget()); + } +} + +void +CClient::cleanupConnection() +{ + if (m_stream != NULL) { + EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputErrorEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getInputShutdownEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputShutdownEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(ISocket::getDisconnectedEvent(), + m_stream->getEventTarget()); + delete m_stream; + m_stream = NULL; + } +} + +void +CClient::cleanupScreen() +{ + if (m_server != NULL) { + if (m_ready) { + m_screen->disable(); + m_ready = false; + } + EVENTQUEUE->removeHandler(IScreen::getShapeChangedEvent(), + getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getClipboardGrabbedEvent(), + getEventTarget()); + delete m_server; + m_server = NULL; + } +} + +void +CClient::cleanupTimer() +{ + if (m_timer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_timer); + EVENTQUEUE->deleteTimer(m_timer); + m_timer = NULL; + } +} + +void +CClient::handleConnected(const CEvent&, void*) +{ + LOG((CLOG_DEBUG1 "connected; wait for hello")); + cleanupConnecting(); + setupConnection(); + + // reset clipboard state + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; + m_timeClipboard[id] = 0; + } +} + +void +CClient::handleConnectionFailed(const CEvent& event, void*) +{ + IDataSocket::CConnectionFailedInfo* info = + reinterpret_cast(event.getData()); + + cleanupTimer(); + cleanupConnecting(); + delete m_stream; + m_stream = NULL; + LOG((CLOG_DEBUG1 "connection failed")); + sendConnectionFailedEvent(info->m_what); +} + +void +CClient::handleConnectTimeout(const CEvent&, void*) +{ + cleanupTimer(); + cleanupConnecting(); + cleanupConnection(); + delete m_stream; + m_stream = NULL; + LOG((CLOG_DEBUG1 "connection timed out")); + sendConnectionFailedEvent("Timed out"); +} + +void +CClient::handleOutputError(const CEvent&, void*) +{ + cleanupTimer(); + cleanupScreen(); + cleanupConnection(); + LOG((CLOG_WARN "error sending to server")); + sendEvent(getDisconnectedEvent(), NULL); +} + +void +CClient::handleDisconnected(const CEvent&, void*) +{ + cleanupTimer(); + cleanupScreen(); + cleanupConnection(); + LOG((CLOG_DEBUG1 "disconnected")); + sendEvent(getDisconnectedEvent(), NULL); +} + +void +CClient::handleShapeChanged(const CEvent&, void*) +{ + LOG((CLOG_DEBUG "resolution changed")); + m_server->onInfoChanged(); +} + +void +CClient::handleClipboardGrabbed(const CEvent& event, void*) +{ + const IScreen::CClipboardInfo* info = + reinterpret_cast(event.getData()); + + // grab ownership + m_server->onGrabClipboard(info->m_id); + + // we now own the clipboard and it has not been sent to the server + m_ownClipboard[info->m_id] = true; + m_sentClipboard[info->m_id] = false; + m_timeClipboard[info->m_id] = 0; + + // if we're not the active screen then send the clipboard now, + // otherwise we'll wait until we leave. + if (!m_active) { + sendClipboard(info->m_id); + } +} + +void +CClient::handleHello(const CEvent&, void*) +{ + SInt16 major, minor; + if (!CProtocolUtil::readf(m_stream, kMsgHello, &major, &minor)) { + sendConnectionFailedEvent("Protocol error from server"); + cleanupTimer(); + cleanupConnection(); + return; + } + + // check versions + LOG((CLOG_DEBUG1 "got hello version %d.%d", major, minor)); + if (major < kProtocolMajorVersion || + (major == kProtocolMajorVersion && minor < kProtocolMinorVersion)) { + sendConnectionFailedEvent(XIncompatibleClient(major, minor).what()); + cleanupTimer(); + cleanupConnection(); + return; + } + + // say hello back + LOG((CLOG_DEBUG1 "say hello version %d.%d", kProtocolMajorVersion, kProtocolMinorVersion)); + CProtocolUtil::writef(m_stream, kMsgHelloBack, + kProtocolMajorVersion, + kProtocolMinorVersion, &m_name); + + // now connected but waiting to complete handshake + setupScreen(); + cleanupTimer(); + + // make sure we process any remaining messages later. we won't + // receive another event for already pending messages so we fake + // one. + if (m_stream->isReady()) { + EVENTQUEUE->addEvent(CEvent(IStream::getInputReadyEvent(), + m_stream->getEventTarget())); + } +} + +void +CClient::handleSuspend(const CEvent&, void*) +{ + LOG((CLOG_INFO "suspend")); + m_suspended = true; + bool wasConnected = isConnected(); + disconnect(NULL); + m_connectOnResume = wasConnected; +} + +void +CClient::handleResume(const CEvent&, void*) +{ + LOG((CLOG_INFO "resume")); + m_suspended = false; + if (m_connectOnResume) { + m_connectOnResume = false; + connect(); + } +} diff --git a/lib/client/CClient.h b/lib/client/CClient.h new file mode 100644 index 00000000..4bd11c66 --- /dev/null +++ b/lib/client/CClient.h @@ -0,0 +1,198 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENT_H +#define CCLIENT_H + +#include "IClient.h" +#include "IClipboard.h" +#include "CNetworkAddress.h" + +class CEventQueueTimer; +class CScreen; +class CServerProxy; +class IDataSocket; +class ISocketFactory; +class IStream; +class IStreamFilterFactory; + +//! Synergy client +/*! +This class implements the top-level client algorithms for synergy. +*/ +class CClient : public IClient { +public: + class CFailInfo { + public: + bool m_retry; + char m_what[1]; + }; + + /*! + This client will attempt to connect to the server using \p name + as its name and \p address as the server's address and \p factory + to create the socket. \p screen is the local screen. + */ + CClient(const CString& name, const CNetworkAddress& address, + ISocketFactory* socketFactory, + IStreamFilterFactory* streamFilterFactory, + CScreen* screen); + ~CClient(); + + //! @name manipulators + //@{ + + //! Connect to server + /*! + Starts an attempt to connect to the server. This is ignored if + the client is trying to connect or is already connected. + */ + void connect(); + + //! Disconnect + /*! + Disconnects from the server with an optional error message. + */ + void disconnect(const char* msg); + + //! Notify of handshake complete + /*! + Notifies the client that the connection handshake has completed. + */ + void handshakeComplete(); + + //@} + //! @name accessors + //@{ + + //! Test if connected + /*! + Returns true iff the client is successfully connected to the server. + */ + bool isConnected() const; + + //! Test if connecting + /*! + Returns true iff the client is currently attempting to connect to + the server. + */ + bool isConnecting() const; + + //! Get address of server + /*! + Returns the address of the server the client is connected (or wants + to connect) to. + */ + CNetworkAddress getServerAddress() const; + + //! Get connected event type + /*! + Returns the connected event type. This is sent when the client has + successfully connected to the server. + */ + static CEvent::Type getConnectedEvent(); + + //! Get connection failed event type + /*! + Returns the connection failed event type. This is sent when the + server fails for some reason. The event data is a CFailInfo*. + */ + static CEvent::Type getConnectionFailedEvent(); + + //! Get disconnected event type + /*! + Returns the disconnected event type. This is sent when the client + has disconnected from the server (and only after having successfully + connected). + */ + static CEvent::Type getDisconnectedEvent(); + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver); + virtual bool leave(); + virtual void setClipboard(ClipboardID, const IClipboard*); + virtual void grabClipboard(ClipboardID); + virtual void setClipboardDirty(ClipboardID, bool); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); + virtual void mouseDown(ButtonID); + virtual void mouseUp(ButtonID); + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs); + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + virtual CString getName() const; + +private: + void sendClipboard(ClipboardID); + void sendEvent(CEvent::Type, void*); + void sendConnectionFailedEvent(const char* msg); + void setupConnecting(); + void setupConnection(); + void setupScreen(); + void setupTimer(); + void cleanupConnecting(); + void cleanupConnection(); + void cleanupScreen(); + void cleanupTimer(); + void handleConnected(const CEvent&, void*); + void handleConnectionFailed(const CEvent&, void*); + void handleConnectTimeout(const CEvent&, void*); + void handleOutputError(const CEvent&, void*); + void handleDisconnected(const CEvent&, void*); + void handleShapeChanged(const CEvent&, void*); + void handleClipboardGrabbed(const CEvent&, void*); + void handleHello(const CEvent&, void*); + void handleSuspend(const CEvent& event, void*); + void handleResume(const CEvent& event, void*); + +private: + CString m_name; + CNetworkAddress m_serverAddress; + ISocketFactory* m_socketFactory; + IStreamFilterFactory* m_streamFilterFactory; + CScreen* m_screen; + IStream* m_stream; + CEventQueueTimer* m_timer; + CServerProxy* m_server; + bool m_ready; + bool m_active; + bool m_suspended; + bool m_connectOnResume; + bool m_ownClipboard[kClipboardEnd]; + bool m_sentClipboard[kClipboardEnd]; + IClipboard::Time m_timeClipboard[kClipboardEnd]; + CString m_dataClipboard[kClipboardEnd]; + + static CEvent::Type s_connectedEvent; + static CEvent::Type s_connectionFailedEvent; + static CEvent::Type s_disconnectedEvent; +}; + +#endif diff --git a/lib/client/CServerProxy.cpp b/lib/client/CServerProxy.cpp new file mode 100644 index 00000000..acef43b2 --- /dev/null +++ b/lib/client/CServerProxy.cpp @@ -0,0 +1,816 @@ +/* + * 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 "CServerProxy.h" +#include "CClient.h" +#include "CClipboard.h" +#include "CProtocolUtil.h" +#include "OptionTypes.h" +#include "ProtocolTypes.h" +#include "IStream.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include "XBase.h" +#include + +// +// CServerProxy +// + +CServerProxy::CServerProxy(CClient* client, IStream* stream) : + m_client(client), + m_stream(stream), + m_seqNum(0), + m_compressMouse(false), + m_compressMouseRelative(false), + m_xMouse(0), + m_yMouse(0), + m_dxMouse(0), + m_dyMouse(0), + m_ignoreMouse(false), + m_keepAliveAlarm(0.0), + m_keepAliveAlarmTimer(NULL), + m_parser(&CServerProxy::parseHandshakeMessage) +{ + assert(m_client != NULL); + assert(m_stream != NULL); + + // initialize modifier translation table + for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) + m_modifierTranslationTable[id] = id; + + // handle data on stream + EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CServerProxy::handleData)); + + // send heartbeat + setKeepAliveRate(kKeepAliveRate); +} + +CServerProxy::~CServerProxy() +{ + setKeepAliveRate(-1.0); + EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget()); +} + +void +CServerProxy::resetKeepAliveAlarm() +{ + if (m_keepAliveAlarmTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_keepAliveAlarmTimer); + EVENTQUEUE->deleteTimer(m_keepAliveAlarmTimer); + m_keepAliveAlarmTimer = NULL; + } + if (m_keepAliveAlarm > 0.0) { + m_keepAliveAlarmTimer = + EVENTQUEUE->newOneShotTimer(m_keepAliveAlarm, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_keepAliveAlarmTimer, + new TMethodEventJob(this, + &CServerProxy::handleKeepAliveAlarm)); + } +} + +void +CServerProxy::setKeepAliveRate(double rate) +{ + m_keepAliveAlarm = rate * kKeepAlivesUntilDeath; + resetKeepAliveAlarm(); +} + +void +CServerProxy::handleData(const CEvent&, void*) +{ + // handle messages until there are no more. first read message code. + UInt8 code[4]; + UInt32 n = m_stream->read(code, 4); + while (n != 0) { + // verify we got an entire code + if (n != 4) { + LOG((CLOG_ERR "incomplete message from server: %d bytes", n)); + m_client->disconnect("incomplete message from server"); + return; + } + + // parse message + LOG((CLOG_DEBUG2 "msg from server: %c%c%c%c", code[0], code[1], code[2], code[3])); + switch ((this->*m_parser)(code)) { + case kOkay: + break; + + case kUnknown: + LOG((CLOG_ERR "invalid message from server: %c%c%c%c", code[0], code[1], code[2], code[3])); + m_client->disconnect("invalid message from server"); + return; + + case kDisconnect: + return; + } + + // next message + n = m_stream->read(code, 4); + } + + flushCompressedMouse(); +} + +CServerProxy::EResult +CServerProxy::parseHandshakeMessage(const UInt8* code) +{ + if (memcmp(code, kMsgQInfo, 4) == 0) { + queryInfo(); + } + + else if (memcmp(code, kMsgCInfoAck, 4) == 0) { + infoAcknowledgment(); + } + + else if (memcmp(code, kMsgDSetOptions, 4) == 0) { + setOptions(); + + // handshake is complete + m_parser = &CServerProxy::parseMessage; + m_client->handshakeComplete(); + } + + else if (memcmp(code, kMsgCResetOptions, 4) == 0) { + resetOptions(); + } + + else if (memcmp(code, kMsgCKeepAlive, 4) == 0) { + // echo keep alives and reset alarm + CProtocolUtil::writef(m_stream, kMsgCKeepAlive); + resetKeepAliveAlarm(); + } + + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // accept and discard no-op + } + + else if (memcmp(code, kMsgCClose, 4) == 0) { + // server wants us to hangup + LOG((CLOG_DEBUG1 "recv close")); + m_client->disconnect(NULL); + return kDisconnect; + } + + else if (memcmp(code, kMsgEIncompatible, 4) == 0) { + SInt32 major, minor; + CProtocolUtil::readf(m_stream, + kMsgEIncompatible + 4, &major, &minor); + LOG((CLOG_ERR "server has incompatible version %d.%d", major, minor)); + m_client->disconnect("server has incompatible version"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEBusy, 4) == 0) { + LOG((CLOG_ERR "server already has a connected client with name \"%s\"", m_client->getName().c_str())); + m_client->disconnect("server already has a connected client with our name"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEUnknown, 4) == 0) { + LOG((CLOG_ERR "server refused client with name \"%s\"", m_client->getName().c_str())); + m_client->disconnect("server refused client with our name"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEBad, 4) == 0) { + LOG((CLOG_ERR "server disconnected due to a protocol error")); + m_client->disconnect("server reported a protocol error"); + return kDisconnect; + } + else { + return kUnknown; + } + + return kOkay; +} + +CServerProxy::EResult +CServerProxy::parseMessage(const UInt8* code) +{ + if (memcmp(code, kMsgDMouseMove, 4) == 0) { + mouseMove(); + } + + else if (memcmp(code, kMsgDMouseRelMove, 4) == 0) { + mouseRelativeMove(); + } + + else if (memcmp(code, kMsgDMouseWheel, 4) == 0) { + mouseWheel(); + } + + else if (memcmp(code, kMsgDKeyDown, 4) == 0) { + keyDown(); + } + + else if (memcmp(code, kMsgDKeyUp, 4) == 0) { + keyUp(); + } + + else if (memcmp(code, kMsgDMouseDown, 4) == 0) { + mouseDown(); + } + + else if (memcmp(code, kMsgDMouseUp, 4) == 0) { + mouseUp(); + } + + else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) { + keyRepeat(); + } + + else if (memcmp(code, kMsgCKeepAlive, 4) == 0) { + // echo keep alives and reset alarm + CProtocolUtil::writef(m_stream, kMsgCKeepAlive); + resetKeepAliveAlarm(); + } + + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // accept and discard no-op + } + + else if (memcmp(code, kMsgCEnter, 4) == 0) { + enter(); + } + + else if (memcmp(code, kMsgCLeave, 4) == 0) { + leave(); + } + + else if (memcmp(code, kMsgCClipboard, 4) == 0) { + grabClipboard(); + } + + else if (memcmp(code, kMsgCScreenSaver, 4) == 0) { + screensaver(); + } + + else if (memcmp(code, kMsgQInfo, 4) == 0) { + queryInfo(); + } + + else if (memcmp(code, kMsgCInfoAck, 4) == 0) { + infoAcknowledgment(); + } + + else if (memcmp(code, kMsgDClipboard, 4) == 0) { + setClipboard(); + } + + else if (memcmp(code, kMsgCResetOptions, 4) == 0) { + resetOptions(); + } + + else if (memcmp(code, kMsgDSetOptions, 4) == 0) { + setOptions(); + } + + else if (memcmp(code, kMsgCClose, 4) == 0) { + // server wants us to hangup + LOG((CLOG_DEBUG1 "recv close")); + m_client->disconnect(NULL); + return kDisconnect; + } + else if (memcmp(code, kMsgEBad, 4) == 0) { + LOG((CLOG_ERR "server disconnected due to a protocol error")); + m_client->disconnect("server reported a protocol error"); + return kDisconnect; + } + else { + return kUnknown; + } + + // send a reply. this is intended to work around a delay when + // running a linux server and an OS X (any BSD?) client. the + // client waits to send an ACK (if the system control flag + // net.inet.tcp.delayed_ack is 1) in hopes of piggybacking it + // on a data packet. we provide that packet here. i don't + // know why a delayed ACK should cause the server to wait since + // TCP_NODELAY is enabled. + CProtocolUtil::writef(m_stream, kMsgCNoop); + + return kOkay; +} + +void +CServerProxy::handleKeepAliveAlarm(const CEvent&, void*) +{ + LOG((CLOG_NOTE "server is dead")); + m_client->disconnect("server is not responding"); +} + +void +CServerProxy::onInfoChanged() +{ + // ignore mouse motion until we receive acknowledgment of our info + // change message. + m_ignoreMouse = true; + + // send info update + queryInfo(); +} + +bool +CServerProxy::onGrabClipboard(ClipboardID id) +{ + LOG((CLOG_DEBUG1 "sending clipboard %d changed", id)); + CProtocolUtil::writef(m_stream, kMsgCClipboard, id, m_seqNum); + return true; +} + +void +CServerProxy::onClipboardChanged(ClipboardID id, const IClipboard* clipboard) +{ + CString data = IClipboard::marshall(clipboard); + LOG((CLOG_DEBUG1 "sending clipboard %d seqnum=%d, size=%d", id, m_seqNum, data.size())); + CProtocolUtil::writef(m_stream, kMsgDClipboard, id, m_seqNum, &data); +} + +void +CServerProxy::flushCompressedMouse() +{ + if (m_compressMouse) { + m_compressMouse = false; + m_client->mouseMove(m_xMouse, m_yMouse); + } + if (m_compressMouseRelative) { + m_compressMouseRelative = false; + m_client->mouseRelativeMove(m_dxMouse, m_dyMouse); + m_dxMouse = 0; + m_dyMouse = 0; + } +} + +void +CServerProxy::sendInfo(const CClientInfo& info) +{ + LOG((CLOG_DEBUG1 "sending info shape=%d,%d %dx%d", info.m_x, info.m_y, info.m_w, info.m_h)); + CProtocolUtil::writef(m_stream, kMsgDInfo, + info.m_x, info.m_y, + info.m_w, info.m_h, 0, + info.m_mx, info.m_my); +} + +KeyID +CServerProxy::translateKey(KeyID id) const +{ + static const KeyID s_translationTable[kKeyModifierIDLast][2] = { + { kKeyNone, kKeyNone }, + { kKeyShift_L, kKeyShift_R }, + { kKeyControl_L, kKeyControl_R }, + { kKeyAlt_L, kKeyAlt_R }, + { kKeyMeta_L, kKeyMeta_R }, + { kKeySuper_L, kKeySuper_R } + }; + + KeyModifierID id2 = kKeyModifierIDNull; + UInt32 side = 0; + switch (id) { + case kKeyShift_L: + id2 = kKeyModifierIDShift; + side = 0; + break; + + case kKeyShift_R: + id2 = kKeyModifierIDShift; + side = 1; + break; + + case kKeyControl_L: + id2 = kKeyModifierIDControl; + side = 0; + break; + + case kKeyControl_R: + id2 = kKeyModifierIDControl; + side = 1; + break; + + case kKeyAlt_L: + id2 = kKeyModifierIDAlt; + side = 0; + break; + + case kKeyAlt_R: + id2 = kKeyModifierIDAlt; + side = 1; + break; + + case kKeyMeta_L: + id2 = kKeyModifierIDMeta; + side = 0; + break; + + case kKeyMeta_R: + id2 = kKeyModifierIDMeta; + side = 1; + break; + + case kKeySuper_L: + id2 = kKeyModifierIDSuper; + side = 0; + break; + + case kKeySuper_R: + id2 = kKeyModifierIDSuper; + side = 1; + break; + } + + if (id2 != kKeyModifierIDNull) { + return s_translationTable[m_modifierTranslationTable[id2]][side]; + } + else { + return id; + } +} + +KeyModifierMask +CServerProxy::translateModifierMask(KeyModifierMask mask) const +{ + static const KeyModifierMask s_masks[kKeyModifierIDLast] = { + 0x0000, + KeyModifierShift, + KeyModifierControl, + KeyModifierAlt, + KeyModifierMeta, + KeyModifierSuper + }; + + KeyModifierMask newMask = mask & ~(KeyModifierShift | + KeyModifierControl | + KeyModifierAlt | + KeyModifierMeta | + KeyModifierSuper); + if ((mask & KeyModifierShift) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDShift]]; + } + if ((mask & KeyModifierControl) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDControl]]; + } + if ((mask & KeyModifierAlt) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDAlt]]; + } + if ((mask & KeyModifierMeta) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDMeta]]; + } + if ((mask & KeyModifierSuper) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDSuper]]; + } + return newMask; +} + +void +CServerProxy::enter() +{ + // parse + SInt16 x, y; + UInt16 mask; + UInt32 seqNum; + CProtocolUtil::readf(m_stream, kMsgCEnter + 4, &x, &y, &seqNum, &mask); + LOG((CLOG_DEBUG1 "recv enter, %d,%d %d %04x", x, y, seqNum, mask)); + + // discard old compressed mouse motion, if any + m_compressMouse = false; + m_compressMouseRelative = false; + m_dxMouse = 0; + m_dyMouse = 0; + m_seqNum = seqNum; + + // forward + m_client->enter(x, y, seqNum, static_cast(mask), false); +} + +void +CServerProxy::leave() +{ + // parse + LOG((CLOG_DEBUG1 "recv leave")); + + // send last mouse motion + flushCompressedMouse(); + + // forward + m_client->leave(); +} + +void +CServerProxy::setClipboard() +{ + // parse + ClipboardID id; + UInt32 seqNum; + CString data; + CProtocolUtil::readf(m_stream, kMsgDClipboard + 4, &id, &seqNum, &data); + LOG((CLOG_DEBUG "recv clipboard %d size=%d", id, data.size())); + + // validate + if (id >= kClipboardEnd) { + return; + } + + // forward + CClipboard clipboard; + clipboard.unmarshall(data, 0); + m_client->setClipboard(id, &clipboard); +} + +void +CServerProxy::grabClipboard() +{ + // parse + ClipboardID id; + UInt32 seqNum; + CProtocolUtil::readf(m_stream, kMsgCClipboard + 4, &id, &seqNum); + LOG((CLOG_DEBUG "recv grab clipboard %d", id)); + + // validate + if (id >= kClipboardEnd) { + return; + } + + // forward + m_client->grabClipboard(id); +} + +void +CServerProxy::keyDown() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, button; + CProtocolUtil::readf(m_stream, kMsgDKeyDown + 4, &id, &mask, &button); + LOG((CLOG_DEBUG1 "recv key down id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button)); + + // translate + KeyID id2 = translateKey(static_cast(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast(mask)); + if (id2 != static_cast(id) || + mask2 != static_cast(mask)) + LOG((CLOG_DEBUG1 "key down translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyDown(id2, mask2, button); +} + +void +CServerProxy::keyRepeat() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, count, button; + CProtocolUtil::readf(m_stream, kMsgDKeyRepeat + 4, + &id, &mask, &count, &button); + LOG((CLOG_DEBUG1 "recv key repeat id=0x%08x, mask=0x%04x, count=%d, button=0x%04x", id, mask, count, button)); + + // translate + KeyID id2 = translateKey(static_cast(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast(mask)); + if (id2 != static_cast(id) || + mask2 != static_cast(mask)) + LOG((CLOG_DEBUG1 "key repeat translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyRepeat(id2, mask2, count, button); +} + +void +CServerProxy::keyUp() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, button; + CProtocolUtil::readf(m_stream, kMsgDKeyUp + 4, &id, &mask, &button); + LOG((CLOG_DEBUG1 "recv key up id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button)); + + // translate + KeyID id2 = translateKey(static_cast(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast(mask)); + if (id2 != static_cast(id) || + mask2 != static_cast(mask)) + LOG((CLOG_DEBUG1 "key up translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyUp(id2, mask2, button); +} + +void +CServerProxy::mouseDown() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt8 id; + CProtocolUtil::readf(m_stream, kMsgDMouseDown + 4, &id); + LOG((CLOG_DEBUG1 "recv mouse down id=%d", id)); + + // forward + m_client->mouseDown(static_cast(id)); +} + +void +CServerProxy::mouseUp() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt8 id; + CProtocolUtil::readf(m_stream, kMsgDMouseUp + 4, &id); + LOG((CLOG_DEBUG1 "recv mouse up id=%d", id)); + + // forward + m_client->mouseUp(static_cast(id)); +} + +void +CServerProxy::mouseMove() +{ + // parse + bool ignore; + SInt16 x, y; + CProtocolUtil::readf(m_stream, kMsgDMouseMove + 4, &x, &y); + + // note if we should ignore the move + ignore = m_ignoreMouse; + + // compress mouse motion events if more input follows + if (!ignore && !m_compressMouse && m_stream->isReady()) { + m_compressMouse = true; + } + + // if compressing then ignore the motion but record it + if (m_compressMouse) { + m_compressMouseRelative = false; + ignore = true; + m_xMouse = x; + m_yMouse = y; + m_dxMouse = 0; + m_dyMouse = 0; + } + LOG((CLOG_DEBUG2 "recv mouse move %d,%d", x, y)); + + // forward + if (!ignore) { + m_client->mouseMove(x, y); + } +} + +void +CServerProxy::mouseRelativeMove() +{ + // parse + bool ignore; + SInt16 dx, dy; + CProtocolUtil::readf(m_stream, kMsgDMouseRelMove + 4, &dx, &dy); + + // note if we should ignore the move + ignore = m_ignoreMouse; + + // compress mouse motion events if more input follows + if (!ignore && !m_compressMouseRelative && m_stream->isReady()) { + m_compressMouseRelative = true; + } + + // if compressing then ignore the motion but record it + if (m_compressMouseRelative) { + ignore = true; + m_dxMouse += dx; + m_dyMouse += dy; + } + LOG((CLOG_DEBUG2 "recv mouse relative move %d,%d", dx, dy)); + + // forward + if (!ignore) { + m_client->mouseRelativeMove(dx, dy); + } +} + +void +CServerProxy::mouseWheel() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt16 xDelta, yDelta; + CProtocolUtil::readf(m_stream, kMsgDMouseWheel + 4, &xDelta, &yDelta); + LOG((CLOG_DEBUG2 "recv mouse wheel %+d,%+d", xDelta, yDelta)); + + // forward + m_client->mouseWheel(xDelta, yDelta); +} + +void +CServerProxy::screensaver() +{ + // parse + SInt8 on; + CProtocolUtil::readf(m_stream, kMsgCScreenSaver + 4, &on); + LOG((CLOG_DEBUG1 "recv screen saver on=%d", on)); + + // forward + m_client->screensaver(on != 0); +} + +void +CServerProxy::resetOptions() +{ + // parse + LOG((CLOG_DEBUG1 "recv reset options")); + + // forward + m_client->resetOptions(); + + // reset keep alive + setKeepAliveRate(kKeepAliveRate); + + // reset modifier translation table + for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) { + m_modifierTranslationTable[id] = id; + } +} + +void +CServerProxy::setOptions() +{ + // parse + COptionsList options; + CProtocolUtil::readf(m_stream, kMsgDSetOptions + 4, &options); + LOG((CLOG_DEBUG1 "recv set options size=%d", options.size())); + + // forward + m_client->setOptions(options); + + // update modifier table + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + KeyModifierID id = kKeyModifierIDNull; + if (options[i] == kOptionModifierMapForShift) { + id = kKeyModifierIDShift; + } + else if (options[i] == kOptionModifierMapForControl) { + id = kKeyModifierIDControl; + } + else if (options[i] == kOptionModifierMapForAlt) { + id = kKeyModifierIDAlt; + } + else if (options[i] == kOptionModifierMapForMeta) { + id = kKeyModifierIDMeta; + } + else if (options[i] == kOptionModifierMapForSuper) { + id = kKeyModifierIDSuper; + } + else if (options[i] == kOptionHeartbeat) { + // update keep alive + setKeepAliveRate(1.0e-3 * static_cast(options[i + 1])); + } + if (id != kKeyModifierIDNull) { + m_modifierTranslationTable[id] = + static_cast(options[i + 1]); + LOG((CLOG_DEBUG1 "modifier %d mapped to %d", id, m_modifierTranslationTable[id])); + } + } +} + +void +CServerProxy::queryInfo() +{ + CClientInfo info; + m_client->getShape(info.m_x, info.m_y, info.m_w, info.m_h); + m_client->getCursorPos(info.m_mx, info.m_my); + sendInfo(info); +} + +void +CServerProxy::infoAcknowledgment() +{ + LOG((CLOG_DEBUG1 "recv info acknowledgment")); + m_ignoreMouse = false; +} diff --git a/lib/client/CServerProxy.h b/lib/client/CServerProxy.h new file mode 100644 index 00000000..f6642696 --- /dev/null +++ b/lib/client/CServerProxy.h @@ -0,0 +1,115 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSERVERPROXY_H +#define CSERVERPROXY_H + +#include "ClipboardTypes.h" +#include "KeyTypes.h" +#include "CEvent.h" + +class CClient; +class CClientInfo; +class CEventQueueTimer; +class IClipboard; +class IStream; + +//! Proxy for server +/*! +This class acts a proxy for the server, converting calls into messages +to the server and messages from the server to calls on the client. +*/ +class CServerProxy { +public: + /*! + Process messages from the server on \p stream and forward to + \p client. + */ + CServerProxy(CClient* client, IStream* stream); + ~CServerProxy(); + + //! @name manipulators + //@{ + + void onInfoChanged(); + bool onGrabClipboard(ClipboardID); + void onClipboardChanged(ClipboardID, const IClipboard*); + + //@} + +protected: + enum EResult { kOkay, kUnknown, kDisconnect }; + EResult parseHandshakeMessage(const UInt8* code); + EResult parseMessage(const UInt8* code); + +private: + // if compressing mouse motion then send the last motion now + void flushCompressedMouse(); + + void sendInfo(const CClientInfo&); + + void resetKeepAliveAlarm(); + void setKeepAliveRate(double); + + // modifier key translation + KeyID translateKey(KeyID) const; + KeyModifierMask translateModifierMask(KeyModifierMask) const; + + // event handlers + void handleData(const CEvent&, void*); + void handleKeepAliveAlarm(const CEvent&, void*); + + // message handlers + void enter(); + void leave(); + void setClipboard(); + void grabClipboard(); + void keyDown(); + void keyRepeat(); + void keyUp(); + void mouseDown(); + void mouseUp(); + void mouseMove(); + void mouseRelativeMove(); + void mouseWheel(); + void screensaver(); + void resetOptions(); + void setOptions(); + void queryInfo(); + void infoAcknowledgment(); + +private: + typedef EResult (CServerProxy::*MessageParser)(const UInt8*); + + CClient* m_client; + IStream* m_stream; + + UInt32 m_seqNum; + + bool m_compressMouse; + bool m_compressMouseRelative; + SInt32 m_xMouse, m_yMouse; + SInt32 m_dxMouse, m_dyMouse; + + bool m_ignoreMouse; + + KeyModifierID m_modifierTranslationTable[kKeyModifierIDLast]; + + double m_keepAliveAlarm; + CEventQueueTimer* m_keepAliveAlarmTimer; + + MessageParser m_parser; +}; + +#endif diff --git a/lib/client/Makefile.am b/lib/client/Makefile.am new file mode 100644 index 00000000..4ea5a230 --- /dev/null +++ b/lib/client/Makefile.am @@ -0,0 +1,40 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libclient.a +libclient_a_SOURCES = \ + CClient.cpp \ + CServerProxy.cpp \ + CClient.h \ + CServerProxy.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + -I$(top_srcdir)/lib/synergy \ + -I$(top_srcdir)/lib/platform \ + $(NULL) diff --git a/lib/client/Makefile.win b/lib/client/Makefile.win new file mode 100644 index 00000000..3da2c73c --- /dev/null +++ b/lib/client/Makefile.win @@ -0,0 +1,63 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +LIB_CLIENT_SRC = lib\client +LIB_CLIENT_DST = $(BUILD_DST)\$(LIB_CLIENT_SRC) +LIB_CLIENT_LIB = "$(LIB_CLIENT_DST)\client.lib" +LIB_CLIENT_CPP = \ + "CClient.cpp" \ + "CServerProxy.cpp" \ + $(NULL) +LIB_CLIENT_OBJ = \ + "$(LIB_CLIENT_DST)\CClient.obj" \ + "$(LIB_CLIENT_DST)\CServerProxy.obj" \ + $(NULL) +LIB_CLIENT_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + /I"lib\platform" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_CLIENT_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_CLIENT_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_CLIENT_LIB) + +# Dependency rules +$(LIB_CLIENT_OBJ): $(AUTODEP) +!if EXIST($(LIB_CLIENT_DST)\deps.mak) +!include $(LIB_CLIENT_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_CLIENT_SRC)\}.cpp{$(LIB_CLIENT_DST)\}.obj:: +!else +{$(LIB_CLIENT_SRC)\}.cpp{$(LIB_CLIENT_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_CLIENT_SRC) + -@$(MKDIR) $(LIB_CLIENT_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_CLIENT_INC) \ + /Fo$(LIB_CLIENT_DST)\ \ + /Fd$(LIB_CLIENT_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_CLIENT_SRC) $(LIB_CLIENT_DST) +$(LIB_CLIENT_LIB): $(LIB_CLIENT_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_CLIENT_SRC) $(LIB_CLIENT_DST) $(**:.obj=.d) diff --git a/lib/common/BasicTypes.h b/lib/common/BasicTypes.h new file mode 100644 index 00000000..b9d8293e --- /dev/null +++ b/lib/common/BasicTypes.h @@ -0,0 +1,86 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BASICTYPES_H +#define BASICTYPES_H + +#include "common.h" + +// +// pick types of particular sizes +// + +#if !defined(TYPE_OF_SIZE_1) +# if SIZEOF_CHAR == 1 +# define TYPE_OF_SIZE_1 char +# endif +#endif + +#if !defined(TYPE_OF_SIZE_2) +# if SIZEOF_INT == 2 +# define TYPE_OF_SIZE_2 int +# else +# define TYPE_OF_SIZE_2 short +# endif +#endif + +#if !defined(TYPE_OF_SIZE_4) + // Carbon defines SInt32 and UInt32 in terms of long +# if SIZEOF_INT == 4 && !defined(__APPLE__) +# define TYPE_OF_SIZE_4 int +# else +# define TYPE_OF_SIZE_4 long +# endif +#endif + +// +// verify existence of required types +// + +#if !defined(TYPE_OF_SIZE_1) +# error No 1 byte integer type +#endif +#if !defined(TYPE_OF_SIZE_2) +# error No 2 byte integer type +#endif +#if !defined(TYPE_OF_SIZE_4) +# error No 4 byte integer type +#endif + + +// +// make typedefs +// +// except for SInt8 and UInt8 these types are only guaranteed to be +// at least as big as indicated (in bits). that is, they may be +// larger than indicated. +// + +typedef signed TYPE_OF_SIZE_1 SInt8; +typedef signed TYPE_OF_SIZE_2 SInt16; +typedef signed TYPE_OF_SIZE_4 SInt32; + +typedef unsigned TYPE_OF_SIZE_1 UInt8; +typedef unsigned TYPE_OF_SIZE_2 UInt16; +typedef unsigned TYPE_OF_SIZE_4 UInt32; + +// +// clean up +// + +#undef TYPE_OF_SIZE_1 +#undef TYPE_OF_SIZE_2 +#undef TYPE_OF_SIZE_4 + +#endif diff --git a/lib/common/IInterface.h b/lib/common/IInterface.h new file mode 100644 index 00000000..97df31c2 --- /dev/null +++ b/lib/common/IInterface.h @@ -0,0 +1,31 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IINTERFACE_H +#define IINTERFACE_H + +#include "common.h" + +//! Base class of interfaces +/*! +This is the base class of all interface classes. An interface class has +only pure virtual methods. +*/ +class IInterface { +public: + //! Interface destructor does nothing + virtual ~IInterface() { } +}; + +#endif diff --git a/lib/common/MacOSXPrecomp.h b/lib/common/MacOSXPrecomp.h new file mode 100644 index 00000000..64e7c90a --- /dev/null +++ b/lib/common/MacOSXPrecomp.h @@ -0,0 +1,8 @@ +// +// Prefix header for all source files of the 'deleteme' target in the 'deleteme' project. +// + +#define MAC_OS_X_VERSION_MAX_ALLOWED MAC_OS_X_VERSION_10_2 + + +#include diff --git a/lib/common/Makefile.am b/lib/common/Makefile.am new file mode 100644 index 00000000..74804748 --- /dev/null +++ b/lib/common/Makefile.am @@ -0,0 +1,48 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + BasicTypes.h \ + IInterface.h \ + MacOSXPrecomp.h \ + common.h \ + stdbitset.h \ + stddeque.h \ + stdfstream.h \ + stdistream.h \ + stdlist.h \ + stdmap.h \ + stdostream.h \ + stdpost.h \ + stdpre.h \ + stdset.h \ + stdsstream.h \ + stdstring.h \ + stdvector.h \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libcommon.a +libcommon_a_SOURCES = \ + Version.cpp \ + Version.h \ + $(NULL) + +INCLUDES = \ + $(NULL) diff --git a/lib/common/Makefile.win b/lib/common/Makefile.win new file mode 100644 index 00000000..9b63a046 --- /dev/null +++ b/lib/common/Makefile.win @@ -0,0 +1,53 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +LIB_COMMON_SRC = lib\common +LIB_COMMON_DST = $(BUILD_DST)\$(LIB_COMMON_SRC) +LIB_COMMON_LIB = "$(LIB_COMMON_DST)\common.lib" +LIB_COMMON_CPP = \ + Version.cpp \ + $(NULL) +LIB_COMMON_OBJ = \ + "$(LIB_COMMON_DST)\Version.obj" \ + $(NULL) +LIB_COMMON_INC = \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_COMMON_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_COMMON_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_COMMON_LIB) + +# Dependency rules +$(LIB_COMMON_OBJ): $(AUTODEP) +!if EXIST($(LIB_COMMON_DST)\deps.mak) +!include $(LIB_COMMON_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_COMMON_SRC)\}.cpp{$(LIB_COMMON_DST)\}.obj:: +!else +{$(LIB_COMMON_SRC)\}.cpp{$(LIB_COMMON_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_COMMON_SRC) + -@$(MKDIR) $(LIB_COMMON_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_COMMON_INC) \ + /Fo$(LIB_COMMON_DST)\ \ + /Fd$(LIB_COMMON_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_COMMON_SRC) $(LIB_COMMON_DST) +$(LIB_COMMON_LIB): $(LIB_COMMON_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_COMMON_SRC) $(LIB_COMMON_DST) $(**:.obj=.d) diff --git a/lib/common/Version.cpp b/lib/common/Version.cpp new file mode 100644 index 00000000..86fcdef7 --- /dev/null +++ b/lib/common/Version.cpp @@ -0,0 +1,22 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "Version.h" + +const char* kApplication = "Synergy"; +const char* kCopyright = "Copyright (C) 2002 Chris Schoeneman"; +const char* kContact = "Chris Schoeneman, crs23@bigfoot.com"; +const char* kWebsite = "http://synergy2.sourceforge.net/"; +const char* kVersion = VERSION; +const char* kAppVersion = "Synergy " VERSION; diff --git a/lib/common/Version.h b/lib/common/Version.h new file mode 100644 index 00000000..43783c8c --- /dev/null +++ b/lib/common/Version.h @@ -0,0 +1,45 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef VERSION_H +#define VERSION_H + +#include "common.h" + +// set version macro if not set yet +#if !defined(VERSION) +# define VERSION "1.3.2" +#endif + +// important strings +extern const char* kApplication; +extern const char* kCopyright; +extern const char* kContact; +extern const char* kWebsite; + +// build version. follows linux kernel style: an even minor number implies +// a release version, odd implies development version. +extern const char* kVersion; + +// application version +extern const char* kAppVersion; + +// exit codes +static const int kExitSuccess = 0; // successful completion +static const int kExitFailed = 1; // general failure +static const int kExitTerminated = 2; // killed by signal +static const int kExitArgs = 3; // bad arguments +static const int kExitConfig = 4; // cannot read configuration + +#endif diff --git a/lib/common/common.h b/lib/common/common.h new file mode 100644 index 00000000..e539a7d6 --- /dev/null +++ b/lib/common/common.h @@ -0,0 +1,135 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COMMON_H +#define COMMON_H + +// this file should be included, directly or indirectly by every other. + +#if HAVE_CONFIG_H +# include "config.h" + + // don't use poll() on mac +# if defined(__APPLE__) +# undef HAVE_POLL +# endif +#else + // we may not have run configure on win32 +# if defined(_WIN32) +# define SYSAPI_WIN32 1 +# define WINAPI_MSWINDOWS 1 +# endif + + // we may not have run configure on OS X +# if defined(__APPLE__) +# define SYSAPI_UNIX 1 +# define WINAPI_CARBON 1 + +# define HAVE_CXX_BOOL 1 +# define HAVE_CXX_CASTS 1 +# define HAVE_CXX_EXCEPTIONS 1 +# define HAVE_CXX_MUTABLE 1 +# define HAVE_CXX_STDLIB 1 +# define HAVE_GETPWUID_R 1 +# define HAVE_GMTIME_R 1 +# define HAVE_INET_ATON 1 +# define HAVE_INTTYPES_H 1 +# define HAVE_ISTREAM 1 +# define HAVE_MEMORY_H 1 +# define HAVE_NANOSLEEP 1 +# define HAVE_OSTREAM 1 +# define HAVE_POSIX_SIGWAIT 1 +# define HAVE_PTHREAD 1 +# define HAVE_PTHREAD_SIGNAL 1 +# include +# include +# if defined(_SOCKLEN_T) +# define HAVE_SOCKLEN_T 1 +# endif +# define HAVE_SSTREAM 1 +# define HAVE_STDINT_H 1 +# define HAVE_STDLIB_H 1 +# define HAVE_STRINGS_H 1 +# define HAVE_STRING_H 1 +# define HAVE_SYS_SELECT_H 1 +# define HAVE_SYS_SOCKET_H 1 +# define HAVE_SYS_STAT_H 1 +# define HAVE_SYS_TIME_H 1 +# define HAVE_SYS_TYPES_H 1 +# define HAVE_SYS_UTSNAME_H 1 +# define HAVE_UNISTD_H 1 +# define HAVE_VSNPRINTF 1 +/* disable this so we can build with the 10.2.8 SDK */ +/*# define HAVE_WCHAR_H 1*/ + +# define SELECT_TYPE_ARG1 int +# define SELECT_TYPE_ARG234 (fd_set *) +# define SELECT_TYPE_ARG5 (struct timeval *) +# define SIZEOF_CHAR 1 +# define SIZEOF_INT 4 +# define SIZEOF_LONG 4 +# define SIZEOF_SHORT 2 +# define STDC_HEADERS 1 +# define TIME_WITH_SYS_TIME 1 +# define X_DISPLAY_MISSING 1 +# endif +#endif + +// VC++ specific +#if (_MSC_VER >= 1200) + // work around for statement scoping bug +# define for if (false) { } else for + + // turn off bonehead warnings +# pragma warning(disable: 4786) // identifier truncated in debug info +# pragma warning(disable: 4514) // unreferenced inline function removed + + // this one's a little too aggressive +# pragma warning(disable: 4127) // conditional expression is constant + + // emitted incorrectly under release build in some circumstances +# if defined(NDEBUG) +# pragma warning(disable: 4702) // unreachable code +# pragma warning(disable: 4701) // variable maybe used uninitialized +# endif +#endif // (_MSC_VER >= 1200) + +// VC++ has built-in sized types +#if defined(_MSC_VER) +# include +# define TYPE_OF_SIZE_1 __int8 +# define TYPE_OF_SIZE_2 __int16 +# define TYPE_OF_SIZE_4 __int32 +#else +# define SIZE_OF_CHAR 1 +# define SIZE_OF_SHORT 2 +# define SIZE_OF_INT 4 +# define SIZE_OF_LONG 4 +#endif + +// FIXME -- including fp.h from Carbon.h causes a undefined symbol error +// on my build system. the symbol is scalb. since we don't need any +// math functions we define __FP__, the include guard macro for fp.h, to +// prevent fp.h from being included. +#if defined(__APPLE__) +#define __FP__ +#endif + +// define NULL +#include + +// make assert available since we use it a lot +#include + +#endif diff --git a/lib/common/stdbitset.h b/lib/common/stdbitset.h new file mode 100644 index 00000000..529772ca --- /dev/null +++ b/lib/common/stdbitset.h @@ -0,0 +1,17 @@ +/* + * 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 "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stddeque.h b/lib/common/stddeque.h new file mode 100644 index 00000000..06bdafd1 --- /dev/null +++ b/lib/common/stddeque.h @@ -0,0 +1,17 @@ +/* + * 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 "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stdfstream.h b/lib/common/stdfstream.h new file mode 100644 index 00000000..9d1f5aa6 --- /dev/null +++ b/lib/common/stdfstream.h @@ -0,0 +1,18 @@ +/* + * 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 "stdpre.h" +#include +#include "stdpost.h" +#include "stdistream.h" diff --git a/lib/common/stdistream.h b/lib/common/stdistream.h new file mode 100644 index 00000000..33667398 --- /dev/null +++ b/lib/common/stdistream.h @@ -0,0 +1,43 @@ +/* + * 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 "stdpre.h" +#if HAVE_ISTREAM +#include +#else +#include +#endif +#include "stdpost.h" + +#if defined(_MSC_VER) && _MSC_VER <= 1200 +// VC++6 istream has no overloads for __int* types, .NET does +inline +std::istream& operator>>(std::istream& s, SInt8& i) +{ return s >> (signed char&)i; } +inline +std::istream& operator>>(std::istream& s, SInt16& i) +{ return s >> (short&)i; } +inline +std::istream& operator>>(std::istream& s, SInt32& i) +{ return s >> (int&)i; } +inline +std::istream& operator>>(std::istream& s, UInt8& i) +{ return s >> (unsigned char&)i; } +inline +std::istream& operator>>(std::istream& s, UInt16& i) +{ return s >> (unsigned short&)i; } +inline +std::istream& operator>>(std::istream& s, UInt32& i) +{ return s >> (unsigned int&)i; } +#endif diff --git a/lib/common/stdlist.h b/lib/common/stdlist.h new file mode 100644 index 00000000..6f0df8e8 --- /dev/null +++ b/lib/common/stdlist.h @@ -0,0 +1,17 @@ +/* + * 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 "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stdmap.h b/lib/common/stdmap.h new file mode 100644 index 00000000..da501615 --- /dev/null +++ b/lib/common/stdmap.h @@ -0,0 +1,17 @@ +/* + * 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 "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stdostream.h b/lib/common/stdostream.h new file mode 100644 index 00000000..48e89862 --- /dev/null +++ b/lib/common/stdostream.h @@ -0,0 +1,21 @@ +/* + * 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 "stdpre.h" +#if HAVE_OSTREAM +#include +#else +#include +#endif +#include "stdpost.h" diff --git a/lib/common/stdpost.h b/lib/common/stdpost.h new file mode 100644 index 00000000..0f06b74a --- /dev/null +++ b/lib/common/stdpost.h @@ -0,0 +1,17 @@ +/* + * 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. + */ + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/lib/common/stdpre.h b/lib/common/stdpre.h new file mode 100644 index 00000000..10fa7d5d --- /dev/null +++ b/lib/common/stdpre.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#if defined(_MSC_VER) +#pragma warning(disable: 4786) // identifier truncated +#pragma warning(disable: 4514) // unreferenced inline +#pragma warning(disable: 4710) // not inlined +#pragma warning(disable: 4663) // C++ change, template specialization +#pragma warning(disable: 4503) // decorated name length too long +#pragma warning(push, 3) +#pragma warning(disable: 4018) // signed/unsigned mismatch +#pragma warning(disable: 4284) +#pragma warning(disable: 4146) // unary minus on unsigned value +#pragma warning(disable: 4127) // conditional expression is constant +#pragma warning(disable: 4701) // variable possibly used uninitialized +#endif diff --git a/lib/common/stdset.h b/lib/common/stdset.h new file mode 100644 index 00000000..aeb491b6 --- /dev/null +++ b/lib/common/stdset.h @@ -0,0 +1,17 @@ +/* + * 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 "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stdsstream.h b/lib/common/stdsstream.h new file mode 100644 index 00000000..45b28124 --- /dev/null +++ b/lib/common/stdsstream.h @@ -0,0 +1,371 @@ +/* + * 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 "stdpre.h" + +#if HAVE_SSTREAM || !defined(__GNUC__) || (__GNUC__ >= 3) + +#include + +#elif defined(__GNUC_MINOR__) && (__GNUC_MINOR__ >= 95) +// g++ 2.95 didn't ship with sstream. the following is a backport +// by Magnus Fromreide of the sstream in g++ 3.0. + +/* This is part of libio/iostream, providing -*- C++ -*- input/output. +Copyright (C) 2000 Free Software Foundation + +This file is part of the GNU IO Library. This library is free +software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this library; see the file COPYING. If not, write to the Free +Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +As a special exception, if you link this library with files +compiled with a GNU compiler to produce an executable, this does not cause +the resulting executable to be covered by the GNU General Public License. +This exception does not however invalidate any other reasons why +the executable file might be covered by the GNU General Public License. */ + +/* Written by Magnus Fromreide (magfr@lysator.liu.se). */ +/* seekoff and ideas for overflow is largely borrowed from libstdc++-v3 */ + +#include +#include +#include + +namespace std +{ + class stringbuf : public streambuf + { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + stringbuf(int which=ios::in|ios::out) + : streambuf(), mode(static_cast(which)), + stream(NULL), stream_len(0) + { + stringbuf_init(); + } + + explicit + stringbuf(const string &str, int which=ios::in|ios::out) + : streambuf(), mode(static_cast(which)), + stream(NULL), stream_len(0) + { + if (mode & (ios::in|ios::out)) + { + stream_len = str.size(); + stream = new char_type[stream_len]; + str.copy(stream, stream_len); + } + stringbuf_init(); + } + + virtual + ~stringbuf() + { + delete[] stream; + } + + string + str() const + { + if (pbase() != 0) + return string(stream, pptr()-pbase()); + else + return string(); + } + + void + str(const string& str) + { + delete[] stream; + stream_len = str.size(); + stream = new char_type[stream_len]; + str.copy(stream, stream_len); + stringbuf_init(); + } + + protected: + // The buffer is already in gptr, so if it ends then it is out of data. + virtual int + underflow() + { + return EOF; + } + + virtual int + overflow(int c = EOF) + { + int res; + if (mode & ios::out) + { + if (c != EOF) + { + streamsize old_stream_len = stream_len; + stream_len += 1; + char_type* new_stream = new char_type[stream_len]; + memcpy(new_stream, stream, old_stream_len); + delete[] stream; + stream = new_stream; + stringbuf_sync(gptr()-eback(), pptr()-pbase()); + sputc(c); + res = c; + } + else + res = EOF; + } + else + res = 0; + return res; + } + + virtual streambuf* + setbuf(char_type* s, streamsize n) + { + if (n != 0) + { + delete[] stream; + stream = new char_type[n]; + memcpy(stream, s, n); + stream_len = n; + stringbuf_sync(0, 0); + } + return this; + } + + virtual pos_type + seekoff(off_type off, ios::seek_dir way, int which = ios::in | ios::out) + { + pos_type ret = pos_type(off_type(-1)); + bool testin = which & ios::in && mode & ios::in; + bool testout = which & ios::out && mode & ios::out; + bool testboth = testin && testout && way != ios::cur; + + if (stream_len && ((testin != testout) || testboth)) + { + char_type* beg = stream; + char_type* curi = NULL; + char_type* curo = NULL; + char_type* endi = NULL; + char_type* endo = NULL; + + if (testin) + { + curi = gptr(); + endi = egptr(); + } + if (testout) + { + curo = pptr(); + endo = epptr(); + } + + off_type newoffi = 0; + off_type newoffo = 0; + if (way == ios::beg) + { + newoffi = beg - curi; + newoffo = beg - curo; + } + else if (way == ios::end) + { + newoffi = endi - curi; + newoffo = endo - curo; + } + + if (testin && newoffi + off + curi - beg >= 0 && + endi - beg >= newoffi + off + curi - beg) + { + gbump(newoffi + off); + ret = pos_type(newoffi + off + curi); + } + if (testout && newoffo + off + curo - beg >= 0 && + endo - beg >= newoffo + off + curo - beg) + { + pbump(newoffo + off); + ret = pos_type(newoffo + off + curo); + } + } + return ret; + } + + virtual pos_type + seekpos(pos_type sp, int which = ios::in | ios::out) + { + pos_type ret = seekoff(sp, ios::beg, which); + return ret; + } + + private: + void + stringbuf_sync(streamsize i, streamsize o) + { + if (mode & ios::in) + setg(stream, stream + i, stream + stream_len); + if (mode & ios::out) + { + setp(stream, stream + stream_len); + pbump(o); + } + } + void + stringbuf_init() + { + if (mode & ios::ate) + stringbuf_sync(0, stream_len); + else + stringbuf_sync(0, 0); + } + + private: + ios::open_mode mode; + char_type* stream; + streamsize stream_len; + }; + + class istringstream : public istream { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + istringstream(int which=ios::in) + : istream(&sb), sb(which | ios::in) + { } + + explicit + istringstream(const string& str, int which=ios::in) + : istream(&sb), sb(str, which | ios::in) + { } + + stringbuf* + rdbuf() const + { + return const_cast(&sb); + } + + string + str() const + { + return rdbuf()->str(); + } + void + str(const string& s) + { + rdbuf()->str(s); + } + private: + stringbuf sb; + }; + + class ostringstream : public ostream { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + ostringstream(int which=ios::out) + : ostream(&sb), sb(which | ios::out) + { } + + explicit + ostringstream(const string& str, int which=ios::out) + : ostream(&sb), sb(str, which | ios::out) + { } + + stringbuf* + rdbuf() const + { + return const_cast(&sb); + } + + string + str() const + { + return rdbuf()->str(); + } + + void str(const string& s) + { + rdbuf()->str(s); + } + private: + stringbuf sb; + }; + + class stringstream : public iostream { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + stringstream(int which=ios::out|ios::in) + : iostream(&sb), sb(which) + { } + + explicit + stringstream(const string& str, int which=ios::out|ios::in) + : iostream(&sb), sb(str, which) + { } + + stringbuf* + rdbuf() const + { + return const_cast(&sb); + } + + string + str() const + { + return rdbuf()->str(); + } + + void + str(const string& s) + { + rdbuf()->str(s); + } + private: + stringbuf sb; + }; +}; + +#else /* not g++ 2.95 and no */ + +#error "Standard C++ library is missing required sstream header." + +#endif /* not g++ 2.95 and no */ + +#include "stdpost.h" +#include "stdistream.h" diff --git a/lib/common/stdstring.h b/lib/common/stdstring.h new file mode 100644 index 00000000..3d83c03c --- /dev/null +++ b/lib/common/stdstring.h @@ -0,0 +1,17 @@ +/* + * 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 "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stdvector.h b/lib/common/stdvector.h new file mode 100644 index 00000000..1056bb46 --- /dev/null +++ b/lib/common/stdvector.h @@ -0,0 +1,17 @@ +/* + * 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 "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/io/CStreamBuffer.cpp b/lib/io/CStreamBuffer.cpp new file mode 100644 index 00000000..d11494b4 --- /dev/null +++ b/lib/io/CStreamBuffer.cpp @@ -0,0 +1,142 @@ +/* + * 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 "CStreamBuffer.h" + +// +// CStreamBuffer +// + +const UInt32 CStreamBuffer::kChunkSize = 4096; + +CStreamBuffer::CStreamBuffer() : + m_size(0), + m_headUsed(0) +{ + // do nothing +} + +CStreamBuffer::~CStreamBuffer() +{ + // do nothing +} + +const void* +CStreamBuffer::peek(UInt32 n) +{ + assert(n <= m_size); + + // if requesting no data then return NULL so we don't try to access + // an empty list. + if (n == 0) { + return NULL; + } + + // reserve space in first chunk + ChunkList::iterator head = m_chunks.begin(); + head->reserve(n + m_headUsed); + + // consolidate chunks into the first chunk until it has n bytes + ChunkList::iterator scan = head; + ++scan; + while (head->size() - m_headUsed < n && scan != m_chunks.end()) { + head->insert(head->end(), scan->begin(), scan->end()); + scan = m_chunks.erase(scan); + } + + return reinterpret_cast(&(head->begin()[m_headUsed])); +} + +void +CStreamBuffer::pop(UInt32 n) +{ + // discard all chunks if n is greater than or equal to m_size + if (n >= m_size) { + m_size = 0; + m_headUsed = 0; + m_chunks.clear(); + return; + } + + // update size + m_size -= n; + + // discard chunks until more than n bytes would've been discarded + ChunkList::iterator scan = m_chunks.begin(); + assert(scan != m_chunks.end()); + while (scan->size() - m_headUsed <= n) { + n -= scan->size() - m_headUsed; + m_headUsed = 0; + scan = m_chunks.erase(scan); + assert(scan != m_chunks.end()); + } + + // remove left over bytes from the head chunk + if (n > 0) { + m_headUsed += n; + } +} + +void +CStreamBuffer::write(const void* vdata, UInt32 n) +{ + assert(vdata != NULL); + + // ignore if no data, otherwise update size + if (n == 0) { + return; + } + m_size += n; + + // cast data to bytes + const UInt8* data = reinterpret_cast(vdata); + + // point to last chunk if it has space, otherwise append an empty chunk + ChunkList::iterator scan = m_chunks.end(); + if (scan != m_chunks.begin()) { + --scan; + if (scan->size() >= kChunkSize) { + ++scan; + } + } + if (scan == m_chunks.end()) { + scan = m_chunks.insert(scan, Chunk()); + } + + // append data in chunks + while (n > 0) { + // choose number of bytes for next chunk + assert(scan->size() <= kChunkSize); + UInt32 count = kChunkSize - scan->size(); + if (count > n) + count = n; + + // transfer data + scan->insert(scan->end(), data, data + count); + n -= count; + data += count; + + // append another empty chunk if we're not done yet + if (n > 0) { + ++scan; + scan = m_chunks.insert(scan, Chunk()); + } + } +} + +UInt32 +CStreamBuffer::getSize() const +{ + return m_size; +} diff --git a/lib/io/CStreamBuffer.h b/lib/io/CStreamBuffer.h new file mode 100644 index 00000000..fafcc72b --- /dev/null +++ b/lib/io/CStreamBuffer.h @@ -0,0 +1,78 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSTREAMBUFFER_H +#define CSTREAMBUFFER_H + +#include "BasicTypes.h" +#include "stdlist.h" +#include "stdvector.h" + +//! FIFO of bytes +/*! +This class maintains a FIFO (first-in, last-out) buffer of bytes. +*/ +class CStreamBuffer { +public: + CStreamBuffer(); + ~CStreamBuffer(); + + //! @name manipulators + //@{ + + //! Read data without removing from buffer + /*! + Return a pointer to memory with the next \c n bytes in the buffer + (which must be <= getSize()). The caller must not modify the returned + memory nor delete it. + */ + const void* peek(UInt32 n); + + //! Discard data + /*! + Discards the next \c n bytes. If \c n >= getSize() then the buffer + is cleared. + */ + void pop(UInt32 n); + + //! Write data to buffer + /*! + Appends \c n bytes from \c data to the buffer. + */ + void write(const void* data, UInt32 n); + + //@} + //! @name accessors + //@{ + + //! Get size of buffer + /*! + Returns the number of bytes in the buffer. + */ + UInt32 getSize() const; + + //@} + +private: + static const UInt32 kChunkSize; + + typedef std::vector Chunk; + typedef std::list ChunkList; + + ChunkList m_chunks; + UInt32 m_size; + UInt32 m_headUsed; +}; + +#endif diff --git a/lib/io/CStreamFilter.cpp b/lib/io/CStreamFilter.cpp new file mode 100644 index 00000000..312b0dfd --- /dev/null +++ b/lib/io/CStreamFilter.cpp @@ -0,0 +1,113 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CStreamFilter.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CStreamFilter +// + +CStreamFilter::CStreamFilter(IStream* stream, bool adoptStream) : + m_stream(stream), + m_adopted(adoptStream) +{ + // replace handlers for m_stream + EVENTQUEUE->removeHandlers(m_stream->getEventTarget()); + EVENTQUEUE->adoptHandler(CEvent::kUnknown, m_stream->getEventTarget(), + new TMethodEventJob(this, + &CStreamFilter::handleUpstreamEvent)); +} + +CStreamFilter::~CStreamFilter() +{ + EVENTQUEUE->removeHandler(CEvent::kUnknown, m_stream->getEventTarget()); + if (m_adopted) { + delete m_stream; + } +} + +void +CStreamFilter::close() +{ + getStream()->close(); +} + +UInt32 +CStreamFilter::read(void* buffer, UInt32 n) +{ + return getStream()->read(buffer, n); +} + +void +CStreamFilter::write(const void* buffer, UInt32 n) +{ + getStream()->write(buffer, n); +} + +void +CStreamFilter::flush() +{ + getStream()->flush(); +} + +void +CStreamFilter::shutdownInput() +{ + getStream()->shutdownInput(); +} + +void +CStreamFilter::shutdownOutput() +{ + getStream()->shutdownOutput(); +} + +void* +CStreamFilter::getEventTarget() const +{ + return const_cast(reinterpret_cast(this)); +} + +bool +CStreamFilter::isReady() const +{ + return getStream()->isReady(); +} + +UInt32 +CStreamFilter::getSize() const +{ + return getStream()->getSize(); +} + +IStream* +CStreamFilter::getStream() const +{ + return m_stream; +} + +void +CStreamFilter::filterEvent(const CEvent& event) +{ + EVENTQUEUE->dispatchEvent(CEvent(event.getType(), + getEventTarget(), event.getData())); +} + +void +CStreamFilter::handleUpstreamEvent(const CEvent& event, void*) +{ + filterEvent(event); +} diff --git a/lib/io/CStreamFilter.h b/lib/io/CStreamFilter.h new file mode 100644 index 00000000..4dc87094 --- /dev/null +++ b/lib/io/CStreamFilter.h @@ -0,0 +1,70 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSTREAMFILTER_H +#define CSTREAMFILTER_H + +#include "IStream.h" + +//! A stream filter +/*! +This class wraps a stream. Subclasses provide indirect access +to the wrapped stream, typically performing some filtering. +*/ +class CStreamFilter : public IStream { +public: + /*! + Create a wrapper around \c stream. Iff \c adoptStream is true then + this object takes ownership of the stream and will delete it in the + d'tor. + */ + CStreamFilter(IStream* stream, bool adoptStream = true); + ~CStreamFilter(); + + // IStream overrides + // These all just forward to the underlying stream except getEventTarget. + // Override as necessary. getEventTarget returns a pointer to this. + virtual void close(); + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void flush(); + virtual void shutdownInput(); + virtual void shutdownOutput(); + virtual void* getEventTarget() const; + virtual bool isReady() const; + virtual UInt32 getSize() const; + +protected: + //! Get the stream + /*! + Returns the stream passed to the c'tor. + */ + IStream* getStream() const; + + //! Handle events from source stream + /*! + Does the event filtering. The default simply dispatches an event + identical except using this object as the event target. + */ + virtual void filterEvent(const CEvent&); + +private: + void handleUpstreamEvent(const CEvent&, void*); + +private: + IStream* m_stream; + bool m_adopted; +}; + +#endif diff --git a/lib/io/IStream.cpp b/lib/io/IStream.cpp new file mode 100644 index 00000000..aec65b61 --- /dev/null +++ b/lib/io/IStream.cpp @@ -0,0 +1,60 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "IStream.h" + +// +// IStream +// + +CEvent::Type IStream::s_inputReadyEvent = CEvent::kUnknown; +CEvent::Type IStream::s_outputFlushedEvent = CEvent::kUnknown; +CEvent::Type IStream::s_outputErrorEvent = CEvent::kUnknown; +CEvent::Type IStream::s_inputShutdownEvent = CEvent::kUnknown; +CEvent::Type IStream::s_outputShutdownEvent = CEvent::kUnknown; + +CEvent::Type +IStream::getInputReadyEvent() +{ + return CEvent::registerTypeOnce(s_inputReadyEvent, + "IStream::inputReady"); +} + +CEvent::Type +IStream::getOutputFlushedEvent() +{ + return CEvent::registerTypeOnce(s_outputFlushedEvent, + "IStream::outputFlushed"); +} + +CEvent::Type +IStream::getOutputErrorEvent() +{ + return CEvent::registerTypeOnce(s_outputErrorEvent, + "IStream::outputError"); +} + +CEvent::Type +IStream::getInputShutdownEvent() +{ + return CEvent::registerTypeOnce(s_inputShutdownEvent, + "IStream::inputShutdown"); +} + +CEvent::Type +IStream::getOutputShutdownEvent() +{ + return CEvent::registerTypeOnce(s_outputShutdownEvent, + "IStream::outputShutdown"); +} diff --git a/lib/io/IStream.h b/lib/io/IStream.h new file mode 100644 index 00000000..cb5b54c9 --- /dev/null +++ b/lib/io/IStream.h @@ -0,0 +1,157 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISTREAM_H +#define ISTREAM_H + +#include "IInterface.h" +#include "CEvent.h" + +//! Bidirectional stream interface +/*! +Defines the interface for all streams. +*/ +class IStream : public IInterface { +public: + //! @name manipulators + //@{ + + //! Close the stream + /*! + Closes the stream. Pending input data and buffered output data + are discarded. Use \c flush() before \c close() to send buffered + output data. Attempts to \c read() after a close return 0, + attempts to \c write() generate output error events, and attempts + to \c flush() return immediately. + */ + virtual void close() = 0; + + //! Read from stream + /*! + Read up to \p n bytes into \p buffer, returning the number read + (zero if no data is available or input is shutdown). \p buffer + may be NULL in which case the data is discarded. + */ + virtual UInt32 read(void* buffer, UInt32 n) = 0; + + //! Write to stream + /*! + Write \c n bytes from \c buffer to the stream. If this can't + complete immediately it will block. Data may be buffered in + order to return more quickly. A output error event is generated + when writing fails. + */ + virtual void write(const void* buffer, UInt32 n) = 0; + + //! Flush the stream + /*! + Waits until all buffered data has been written to the stream. + */ + virtual void flush() = 0; + + //! Shutdown input + /*! + Shutdown the input side of the stream. Any pending input data is + discarded and further reads immediately return 0. + */ + virtual void shutdownInput() = 0; + + //! Shutdown output + /*! + Shutdown the output side of the stream. Any buffered output data + is discarded and further writes generate output error events. Use + \c flush() before \c shutdownOutput() to send buffered output data. + */ + virtual void shutdownOutput() = 0; + + //@} + //! @name accessors + //@{ + + //! Get event target + /*! + Returns the event target for events generated by this stream. It + should be the source stream in a chain of stream filters. + */ + virtual void* getEventTarget() const = 0; + + //! Test if \c read() will succeed + /*! + Returns true iff an immediate \c read() will return data. This + may or may not be the same as \c getSize() > 0, depending on the + stream type. + */ + virtual bool isReady() const = 0; + + //! Get bytes available to read + /*! + Returns a conservative estimate of the available bytes to read + (i.e. a number not greater than the actual number of bytes). + Some streams may not be able to determine this and will always + return zero. + */ + virtual UInt32 getSize() const = 0; + + //! Get input ready event type + /*! + Returns the input ready event type. A stream sends this event + when \c read() will return with data. + */ + static CEvent::Type getInputReadyEvent(); + + //! Get output flushed event type + /*! + Returns the output flushed event type. A stream sends this event + when the output buffer has been flushed. If there have been no + writes since the event was posted, calling \c shutdownOutput() or + \c close() will not discard any data and \c flush() will return + immediately. + */ + static CEvent::Type getOutputFlushedEvent(); + + //! Get output error event type + /*! + Returns the output error event type. A stream sends this event + when a write has failed. + */ + static CEvent::Type getOutputErrorEvent(); + + //! Get input shutdown event type + /*! + Returns the input shutdown event type. This is sent when the + input side of the stream has shutdown. When the input has + shutdown, no more data will ever be available to read. + */ + static CEvent::Type getInputShutdownEvent(); + + //! Get output shutdown event type + /*! + Returns the output shutdown event type. This is sent when the + output side of the stream has shutdown. When the output has + shutdown, no more data can ever be written to the stream. Any + attempt to do so will generate a output error event. + */ + static CEvent::Type getOutputShutdownEvent(); + + //@} + +private: + static CEvent::Type s_inputReadyEvent; + static CEvent::Type s_outputFlushedEvent; + static CEvent::Type s_outputErrorEvent; + static CEvent::Type s_inputShutdownEvent; + static CEvent::Type s_outputShutdownEvent; +}; + +#endif diff --git a/lib/io/IStreamFilterFactory.h b/lib/io/IStreamFilterFactory.h new file mode 100644 index 00000000..6e6c86ef --- /dev/null +++ b/lib/io/IStreamFilterFactory.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISTREAMFILTERFACTORY_H +#define ISTREAMFILTERFACTORY_H + +#include "IInterface.h" + +class IStream; + +//! Stream filter factory interface +/*! +This interface provides factory methods to create stream filters. +*/ +class IStreamFilterFactory : public IInterface { +public: + //! Create filter + /*! + Create and return a stream filter on \p stream. The caller must + delete the returned object. + */ + virtual IStream* create(IStream* stream, bool adoptStream) = 0; +}; + +#endif diff --git a/lib/io/Makefile.am b/lib/io/Makefile.am new file mode 100644 index 00000000..fec1dd31 --- /dev/null +++ b/lib/io/Makefile.am @@ -0,0 +1,41 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libio.a +libio_a_SOURCES = \ + CStreamBuffer.cpp \ + CStreamFilter.cpp \ + IStream.cpp \ + XIO.cpp \ + CStreamBuffer.h \ + CStreamFilter.h \ + IStream.h \ + IStreamFilterFactory.h \ + XIO.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + $(NULL) diff --git a/lib/io/Makefile.win b/lib/io/Makefile.win new file mode 100644 index 00000000..765c6909 --- /dev/null +++ b/lib/io/Makefile.win @@ -0,0 +1,63 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +LIB_IO_SRC = lib\io +LIB_IO_DST = $(BUILD_DST)\$(LIB_IO_SRC) +LIB_IO_LIB = "$(LIB_IO_DST)\io.lib" +LIB_IO_CPP = \ + "CStreamBuffer.cpp" \ + "CStreamFilter.cpp" \ + "IStream.cpp" \ + "XIO.cpp" \ + $(NULL) +LIB_IO_OBJ = \ + "$(LIB_IO_DST)\CStreamBuffer.obj" \ + "$(LIB_IO_DST)\CStreamFilter.obj" \ + "$(LIB_IO_DST)\IStream.obj" \ + "$(LIB_IO_DST)\XIO.obj" \ + $(NULL) +LIB_IO_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_IO_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_IO_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_IO_LIB) + +# Dependency rules +$(LIB_IO_OBJ): $(AUTODEP) +!if EXIST($(LIB_IO_DST)\deps.mak) +!include $(LIB_IO_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_IO_SRC)\}.cpp{$(LIB_IO_DST)\}.obj:: +!else +{$(LIB_IO_SRC)\}.cpp{$(LIB_IO_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_IO_SRC) + -@$(MKDIR) $(LIB_IO_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_IO_INC) \ + /Fo$(LIB_IO_DST)\ \ + /Fd$(LIB_IO_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_IO_SRC) $(LIB_IO_DST) +$(LIB_IO_LIB): $(LIB_IO_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_IO_SRC) $(LIB_IO_DST) $(**:.obj=.d) diff --git a/lib/io/XIO.cpp b/lib/io/XIO.cpp new file mode 100644 index 00000000..b7101a4d --- /dev/null +++ b/lib/io/XIO.cpp @@ -0,0 +1,47 @@ +/* + * 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 "XIO.h" + +// +// XIOClosed +// + +CString +XIOClosed::getWhat() const throw() +{ + return format("XIOClosed", "already closed"); +} + + +// +// XIOEndOfStream +// + +CString +XIOEndOfStream::getWhat() const throw() +{ + return format("XIOEndOfStream", "reached end of stream"); +} + + +// +// XIOWouldBlock +// + +CString +XIOWouldBlock::getWhat() const throw() +{ + return format("XIOWouldBlock", "stream operation would block"); +} diff --git a/lib/io/XIO.h b/lib/io/XIO.h new file mode 100644 index 00000000..cc41ef40 --- /dev/null +++ b/lib/io/XIO.h @@ -0,0 +1,48 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XIO_H +#define XIO_H + +#include "XBase.h" + +//! Generic I/O exception +XBASE_SUBCLASS(XIO, XBase); + +//! I/O closing exception +/*! +Thrown if a stream cannot be closed. +*/ +XBASE_SUBCLASS(XIOClose, XIO); + +//! I/O already closed exception +/*! +Thrown when attempting to close or perform I/O on an already closed. +stream. +*/ +XBASE_SUBCLASS_WHAT(XIOClosed, XIO); + +//! I/O end of stream exception +/*! +Thrown when attempting to read beyond the end of a stream. +*/ +XBASE_SUBCLASS_WHAT(XIOEndOfStream, XIO); + +//! I/O would block exception +/*! +Thrown if an operation on a stream would block. +*/ +XBASE_SUBCLASS_WHAT(XIOWouldBlock, XIO); + +#endif diff --git a/lib/mt/CCondVar.cpp b/lib/mt/CCondVar.cpp new file mode 100644 index 00000000..d13aedfd --- /dev/null +++ b/lib/mt/CCondVar.cpp @@ -0,0 +1,81 @@ +/* + * 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 "CCondVar.h" +#include "CStopwatch.h" +#include "CArch.h" + +// +// CCondVarBase +// + +CCondVarBase::CCondVarBase(CMutex* mutex) : + m_mutex(mutex) +{ + assert(m_mutex != NULL); + m_cond = ARCH->newCondVar(); +} + +CCondVarBase::~CCondVarBase() +{ + ARCH->closeCondVar(m_cond); +} + +void +CCondVarBase::lock() const +{ + m_mutex->lock(); +} + +void +CCondVarBase::unlock() const +{ + m_mutex->unlock(); +} + +void +CCondVarBase::signal() +{ + ARCH->signalCondVar(m_cond); +} + +void +CCondVarBase::broadcast() +{ + ARCH->broadcastCondVar(m_cond); +} + +bool +CCondVarBase::wait(CStopwatch& timer, double timeout) const +{ + // check timeout against timer + if (timeout >= 0.0) { + timeout -= timer.getTime(); + if (timeout < 0.0) + return false; + } + return wait(timeout); +} + +bool +CCondVarBase::wait(double timeout) const +{ + return ARCH->waitCondVar(m_cond, m_mutex->m_mutex, timeout); +} + +CMutex* +CCondVarBase::getMutex() const +{ + return m_mutex; +} diff --git a/lib/mt/CCondVar.h b/lib/mt/CCondVar.h new file mode 100644 index 00000000..a04102c4 --- /dev/null +++ b/lib/mt/CCondVar.h @@ -0,0 +1,224 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCONDVAR_H +#define CCONDVAR_H + +#include "CMutex.h" +#include "BasicTypes.h" + +class CStopwatch; + +//! Generic condition variable +/*! +This class provides functionality common to all condition variables +but doesn't provide the actual variable storage. A condition variable +is a multiprocessing primitive that can be waited on. Every condition +variable has an associated mutex. +*/ +class CCondVarBase { +public: + /*! + \c mutex must not be NULL. All condition variables have an + associated mutex. The mutex needn't be unique to one condition + variable. + */ + CCondVarBase(CMutex* mutex); + ~CCondVarBase(); + + //! @name manipulators + //@{ + + //! Lock the condition variable's mutex + /*! + Lock the condition variable's mutex. The condition variable should + be locked before reading or writing it. It must be locked for a + call to wait(). Locks are not recursive; locking a locked mutex + will deadlock the thread. + */ + void lock() const; + + //! Unlock the condition variable's mutex + void unlock() const; + + //! Signal the condition variable + /*! + Wake up one waiting thread, if there are any. Which thread gets + woken is undefined. + */ + void signal(); + + //! Signal the condition variable + /*! + Wake up all waiting threads, if any. + */ + void broadcast(); + + //@} + //! @name accessors + //@{ + + //! Wait on the condition variable + /*! + Wait on the condition variable. If \c timeout < 0 then wait until + signalled, otherwise up to \c timeout seconds or until signalled, + whichever comes first. Returns true if the object was signalled + during the wait, false otherwise. + + The proper way to wait for a condition is: + \code + cv.lock(); + while (cv-expr) { + cv.wait(); + } + cv.unlock(); + \endcode + where \c cv-expr involves the value of \c cv and is false when the + condition is satisfied. + + (cancellation point) + */ + bool wait(double timeout = -1.0) const; + + //! Wait on the condition variable + /*! + Same as \c wait(double) but use \c timer to compare against \timeout. + Since clients normally wait on condition variables in a loop, clients + can use this to avoid recalculating \c timeout on each iteration. + Passing a stopwatch with a negative \c timeout is pointless (it will + never time out) but permitted. + + (cancellation point) + */ + bool wait(CStopwatch& timer, double timeout) const; + + //! Get the mutex + /*! + Get the mutex passed to the c'tor. + */ + CMutex* getMutex() const; + + //@} + +private: + // not implemented + CCondVarBase(const CCondVarBase&); + CCondVarBase& operator=(const CCondVarBase&); + +private: + CMutex* m_mutex; + CArchCond m_cond; +}; + +//! Condition variable +/*! +A condition variable with storage for type \c T. +*/ +template +class CCondVar : public CCondVarBase { +public: + //! Initialize using \c value + CCondVar(CMutex* mutex, const T& value); + //! Initialize using another condition variable's value + CCondVar(const CCondVar&); + ~CCondVar(); + + //! @name manipulators + //@{ + + //! Assigns the value of \c cv to this + /*! + Set the variable's value. The condition variable should be locked + before calling this method. + */ + CCondVar& operator=(const CCondVar& cv); + + //! Assigns \c value to this + /*! + Set the variable's value. The condition variable should be locked + before calling this method. + */ + CCondVar& operator=(const T& v); + + //@} + //! @name accessors + //@{ + + //! Get the variable's value + /*! + Get the variable's value. The condition variable should be locked + before calling this method. + */ + operator const volatile T&() const; + + //@} + +private: + volatile T m_data; +}; + +template +inline +CCondVar::CCondVar( + CMutex* mutex, + const T& data) : + CCondVarBase(mutex), + m_data(data) +{ + // do nothing +} + +template +inline +CCondVar::CCondVar( + const CCondVar& cv) : + CCondVarBase(cv.getMutex()), + m_data(cv.m_data) +{ + // do nothing +} + +template +inline +CCondVar::~CCondVar() +{ + // do nothing +} + +template +inline +CCondVar& +CCondVar::operator=(const CCondVar& cv) +{ + m_data = cv.m_data; + return *this; +} + +template +inline +CCondVar& +CCondVar::operator=(const T& data) +{ + m_data = data; + return *this; +} + +template +inline +CCondVar::operator const volatile T&() const +{ + return m_data; +} + +#endif diff --git a/lib/mt/CLock.cpp b/lib/mt/CLock.cpp new file mode 100644 index 00000000..c943395b --- /dev/null +++ b/lib/mt/CLock.cpp @@ -0,0 +1,38 @@ +/* + * 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 "CLock.h" +#include "CCondVar.h" +#include "CMutex.h" + +// +// CLock +// + +CLock::CLock(const CMutex* mutex) : + m_mutex(mutex) +{ + m_mutex->lock(); +} + +CLock::CLock(const CCondVarBase* cv) : + m_mutex(cv->getMutex()) +{ + m_mutex->lock(); +} + +CLock::~CLock() +{ + m_mutex->unlock(); +} diff --git a/lib/mt/CLock.h b/lib/mt/CLock.h new file mode 100644 index 00000000..f00e0cd1 --- /dev/null +++ b/lib/mt/CLock.h @@ -0,0 +1,48 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CLOCK_H +#define CLOCK_H + +#include "common.h" + +class CMutex; +class CCondVarBase; + +//! Mutual exclusion lock utility +/*! +This class locks a mutex or condition variable in the c'tor and unlocks +it in the d'tor. It's easier and safer than manually locking and +unlocking since unlocking must usually be done no matter how a function +exits (including by unwinding due to an exception). +*/ +class CLock { +public: + //! Lock the mutex \c mutex + CLock(const CMutex* mutex); + //! Lock the condition variable \c cv + CLock(const CCondVarBase* cv); + //! Unlock the mutex or condition variable + ~CLock(); + +private: + // not implemented + CLock(const CLock&); + CLock& operator=(const CLock&); + +private: + const CMutex* m_mutex; +}; + +#endif diff --git a/lib/mt/CMutex.cpp b/lib/mt/CMutex.cpp new file mode 100644 index 00000000..0d2bab3c --- /dev/null +++ b/lib/mt/CMutex.cpp @@ -0,0 +1,53 @@ +/* + * 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 "CMutex.h" +#include "CArch.h" + +// +// CMutex +// + +CMutex::CMutex() +{ + m_mutex = ARCH->newMutex(); +} + +CMutex::CMutex(const CMutex&) +{ + m_mutex = ARCH->newMutex(); +} + +CMutex::~CMutex() +{ + ARCH->closeMutex(m_mutex); +} + +CMutex& +CMutex::operator=(const CMutex&) +{ + return *this; +} + +void +CMutex::lock() const +{ + ARCH->lockMutex(m_mutex); +} + +void +CMutex::unlock() const +{ + ARCH->unlockMutex(m_mutex); +} diff --git a/lib/mt/CMutex.h b/lib/mt/CMutex.h new file mode 100644 index 00000000..c11eeee3 --- /dev/null +++ b/lib/mt/CMutex.h @@ -0,0 +1,78 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMUTEX_H +#define CMUTEX_H + +#include "IArchMultithread.h" + +//! Mutual exclusion +/*! +A non-recursive mutual exclusion object. Only one thread at a time can +hold a lock on a mutex. Any thread that attempts to lock a locked mutex +will block until the mutex is unlocked. At that time, if any threads are +blocked, exactly one waiting thread will acquire the lock and continue +running. A thread may not lock a mutex it already owns the lock on; if +it tries it will deadlock itself. +*/ +class CMutex { +public: + CMutex(); + //! Equivalent to default c'tor + /*! + Copy c'tor doesn't copy anything. It just makes it possible to + copy objects that contain a mutex. + */ + CMutex(const CMutex&); + ~CMutex(); + + //! @name manipulators + //@{ + + //! Does nothing + /*! + This does nothing. It just makes it possible to assign objects + that contain a mutex. + */ + CMutex& operator=(const CMutex&); + + //@} + //! @name accessors + //@{ + + //! Lock the mutex + /*! + Locks the mutex, which must not have been previously locked by the + calling thread. This blocks if the mutex is already locked by another + thread. + + (cancellation point) + */ + void lock() const; + + //! Unlock the mutex + /*! + Unlocks the mutex, which must have been previously locked by the + calling thread. + */ + void unlock() const; + + //@} + +private: + friend class CCondVarBase; + CArchMutex m_mutex; +}; + +#endif diff --git a/lib/mt/CThread.cpp b/lib/mt/CThread.cpp new file mode 100644 index 00000000..195b7b67 --- /dev/null +++ b/lib/mt/CThread.cpp @@ -0,0 +1,183 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CThread.h" +#include "XMT.h" +#include "XThread.h" +#include "CLog.h" +#include "IJob.h" +#include "CArch.h" + +// +// CThread +// + +CThread::CThread(IJob* job) +{ + m_thread = ARCH->newThread(&CThread::threadFunc, job); + if (m_thread == NULL) { + // couldn't create thread + delete job; + throw XMTThreadUnavailable(); + } +} + +CThread::CThread(const CThread& thread) +{ + m_thread = ARCH->copyThread(thread.m_thread); +} + +CThread::CThread(CArchThread adoptedThread) +{ + m_thread = adoptedThread; +} + +CThread::~CThread() +{ + ARCH->closeThread(m_thread); +} + +CThread& +CThread::operator=(const CThread& thread) +{ + // copy given thread and release ours + CArchThread copy = ARCH->copyThread(thread.m_thread); + ARCH->closeThread(m_thread); + + // cut over + m_thread = copy; + + return *this; +} + +void +CThread::exit(void* result) +{ + throw XThreadExit(result); +} + +void +CThread::cancel() +{ + ARCH->cancelThread(m_thread); +} + +void +CThread::setPriority(int n) +{ + ARCH->setPriorityOfThread(m_thread, n); +} + +void +CThread::unblockPollSocket() +{ + ARCH->unblockPollSocket(m_thread); +} + +CThread +CThread::getCurrentThread() +{ + return CThread(ARCH->newCurrentThread()); +} + +void +CThread::testCancel() +{ + ARCH->testCancelThread(); +} + +bool +CThread::wait(double timeout) const +{ + return ARCH->wait(m_thread, timeout); +} + +void* +CThread::getResult() const +{ + if (wait()) + return ARCH->getResultOfThread(m_thread); + else + return NULL; +} + +IArchMultithread::ThreadID +CThread::getID() const +{ + return ARCH->getIDOfThread(m_thread); +} + +bool +CThread::operator==(const CThread& thread) const +{ + return ARCH->isSameThread(m_thread, thread.m_thread); +} + +bool +CThread::operator!=(const CThread& thread) const +{ + return !ARCH->isSameThread(m_thread, thread.m_thread); +} + +void* +CThread::threadFunc(void* vjob) +{ + // get this thread's id for logging + IArchMultithread::ThreadID id; + { + CArchThread thread = ARCH->newCurrentThread(); + id = ARCH->getIDOfThread(thread); + ARCH->closeThread(thread); + } + + // get job + IJob* job = reinterpret_cast(vjob); + + // run job + void* result = NULL; + try { + // go + LOG((CLOG_DEBUG1 "thread 0x%08x entry", id)); + job->run(); + LOG((CLOG_DEBUG1 "thread 0x%08x exit", id)); + } + + catch (XThreadCancel&) { + // client called cancel() + LOG((CLOG_DEBUG1 "caught cancel on thread 0x%08x", id)); + delete job; + throw; + } + catch (XThreadExit& e) { + // client called exit() + result = e.m_result; + LOG((CLOG_DEBUG1 "caught exit on thread 0x%08x, result %p", id, result)); + } + catch (XBase& e) { + LOG((CLOG_ERR "exception on thread 0x%08x: %s", id, e.what())); + delete job; + throw; + } + catch (...) { + LOG((CLOG_ERR "exception on thread 0x%08x: ", id)); + delete job; + throw; + } + + // done with job + delete job; + + // return exit result + return result; +} diff --git a/lib/mt/CThread.h b/lib/mt/CThread.h new file mode 100644 index 00000000..896d3778 --- /dev/null +++ b/lib/mt/CThread.h @@ -0,0 +1,209 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CTHREAD_H +#define CTHREAD_H + +#include "IArchMultithread.h" + +class IJob; + +//! Thread handle +/*! +Creating a CThread creates a new context of execution (i.e. thread) that +runs simulatenously with the calling thread. A CThread is only a handle +to a thread; deleting a CThread does not cancel or destroy the thread it +refers to and multiple CThread objects can refer to the same thread. + +Threads can terminate themselves but cannot be forced to terminate by +other threads. However, other threads can signal a thread to terminate +itself by cancelling it. And a thread can wait (block) on another thread +to terminate. + +Most functions that can block for an arbitrary time are cancellation +points. A cancellation point is a function that can be interrupted by +a request to cancel the thread. Cancellation points are noted in the +documentation. +*/ +// note -- do not derive from this class +class CThread { +public: + //! Run \c adoptedJob in a new thread + /*! + Create and start a new thread executing the \c adoptedJob. The + new thread takes ownership of \c adoptedJob and will delete it. + */ + CThread(IJob* adoptedJob); + + //! Duplicate a thread handle + /*! + Make a new thread object that refers to an existing thread. + This does \b not start a new thread. + */ + CThread(const CThread&); + + //! Release a thread handle + /*! + Release a thread handle. This does not terminate the thread. A thread + will keep running until the job completes or calls exit() or allows + itself to be cancelled. + */ + ~CThread(); + + //! @name manipulators + //@{ + + //! Assign thread handle + /*! + Assign a thread handle. This has no effect on the threads, it simply + makes this thread object refer to another thread. It does \b not + start a new thread. + */ + CThread& operator=(const CThread&); + + //! Terminate the calling thread + /*! + Terminate the calling thread. This function does not return but + the stack is unwound and automatic objects are destroyed, as if + exit() threw an exception (which is, in fact, what it does). The + argument is saved as the result returned by getResult(). If you + have \c catch(...) blocks then you should add the following before + each to avoid catching the exit: + \code + catch(CThreadExit&) { throw; } + \endcode + or add the \c RETHROW_XTHREAD macro to the \c catch(...) block. + */ + static void exit(void*); + + //! Cancel thread + /*! + Cancel the thread. cancel() never waits for the thread to + terminate; it just posts the cancel and returns. A thread will + terminate when it enters a cancellation point with cancellation + enabled. If cancellation is disabled then the cancel is + remembered but not acted on until the first call to a + cancellation point after cancellation is enabled. + + A cancellation point is a function that can act on cancellation. + A cancellation point does not return if there's a cancel pending. + Instead, it unwinds the stack and destroys automatic objects, as + if cancel() threw an exception (which is, in fact, what it does). + Threads must take care to unlock and clean up any resources they + may have, especially mutexes. They can \c catch(XThreadCancel) to + do that then rethrow the exception or they can let it happen + automatically by doing clean up in the d'tors of automatic + objects (like CLock). Clients are strongly encouraged to do the latter. + During cancellation, further cancel() calls are ignored (i.e. + a thread cannot be interrupted by a cancel during cancellation). + + Clients that \c catch(XThreadCancel) must always rethrow the + exception. Clients that \c catch(...) must either rethrow the + exception or include a \c catch(XThreadCancel) handler that + rethrows. The \c RETHROW_XTHREAD macro may be useful for that. + */ + void cancel(); + + //! Change thread priority + /*! + Change the priority of the thread. Normal priority is 0, 1 is + the next lower, etc. -1 is the next higher, etc. but boosting + the priority may not be permitted and will be silenty ignored. + */ + void setPriority(int n); + + //! Force pollSocket() to return + /*! + Forces a currently blocked pollSocket() in the thread to return + immediately. + */ + void unblockPollSocket(); + + //@} + //! @name accessors + //@{ + + //! Get current thread's handle + /*! + Return a CThread object representing the calling thread. + */ + static CThread getCurrentThread(); + + //! Test for cancellation + /*! + testCancel() does nothing but is a cancellation point. Call + this to make a function itself a cancellation point. If the + thread was cancelled and cancellation is enabled this will + cause the thread to unwind the stack and terminate. + + (cancellation point) + */ + static void testCancel(); + + //! Wait for thread to terminate + /*! + Waits for the thread to terminate (by exit() or cancel() or + by returning from the thread job) for up to \c timeout seconds, + returning true if the thread terminated and false otherwise. + This returns immediately with false if called by a thread on + itself and immediately with true if the thread has already + terminated. This will wait forever if \c timeout < 0.0. + + (cancellation point) + */ + bool wait(double timeout = -1.0) const; + + //! Get the exit result + /*! + Returns the exit result. This does an implicit wait(). It returns + NULL immediately if called by a thread on itself or on a thread that + was cancelled. + + (cancellation point) + */ + void* getResult() const; + + //! Get the thread id + /*! + Returns an integer id for this thread. This id must not be used to + check if two CThread objects refer to the same thread. Use + operator==() for that. + */ + IArchMultithread::ThreadID + getID() const; + + //! Compare thread handles + /*! + Returns true if two CThread objects refer to the same thread. + */ + bool operator==(const CThread&) const; + + //! Compare thread handles + /*! + Returns true if two CThread objects do not refer to the same thread. + */ + bool operator!=(const CThread&) const; + + //@} + +private: + CThread(CArchThread); + + static void* threadFunc(void*); + +private: + CArchThread m_thread; +}; + +#endif diff --git a/lib/mt/Makefile.am b/lib/mt/Makefile.am new file mode 100644 index 00000000..fbf01c03 --- /dev/null +++ b/lib/mt/Makefile.am @@ -0,0 +1,42 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libmt.a +libmt_a_SOURCES = \ + CCondVar.cpp \ + CLock.cpp \ + CMutex.cpp \ + CThread.cpp \ + XMT.cpp \ + CCondVar.h \ + CLock.h \ + CMutex.h \ + CThread.h \ + XMT.h \ + XThread.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + $(NULL) diff --git a/lib/mt/Makefile.win b/lib/mt/Makefile.win new file mode 100644 index 00000000..4ae79130 --- /dev/null +++ b/lib/mt/Makefile.win @@ -0,0 +1,64 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +LIB_MT_SRC = lib\mt +LIB_MT_DST = $(BUILD_DST)\$(LIB_MT_SRC) +LIB_MT_LIB = "$(LIB_MT_DST)\mt.lib" +LIB_MT_CPP = \ + "CCondVar.cpp" \ + "CLock.cpp" \ + "CMutex.cpp" \ + "CThread.cpp" \ + "XMT.cpp" \ + $(NULL) +LIB_MT_OBJ = \ + "$(LIB_MT_DST)\CCondVar.obj" \ + "$(LIB_MT_DST)\CLock.obj" \ + "$(LIB_MT_DST)\CMutex.obj" \ + "$(LIB_MT_DST)\CThread.obj" \ + "$(LIB_MT_DST)\XMT.obj" \ + $(NULL) +LIB_MT_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_MT_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_MT_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_MT_LIB) + +# Dependency rules +$(LIB_MT_OBJ): $(AUTODEP) +!if EXIST($(LIB_MT_DST)\deps.mak) +!include $(LIB_MT_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_MT_SRC)\}.cpp{$(LIB_MT_DST)\}.obj:: +!else +{$(LIB_MT_SRC)\}.cpp{$(LIB_MT_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_MT_SRC) + -@$(MKDIR) $(LIB_MT_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_MT_INC) \ + /Fo$(LIB_MT_DST)\ \ + /Fd$(LIB_MT_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_MT_SRC) $(LIB_MT_DST) +$(LIB_MT_LIB): $(LIB_MT_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_MT_SRC) $(LIB_MT_DST) $(**:.obj=.d) diff --git a/lib/mt/XMT.cpp b/lib/mt/XMT.cpp new file mode 100644 index 00000000..b10d2d79 --- /dev/null +++ b/lib/mt/XMT.cpp @@ -0,0 +1,25 @@ +/* + * 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 "XMT.h" + +// +// XMTThreadUnavailable +// + +CString +XMTThreadUnavailable::getWhat() const throw() +{ + return format("XMTThreadUnavailable", "cannot create thread"); +} diff --git a/lib/mt/XMT.h b/lib/mt/XMT.h new file mode 100644 index 00000000..f66428ea --- /dev/null +++ b/lib/mt/XMT.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. + */ + +#ifndef XMT_H +#define XMT_H + +#include "XBase.h" + +//! Generic multithreading exception +XBASE_SUBCLASS(XMT, XBase); + +//! Thread creation exception +/*! +Thrown when a thread cannot be created. +*/ +XBASE_SUBCLASS_WHAT(XMTThreadUnavailable, XMT); + +#endif diff --git a/lib/mt/XThread.h b/lib/mt/XThread.h new file mode 100644 index 00000000..30dff1da --- /dev/null +++ b/lib/mt/XThread.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XTHREAD_H +#define XTHREAD_H + +#include "XArch.h" + +//! Thread exception to exit +/*! +Thrown by CThread::exit() to exit a thread. Clients of CThread +must not throw this type but must rethrow it if caught (by +XThreadExit, XThread, or ...). +*/ +class XThreadExit : public XThread { +public: + //! \c result is the result of the thread + XThreadExit(void* result) : m_result(result) { } + ~XThreadExit() { } + +public: + void* m_result; +}; + +#endif diff --git a/lib/net/CNetworkAddress.cpp b/lib/net/CNetworkAddress.cpp new file mode 100644 index 00000000..7daeed55 --- /dev/null +++ b/lib/net/CNetworkAddress.cpp @@ -0,0 +1,208 @@ +/* + * 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 "CNetworkAddress.h" +#include "XSocket.h" +#include "CArch.h" +#include "XArch.h" +#include + +// +// CNetworkAddress +// + +// name re-resolution adapted from a patch by Brent Priddy. + +CNetworkAddress::CNetworkAddress() : + m_address(NULL), + m_hostname(), + m_port(0) +{ + // note -- make no calls to CNetwork socket interface here; + // we're often called prior to CNetwork::init(). +} + +CNetworkAddress::CNetworkAddress(int port) : + m_address(NULL), + m_hostname(), + m_port(port) +{ + checkPort(); + m_address = ARCH->newAnyAddr(IArchNetwork::kINET); + ARCH->setAddrPort(m_address, m_port); +} + +CNetworkAddress::CNetworkAddress(const CNetworkAddress& addr) : + m_address(addr.m_address != NULL ? ARCH->copyAddr(addr.m_address) : NULL), + m_hostname(addr.m_hostname), + m_port(addr.m_port) +{ + // do nothing +} + +CNetworkAddress::CNetworkAddress(const CString& hostname, int port) : + m_address(NULL), + m_hostname(hostname), + m_port(port) +{ + // check for port suffix + CString::size_type i = m_hostname.rfind(':'); + if (i != CString::npos && i + 1 < m_hostname.size()) { + // found a colon. see if it looks like an IPv6 address. + bool colonNotation = false; + bool dotNotation = false; + bool doubleColon = false; + for (CString::size_type j = 0; j < i; ++j) { + if (m_hostname[j] == ':') { + colonNotation = true; + dotNotation = false; + if (m_hostname[j + 1] == ':') { + doubleColon = true; + } + } + else if (m_hostname[j] == '.' && colonNotation) { + dotNotation = true; + } + } + + // port suffix is ambiguous with IPv6 notation if there's + // a double colon and the end of the address is not in dot + // notation. in that case we assume it's not a port suffix. + // the user can replace the double colon with zeros to + // disambiguate. + if ((!doubleColon || dotNotation) || !colonNotation) { + // parse port from hostname + char* end; + const char* chostname = m_hostname.c_str(); + long suffixPort = strtol(chostname + i + 1, &end, 10); + if (end == chostname + i + 1 || *end != '\0') { + throw XSocketAddress(XSocketAddress::kBadPort, + m_hostname, m_port); + } + + // trim port from hostname + m_hostname.erase(i); + + // save port + m_port = static_cast(suffixPort); + } + } + + // check port number + checkPort(); +} + +CNetworkAddress::~CNetworkAddress() +{ + if (m_address != NULL) { + ARCH->closeAddr(m_address); + } +} + +CNetworkAddress& +CNetworkAddress::operator=(const CNetworkAddress& addr) +{ + CArchNetAddress newAddr = NULL; + if (addr.m_address != NULL) { + newAddr = ARCH->copyAddr(addr.m_address); + } + if (m_address != NULL) { + ARCH->closeAddr(m_address); + } + m_address = newAddr; + m_hostname = addr.m_hostname; + m_port = addr.m_port; + return *this; +} + +void +CNetworkAddress::resolve() +{ + // discard previous address + if (m_address != NULL) { + ARCH->closeAddr(m_address); + m_address = NULL; + } + + try { + // if hostname is empty then use wildcard address otherwise look + // up the name. + if (m_hostname.empty()) { + m_address = ARCH->newAnyAddr(IArchNetwork::kINET); + } + else { + m_address = ARCH->nameToAddr(m_hostname); + } + } + catch (XArchNetworkNameUnknown&) { + throw XSocketAddress(XSocketAddress::kNotFound, m_hostname, m_port); + } + catch (XArchNetworkNameNoAddress&) { + throw XSocketAddress(XSocketAddress::kNoAddress, m_hostname, m_port); + } + catch (XArchNetworkNameUnsupported&) { + throw XSocketAddress(XSocketAddress::kUnsupported, m_hostname, m_port); + } + catch (XArchNetworkName&) { + throw XSocketAddress(XSocketAddress::kUnknown, m_hostname, m_port); + } + + // set port in address + ARCH->setAddrPort(m_address, m_port); +} + +bool +CNetworkAddress::operator==(const CNetworkAddress& addr) const +{ + return ARCH->isEqualAddr(m_address, addr.m_address); +} + +bool +CNetworkAddress::operator!=(const CNetworkAddress& addr) const +{ + return !operator==(addr); +} + +bool +CNetworkAddress::isValid() const +{ + return (m_address != NULL); +} + +const CArchNetAddress& +CNetworkAddress::getAddress() const +{ + return m_address; +} + +int +CNetworkAddress::getPort() const +{ + return m_port; +} + +CString +CNetworkAddress::getHostname() const +{ + return m_hostname; +} + +void +CNetworkAddress::checkPort() +{ + // check port number + if (m_port <= 0 || m_port > 65535) { + throw XSocketAddress(XSocketAddress::kBadPort, m_hostname, m_port); + } +} diff --git a/lib/net/CNetworkAddress.h b/lib/net/CNetworkAddress.h new file mode 100644 index 00000000..d29b93b1 --- /dev/null +++ b/lib/net/CNetworkAddress.h @@ -0,0 +1,122 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CNETWORKADDRESS_H +#define CNETWORKADDRESS_H + +#include "CString.h" +#include "BasicTypes.h" +#include "IArchNetwork.h" + +//! Network address type +/*! +This class represents a network address. +*/ +class CNetworkAddress { +public: + /*! + Constructs the invalid address + */ + CNetworkAddress(); + + /*! + Construct the wildcard address with the given port. \c port must + not be zero. + */ + CNetworkAddress(int port); + + /*! + Construct the network address for the given \c hostname and \c port. + If \c hostname can be parsed as a numerical address then that's how + it's used, otherwise it's used as a host name. If \c hostname ends + in ":[0-9]+" then that suffix is extracted and used as the port, + overridding the port parameter. The resulting port must be a valid + port number (zero is not a valid port number) otherwise \c XSocketAddress + is thrown with an error of \c XSocketAddress::kBadPort. The hostname + is not resolved by the c'tor; use \c resolve to do that. + */ + CNetworkAddress(const CString& hostname, int port); + + CNetworkAddress(const CNetworkAddress&); + + ~CNetworkAddress(); + + CNetworkAddress& operator=(const CNetworkAddress&); + + //! @name manipulators + //@{ + + //! Resolve address + /*! + Resolves the hostname to an address. This can be done any number of + times and is done automatically by the c'tor taking a hostname. + Throws XSocketAddress if resolution is unsuccessful, after which + \c isValid returns false until the next call to this method. + */ + void resolve(); + + //@} + //! @name accessors + //@{ + + //! Check address equality + /*! + Returns true if this address is equal to \p address. + */ + bool operator==(const CNetworkAddress& address) const; + + //! Check address inequality + /*! + Returns true if this address is not equal to \p address. + */ + bool operator!=(const CNetworkAddress& address) const; + + //! Check address validity + /*! + Returns true if this is not the invalid address. + */ + bool isValid() const; + + //! Get address + /*! + Returns the address in the platform's native network address + structure. + */ + const CArchNetAddress& getAddress() const; + + //! Get port + /*! + Returns the port passed to the c'tor as a suffix to the hostname, + if that existed, otherwise as passed directly to the c'tor. + */ + int getPort() const; + + //! Get hostname + /*! + Returns the hostname passed to the c'tor sans any port suffix. + */ + CString getHostname() const; + + //@} + +private: + void checkPort(); + +private: + CArchNetAddress m_address; + CString m_hostname; + int m_port; +}; + +#endif diff --git a/lib/net/CSocketMultiplexer.cpp b/lib/net/CSocketMultiplexer.cpp new file mode 100644 index 00000000..2082730b --- /dev/null +++ b/lib/net/CSocketMultiplexer.cpp @@ -0,0 +1,359 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CSocketMultiplexer.h" +#include "ISocketMultiplexerJob.h" +#include "CCondVar.h" +#include "CLock.h" +#include "CMutex.h" +#include "CThread.h" +#include "CLog.h" +#include "TMethodJob.h" +#include "CArch.h" +#include "XArch.h" +#include "stdvector.h" + +// +// CSocketMultiplexer +// + +CSocketMultiplexer* CSocketMultiplexer::s_instance = NULL; + +CSocketMultiplexer::CSocketMultiplexer() : + m_mutex(new CMutex), + m_thread(NULL), + m_update(false), + m_jobsReady(new CCondVar(m_mutex, false)), + m_jobListLock(new CCondVar(m_mutex, false)), + m_jobListLockLocked(new CCondVar(m_mutex, false)), + m_jobListLocker(NULL), + m_jobListLockLocker(NULL) +{ + assert(s_instance == NULL); + + // this pointer just has to be unique and not NULL. it will + // never be dereferenced. it's used to identify cursor nodes + // in the jobs list. + m_cursorMark = reinterpret_cast(this); + + // start thread + m_thread = new CThread(new TMethodJob( + this, &CSocketMultiplexer::serviceThread)); + + s_instance = this; +} + +CSocketMultiplexer::~CSocketMultiplexer() +{ + m_thread->cancel(); + m_thread->unblockPollSocket(); + m_thread->wait(); + delete m_thread; + delete m_jobsReady; + delete m_jobListLock; + delete m_jobListLockLocked; + delete m_jobListLocker; + delete m_jobListLockLocker; + delete m_mutex; + + // clean up jobs + for (CSocketJobMap::iterator i = m_socketJobMap.begin(); + i != m_socketJobMap.end(); ++i) { + delete *(i->second); + } + + s_instance = NULL; +} + +CSocketMultiplexer* +CSocketMultiplexer::getInstance() +{ + return s_instance; +} + +void +CSocketMultiplexer::addSocket(ISocket* socket, ISocketMultiplexerJob* job) +{ + assert(socket != NULL); + assert(job != NULL); + + // prevent other threads from locking the job list + lockJobListLock(); + + // break thread out of poll + m_thread->unblockPollSocket(); + + // lock the job list + lockJobList(); + + // insert/replace job + CSocketJobMap::iterator i = m_socketJobMap.find(socket); + if (i == m_socketJobMap.end()) { + // we *must* put the job at the end so the order of jobs in + // the list continue to match the order of jobs in pfds in + // serviceThread(). + CJobCursor j = m_socketJobs.insert(m_socketJobs.end(), job); + m_update = true; + m_socketJobMap.insert(std::make_pair(socket, j)); + } + else { + CJobCursor j = i->second; + if (*j != job) { + delete *j; + *j = job; + } + m_update = true; + } + + // unlock the job list + unlockJobList(); +} + +void +CSocketMultiplexer::removeSocket(ISocket* socket) +{ + assert(socket != NULL); + + // prevent other threads from locking the job list + lockJobListLock(); + + // break thread out of poll + m_thread->unblockPollSocket(); + + // lock the job list + lockJobList(); + + // remove job. rather than removing it from the map we put NULL + // in the list instead so the order of jobs in the list continues + // to match the order of jobs in pfds in serviceThread(). + CSocketJobMap::iterator i = m_socketJobMap.find(socket); + if (i != m_socketJobMap.end()) { + if (*(i->second) != NULL) { + delete *(i->second); + *(i->second) = NULL; + m_update = true; + } + } + + // unlock the job list + unlockJobList(); +} + +void +CSocketMultiplexer::serviceThread(void*) +{ + std::vector pfds; + IArchNetwork::CPollEntry pfd; + + // service the connections + for (;;) { + CThread::testCancel(); + + // wait until there are jobs to handle + { + CLock lock(m_mutex); + while (!(bool)*m_jobsReady) { + m_jobsReady->wait(); + } + } + + // lock the job list + lockJobListLock(); + lockJobList(); + + // collect poll entries + if (m_update) { + m_update = false; + pfds.clear(); + pfds.reserve(m_socketJobMap.size()); + + CJobCursor cursor = newCursor(); + CJobCursor jobCursor = nextCursor(cursor); + while (jobCursor != m_socketJobs.end()) { + ISocketMultiplexerJob* job = *jobCursor; + if (job != NULL) { + pfd.m_socket = job->getSocket(); + pfd.m_events = 0; + if (job->isReadable()) { + pfd.m_events |= IArchNetwork::kPOLLIN; + } + if (job->isWritable()) { + pfd.m_events |= IArchNetwork::kPOLLOUT; + } + pfds.push_back(pfd); + } + jobCursor = nextCursor(cursor); + } + deleteCursor(cursor); + } + + int status; + try { + // check for status + if (!pfds.empty()) { + status = ARCH->pollSocket(&pfds[0], pfds.size(), -1); + } + else { + status = 0; + } + } + catch (XArchNetwork& e) { + LOG((CLOG_WARN "error in socket multiplexer: %s", e.what().c_str())); + status = 0; + } + + if (status != 0) { + // iterate over socket jobs, invoking each and saving the + // new job. + UInt32 i = 0; + CJobCursor cursor = newCursor(); + CJobCursor jobCursor = nextCursor(cursor); + while (i < pfds.size() && jobCursor != m_socketJobs.end()) { + if (*jobCursor != NULL) { + // get poll state + unsigned short revents = pfds[i].m_revents; + bool read = ((revents & IArchNetwork::kPOLLIN) != 0); + bool write = ((revents & IArchNetwork::kPOLLOUT) != 0); + bool error = ((revents & (IArchNetwork::kPOLLERR | + IArchNetwork::kPOLLNVAL)) != 0); + + // run job + ISocketMultiplexerJob* job = *jobCursor; + ISocketMultiplexerJob* newJob = job->run(read, write, error); + + // save job, if different + if (newJob != job) { + CLock lock(m_mutex); + delete job; + *jobCursor = newJob; + m_update = true; + } + ++i; + } + + // next job + jobCursor = nextCursor(cursor); + } + deleteCursor(cursor); + } + + // delete any removed socket jobs + for (CSocketJobMap::iterator i = m_socketJobMap.begin(); + i != m_socketJobMap.end();) { + if (*(i->second) == NULL) { + m_socketJobMap.erase(i++); + m_update = true; + } + else { + ++i; + } + } + + // unlock the job list + unlockJobList(); + } +} + +CSocketMultiplexer::CJobCursor +CSocketMultiplexer::newCursor() +{ + CLock lock(m_mutex); + return m_socketJobs.insert(m_socketJobs.begin(), m_cursorMark); +} + +CSocketMultiplexer::CJobCursor +CSocketMultiplexer::nextCursor(CJobCursor cursor) +{ + CLock lock(m_mutex); + CJobCursor j = m_socketJobs.end(); + CJobCursor i = cursor; + while (++i != m_socketJobs.end()) { + if (*i != m_cursorMark) { + // found a real job (as opposed to a cursor) + j = i; + + // move our cursor just past the job + m_socketJobs.splice(++i, m_socketJobs, cursor); + break; + } + } + return j; +} + +void +CSocketMultiplexer::deleteCursor(CJobCursor cursor) +{ + CLock lock(m_mutex); + m_socketJobs.erase(cursor); +} + +void +CSocketMultiplexer::lockJobListLock() +{ + CLock lock(m_mutex); + + // wait for the lock on the lock + while (*m_jobListLockLocked) { + m_jobListLockLocked->wait(); + } + + // take ownership of the lock on the lock + *m_jobListLockLocked = true; + m_jobListLockLocker = new CThread(CThread::getCurrentThread()); +} + +void +CSocketMultiplexer::lockJobList() +{ + CLock lock(m_mutex); + + // make sure we're the one that called lockJobListLock() + assert(*m_jobListLockLocker == CThread::getCurrentThread()); + + // wait for the job list lock + while (*m_jobListLock) { + m_jobListLock->wait(); + } + + // take ownership of the lock + *m_jobListLock = true; + m_jobListLocker = m_jobListLockLocker; + m_jobListLockLocker = NULL; + + // release the lock on the lock + *m_jobListLockLocked = false; + m_jobListLockLocked->broadcast(); +} + +void +CSocketMultiplexer::unlockJobList() +{ + CLock lock(m_mutex); + + // make sure we're the one that called lockJobList() + assert(*m_jobListLocker == CThread::getCurrentThread()); + + // release the lock + delete m_jobListLocker; + m_jobListLocker = NULL; + *m_jobListLock = false; + m_jobListLock->signal(); + + // set new jobs ready state + bool isReady = !m_socketJobMap.empty(); + if (*m_jobsReady != isReady) { + *m_jobsReady = isReady; + m_jobsReady->signal(); + } +} diff --git a/lib/net/CSocketMultiplexer.h b/lib/net/CSocketMultiplexer.h new file mode 100644 index 00000000..bb96ca70 --- /dev/null +++ b/lib/net/CSocketMultiplexer.h @@ -0,0 +1,111 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSOCKETMULTIPLEXER_H +#define CSOCKETMULTIPLEXER_H + +#include "IArchNetwork.h" +#include "stdlist.h" +#include "stdmap.h" + +template +class CCondVar; +class CMutex; +class CThread; +class ISocket; +class ISocketMultiplexerJob; + +//! Socket multiplexer +/*! +A socket multiplexer services multiple sockets simultaneously. +*/ +class CSocketMultiplexer { +public: + CSocketMultiplexer(); + ~CSocketMultiplexer(); + + //! @name manipulators + //@{ + + void addSocket(ISocket*, ISocketMultiplexerJob*); + + void removeSocket(ISocket*); + + //@} + //! @name accessors + //@{ + + // maybe belongs on ISocketMultiplexer + static CSocketMultiplexer* + getInstance(); + + //@} + +private: + // list of jobs. we use a list so we can safely iterate over it + // while other threads modify it. + typedef std::list CSocketJobs; + typedef CSocketJobs::iterator CJobCursor; + typedef std::map CSocketJobMap; + + // service sockets. the service thread will only access m_sockets + // and m_update while m_pollable and m_polling are true. all other + // threads must only modify these when m_pollable and m_polling are + // false. only the service thread sets m_polling. + void serviceThread(void*); + + // create, iterate, and destroy a cursor. a cursor is used to + // safely iterate through the job list while other threads modify + // the list. it works by inserting a dummy item in the list and + // moving that item through the list. the dummy item will never + // be removed by other edits so an iterator pointing at the item + // remains valid until we remove the dummy item in deleteCursor(). + // nextCursor() finds the next non-dummy item, moves our dummy + // item just past it, and returns an iterator for the non-dummy + // item. all cursor calls lock the mutex for their duration. + CJobCursor newCursor(); + CJobCursor nextCursor(CJobCursor); + void deleteCursor(CJobCursor); + + // lock out locking the job list. this blocks if another thread + // has already locked out locking. once it returns, only the + // calling thread will be able to lock the job list after any + // current lock is released. + void lockJobListLock(); + + // lock the job list. this blocks if the job list is already + // locked. the calling thread must have called requestJobLock. + void lockJobList(); + + // unlock the job list and the lock out on locking. + void unlockJobList(); + +private: + CMutex* m_mutex; + CThread* m_thread; + bool m_update; + CCondVar* m_jobsReady; + CCondVar* m_jobListLock; + CCondVar* m_jobListLockLocked; + CThread* m_jobListLocker; + CThread* m_jobListLockLocker; + + CSocketJobs m_socketJobs; + CSocketJobMap m_socketJobMap; + ISocketMultiplexerJob* m_cursorMark; + + static CSocketMultiplexer* s_instance; +}; + +#endif diff --git a/lib/net/CTCPListenSocket.cpp b/lib/net/CTCPListenSocket.cpp new file mode 100644 index 00000000..938bf95a --- /dev/null +++ b/lib/net/CTCPListenSocket.cpp @@ -0,0 +1,134 @@ +/* + * 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 "CTCPListenSocket.h" +#include "CNetworkAddress.h" +#include "CSocketMultiplexer.h" +#include "CTCPSocket.h" +#include "TSocketMultiplexerMethodJob.h" +#include "XSocket.h" +#include "XIO.h" +#include "CLock.h" +#include "CMutex.h" +#include "IEventQueue.h" +#include "CArch.h" +#include "XArch.h" + +// +// CTCPListenSocket +// + +CTCPListenSocket::CTCPListenSocket() +{ + m_mutex = new CMutex; + try { + m_socket = ARCH->newSocket(IArchNetwork::kINET, IArchNetwork::kSTREAM); + } + catch (XArchNetwork& e) { + throw XSocketCreate(e.what()); + } +} + +CTCPListenSocket::~CTCPListenSocket() +{ + try { + if (m_socket != NULL) { + CSocketMultiplexer::getInstance()->removeSocket(this); + ARCH->closeSocket(m_socket); + } + } + catch (...) { + // ignore + } + delete m_mutex; +} + +void +CTCPListenSocket::bind(const CNetworkAddress& addr) +{ + try { + CLock lock(m_mutex); + ARCH->setReuseAddrOnSocket(m_socket, true); + ARCH->bindSocket(m_socket, addr.getAddress()); + ARCH->listenOnSocket(m_socket); + CSocketMultiplexer::getInstance()->addSocket(this, + new TSocketMultiplexerMethodJob( + this, &CTCPListenSocket::serviceListening, + m_socket, true, false)); + } + catch (XArchNetworkAddressInUse& e) { + throw XSocketAddressInUse(e.what()); + } + catch (XArchNetwork& e) { + throw XSocketBind(e.what()); + } +} + +void +CTCPListenSocket::close() +{ + CLock lock(m_mutex); + if (m_socket == NULL) { + throw XIOClosed(); + } + try { + CSocketMultiplexer::getInstance()->removeSocket(this); + ARCH->closeSocket(m_socket); + m_socket = NULL; + } + catch (XArchNetwork& e) { + throw XSocketIOClose(e.what()); + } +} + +void* +CTCPListenSocket::getEventTarget() const +{ + return const_cast(reinterpret_cast(this)); +} + +IDataSocket* +CTCPListenSocket::accept() +{ + try { + IDataSocket* socket = + new CTCPSocket(ARCH->acceptSocket(m_socket, NULL)); + if (socket != NULL) { + CSocketMultiplexer::getInstance()->addSocket(this, + new TSocketMultiplexerMethodJob( + this, &CTCPListenSocket::serviceListening, + m_socket, true, false)); + } + return socket; + } + catch (XArchNetwork&) { + return NULL; + } +} + +ISocketMultiplexerJob* +CTCPListenSocket::serviceListening(ISocketMultiplexerJob* job, + bool read, bool, bool error) +{ + if (error) { + close(); + return NULL; + } + if (read) { + EVENTQUEUE->addEvent(CEvent(getConnectingEvent(), this, NULL)); + // stop polling on this socket until the client accepts + return NULL; + } + return job; +} diff --git a/lib/net/CTCPListenSocket.h b/lib/net/CTCPListenSocket.h new file mode 100644 index 00000000..5321b8db --- /dev/null +++ b/lib/net/CTCPListenSocket.h @@ -0,0 +1,51 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CTCPLISTENSOCKET_H +#define CTCPLISTENSOCKET_H + +#include "IListenSocket.h" +#include "IArchNetwork.h" + +class CMutex; +class ISocketMultiplexerJob; + +//! TCP listen socket +/*! +A listen socket using TCP. +*/ +class CTCPListenSocket : public IListenSocket { +public: + CTCPListenSocket(); + ~CTCPListenSocket(); + + // ISocket overrides + virtual void bind(const CNetworkAddress&); + virtual void close(); + virtual void* getEventTarget() const; + + // IListenSocket overrides + virtual IDataSocket* accept(); + +private: + ISocketMultiplexerJob* + serviceListening(ISocketMultiplexerJob*, + bool, bool, bool); + +private: + CArchSocket m_socket; + CMutex* m_mutex; +}; + +#endif diff --git a/lib/net/CTCPSocket.cpp b/lib/net/CTCPSocket.cpp new file mode 100644 index 00000000..c44b41ea --- /dev/null +++ b/lib/net/CTCPSocket.cpp @@ -0,0 +1,542 @@ +/* + * 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 "CTCPSocket.h" +#include "CNetworkAddress.h" +#include "CSocketMultiplexer.h" +#include "TSocketMultiplexerMethodJob.h" +#include "XSocket.h" +#include "CLock.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "IEventJob.h" +#include "CArch.h" +#include "XArch.h" +#include + +// +// CTCPSocket +// + +CTCPSocket::CTCPSocket() : + m_mutex(), + m_flushed(&m_mutex, true) +{ + try { + m_socket = ARCH->newSocket(IArchNetwork::kINET, IArchNetwork::kSTREAM); + } + catch (XArchNetwork& e) { + throw XSocketCreate(e.what()); + } + + init(); +} + +CTCPSocket::CTCPSocket(CArchSocket socket) : + m_mutex(), + m_socket(socket), + m_flushed(&m_mutex, true) +{ + assert(m_socket != NULL); + + // socket starts in connected state + init(); + onConnected(); + setJob(newJob()); +} + +CTCPSocket::~CTCPSocket() +{ + try { + close(); + } + catch (...) { + // ignore + } +} + +void +CTCPSocket::bind(const CNetworkAddress& addr) +{ + try { + ARCH->bindSocket(m_socket, addr.getAddress()); + } + catch (XArchNetworkAddressInUse& e) { + throw XSocketAddressInUse(e.what()); + } + catch (XArchNetwork& e) { + throw XSocketBind(e.what()); + } +} + +void +CTCPSocket::close() +{ + // remove ourself from the multiplexer + setJob(NULL); + + CLock lock(&m_mutex); + + // clear buffers and enter disconnected state + if (m_connected) { + sendEvent(getDisconnectedEvent()); + } + onDisconnected(); + + // close the socket + if (m_socket != NULL) { + CArchSocket socket = m_socket; + m_socket = NULL; + try { + ARCH->closeSocket(socket); + } + catch (XArchNetwork& e) { + // ignore, there's not much we can do + LOG((CLOG_WARN "error closing socket: %s", e.what().c_str())); + } + } +} + +void* +CTCPSocket::getEventTarget() const +{ + return const_cast(reinterpret_cast(this)); +} + +UInt32 +CTCPSocket::read(void* buffer, UInt32 n) +{ + // copy data directly from our input buffer + CLock lock(&m_mutex); + UInt32 size = m_inputBuffer.getSize(); + if (n > size) { + n = size; + } + if (buffer != NULL && n != 0) { + memcpy(buffer, m_inputBuffer.peek(n), n); + } + m_inputBuffer.pop(n); + + // if no more data and we cannot read or write then send disconnected + if (n > 0 && m_inputBuffer.getSize() == 0 && !m_readable && !m_writable) { + sendEvent(getDisconnectedEvent()); + m_connected = false; + } + + return n; +} + +void +CTCPSocket::write(const void* buffer, UInt32 n) +{ + bool wasEmpty; + { + CLock lock(&m_mutex); + + // must not have shutdown output + if (!m_writable) { + sendEvent(getOutputErrorEvent()); + return; + } + + // ignore empty writes + if (n == 0) { + return; + } + + // copy data to the output buffer + wasEmpty = (m_outputBuffer.getSize() == 0); + m_outputBuffer.write(buffer, n); + + // there's data to write + m_flushed = false; + } + + // make sure we're waiting to write + if (wasEmpty) { + setJob(newJob()); + } +} + +void +CTCPSocket::flush() +{ + CLock lock(&m_mutex); + while (m_flushed == false) { + m_flushed.wait(); + } +} + +void +CTCPSocket::shutdownInput() +{ + bool useNewJob = false; + { + CLock lock(&m_mutex); + + // shutdown socket for reading + try { + ARCH->closeSocketForRead(m_socket); + } + catch (XArchNetwork&) { + // ignore + } + + // shutdown buffer for reading + if (m_readable) { + sendEvent(getInputShutdownEvent()); + onInputShutdown(); + useNewJob = true; + } + } + if (useNewJob) { + setJob(newJob()); + } +} + +void +CTCPSocket::shutdownOutput() +{ + bool useNewJob = false; + { + CLock lock(&m_mutex); + + // shutdown socket for writing + try { + ARCH->closeSocketForWrite(m_socket); + } + catch (XArchNetwork&) { + // ignore + } + + // shutdown buffer for writing + if (m_writable) { + sendEvent(getOutputShutdownEvent()); + onOutputShutdown(); + useNewJob = true; + } + } + if (useNewJob) { + setJob(newJob()); + } +} + +bool +CTCPSocket::isReady() const +{ + CLock lock(&m_mutex); + return (m_inputBuffer.getSize() > 0); +} + +UInt32 +CTCPSocket::getSize() const +{ + CLock lock(&m_mutex); + return m_inputBuffer.getSize(); +} + +void +CTCPSocket::connect(const CNetworkAddress& addr) +{ + { + CLock lock(&m_mutex); + + // fail on attempts to reconnect + if (m_socket == NULL || m_connected) { + sendConnectionFailedEvent("busy"); + return; + } + + try { + if (ARCH->connectSocket(m_socket, addr.getAddress())) { + sendEvent(getConnectedEvent()); + onConnected(); + } + else { + // connection is in progress + m_writable = true; + } + } + catch (XArchNetwork& e) { + throw XSocketConnect(e.what()); + } + } + setJob(newJob()); +} + +void +CTCPSocket::init() +{ + // default state + m_connected = false; + m_readable = false; + m_writable = false; + + try { + // turn off Nagle algorithm. we send lots of very short messages + // that should be sent without (much) delay. for example, the + // mouse motion messages are much less useful if they're delayed. + ARCH->setNoDelayOnSocket(m_socket, true); + } + catch (XArchNetwork& e) { + try { + ARCH->closeSocket(m_socket); + m_socket = NULL; + } + catch (XArchNetwork&) { + // ignore + } + throw XSocketCreate(e.what()); + } +} + +void +CTCPSocket::setJob(ISocketMultiplexerJob* job) +{ + // multiplexer will delete the old job + if (job == NULL) { + CSocketMultiplexer::getInstance()->removeSocket(this); + } + else { + CSocketMultiplexer::getInstance()->addSocket(this, job); + } +} + +ISocketMultiplexerJob* +CTCPSocket::newJob() +{ + // note -- must have m_mutex locked on entry + + if (m_socket == NULL) { + return NULL; + } + else if (!m_connected) { + assert(!m_readable); + if (!(m_readable || m_writable)) { + return NULL; + } + return new TSocketMultiplexerMethodJob( + this, &CTCPSocket::serviceConnecting, + m_socket, m_readable, m_writable); + } + else { + if (!(m_readable || (m_writable && (m_outputBuffer.getSize() > 0)))) { + return NULL; + } + return new TSocketMultiplexerMethodJob( + this, &CTCPSocket::serviceConnected, + m_socket, m_readable, + m_writable && (m_outputBuffer.getSize() > 0)); + } +} + +void +CTCPSocket::sendConnectionFailedEvent(const char* msg) +{ + CConnectionFailedInfo* info = (CConnectionFailedInfo*)malloc( + sizeof(CConnectionFailedInfo) + strlen(msg)); + strcpy(info->m_what, msg); + EVENTQUEUE->addEvent(CEvent(getConnectionFailedEvent(), + getEventTarget(), info)); +} + +void +CTCPSocket::sendEvent(CEvent::Type type) +{ + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), NULL)); +} + +void +CTCPSocket::onConnected() +{ + m_connected = true; + m_readable = true; + m_writable = true; +} + +void +CTCPSocket::onInputShutdown() +{ + m_inputBuffer.pop(m_inputBuffer.getSize()); + m_readable = false; +} + +void +CTCPSocket::onOutputShutdown() +{ + m_outputBuffer.pop(m_outputBuffer.getSize()); + m_writable = false; + + // we're now flushed + m_flushed = true; + m_flushed.broadcast(); +} + +void +CTCPSocket::onDisconnected() +{ + // disconnected + onInputShutdown(); + onOutputShutdown(); + m_connected = false; +} + +ISocketMultiplexerJob* +CTCPSocket::serviceConnecting(ISocketMultiplexerJob* job, + bool, bool write, bool error) +{ + CLock lock(&m_mutex); + + // should only check for errors if error is true but checking a new + // socket (and a socket that's connecting should be new) for errors + // should be safe and Mac OS X appears to have a bug where a + // non-blocking stream socket that fails to connect immediately is + // reported by select as being writable (i.e. connected) even when + // the connection has failed. this is easily demonstrated on OS X + // 10.3.4 by starting a synergy client and telling to connect to + // another system that's not running a synergy server. it will + // claim to have connected then quickly disconnect (i guess because + // read returns 0 bytes). unfortunately, synergy attempts to + // reconnect immediately, the process repeats and we end up + // spinning the CPU. luckily, OS X does set SO_ERROR on the + // socket correctly when the connection has failed so checking for + // errors works. (curiously, sometimes OS X doesn't report + // connection refused. when that happens it at least doesn't + // report the socket as being writable so synergy is able to time + // out the attempt.) + if (error || true) { + try { + // connection may have failed or succeeded + ARCH->throwErrorOnSocket(m_socket); + } + catch (XArchNetwork& e) { + sendConnectionFailedEvent(e.what().c_str()); + onDisconnected(); + return newJob(); + } + } + + if (write) { + sendEvent(getConnectedEvent()); + onConnected(); + return newJob(); + } + + return job; +} + +ISocketMultiplexerJob* +CTCPSocket::serviceConnected(ISocketMultiplexerJob* job, + bool read, bool write, bool error) +{ + CLock lock(&m_mutex); + + if (error) { + sendEvent(getDisconnectedEvent()); + onDisconnected(); + return newJob(); + } + + bool needNewJob = false; + + if (write) { + try { + // write data + UInt32 n = m_outputBuffer.getSize(); + const void* buffer = m_outputBuffer.peek(n); + n = (UInt32)ARCH->writeSocket(m_socket, buffer, n); + + // discard written data + if (n > 0) { + m_outputBuffer.pop(n); + if (m_outputBuffer.getSize() == 0) { + sendEvent(getOutputFlushedEvent()); + m_flushed = true; + m_flushed.broadcast(); + needNewJob = true; + } + } + } + catch (XArchNetworkShutdown&) { + // remote read end of stream hungup. our output side + // has therefore shutdown. + onOutputShutdown(); + sendEvent(getOutputShutdownEvent()); + if (!m_readable && m_inputBuffer.getSize() == 0) { + sendEvent(getDisconnectedEvent()); + m_connected = false; + } + needNewJob = true; + } + catch (XArchNetworkDisconnected&) { + // stream hungup + onDisconnected(); + sendEvent(getDisconnectedEvent()); + needNewJob = true; + } + catch (XArchNetwork& e) { + // other write error + LOG((CLOG_WARN "error writing socket: %s", e.what().c_str())); + onDisconnected(); + sendEvent(getOutputErrorEvent()); + sendEvent(getDisconnectedEvent()); + needNewJob = true; + } + } + + if (read && m_readable) { + try { + UInt8 buffer[4096]; + size_t n = ARCH->readSocket(m_socket, buffer, sizeof(buffer)); + if (n > 0) { + bool wasEmpty = (m_inputBuffer.getSize() == 0); + + // slurp up as much as possible + do { + m_inputBuffer.write(buffer, n); + n = ARCH->readSocket(m_socket, buffer, sizeof(buffer)); + } while (n > 0); + + // send input ready if input buffer was empty + if (wasEmpty) { + sendEvent(getInputReadyEvent()); + } + } + else { + // remote write end of stream hungup. our input side + // has therefore shutdown but don't flush our buffer + // since there's still data to be read. + sendEvent(getInputShutdownEvent()); + if (!m_writable && m_inputBuffer.getSize() == 0) { + sendEvent(getDisconnectedEvent()); + m_connected = false; + } + m_readable = false; + needNewJob = true; + } + } + catch (XArchNetworkDisconnected&) { + // stream hungup + sendEvent(getDisconnectedEvent()); + onDisconnected(); + needNewJob = true; + } + catch (XArchNetwork& e) { + // ignore other read error + LOG((CLOG_WARN "error reading socket: %s", e.what().c_str())); + } + } + + return needNewJob ? newJob() : job; +} diff --git a/lib/net/CTCPSocket.h b/lib/net/CTCPSocket.h new file mode 100644 index 00000000..aa1df8c1 --- /dev/null +++ b/lib/net/CTCPSocket.h @@ -0,0 +1,86 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CTCPSOCKET_H +#define CTCPSOCKET_H + +#include "IDataSocket.h" +#include "CStreamBuffer.h" +#include "CCondVar.h" +#include "CMutex.h" +#include "IArchNetwork.h" + +class CMutex; +class CThread; +class ISocketMultiplexerJob; + +//! TCP data socket +/*! +A data socket using TCP. +*/ +class CTCPSocket : public IDataSocket { +public: + CTCPSocket(); + CTCPSocket(CArchSocket); + ~CTCPSocket(); + + // ISocket overrides + virtual void bind(const CNetworkAddress&); + virtual void close(); + virtual void* getEventTarget() const; + + // IStream overrides + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void flush(); + virtual void shutdownInput(); + virtual void shutdownOutput(); + virtual bool isReady() const; + virtual UInt32 getSize() const; + + // IDataSocket overrides + virtual void connect(const CNetworkAddress&); + +private: + void init(); + + void setJob(ISocketMultiplexerJob*); + ISocketMultiplexerJob* newJob(); + void sendConnectionFailedEvent(const char*); + void sendEvent(CEvent::Type); + + void onConnected(); + void onInputShutdown(); + void onOutputShutdown(); + void onDisconnected(); + + ISocketMultiplexerJob* + serviceConnecting(ISocketMultiplexerJob*, + bool, bool, bool); + ISocketMultiplexerJob* + serviceConnected(ISocketMultiplexerJob*, + bool, bool, bool); + +private: + CMutex m_mutex; + CArchSocket m_socket; + CStreamBuffer m_inputBuffer; + CStreamBuffer m_outputBuffer; + CCondVar m_flushed; + bool m_connected; + bool m_readable; + bool m_writable; +}; + +#endif diff --git a/lib/net/CTCPSocketFactory.cpp b/lib/net/CTCPSocketFactory.cpp new file mode 100644 index 00000000..f590efa0 --- /dev/null +++ b/lib/net/CTCPSocketFactory.cpp @@ -0,0 +1,43 @@ +/* + * 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 "CTCPSocketFactory.h" +#include "CTCPSocket.h" +#include "CTCPListenSocket.h" + +// +// CTCPSocketFactory +// + +CTCPSocketFactory::CTCPSocketFactory() +{ + // do nothing +} + +CTCPSocketFactory::~CTCPSocketFactory() +{ + // do nothing +} + +IDataSocket* +CTCPSocketFactory::create() const +{ + return new CTCPSocket; +} + +IListenSocket* +CTCPSocketFactory::createListen() const +{ + return new CTCPListenSocket; +} diff --git a/lib/net/CTCPSocketFactory.h b/lib/net/CTCPSocketFactory.h new file mode 100644 index 00000000..2b946d19 --- /dev/null +++ b/lib/net/CTCPSocketFactory.h @@ -0,0 +1,31 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CTCPSOCKETFACTORY_H +#define CTCPSOCKETFACTORY_H + +#include "ISocketFactory.h" + +//! Socket factory for TCP sockets +class CTCPSocketFactory : public ISocketFactory { +public: + CTCPSocketFactory(); + virtual ~CTCPSocketFactory(); + + // ISocketFactory overrides + virtual IDataSocket* create() const; + virtual IListenSocket* createListen() const; +}; + +#endif diff --git a/lib/net/IDataSocket.cpp b/lib/net/IDataSocket.cpp new file mode 100644 index 00000000..dc4b08ed --- /dev/null +++ b/lib/net/IDataSocket.cpp @@ -0,0 +1,51 @@ +/* + * 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 "IDataSocket.h" + +// +// IDataSocket +// + +CEvent::Type IDataSocket::s_connectedEvent = CEvent::kUnknown; +CEvent::Type IDataSocket::s_failedEvent = CEvent::kUnknown; + +CEvent::Type +IDataSocket::getConnectedEvent() +{ + return CEvent::registerTypeOnce(s_connectedEvent, + "IDataSocket::connected"); +} + +CEvent::Type +IDataSocket::getConnectionFailedEvent() +{ + return CEvent::registerTypeOnce(s_failedEvent, + "IDataSocket::failed"); +} + +void +IDataSocket::close() +{ + // this is here to work around a VC++6 bug. see the header file. + assert(0 && "bad call"); +} + +void* +IDataSocket::getEventTarget() const +{ + // this is here to work around a VC++6 bug. see the header file. + assert(0 && "bad call"); + return NULL; +} diff --git a/lib/net/IDataSocket.h b/lib/net/IDataSocket.h new file mode 100644 index 00000000..d760d4ab --- /dev/null +++ b/lib/net/IDataSocket.h @@ -0,0 +1,90 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IDATASOCKET_H +#define IDATASOCKET_H + +#include "ISocket.h" +#include "IStream.h" + +//! Data stream socket interface +/*! +This interface defines the methods common to all network sockets that +represent a full-duplex data stream. +*/ +class IDataSocket : public ISocket, public IStream { +public: + class CConnectionFailedInfo { + public: + // pointer to a string describing the failure + char m_what[1]; + }; + + //! @name manipulators + //@{ + + //! Connect socket + /*! + Attempt to connect to a remote endpoint. This returns immediately + and sends a connected event when successful or a connection failed + event when it fails. The stream acts as if shutdown for input and + output until the stream connects. + */ + virtual void connect(const CNetworkAddress&) = 0; + + //@} + //! @name accessors + //@{ + + //! Get connected event type + /*! + Returns the socket connected event type. A socket sends this + event when a remote connection has been established. + */ + static CEvent::Type getConnectedEvent(); + + //! Get connection failed event type + /*! + Returns the socket connection failed event type. A socket sends + this event when an attempt to connect to a remote port has failed. + The data is a pointer to a CConnectionFailedInfo. + */ + static CEvent::Type getConnectionFailedEvent(); + + //@} + + // ISocket overrides + // close() and getEventTarget() aren't pure to work around a bug + // in VC++6. it claims the methods are unused locals and warns + // that it's removing them. it's presumably tickled by inheriting + // methods with identical signatures from both superclasses. + virtual void bind(const CNetworkAddress&) = 0; + virtual void close(); + virtual void* getEventTarget() const; + + // IStream overrides + virtual UInt32 read(void* buffer, UInt32 n) = 0; + virtual void write(const void* buffer, UInt32 n) = 0; + virtual void flush() = 0; + virtual void shutdownInput() = 0; + virtual void shutdownOutput() = 0; + virtual bool isReady() const = 0; + virtual UInt32 getSize() const = 0; + +private: + static CEvent::Type s_connectedEvent; + static CEvent::Type s_failedEvent; +}; + +#endif diff --git a/lib/net/IListenSocket.cpp b/lib/net/IListenSocket.cpp new file mode 100644 index 00000000..20dbc9a4 --- /dev/null +++ b/lib/net/IListenSocket.cpp @@ -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. + */ + +#include "IListenSocket.h" + +// +// IListenSocket +// + +CEvent::Type IListenSocket::s_connectingEvent = CEvent::kUnknown; + +CEvent::Type +IListenSocket::getConnectingEvent() +{ + return CEvent::registerTypeOnce(s_connectingEvent, + "IListenSocket::connecting"); +} diff --git a/lib/net/IListenSocket.h b/lib/net/IListenSocket.h new file mode 100644 index 00000000..894234f4 --- /dev/null +++ b/lib/net/IListenSocket.h @@ -0,0 +1,62 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ILISTENSOCKET_H +#define ILISTENSOCKET_H + +#include "ISocket.h" + +class IDataSocket; + +//! Listen socket interface +/*! +This interface defines the methods common to all network sockets that +listen for incoming connections. +*/ +class IListenSocket : public ISocket { +public: + //! @name manipulators + //@{ + + //! Accept connection + /*! + Accept a connection, returning a socket representing the full-duplex + data stream. Returns NULL if no socket is waiting to be accepted. + This is only valid after a call to \c bind(). + */ + virtual IDataSocket* accept() = 0; + + //@} + //! @name accessors + //@{ + + //! Get connecting event type + /*! + Returns the socket connecting event type. A socket sends this + event when a remote connection is waiting to be accepted. + */ + static CEvent::Type getConnectingEvent(); + + //@} + + // ISocket overrides + virtual void bind(const CNetworkAddress&) = 0; + virtual void close() = 0; + virtual void* getEventTarget() const = 0; + +private: + static CEvent::Type s_connectingEvent; +}; + +#endif diff --git a/lib/net/ISocket.cpp b/lib/net/ISocket.cpp new file mode 100644 index 00000000..a9aaf1ac --- /dev/null +++ b/lib/net/ISocket.cpp @@ -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. + */ + +#include "ISocket.h" + +// +// ISocket +// + +CEvent::Type ISocket::s_disconnectedEvent = CEvent::kUnknown; + +CEvent::Type +ISocket::getDisconnectedEvent() +{ + return CEvent::registerTypeOnce(s_disconnectedEvent, + "ISocket::disconnected"); +} diff --git a/lib/net/ISocket.h b/lib/net/ISocket.h new file mode 100644 index 00000000..4452e023 --- /dev/null +++ b/lib/net/ISocket.h @@ -0,0 +1,69 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISOCKET_H +#define ISOCKET_H + +#include "IInterface.h" +#include "CEvent.h" + +class CNetworkAddress; + +//! Generic socket interface +/*! +This interface defines the methods common to all network sockets. +Generated events use \c this as the target. +*/ +class ISocket : public IInterface { +public: + //! @name manipulators + //@{ + + //! Bind socket to address + /*! + Binds the socket to a particular address. + */ + virtual void bind(const CNetworkAddress&) = 0; + + //! Close socket + /*! + Closes the socket. This should flush the output stream. + */ + virtual void close() = 0; + + //@} + //! @name accessors + //@{ + + //! Get event target + /*! + Returns the event target for events generated by this socket. + */ + virtual void* getEventTarget() const = 0; + + //! Get disconnected event type + /*! + Returns the socket disconnected event type. A socket sends this + event when the remote side of the socket has disconnected or + shutdown both input and output. + */ + static CEvent::Type getDisconnectedEvent(); + + //@} + +private: + static CEvent::Type s_disconnectedEvent; +}; + +#endif diff --git a/lib/net/ISocketFactory.h b/lib/net/ISocketFactory.h new file mode 100644 index 00000000..dbf9ae26 --- /dev/null +++ b/lib/net/ISocketFactory.h @@ -0,0 +1,42 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISOCKETFACTORY_H +#define ISOCKETFACTORY_H + +#include "IInterface.h" + +class IDataSocket; +class IListenSocket; + +//! Socket factory +/*! +This interface defines the methods common to all factories used to +create sockets. +*/ +class ISocketFactory : public IInterface { +public: + //! @name accessors + //@{ + + //! Create data socket + virtual IDataSocket* create() const = 0; + + //! Create listen socket + virtual IListenSocket* createListen() const = 0; + + //@} +}; + +#endif diff --git a/lib/net/ISocketMultiplexerJob.h b/lib/net/ISocketMultiplexerJob.h new file mode 100644 index 00000000..ac722326 --- /dev/null +++ b/lib/net/ISocketMultiplexerJob.h @@ -0,0 +1,75 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISOCKETMULTIPLEXERJOB_H +#define ISOCKETMULTIPLEXERJOB_H + +#include "IArchNetwork.h" +#include "IInterface.h" + +//! Socket multiplexer job +/*! +A socket multiplexer job handles events on a socket. +*/ +class ISocketMultiplexerJob : public IInterface { +public: + //! @name manipulators + //@{ + + //! Handle socket event + /*! + Called by a socket multiplexer when the socket becomes readable, + writable, or has an error. It should return itself if the same + job can continue to service events, a new job if the socket must + be serviced differently, or NULL if the socket should no longer + be serviced. The socket is readable if \p readable is true, + writable if \p writable is true, and in error if \p error is + true. + + This call must not attempt to directly change the job for this + socket by calling \c addSocket() or \c removeSocket() on the + multiplexer. It must instead return the new job. It can, + however, add or remove jobs for other sockets. + */ + virtual ISocketMultiplexerJob* + run(bool readable, bool writable, bool error) = 0; + + //@} + //! @name accessors + //@{ + + //! Get the socket + /*! + Return the socket to multiplex + */ + virtual CArchSocket getSocket() const = 0; + + //! Check for interest in readability + /*! + Return true if the job is interested in being run if the socket + becomes readable. + */ + virtual bool isReadable() const = 0; + + //! Check for interest in writability + /*! + Return true if the job is interested in being run if the socket + becomes writable. + */ + virtual bool isWritable() const = 0; + + //@} +}; + +#endif diff --git a/lib/net/Makefile.am b/lib/net/Makefile.am new file mode 100644 index 00000000..b49fe363 --- /dev/null +++ b/lib/net/Makefile.am @@ -0,0 +1,54 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libnet.a +libnet_a_SOURCES = \ + CNetworkAddress.cpp \ + CSocketMultiplexer.cpp \ + CTCPListenSocket.cpp \ + CTCPSocket.cpp \ + CTCPSocketFactory.cpp \ + IDataSocket.cpp \ + IListenSocket.cpp \ + ISocket.cpp \ + XSocket.cpp \ + CNetworkAddress.h \ + CSocketMultiplexer.h \ + CTCPListenSocket.h \ + CTCPSocket.h \ + CTCPSocketFactory.h \ + IDataSocket.h \ + IListenSocket.h \ + ISocket.h \ + ISocketFactory.h \ + ISocketMultiplexerJob.h \ + TSocketMultiplexerMethodJob.h \ + XSocket.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + $(NULL) diff --git a/lib/net/Makefile.win b/lib/net/Makefile.win new file mode 100644 index 00000000..d4cd357c --- /dev/null +++ b/lib/net/Makefile.win @@ -0,0 +1,74 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +LIB_NET_SRC = lib\net +LIB_NET_DST = $(BUILD_DST)\$(LIB_NET_SRC) +LIB_NET_LIB = "$(LIB_NET_DST)\net.lib" +LIB_NET_CPP = \ + "CNetworkAddress.cpp" \ + "CSocketMultiplexer.cpp" \ + "CTCPListenSocket.cpp" \ + "CTCPSocket.cpp" \ + "CTCPSocketFactory.cpp" \ + "IDataSocket.cpp" \ + "IListenSocket.cpp" \ + "ISocket.cpp" \ + "XSocket.cpp" \ + $(NULL) +LIB_NET_OBJ = \ + "$(LIB_NET_DST)\CNetworkAddress.obj" \ + "$(LIB_NET_DST)\CSocketMultiplexer.obj" \ + "$(LIB_NET_DST)\CTCPListenSocket.obj" \ + "$(LIB_NET_DST)\CTCPSocket.obj" \ + "$(LIB_NET_DST)\CTCPSocketFactory.obj" \ + "$(LIB_NET_DST)\IDataSocket.obj" \ + "$(LIB_NET_DST)\IListenSocket.obj" \ + "$(LIB_NET_DST)\ISocket.obj" \ + "$(LIB_NET_DST)\XSocket.obj" \ + $(NULL) +LIB_NET_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_NET_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_NET_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_NET_LIB) + +# Dependency rules +$(LIB_NET_OBJ): $(AUTODEP) +!if EXIST($(LIB_NET_DST)\deps.mak) +!include $(LIB_NET_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_NET_SRC)\}.cpp{$(LIB_NET_DST)\}.obj:: +!else +{$(LIB_NET_SRC)\}.cpp{$(LIB_NET_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_NET_SRC) + -@$(MKDIR) $(LIB_NET_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_NET_INC) \ + /Fo$(LIB_NET_DST)\ \ + /Fd$(LIB_NET_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_NET_SRC) $(LIB_NET_DST) +$(LIB_NET_LIB): $(LIB_NET_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_NET_SRC) $(LIB_NET_DST) $(**:.obj=.d) diff --git a/lib/net/TSocketMultiplexerMethodJob.h b/lib/net/TSocketMultiplexerMethodJob.h new file mode 100644 index 00000000..992885c4 --- /dev/null +++ b/lib/net/TSocketMultiplexerMethodJob.h @@ -0,0 +1,108 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef TSOCKERMULTIPLEXERMETHODJOB_H +#define TSOCKERMULTIPLEXERMETHODJOB_H + +#include "ISocketMultiplexerJob.h" +#include "CArch.h" + +//! Use a method as a socket multiplexer job +/*! +A socket multiplexer job class that invokes a member function. +*/ +template +class TSocketMultiplexerMethodJob : public ISocketMultiplexerJob { +public: + typedef ISocketMultiplexerJob* + (T::*Method)(ISocketMultiplexerJob*, bool, bool, bool); + + //! run() invokes \c object->method(arg) + TSocketMultiplexerMethodJob(T* object, Method method, + CArchSocket socket, bool readable, bool writeable); + virtual ~TSocketMultiplexerMethodJob(); + + // IJob overrides + virtual ISocketMultiplexerJob* + run(bool readable, bool writable, bool error); + virtual CArchSocket getSocket() const; + virtual bool isReadable() const; + virtual bool isWritable() const; + +private: + T* m_object; + Method m_method; + CArchSocket m_socket; + bool m_readable; + bool m_writable; + void* m_arg; +}; + +template +inline +TSocketMultiplexerMethodJob::TSocketMultiplexerMethodJob(T* object, + Method method, CArchSocket socket, + bool readable, bool writable) : + m_object(object), + m_method(method), + m_socket(ARCH->copySocket(socket)), + m_readable(readable), + m_writable(writable) +{ + // do nothing +} + +template +inline +TSocketMultiplexerMethodJob::~TSocketMultiplexerMethodJob() +{ + ARCH->closeSocket(m_socket); +} + +template +inline +ISocketMultiplexerJob* +TSocketMultiplexerMethodJob::run(bool read, bool write, bool error) +{ + if (m_object != NULL) { + return (m_object->*m_method)(this, read, write, error); + } + return NULL; +} + +template +inline +CArchSocket +TSocketMultiplexerMethodJob::getSocket() const +{ + return m_socket; +} + +template +inline +bool +TSocketMultiplexerMethodJob::isReadable() const +{ + return m_readable; +} + +template +inline +bool +TSocketMultiplexerMethodJob::isWritable() const +{ + return m_writable; +} + +#endif diff --git a/lib/net/XSocket.cpp b/lib/net/XSocket.cpp new file mode 100644 index 00000000..8471538a --- /dev/null +++ b/lib/net/XSocket.cpp @@ -0,0 +1,113 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "XSocket.h" +#include "CStringUtil.h" + +// +// XSocketAddress +// + +XSocketAddress::XSocketAddress(EError error, + const CString& hostname, int port) throw() : + m_error(error), + m_hostname(hostname), + m_port(port) +{ + // do nothing +} + +XSocketAddress::EError +XSocketAddress::getError() const throw() +{ + return m_error; +} + +CString +XSocketAddress::getHostname() const throw() +{ + return m_hostname; +} + +int +XSocketAddress::getPort() const throw() +{ + return m_port; +} + +CString +XSocketAddress::getWhat() const throw() +{ + static const char* s_errorID[] = { + "XSocketAddressUnknown", + "XSocketAddressNotFound", + "XSocketAddressNoAddress", + "XSocketAddressUnsupported", + "XSocketAddressBadPort" + }; + static const char* s_errorMsg[] = { + "unknown error for: %{1}:%{2}", + "address not found for: %{1}", + "no address for: %{1}", + "unsupported address for: %{1}", + "invalid port" // m_port may not be set to the bad port + }; + return format(s_errorID[m_error], s_errorMsg[m_error], + m_hostname.c_str(), + CStringUtil::print("%d", m_port).c_str()); +} + + +// +// XSocketIOClose +// + +CString +XSocketIOClose::getWhat() const throw() +{ + return format("XSocketIOClose", "close: %{1}", what()); +} + + +// +// XSocketBind +// + +CString +XSocketBind::getWhat() const throw() +{ + return format("XSocketBind", "cannot bind address: %{1}", what()); +} + + +// +// XSocketConnect +// + +CString +XSocketConnect::getWhat() const throw() +{ + return format("XSocketConnect", "cannot connect socket: %{1}", what()); +} + + +// +// XSocketCreate +// + +CString +XSocketCreate::getWhat() const throw() +{ + return format("XSocketCreate", "cannot create socket: %{1}", what()); +} diff --git a/lib/net/XSocket.h b/lib/net/XSocket.h new file mode 100644 index 00000000..f84285b9 --- /dev/null +++ b/lib/net/XSocket.h @@ -0,0 +1,96 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XSOCKET_H +#define XSOCKET_H + +#include "XIO.h" +#include "XBase.h" +#include "CString.h" +#include "BasicTypes.h" + +//! Generic socket exception +XBASE_SUBCLASS(XSocket, XBase); + +//! Socket bad address exception +/*! +Thrown when attempting to create an invalid network address. +*/ +class XSocketAddress : public XSocket { +public: + //! Failure codes + enum EError { + kUnknown, //!< Unknown error + kNotFound, //!< The hostname is unknown + kNoAddress, //!< The hostname is valid but has no IP address + kUnsupported, //!< The hostname is valid but has no supported address + kBadPort //!< The port is invalid + }; + + XSocketAddress(EError, const CString& hostname, int port) throw(); + + //! @name accessors + //@{ + + //! Get the error code + EError getError() const throw(); + //! Get the hostname + CString getHostname() const throw(); + //! Get the port + int getPort() const throw(); + + //@} + +protected: + // XBase overrides + virtual CString getWhat() const throw(); + +private: + EError m_error; + CString m_hostname; + int m_port; +}; + +//! I/O closing exception +/*! +Thrown if a stream cannot be closed. +*/ +XBASE_SUBCLASS_FORMAT(XSocketIOClose, XIOClose); + +//! Socket cannot bind address exception +/*! +Thrown when a socket cannot be bound to an address. +*/ +XBASE_SUBCLASS_FORMAT(XSocketBind, XSocket); + +//! Socket address in use exception +/*! +Thrown when a socket cannot be bound to an address because the address +is already in use. +*/ +XBASE_SUBCLASS(XSocketAddressInUse, XSocketBind); + +//! Cannot connect socket exception +/*! +Thrown when a socket cannot connect to a remote endpoint. +*/ +XBASE_SUBCLASS_FORMAT(XSocketConnect, XSocket); + +//! Cannot create socket exception +/*! +Thrown when a socket cannot be created (by the operating system). +*/ +XBASE_SUBCLASS_FORMAT(XSocketCreate, XSocket); + +#endif diff --git a/lib/platform/CMSWindowsClipboard.cpp b/lib/platform/CMSWindowsClipboard.cpp new file mode 100644 index 00000000..7df9db9f --- /dev/null +++ b/lib/platform/CMSWindowsClipboard.cpp @@ -0,0 +1,209 @@ +/* + * 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 "CMSWindowsClipboard.h" +#include "CMSWindowsClipboardTextConverter.h" +#include "CMSWindowsClipboardUTF16Converter.h" +#include "CMSWindowsClipboardBitmapConverter.h" +#include "CMSWindowsClipboardHTMLConverter.h" +#include "CLog.h" +#include "CArchMiscWindows.h" + +// +// CMSWindowsClipboard +// + +UINT CMSWindowsClipboard::s_ownershipFormat = 0; + +CMSWindowsClipboard::CMSWindowsClipboard(HWND window) : + m_window(window), + m_time(0) +{ + // add converters, most desired first + m_converters.push_back(new CMSWindowsClipboardUTF16Converter); + if (CArchMiscWindows::isWindows95Family()) { + // windows nt family converts to/from unicode automatically. + // let it do so to avoid text encoding issues. + m_converters.push_back(new CMSWindowsClipboardTextConverter); + } + m_converters.push_back(new CMSWindowsClipboardBitmapConverter); + m_converters.push_back(new CMSWindowsClipboardHTMLConverter); +} + +CMSWindowsClipboard::~CMSWindowsClipboard() +{ + clearConverters(); +} + +bool +CMSWindowsClipboard::emptyUnowned() +{ + LOG((CLOG_DEBUG "empty clipboard")); + + // empty the clipboard (and take ownership) + if (!EmptyClipboard()) { + LOG((CLOG_DEBUG "failed to grab clipboard")); + return false; + } + + return true; +} + +bool +CMSWindowsClipboard::empty() +{ + if (!emptyUnowned()) { + return false; + } + + // mark clipboard as being owned by synergy + HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, 1); + SetClipboardData(getOwnershipFormat(), data); + + return true; +} + +void +CMSWindowsClipboard::add(EFormat format, const CString& data) +{ + LOG((CLOG_DEBUG "add %d bytes to clipboard format: %d", data.size(), format)); + + // convert data to win32 form + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IMSWindowsClipboardConverter* converter = *index; + + // skip converters for other formats + if (converter->getFormat() == format) { + HANDLE win32Data = converter->fromIClipboard(data); + if (win32Data != NULL) { + UINT win32Format = converter->getWin32Format(); + if (SetClipboardData(win32Format, win32Data) == NULL) { + // free converted data if we couldn't put it on + // the clipboard + GlobalFree(win32Data); + } + } + } + } +} + +bool +CMSWindowsClipboard::open(Time time) const +{ + LOG((CLOG_DEBUG "open clipboard")); + + if (!OpenClipboard(m_window)) { + LOG((CLOG_WARN "failed to open clipboard")); + return false; + } + + m_time = time; + + return true; +} + +void +CMSWindowsClipboard::close() const +{ + LOG((CLOG_DEBUG "close clipboard")); + CloseClipboard(); +} + +IClipboard::Time +CMSWindowsClipboard::getTime() const +{ + return m_time; +} + +bool +CMSWindowsClipboard::has(EFormat format) const +{ + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IMSWindowsClipboardConverter* converter = *index; + if (converter->getFormat() == format) { + if (IsClipboardFormatAvailable(converter->getWin32Format())) { + return true; + } + } + } + return false; +} + +CString +CMSWindowsClipboard::get(EFormat format) const +{ + // find the converter for the first clipboard format we can handle + IMSWindowsClipboardConverter* converter = NULL; + UINT win32Format = EnumClipboardFormats(0); + while (converter == NULL && win32Format != 0) { + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + converter = *index; + if (converter->getWin32Format() == win32Format && + converter->getFormat() == format) { + break; + } + converter = NULL; + } + win32Format = EnumClipboardFormats(win32Format); + } + + // if no converter then we don't recognize any formats + if (converter == NULL) { + return CString(); + } + + // get a handle to the clipboard data + HANDLE win32Data = GetClipboardData(converter->getWin32Format()); + if (win32Data == NULL) { + return CString(); + } + + // convert + return converter->toIClipboard(win32Data); +} + +void +CMSWindowsClipboard::clearConverters() +{ + for (ConverterList::iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + delete *index; + } + m_converters.clear(); +} + +bool +CMSWindowsClipboard::isOwnedBySynergy() +{ + // create ownership format if we haven't yet + if (s_ownershipFormat == 0) { + s_ownershipFormat = RegisterClipboardFormat(TEXT("SynergyOwnership")); + } + return (IsClipboardFormatAvailable(getOwnershipFormat()) != 0); +} + +UINT +CMSWindowsClipboard::getOwnershipFormat() +{ + // create ownership format if we haven't yet + if (s_ownershipFormat == 0) { + s_ownershipFormat = RegisterClipboardFormat(TEXT("SynergyOwnership")); + } + + // return the format + return s_ownershipFormat; +} diff --git a/lib/platform/CMSWindowsClipboard.h b/lib/platform/CMSWindowsClipboard.h new file mode 100644 index 00000000..e9b59fb9 --- /dev/null +++ b/lib/platform/CMSWindowsClipboard.h @@ -0,0 +1,104 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIPBOARD_H +#define CMSWINDOWSCLIPBOARD_H + +#include "IClipboard.h" +#include "stdvector.h" +#define WIN32_LEAN_AND_MEAN +#include + +class IMSWindowsClipboardConverter; + +//! Microsoft windows clipboard implementation +class CMSWindowsClipboard : public IClipboard { +public: + CMSWindowsClipboard(HWND window); + virtual ~CMSWindowsClipboard(); + + //! Empty clipboard without ownership + /*! + Take ownership of the clipboard and clear all data from it. + This must be called between a successful open() and close(). + Return false if the clipboard ownership could not be taken; + the clipboard should not be emptied in this case. Unlike + empty(), isOwnedBySynergy() will return false when emptied + this way. This is useful when synergy wants to put data on + clipboard but pretend (to itself) that some other app did it. + When using empty(), synergy assumes the data came from the + server and doesn't need to be sent back. emptyUnowned() + makes synergy send the data to the server. + */ + bool emptyUnowned(); + + //! Test if clipboard is owned by synergy + static bool isOwnedBySynergy(); + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const CString& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual CString get(EFormat) const; + +private: + void clearConverters(); + + UINT convertFormatToWin32(EFormat) const; + HANDLE convertTextToWin32(const CString& data) const; + CString convertTextFromWin32(HANDLE) const; + + static UINT getOwnershipFormat(); + +private: + typedef std::vector ConverterList; + + HWND m_window; + mutable Time m_time; + ConverterList m_converters; + static UINT s_ownershipFormat; +}; + +//! Clipboard format converter interface +/*! +This interface defines the methods common to all win32 clipboard format +converters. +*/ +class IMSWindowsClipboardConverter : public IInterface { +public: + // accessors + + // return the clipboard format this object converts from/to + virtual IClipboard::EFormat + getFormat() const = 0; + + // return the atom representing the win32 clipboard format that + // this object converts from/to + virtual UINT getWin32Format() const = 0; + + // convert from the IClipboard format to the win32 clipboard format. + // the input data must be in the IClipboard format returned by + // getFormat(). the return data will be in the win32 clipboard + // format returned by getWin32Format(), allocated by GlobalAlloc(). + virtual HANDLE fromIClipboard(const CString&) const = 0; + + // convert from the win32 clipboard format to the IClipboard format + // (i.e., the reverse of fromIClipboard()). + virtual CString toIClipboard(HANDLE data) const = 0; +}; + +#endif diff --git a/lib/platform/CMSWindowsClipboardAnyTextConverter.cpp b/lib/platform/CMSWindowsClipboardAnyTextConverter.cpp new file mode 100644 index 00000000..730f3424 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardAnyTextConverter.cpp @@ -0,0 +1,145 @@ +/* + * 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 "CMSWindowsClipboardAnyTextConverter.h" + +// +// CMSWindowsClipboardAnyTextConverter +// + +CMSWindowsClipboardAnyTextConverter::CMSWindowsClipboardAnyTextConverter() +{ + // do nothing +} + +CMSWindowsClipboardAnyTextConverter::~CMSWindowsClipboardAnyTextConverter() +{ + // do nothing +} + +IClipboard::EFormat +CMSWindowsClipboardAnyTextConverter::getFormat() const +{ + return IClipboard::kText; +} + +HANDLE +CMSWindowsClipboardAnyTextConverter::fromIClipboard(const CString& data) const +{ + // convert linefeeds and then convert to desired encoding + CString text = doFromIClipboard(convertLinefeedToWin32(data)); + UInt32 size = text.size(); + + // copy to memory handle + HGLOBAL gData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, size); + if (gData != NULL) { + // get a pointer to the allocated memory + char* dst = (char*)GlobalLock(gData); + if (dst != NULL) { + memcpy(dst, text.data(), size); + GlobalUnlock(gData); + } + else { + GlobalFree(gData); + gData = NULL; + } + } + + return gData; +} + +CString +CMSWindowsClipboardAnyTextConverter::toIClipboard(HANDLE data) const +{ + // get datator + const char* src = (const char*)GlobalLock(data); + UInt32 srcSize = (UInt32)GlobalSize(data); + if (src == NULL || srcSize <= 1) { + return CString(); + } + + // convert text + CString text = doToIClipboard(CString(src, srcSize)); + + // release handle + GlobalUnlock(data); + + // convert newlines + return convertLinefeedToUnix(text); +} + +CString +CMSWindowsClipboardAnyTextConverter::convertLinefeedToWin32( + const CString& src) const +{ + // note -- we assume src is a valid UTF-8 string + + // count newlines in string + UInt32 numNewlines = 0; + UInt32 n = src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (*scan == '\n') { + ++numNewlines; + } + } + if (numNewlines == 0) { + return src; + } + + // allocate new string + CString dst; + dst.reserve(src.size() + numNewlines); + + // copy string, converting newlines + n = src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (scan[0] == '\n') { + dst += '\r'; + } + dst += scan[0]; + } + + return dst; +} + +CString +CMSWindowsClipboardAnyTextConverter::convertLinefeedToUnix( + const CString& src) const +{ + // count newlines in string + UInt32 numNewlines = 0; + UInt32 n = src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (scan[0] == '\r' && scan[1] == '\n') { + ++numNewlines; + } + } + if (numNewlines == 0) { + return src; + } + + // allocate new string + CString dst; + dst.reserve(src.size()); + + // copy string, converting newlines + n = src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (scan[0] != '\r' || scan[1] != '\n') { + dst += scan[0]; + } + } + + return dst; +} diff --git a/lib/platform/CMSWindowsClipboardAnyTextConverter.h b/lib/platform/CMSWindowsClipboardAnyTextConverter.h new file mode 100644 index 00000000..254099f2 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardAnyTextConverter.h @@ -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. + */ + +#ifndef CMSWINDOWSCLIPBOARDANYTEXTCONVERTER_H +#define CMSWINDOWSCLIPBOARDANYTEXTCONVERTER_H + +#include "CMSWindowsClipboard.h" + +//! Convert to/from some text encoding +class CMSWindowsClipboardAnyTextConverter : + public IMSWindowsClipboardConverter { +public: + CMSWindowsClipboardAnyTextConverter(); + virtual ~CMSWindowsClipboardAnyTextConverter(); + + // IMSWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual UINT getWin32Format() const = 0; + virtual HANDLE fromIClipboard(const CString&) const; + virtual CString toIClipboard(HANDLE) const; + +protected: + //! Convert from IClipboard format + /*! + Do UTF-8 conversion only. Memory handle allocation and + linefeed conversion is done by this class. doFromIClipboard() + must include the nul terminator in the returned string (not + including the CString's nul terminator). + */ + virtual CString doFromIClipboard(const CString&) const = 0; + + //! Convert to IClipboard format + /*! + Do UTF-8 conversion only. Memory handle allocation and + linefeed conversion is done by this class. + */ + virtual CString doToIClipboard(const CString&) const = 0; + +private: + CString convertLinefeedToWin32(const CString&) const; + CString convertLinefeedToUnix(const CString&) const; +}; + +#endif diff --git a/lib/platform/CMSWindowsClipboardBitmapConverter.cpp b/lib/platform/CMSWindowsClipboardBitmapConverter.cpp new file mode 100644 index 00000000..a17c66cf --- /dev/null +++ b/lib/platform/CMSWindowsClipboardBitmapConverter.cpp @@ -0,0 +1,146 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CMSWindowsClipboardBitmapConverter.h" +#include "CLog.h" + +// +// CMSWindowsClipboardBitmapConverter +// + +CMSWindowsClipboardBitmapConverter::CMSWindowsClipboardBitmapConverter() +{ + // do nothing +} + +CMSWindowsClipboardBitmapConverter::~CMSWindowsClipboardBitmapConverter() +{ + // do nothing +} + +IClipboard::EFormat +CMSWindowsClipboardBitmapConverter::getFormat() const +{ + return IClipboard::kBitmap; +} + +UINT +CMSWindowsClipboardBitmapConverter::getWin32Format() const +{ + return CF_DIB; +} + +HANDLE +CMSWindowsClipboardBitmapConverter::fromIClipboard(const CString& data) const +{ + // copy to memory handle + HGLOBAL gData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, data.size()); + if (gData != NULL) { + // get a pointer to the allocated memory + char* dst = (char*)GlobalLock(gData); + if (dst != NULL) { + memcpy(dst, data.data(), data.size()); + GlobalUnlock(gData); + } + else { + GlobalFree(gData); + gData = NULL; + } + } + + return gData; +} + +CString +CMSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const +{ + // get datator + const char* src = (const char*)GlobalLock(data); + if (src == NULL) { + return CString(); + } + UInt32 srcSize = (UInt32)GlobalSize(data); + + // check image type + const BITMAPINFO* bitmap = reinterpret_cast(src); + LOG((CLOG_INFO "bitmap: %dx%d %d", bitmap->bmiHeader.biWidth, bitmap->bmiHeader.biHeight, (int)bitmap->bmiHeader.biBitCount)); + if (bitmap->bmiHeader.biPlanes == 1 && + (bitmap->bmiHeader.biBitCount == 24 || + bitmap->bmiHeader.biBitCount == 32) && + bitmap->bmiHeader.biCompression == BI_RGB) { + // already in canonical form + CString image(src, srcSize); + GlobalUnlock(data); + return image; + } + + // create a destination DIB section + LOG((CLOG_INFO "convert image from: depth=%d comp=%d", bitmap->bmiHeader.biBitCount, bitmap->bmiHeader.biCompression)); + void* raw; + BITMAPINFOHEADER info; + LONG w = bitmap->bmiHeader.biWidth; + LONG h = bitmap->bmiHeader.biHeight; + info.biSize = sizeof(BITMAPINFOHEADER); + info.biWidth = w; + info.biHeight = h; + info.biPlanes = 1; + info.biBitCount = 32; + info.biCompression = BI_RGB; + info.biSizeImage = 0; + info.biXPelsPerMeter = 1000; + info.biYPelsPerMeter = 1000; + info.biClrUsed = 0; + info.biClrImportant = 0; + HDC dc = GetDC(NULL); + HBITMAP dst = CreateDIBSection(dc, (BITMAPINFO*)&info, + DIB_RGB_COLORS, &raw, NULL, 0); + + // find the start of the pixel data + const char* srcBits = (const char*)bitmap + bitmap->bmiHeader.biSize; + if (bitmap->bmiHeader.biBitCount >= 16) { + if (bitmap->bmiHeader.biCompression == BI_BITFIELDS && + (bitmap->bmiHeader.biBitCount == 16 || + bitmap->bmiHeader.biBitCount == 32)) { + srcBits += 3 * sizeof(DWORD); + } + } + else if (bitmap->bmiHeader.biClrUsed != 0) { + srcBits += bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD); + } + else { + srcBits += (1 << bitmap->bmiHeader.biBitCount) * sizeof(RGBQUAD); + } + + // copy source image to destination image + HDC dstDC = CreateCompatibleDC(dc); + HGDIOBJ oldBitmap = SelectObject(dstDC, dst); + SetDIBitsToDevice(dstDC, 0, 0, w, h, 0, 0, 0, h, + srcBits, bitmap, DIB_RGB_COLORS); + SelectObject(dstDC, oldBitmap); + DeleteDC(dstDC); + GdiFlush(); + + // extract data + CString image((const char*)&info, info.biSize); + image.append((const char*)raw, 4 * w * h); + + // clean up GDI + DeleteObject(dst); + ReleaseDC(NULL, dc); + + // release handle + GlobalUnlock(data); + + return image; +} diff --git a/lib/platform/CMSWindowsClipboardBitmapConverter.h b/lib/platform/CMSWindowsClipboardBitmapConverter.h new file mode 100644 index 00000000..6ddd7ce8 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardBitmapConverter.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIPBOARDBITMAPCONVERTER_H +#define CMSWINDOWSCLIPBOARDBITMAPCONVERTER_H + +#include "CMSWindowsClipboard.h" + +//! Convert to/from some text encoding +class CMSWindowsClipboardBitmapConverter : + public IMSWindowsClipboardConverter { +public: + CMSWindowsClipboardBitmapConverter(); + virtual ~CMSWindowsClipboardBitmapConverter(); + + // IMSWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual UINT getWin32Format() const; + virtual HANDLE fromIClipboard(const CString&) const; + virtual CString toIClipboard(HANDLE) const; +}; + +#endif diff --git a/lib/platform/CMSWindowsClipboardHTMLConverter.cpp b/lib/platform/CMSWindowsClipboardHTMLConverter.cpp new file mode 100644 index 00000000..a64a0f78 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardHTMLConverter.cpp @@ -0,0 +1,107 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CMSWindowsClipboardHTMLConverter.h" +#include "CStringUtil.h" + +// +// CMSWindowsClipboardHTMLConverter +// + +CMSWindowsClipboardHTMLConverter::CMSWindowsClipboardHTMLConverter() +{ + m_format = RegisterClipboardFormat("HTML Format"); +} + +CMSWindowsClipboardHTMLConverter::~CMSWindowsClipboardHTMLConverter() +{ + // do nothing +} + +IClipboard::EFormat +CMSWindowsClipboardHTMLConverter::getFormat() const +{ + return IClipboard::kHTML; +} + +UINT +CMSWindowsClipboardHTMLConverter::getWin32Format() const +{ + return m_format; +} + +CString +CMSWindowsClipboardHTMLConverter::doFromIClipboard(const CString& data) const +{ + // prepare to CF_HTML format prefix and suffix + CString prefix("Version:0.9\nStartHTML:-1\nEndHTML:-1\n" + "StartFragment:XXXXXXXXXX\nEndFragment:YYYYYYYYYY\n" + ""); + CString suffix("\n"); + UInt32 start = prefix.size(); + UInt32 end = start + data.size(); + prefix.replace(prefix.find("XXXXXXXXXX"), 10, + CStringUtil::print("%010u", start)); + prefix.replace(prefix.find("YYYYYYYYYY"), 10, + CStringUtil::print("%010u", end)); + + // concatenate + prefix += data; + prefix += suffix; + return prefix; +} + +CString +CMSWindowsClipboardHTMLConverter::doToIClipboard(const CString& data) const +{ + // get fragment start/end args + CString startArg = findArg(data, "StartFragment"); + CString endArg = findArg(data, "EndFragment"); + if (startArg.empty() || endArg.empty()) { + return CString(); + } + + // convert args to integers + SInt32 start = (SInt32)atoi(startArg.c_str()); + SInt32 end = (SInt32)atoi(endArg.c_str()); + if (start <= 0 || end <= 0 || start >= end) { + return CString(); + } + + // extract the fragment + return data.substr(start, end - start); +} + +CString +CMSWindowsClipboardHTMLConverter::findArg( + const CString& data, const CString& name) const +{ + CString::size_type i = data.find(name); + if (i == CString::npos) { + return CString(); + } + i = data.find_first_of(":\r\n", i); + if (i == CString::npos || data[i] != ':') { + return CString(); + } + i = data.find_first_of("0123456789\r\n", i + 1); + if (i == CString::npos || data[i] == '\r' || data[i] == '\n') { + return CString(); + } + CString::size_type j = data.find_first_not_of("0123456789", i); + if (j == CString::npos) { + j = data.size(); + } + return data.substr(i, j - i); +} diff --git a/lib/platform/CMSWindowsClipboardHTMLConverter.h b/lib/platform/CMSWindowsClipboardHTMLConverter.h new file mode 100644 index 00000000..02cd8f88 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardHTMLConverter.h @@ -0,0 +1,44 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIPBOARDHTMLCONVERTER_H +#define CMSWINDOWSCLIPBOARDHTMLCONVERTER_H + +#include "CMSWindowsClipboardAnyTextConverter.h" + +//! Convert to/from HTML encoding +class CMSWindowsClipboardHTMLConverter : + public CMSWindowsClipboardAnyTextConverter { +public: + CMSWindowsClipboardHTMLConverter(); + virtual ~CMSWindowsClipboardHTMLConverter(); + + // IMSWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual UINT getWin32Format() const; + +protected: + // CMSWindowsClipboardAnyTextConverter overrides + virtual CString doFromIClipboard(const CString&) const; + virtual CString doToIClipboard(const CString&) const; + +private: + CString findArg(const CString& data, const CString& name) const; + +private: + UINT m_format; +}; + +#endif diff --git a/lib/platform/CMSWindowsClipboardTextConverter.cpp b/lib/platform/CMSWindowsClipboardTextConverter.cpp new file mode 100644 index 00000000..a735094a --- /dev/null +++ b/lib/platform/CMSWindowsClipboardTextConverter.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 "CMSWindowsClipboardTextConverter.h" +#include "CUnicode.h" + +// +// CMSWindowsClipboardTextConverter +// + +CMSWindowsClipboardTextConverter::CMSWindowsClipboardTextConverter() +{ + // do nothing +} + +CMSWindowsClipboardTextConverter::~CMSWindowsClipboardTextConverter() +{ + // do nothing +} + +UINT +CMSWindowsClipboardTextConverter::getWin32Format() const +{ + return CF_TEXT; +} + +CString +CMSWindowsClipboardTextConverter::doFromIClipboard(const CString& data) const +{ + // convert and add nul terminator + return CUnicode::UTF8ToText(data) += '\0'; +} + +CString +CMSWindowsClipboardTextConverter::doToIClipboard(const CString& data) const +{ + // convert and truncate at first nul terminator + CString dst = CUnicode::textToUTF8(data); + CString::size_type n = dst.find('\0'); + if (n != CString::npos) { + dst.erase(n); + } + return dst; +} diff --git a/lib/platform/CMSWindowsClipboardTextConverter.h b/lib/platform/CMSWindowsClipboardTextConverter.h new file mode 100644 index 00000000..6f00d475 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardTextConverter.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIPBOARDTEXTCONVERTER_H +#define CMSWINDOWSCLIPBOARDTEXTCONVERTER_H + +#include "CMSWindowsClipboardAnyTextConverter.h" + +//! Convert to/from locale text encoding +class CMSWindowsClipboardTextConverter : + public CMSWindowsClipboardAnyTextConverter { +public: + CMSWindowsClipboardTextConverter(); + virtual ~CMSWindowsClipboardTextConverter(); + + // IMSWindowsClipboardConverter overrides + virtual UINT getWin32Format() const; + +protected: + // CMSWindowsClipboardAnyTextConverter overrides + virtual CString doFromIClipboard(const CString&) const; + virtual CString doToIClipboard(const CString&) const; +}; + +#endif diff --git a/lib/platform/CMSWindowsClipboardUTF16Converter.cpp b/lib/platform/CMSWindowsClipboardUTF16Converter.cpp new file mode 100644 index 00000000..81b85c60 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardUTF16Converter.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 "CMSWindowsClipboardUTF16Converter.h" +#include "CUnicode.h" + +// +// CMSWindowsClipboardUTF16Converter +// + +CMSWindowsClipboardUTF16Converter::CMSWindowsClipboardUTF16Converter() +{ + // do nothing +} + +CMSWindowsClipboardUTF16Converter::~CMSWindowsClipboardUTF16Converter() +{ + // do nothing +} + +UINT +CMSWindowsClipboardUTF16Converter::getWin32Format() const +{ + return CF_UNICODETEXT; +} + +CString +CMSWindowsClipboardUTF16Converter::doFromIClipboard(const CString& data) const +{ + // convert and add nul terminator + return CUnicode::UTF8ToUTF16(data).append(sizeof(wchar_t), 0); +} + +CString +CMSWindowsClipboardUTF16Converter::doToIClipboard(const CString& data) const +{ + // convert and strip nul terminator + CString dst = CUnicode::UTF16ToUTF8(data); + CString::size_type n = dst.find('\0'); + if (n != CString::npos) { + dst.erase(n); + } + return dst; +} diff --git a/lib/platform/CMSWindowsClipboardUTF16Converter.h b/lib/platform/CMSWindowsClipboardUTF16Converter.h new file mode 100644 index 00000000..51f477fa --- /dev/null +++ b/lib/platform/CMSWindowsClipboardUTF16Converter.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIPBOARDUTF16CONVERTER_H +#define CMSWINDOWSCLIPBOARDUTF16CONVERTER_H + +#include "CMSWindowsClipboardAnyTextConverter.h" + +//! Convert to/from UTF-16 encoding +class CMSWindowsClipboardUTF16Converter : + public CMSWindowsClipboardAnyTextConverter { +public: + CMSWindowsClipboardUTF16Converter(); + virtual ~CMSWindowsClipboardUTF16Converter(); + + // IMSWindowsClipboardConverter overrides + virtual UINT getWin32Format() const; + +protected: + // CMSWindowsClipboardAnyTextConverter overrides + virtual CString doFromIClipboard(const CString&) const; + virtual CString doToIClipboard(const CString&) const; +}; + +#endif diff --git a/lib/platform/CMSWindowsDesks.cpp b/lib/platform/CMSWindowsDesks.cpp new file mode 100644 index 00000000..4a1c4474 --- /dev/null +++ b/lib/platform/CMSWindowsDesks.cpp @@ -0,0 +1,1033 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CMSWindowsDesks.h" +#include "CMSWindowsScreen.h" +#include "CSynergyHook.h" +#include "IScreenSaver.h" +#include "XScreen.h" +#include "CLock.h" +#include "CThread.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "IJob.h" +#include "TMethodEventJob.h" +#include "TMethodJob.h" +#include "CArchMiscWindows.h" +#include + +// these are only defined when WINVER >= 0x0500 +#if !defined(SPI_GETMOUSESPEED) +#define SPI_GETMOUSESPEED 112 +#endif +#if !defined(SPI_SETMOUSESPEED) +#define SPI_SETMOUSESPEED 113 +#endif +#if !defined(SPI_GETSCREENSAVERRUNNING) +#define SPI_GETSCREENSAVERRUNNING 114 +#endif + +// X button stuff +#if !defined(WM_XBUTTONDOWN) +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define WM_NCXBUTTONDOWN 0x00AB +#define WM_NCXBUTTONUP 0x00AC +#define WM_NCXBUTTONDBLCLK 0x00AD +#define MOUSEEVENTF_XDOWN 0x0080 +#define MOUSEEVENTF_XUP 0x0100 +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif + +// ; +#define SYNERGY_MSG_SWITCH SYNERGY_HOOK_LAST_MSG + 1 +// ; +#define SYNERGY_MSG_ENTER SYNERGY_HOOK_LAST_MSG + 2 +// ; +#define SYNERGY_MSG_LEAVE SYNERGY_HOOK_LAST_MSG + 3 +// wParam = flags, HIBYTE(lParam) = virtual key, LOBYTE(lParam) = scan code +#define SYNERGY_MSG_FAKE_KEY SYNERGY_HOOK_LAST_MSG + 4 + // flags, XBUTTON id +#define SYNERGY_MSG_FAKE_BUTTON SYNERGY_HOOK_LAST_MSG + 5 +// x; y +#define SYNERGY_MSG_FAKE_MOVE SYNERGY_HOOK_LAST_MSG + 6 +// xDelta; yDelta +#define SYNERGY_MSG_FAKE_WHEEL SYNERGY_HOOK_LAST_MSG + 7 +// POINT*; +#define SYNERGY_MSG_CURSOR_POS SYNERGY_HOOK_LAST_MSG + 8 +// IKeyState*; +#define SYNERGY_MSG_SYNC_KEYS SYNERGY_HOOK_LAST_MSG + 9 +// install; +#define SYNERGY_MSG_SCREENSAVER SYNERGY_HOOK_LAST_MSG + 10 +// dx; dy +#define SYNERGY_MSG_FAKE_REL_MOVE SYNERGY_HOOK_LAST_MSG + 11 +// enable; +#define SYNERGY_MSG_FAKE_INPUT SYNERGY_HOOK_LAST_MSG + 12 + +// +// CMSWindowsDesks +// + +CMSWindowsDesks::CMSWindowsDesks( + bool isPrimary, HINSTANCE hookLibrary, + const IScreenSaver* screensaver, IJob* updateKeys) : + m_isPrimary(isPrimary), + m_is95Family(CArchMiscWindows::isWindows95Family()), + m_isModernFamily(CArchMiscWindows::isWindowsModern()), + m_isOnScreen(m_isPrimary), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_multimon(false), + m_timer(NULL), + m_screensaver(screensaver), + m_screensaverNotify(false), + m_activeDesk(NULL), + m_activeDeskName(), + m_mutex(), + m_deskReady(&m_mutex, false), + m_updateKeys(updateKeys) +{ + queryHookLibrary(hookLibrary); + m_cursor = createBlankCursor(); + m_deskClass = createDeskWindowClass(m_isPrimary); + m_keyLayout = GetKeyboardLayout(GetCurrentThreadId()); + resetOptions(); +} + +CMSWindowsDesks::~CMSWindowsDesks() +{ + disable(); + destroyClass(m_deskClass); + destroyCursor(m_cursor); + delete m_updateKeys; +} + +void +CMSWindowsDesks::enable() +{ + m_threadID = GetCurrentThreadId(); + + // set the active desk and (re)install the hooks + checkDesk(); + + // install the desk timer. this timer periodically checks + // which desk is active and reinstalls the hooks as necessary. + // we wouldn't need this if windows notified us of a desktop + // change but as far as i can tell it doesn't. + m_timer = EVENTQUEUE->newTimer(0.2, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_timer, + new TMethodEventJob( + this, &CMSWindowsDesks::handleCheckDesk)); + + updateKeys(); +} + +void +CMSWindowsDesks::disable() +{ + // remove timer + if (m_timer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_timer); + EVENTQUEUE->deleteTimer(m_timer); + m_timer = NULL; + } + + // destroy desks + removeDesks(); + + m_isOnScreen = m_isPrimary; +} + +void +CMSWindowsDesks::enter() +{ + sendMessage(SYNERGY_MSG_ENTER, 0, 0); +} + +void +CMSWindowsDesks::leave(HKL keyLayout) +{ + sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)keyLayout, 0); +} + +void +CMSWindowsDesks::resetOptions() +{ + m_leaveForegroundOption = false; +} + +void +CMSWindowsDesks::setOptions(const COptionsList& options) +{ + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + if (options[i] == kOptionWin32KeepForeground) { + m_leaveForegroundOption = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "%s the foreground window", m_leaveForegroundOption ? "Don\'t grab" : "Grab")); + } + } +} + +void +CMSWindowsDesks::updateKeys() +{ + sendMessage(SYNERGY_MSG_SYNC_KEYS, 0, 0); +} + +void +CMSWindowsDesks::setShape(SInt32 x, SInt32 y, + SInt32 width, SInt32 height, + SInt32 xCenter, SInt32 yCenter, bool isMultimon) +{ + m_x = x; + m_y = y; + m_w = width; + m_h = height; + m_xCenter = xCenter; + m_yCenter = yCenter; + m_multimon = isMultimon; +} + +void +CMSWindowsDesks::installScreensaverHooks(bool install) +{ + if (m_isPrimary && m_screensaverNotify != install) { + m_screensaverNotify = install; + sendMessage(SYNERGY_MSG_SCREENSAVER, install, 0); + } +} + +void +CMSWindowsDesks::fakeInputBegin() +{ + sendMessage(SYNERGY_MSG_FAKE_INPUT, 1, 0); +} + +void +CMSWindowsDesks::fakeInputEnd() +{ + sendMessage(SYNERGY_MSG_FAKE_INPUT, 0, 0); +} + +void +CMSWindowsDesks::getCursorPos(SInt32& x, SInt32& y) const +{ + POINT pos; + sendMessage(SYNERGY_MSG_CURSOR_POS, reinterpret_cast(&pos), 0); + x = pos.x; + y = pos.y; +} + +void +CMSWindowsDesks::fakeKeyEvent( + KeyButton button, UINT virtualKey, + bool press, bool /*isAutoRepeat*/) const +{ + // win 95 family doesn't understand handed modifier virtual keys + if (m_is95Family) { + switch (virtualKey) { + case VK_LSHIFT: + case VK_RSHIFT: + virtualKey = VK_SHIFT; + break; + + case VK_LCONTROL: + case VK_RCONTROL: + virtualKey = VK_CONTROL; + break; + + case VK_LMENU: + case VK_RMENU: + virtualKey = VK_MENU; + break; + } + } + + // synthesize event + DWORD flags = 0; + if (((button & 0x100u) != 0)) { + flags |= KEYEVENTF_EXTENDEDKEY; + } + if (!press) { + flags |= KEYEVENTF_KEYUP; + } + sendMessage(SYNERGY_MSG_FAKE_KEY, flags, + MAKEWORD(static_cast(button & 0xffu), + static_cast(virtualKey & 0xffu))); +} + +void +CMSWindowsDesks::fakeMouseButton(ButtonID button, bool press) const +{ + // the system will swap the meaning of left/right for us if + // the user has configured a left-handed mouse but we don't + // want it to swap since we want the handedness of the + // server's mouse. so pre-swap for a left-handed mouse. + if (GetSystemMetrics(SM_SWAPBUTTON)) { + switch (button) { + case kButtonLeft: + button = kButtonRight; + break; + + case kButtonRight: + button = kButtonLeft; + break; + } + } + + // map button id to button flag and button data + DWORD data = 0; + DWORD flags; + switch (button) { + case kButtonLeft: + flags = press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; + break; + + case kButtonMiddle: + flags = press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; + break; + + case kButtonRight: + flags = press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; + break; + + case kButtonExtra0 + 0: + data = XBUTTON1; + flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + break; + + case kButtonExtra0 + 1: + data = XBUTTON2; + flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + break; + + default: + return; + } + + // do it + sendMessage(SYNERGY_MSG_FAKE_BUTTON, flags, data); +} + +void +CMSWindowsDesks::fakeMouseMove(SInt32 x, SInt32 y) const +{ + sendMessage(SYNERGY_MSG_FAKE_MOVE, + static_cast(x), + static_cast(y)); +} + +void +CMSWindowsDesks::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + sendMessage(SYNERGY_MSG_FAKE_REL_MOVE, + static_cast(dx), + static_cast(dy)); +} + +void +CMSWindowsDesks::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + sendMessage(SYNERGY_MSG_FAKE_WHEEL, xDelta, yDelta); +} + +void +CMSWindowsDesks::sendMessage(UINT msg, WPARAM wParam, LPARAM lParam) const +{ + if (m_activeDesk != NULL && m_activeDesk->m_window != NULL) { + PostThreadMessage(m_activeDesk->m_threadID, msg, wParam, lParam); + waitForDesk(); + } +} + +void +CMSWindowsDesks::queryHookLibrary(HINSTANCE hookLibrary) +{ + // look up functions + if (m_isPrimary) { + m_install = (InstallFunc)GetProcAddress(hookLibrary, "install"); + m_uninstall = (UninstallFunc)GetProcAddress(hookLibrary, "uninstall"); + m_installScreensaver = + (InstallScreenSaverFunc)GetProcAddress( + hookLibrary, "installScreenSaver"); + m_uninstallScreensaver = + (UninstallScreenSaverFunc)GetProcAddress( + hookLibrary, "uninstallScreenSaver"); + if (m_install == NULL || + m_uninstall == NULL || + m_installScreensaver == NULL || + m_uninstallScreensaver == NULL) { + LOG((CLOG_ERR "Invalid hook library")); + throw XScreenOpenFailure(); + } + } + else { + m_install = NULL; + m_uninstall = NULL; + m_installScreensaver = NULL; + m_uninstallScreensaver = NULL; + } +} + +HCURSOR +CMSWindowsDesks::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)); + memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2)); + HCURSOR c = CreateCursor(CMSWindowsScreen::getInstance(), + 0, 0, cw, ch, cursorAND, cursorXOR); + delete[] cursorXOR; + delete[] cursorAND; + return c; +} + +void +CMSWindowsDesks::destroyCursor(HCURSOR cursor) const +{ + if (cursor != NULL) { + DestroyCursor(cursor); + } +} + +ATOM +CMSWindowsDesks::createDeskWindowClass(bool isPrimary) const +{ + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_DBLCLKS | CS_NOCLOSE; + classInfo.lpfnWndProc = isPrimary ? + &CMSWindowsDesks::primaryDeskProc : + &CMSWindowsDesks::secondaryDeskProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = 0; + classInfo.hInstance = CMSWindowsScreen::getInstance(); + classInfo.hIcon = NULL; + classInfo.hCursor = m_cursor; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = "SynergyDesk"; + classInfo.hIconSm = NULL; + return RegisterClassEx(&classInfo); +} + +void +CMSWindowsDesks::destroyClass(ATOM windowClass) const +{ + if (windowClass != 0) { + UnregisterClass(reinterpret_cast(windowClass), + CMSWindowsScreen::getInstance()); + } +} + +HWND +CMSWindowsDesks::createWindow(ATOM windowClass, const char* name) const +{ + HWND window = CreateWindowEx(WS_EX_TOPMOST | + WS_EX_TRANSPARENT | + WS_EX_TOOLWINDOW, + reinterpret_cast(windowClass), + name, + WS_POPUP, + 0, 0, 1, 1, + NULL, NULL, + CMSWindowsScreen::getInstance(), + NULL); + if (window == NULL) { + LOG((CLOG_ERR "failed to create window: %d", GetLastError())); + throw XScreenOpenFailure(); + } + return window; +} + +void +CMSWindowsDesks::destroyWindow(HWND hwnd) const +{ + if (hwnd != NULL) { + DestroyWindow(hwnd); + } +} + +LRESULT CALLBACK +CMSWindowsDesks::primaryDeskProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT CALLBACK +CMSWindowsDesks::secondaryDeskProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + // would like to detect any local user input and hide the hider + // window but for now we just detect mouse motion. + bool hide = false; + switch (msg) { + case WM_MOUSEMOVE: + if (LOWORD(lParam) != 0 || HIWORD(lParam) != 0) { + hide = true; + } + break; + } + + if (hide && IsWindowVisible(hwnd)) { + ReleaseCapture(); + SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + SWP_NOACTIVATE | SWP_HIDEWINDOW); + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +void +CMSWindowsDesks::deskMouseMove(SInt32 x, SInt32 y) const +{ + // motion is simple (i.e. it's on the primary monitor) if there + // is only one monitor. it's also simple if we're not on the + // windows 95 family since those platforms don't have a broken + // mouse_event() function (see the comment below). + bool simple = (!m_multimon || !m_is95Family); + if (!simple) { + // also simple if motion is within the primary monitor + simple = (x >= 0 && x < GetSystemMetrics(SM_CXSCREEN) && + y >= 0 && y < GetSystemMetrics(SM_CYSCREEN)); + } + + // move the mouse directly to target position if motion is simple + if (simple) { + // when using absolute positioning with mouse_event(), + // the normalized device coordinates range over only + // the primary screen. + SInt32 w = GetSystemMetrics(SM_CXSCREEN); + SInt32 h = GetSystemMetrics(SM_CYSCREEN); + mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, + (DWORD)((65535.0f * x) / (w - 1) + 0.5f), + (DWORD)((65535.0f * y) / (h - 1) + 0.5f), + 0, 0); + } + + // windows 98 and Me are broken. you cannot set the absolute + // position of the mouse except on the primary monitor but you + // can do relative moves onto any monitor. this is, in microsoft's + // words, "by design." apparently the designers of windows 2000 + // we're a little less lazy and did it right. + // + // microsoft recommends in Q193003 to absolute position the cursor + // somewhere on the primary monitor then relative move to the + // desired location. this doesn't work for us because when the + // user drags a scrollbar, a window, etc. it causes the dragged + // item to jump back and forth between the position on the primary + // monitor and the desired position. while it always ends up in + // the right place, the effect is disconcerting. + // + // instead we'll get the cursor's current position and do just a + // relative move from there to the desired position. + else { + POINT pos; + GetCursorPos(&pos); + deskMouseRelativeMove(x - pos.x, y - pos.y); + } +} + +void +CMSWindowsDesks::deskMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + // relative moves are subject to cursor acceleration which we don't + // want.so we disable acceleration, do the relative move, then + // restore acceleration. there's a slight chance we'll end up in + // the wrong place if the user moves the cursor using this system's + // mouse while simultaneously moving the mouse on the server + // system. that defeats the purpose of synergy so we'll assume + // that won't happen. even if it does, the next mouse move will + // correct the position. + + // save mouse speed & acceleration + int oldSpeed[4]; + bool accelChanged = + SystemParametersInfo(SPI_GETMOUSE,0, oldSpeed, 0) && + SystemParametersInfo(SPI_GETMOUSESPEED, 0, oldSpeed + 3, 0); + + // use 1:1 motion + if (accelChanged) { + int newSpeed[4] = { 0, 0, 0, 1 }; + accelChanged = + SystemParametersInfo(SPI_SETMOUSE, 0, newSpeed, 0) || + SystemParametersInfo(SPI_SETMOUSESPEED, 0, newSpeed + 3, 0); + } + + // move relative to mouse position + mouse_event(MOUSEEVENTF_MOVE, dx, dy, 0, 0); + + // restore mouse speed & acceleration + if (accelChanged) { + SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0); + SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0); + } +} + +void +CMSWindowsDesks::deskEnter(CDesk* desk) +{ + if (!m_isPrimary) { + ReleaseCapture(); + } + ShowCursor(TRUE); + SetWindowPos(desk->m_window, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + SWP_NOACTIVATE | SWP_HIDEWINDOW); + + // restore the foreground window + // XXX -- this raises the window to the top of the Z-order. we + // want it to stay wherever it was to properly support X-mouse + // (mouse over activation) but i've no idea how to do that. + // the obvious workaround of using SetWindowPos() to move it back + // after being raised doesn't work. + DWORD thisThread = + GetWindowThreadProcessId(desk->m_window, NULL); + DWORD thatThread = + GetWindowThreadProcessId(desk->m_foregroundWindow, NULL); + AttachThreadInput(thatThread, thisThread, TRUE); + SetForegroundWindow(desk->m_foregroundWindow); + AttachThreadInput(thatThread, thisThread, FALSE); + EnableWindow(desk->m_window, desk->m_lowLevel ? FALSE : TRUE); + desk->m_foregroundWindow = NULL; +} + +void +CMSWindowsDesks::deskLeave(CDesk* desk, HKL keyLayout) +{ + ShowCursor(FALSE); + if (m_isPrimary) { + // map a window to hide the cursor and to use whatever keyboard + // layout we choose rather than the keyboard layout of the last + // active window. + int x, y, w, h; + if (desk->m_lowLevel) { + // with a low level hook the cursor will never budge so + // just a 1x1 window is sufficient. + x = m_xCenter; + y = m_yCenter; + w = 1; + h = 1; + } + else { + // with regular hooks the cursor will jitter as it's moved + // by the user then back to the center by us. to be sure + // we never lose it, cover all the monitors with the window. + x = m_x; + y = m_y; + w = m_w; + h = m_h; + } + SetWindowPos(desk->m_window, HWND_TOPMOST, x, y, w, h, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + + // if not using low-level hooks we have to also activate the + // window to ensure we don't lose keyboard focus. + // FIXME -- see if this can be avoided. if so then always + // disable the window (see handling of SYNERGY_MSG_SWITCH). + if (!desk->m_lowLevel) { + SetActiveWindow(desk->m_window); + } + + // if using low-level hooks then disable the foreground window + // so it can't mess up any of our keyboard events. the console + // program, for example, will cause characters to be reported as + // unshifted, regardless of the shift key state. interestingly + // we do see the shift key go down and up. + // + // note that we must enable the window to activate it and we + // need to disable the window on deskEnter. + else { + desk->m_foregroundWindow = getForegroundWindow(); + if (desk->m_foregroundWindow != NULL) { + EnableWindow(desk->m_window, TRUE); + SetActiveWindow(desk->m_window); + DWORD thisThread = + GetWindowThreadProcessId(desk->m_window, NULL); + DWORD thatThread = + GetWindowThreadProcessId(desk->m_foregroundWindow, NULL); + AttachThreadInput(thatThread, thisThread, TRUE); + SetForegroundWindow(desk->m_window); + AttachThreadInput(thatThread, thisThread, FALSE); + } + } + + // switch to requested keyboard layout + ActivateKeyboardLayout(keyLayout, 0); + } + else { + // move hider window under the cursor center, raise, and show it + SetWindowPos(desk->m_window, HWND_TOPMOST, + m_xCenter, m_yCenter, 1, 1, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + + // watch for mouse motion. if we see any then we hide the + // hider window so the user can use the physically attached + // mouse if desired. we'd rather not capture the mouse but + // we aren't notified when the mouse leaves our window. + SetCapture(desk->m_window); + + // warp the mouse to the cursor center + deskMouseMove(m_xCenter, m_yCenter); + } +} + +void +CMSWindowsDesks::deskThread(void* vdesk) +{ + MSG msg; + + // use given desktop for this thread + CDesk* desk = reinterpret_cast(vdesk); + desk->m_threadID = GetCurrentThreadId(); + desk->m_window = NULL; + desk->m_foregroundWindow = NULL; + if (desk->m_desk != NULL && SetThreadDesktop(desk->m_desk) != 0) { + // create a message queue + PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE); + + // create a window. we use this window to hide the cursor. + try { + desk->m_window = createWindow(m_deskClass, "SynergyDesk"); + LOG((CLOG_DEBUG "desk %s window is 0x%08x", desk->m_name.c_str(), desk->m_window)); + } + catch (...) { + // ignore + LOG((CLOG_DEBUG "can't create desk window for %s", desk->m_name.c_str())); + } + } + + // tell main thread that we're ready + { + CLock lock(&m_mutex); + m_deskReady = true; + m_deskReady.broadcast(); + } + + while (GetMessage(&msg, NULL, 0, 0)) { + switch (msg.message) { + default: + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + + case SYNERGY_MSG_SWITCH: + if (m_isPrimary) { + m_uninstall(); + if (m_screensaverNotify) { + m_uninstallScreensaver(); + m_installScreensaver(); + } + switch (m_install()) { + case kHOOK_FAILED: + // we won't work on this desk + desk->m_lowLevel = false; + break; + + case kHOOK_OKAY: + desk->m_lowLevel = false; + break; + + case kHOOK_OKAY_LL: + desk->m_lowLevel = true; + break; + } + + // a window on the primary screen with low-level hooks + // should never activate. + EnableWindow(desk->m_window, desk->m_lowLevel ? FALSE : TRUE); + } + break; + + case SYNERGY_MSG_ENTER: + m_isOnScreen = true; + deskEnter(desk); + break; + + case SYNERGY_MSG_LEAVE: + m_isOnScreen = false; + m_keyLayout = (HKL)msg.wParam; + deskLeave(desk, m_keyLayout); + break; + + case SYNERGY_MSG_FAKE_KEY: + keybd_event(HIBYTE(msg.lParam), LOBYTE(msg.lParam), msg.wParam, 0); + break; + + case SYNERGY_MSG_FAKE_BUTTON: + if (msg.wParam != 0) { + mouse_event(msg.wParam, 0, 0, msg.lParam, 0); + } + break; + + case SYNERGY_MSG_FAKE_MOVE: + deskMouseMove(static_cast(msg.wParam), + static_cast(msg.lParam)); + break; + + case SYNERGY_MSG_FAKE_REL_MOVE: + deskMouseRelativeMove(static_cast(msg.wParam), + static_cast(msg.lParam)); + break; + + case SYNERGY_MSG_FAKE_WHEEL: + // XXX -- add support for x-axis scrolling + if (msg.lParam != 0) { + mouse_event(MOUSEEVENTF_WHEEL, 0, 0, msg.lParam, 0); + } + break; + + case SYNERGY_MSG_CURSOR_POS: { + POINT* pos = reinterpret_cast(msg.wParam); + if (!GetCursorPos(pos)) { + pos->x = m_xCenter; + pos->y = m_yCenter; + } + break; + } + + case SYNERGY_MSG_SYNC_KEYS: + m_updateKeys->run(); + break; + + case SYNERGY_MSG_SCREENSAVER: + if (msg.wParam != 0) { + m_installScreensaver(); + } + else { + m_uninstallScreensaver(); + } + break; + + case SYNERGY_MSG_FAKE_INPUT: + keybd_event(SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY, + SYNERGY_HOOK_FAKE_INPUT_SCANCODE, + msg.wParam ? 0 : KEYEVENTF_KEYUP, 0); + break; + } + + // notify that message was processed + CLock lock(&m_mutex); + m_deskReady = true; + m_deskReady.broadcast(); + } + + // clean up + deskEnter(desk); + if (desk->m_window != NULL) { + DestroyWindow(desk->m_window); + } + if (desk->m_desk != NULL) { + closeDesktop(desk->m_desk); + } +} + +CMSWindowsDesks::CDesk* +CMSWindowsDesks::addDesk(const CString& name, HDESK hdesk) +{ + CDesk* desk = new CDesk; + desk->m_name = name; + desk->m_desk = hdesk; + desk->m_targetID = GetCurrentThreadId(); + desk->m_thread = new CThread(new TMethodJob( + this, &CMSWindowsDesks::deskThread, desk)); + waitForDesk(); + m_desks.insert(std::make_pair(name, desk)); + return desk; +} + +void +CMSWindowsDesks::removeDesks() +{ + for (CDesks::iterator index = m_desks.begin(); + index != m_desks.end(); ++index) { + CDesk* desk = index->second; + PostThreadMessage(desk->m_threadID, WM_QUIT, 0, 0); + desk->m_thread->wait(); + delete desk->m_thread; + delete desk; + } + m_desks.clear(); + m_activeDesk = NULL; + m_activeDeskName = ""; +} + +void +CMSWindowsDesks::checkDesk() +{ + // get current desktop. if we already know about it then return. + CDesk* desk; + HDESK hdesk = openInputDesktop(); + CString name = getDesktopName(hdesk); + CDesks::const_iterator index = m_desks.find(name); + if (index == m_desks.end()) { + desk = addDesk(name, hdesk); + // hold on to hdesk until thread exits so the desk can't + // be removed by the system + } + else { + closeDesktop(hdesk); + desk = index->second; + } + + // if active desktop changed then tell the old and new desk threads + // about the change. don't switch desktops when the screensaver is + // active becaue we'd most likely switch to the screensaver desktop + // which would have the side effect of forcing the screensaver to + // stop. + if (name != m_activeDeskName && !m_screensaver->isActive()) { + // show cursor on previous desk + bool wasOnScreen = m_isOnScreen; + if (!wasOnScreen) { + sendMessage(SYNERGY_MSG_ENTER, 0, 0); + } + + // check for desk accessibility change. we don't get events + // from an inaccessible desktop so when we switch from an + // inaccessible desktop to an accessible one we have to + // update the keyboard state. + LOG((CLOG_DEBUG "switched to desk \"%s\"", name.c_str())); + bool syncKeys = false; + bool isAccessible = isDeskAccessible(desk); + if (isDeskAccessible(m_activeDesk) != isAccessible) { + if (isAccessible) { + LOG((CLOG_DEBUG "desktop is now accessible")); + syncKeys = true; + } + else { + LOG((CLOG_DEBUG "desktop is now inaccessible")); + } + } + + // switch desk + m_activeDesk = desk; + m_activeDeskName = name; + sendMessage(SYNERGY_MSG_SWITCH, 0, 0); + + // hide cursor on new desk + if (!wasOnScreen) { + sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0); + } + + // update keys if necessary + if (syncKeys) { + updateKeys(); + } + } + else if (name != m_activeDeskName) { + // screen saver might have started + PostThreadMessage(m_threadID, SYNERGY_MSG_SCREEN_SAVER, TRUE, 0); + } +} + +bool +CMSWindowsDesks::isDeskAccessible(const CDesk* desk) const +{ + return (desk != NULL && desk->m_desk != NULL); +} + +void +CMSWindowsDesks::waitForDesk() const +{ + CMSWindowsDesks* self = const_cast(this); + + CLock lock(&m_mutex); + while (!(bool)m_deskReady) { + m_deskReady.wait(); + } + self->m_deskReady = false; +} + +void +CMSWindowsDesks::handleCheckDesk(const CEvent&, void*) +{ + checkDesk(); + + // also check if screen saver is running if on a modern OS and + // this is the primary screen. + if (m_isPrimary && m_isModernFamily) { + BOOL running; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, FALSE); + PostThreadMessage(m_threadID, SYNERGY_MSG_SCREEN_SAVER, running, 0); + } +} + +HDESK +CMSWindowsDesks::openInputDesktop() +{ + if (m_is95Family) { + // there's only one desktop on windows 95 et al. + return GetThreadDesktop(GetCurrentThreadId()); + } + else { + return OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, TRUE, + DESKTOP_CREATEWINDOW | + DESKTOP_HOOKCONTROL | + GENERIC_WRITE); + } +} + +void +CMSWindowsDesks::closeDesktop(HDESK desk) +{ + // on 95/98/me we don't need to close the desktop returned by + // openInputDesktop(). + if (desk != NULL && !m_is95Family) { + CloseDesktop(desk); + } +} + +CString +CMSWindowsDesks::getDesktopName(HDESK desk) +{ + if (desk == NULL) { + return CString(); + } + else if (m_is95Family) { + return "desktop"; + } + else { + DWORD size; + GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size); + TCHAR* name = (TCHAR*)alloca(size + sizeof(TCHAR)); + GetUserObjectInformation(desk, UOI_NAME, name, size, &size); + CString result(name); + return result; + } +} + +HWND +CMSWindowsDesks::getForegroundWindow() const +{ + // Ideally we'd return NULL as much as possible, only returning + // the actual foreground window when we know it's going to mess + // up our keyboard input. For now we'll just let the user + // decide. + if (m_leaveForegroundOption) { + return NULL; + } + return GetForegroundWindow(); +} diff --git a/lib/platform/CMSWindowsDesks.h b/lib/platform/CMSWindowsDesks.h new file mode 100644 index 00000000..f0388f98 --- /dev/null +++ b/lib/platform/CMSWindowsDesks.h @@ -0,0 +1,296 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSDESKS_H +#define CMSWINDOWSDESKS_H + +#include "CSynergyHook.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "OptionTypes.h" +#include "CCondVar.h" +#include "CMutex.h" +#include "CString.h" +#include "stdmap.h" +#define WIN32_LEAN_AND_MEAN +#include + +class CEvent; +class CEventQueueTimer; +class CThread; +class IJob; +class IScreenSaver; + +//! Microsoft Windows desk handling +/*! +Desks in Microsoft Windows are only remotely like desktops on X11 +systems. A desk is another virtual surface for windows but desks +impose serious restrictions: a thread can interact with only one +desk at a time, you can't switch desks if the thread has any hooks +installed or owns any windows, windows cannot exist on multiple +desks at once, etc. Basically, they're useless except for running +the login window or the screensaver, which is what they're used +for. Synergy must deal with them mainly because of the login +window and screensaver but users can create their own desks and +synergy should work on those too. + +This class encapsulates all the desk nastiness. Clients of this +object don't have to know anything about desks. +*/ +class CMSWindowsDesks { +public: + //! Constructor + /*! + \p isPrimary is true iff the desk is for a primary screen. + \p screensaver points to a screensaver object and it's used + only to check if the screensaver is active. The \p updateKeys + job is adopted and is called when the key state should be + updated in a thread attached to the current desk. + \p hookLibrary must be a handle to the hook library. + */ + CMSWindowsDesks(bool isPrimary, HINSTANCE hookLibrary, + const IScreenSaver* screensaver, IJob* updateKeys); + ~CMSWindowsDesks(); + + //! @name manipulators + //@{ + + //! Enable desk tracking + /*! + Enables desk tracking. While enabled, this object checks to see + if the desk has changed and ensures that the hooks are installed + on the new desk. \c setShape should be called at least once + before calling \c enable. + */ + void enable(); + + //! Disable desk tracking + /*! + Disables desk tracking. \sa enable. + */ + void disable(); + + //! Notify of entering a desk + /*! + Prepares a desk for when the cursor enters it. + */ + void enter(); + + //! Notify of leaving a desk + /*! + Prepares a desk for when the cursor leaves it. + */ + void leave(HKL keyLayout); + + //! Notify of options changes + /*! + Resets all options to their default values. + */ + void resetOptions(); + + //! Notify of options changes + /*! + Set options to given values. Ignores unknown options and doesn't + modify options that aren't given in \c options. + */ + void setOptions(const COptionsList& options); + + //! Update the key state + /*! + Causes the key state to get updated to reflect the physical keyboard + state and current keyboard mapping. + */ + void updateKeys(); + + //! Tell desk about new size + /*! + This tells the desks that the display size has changed. + */ + void setShape(SInt32 x, SInt32 y, + SInt32 width, SInt32 height, + SInt32 xCenter, SInt32 yCenter, bool isMultimon); + + //! Install/uninstall screensaver hooks + /*! + If \p install is true then the screensaver hooks are installed and, + if desk tracking is enabled, updated whenever the desk changes. If + \p install is false then the screensaver hooks are uninstalled. + */ + void installScreensaverHooks(bool install); + + //! Start ignoring user input + /*! + Starts ignoring user input so we don't pick up our own synthesized events. + */ + void fakeInputBegin(); + + //! Stop ignoring user input + /*! + Undoes whatever \c fakeInputBegin() did. + */ + void fakeInputEnd(); + + //@} + //! @name accessors + //@{ + + //! Get cursor position + /*! + Return the current position of the cursor in \c x and \c y. + */ + void getCursorPos(SInt32& x, SInt32& y) const; + + //! Fake key press/release + /*! + Synthesize a press or release of key \c button. + */ + void fakeKeyEvent(KeyButton button, UINT virtualKey, + bool press, bool isAutoRepeat) const; + + //! Fake mouse press/release + /*! + Synthesize a press or release of mouse button \c id. + */ + void fakeMouseButton(ButtonID id, bool press) const; + + //! Fake mouse move + /*! + Synthesize a mouse move to the absolute coordinates \c x,y. + */ + void fakeMouseMove(SInt32 x, SInt32 y) const; + + //! Fake mouse move + /*! + Synthesize a mouse move to the relative coordinates \c dx,dy. + */ + void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + + //! Fake mouse wheel + /*! + Synthesize a mouse wheel event of amount \c delta in direction \c axis. + */ + void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + //@} + +private: + class CDesk { + public: + CString m_name; + CThread* m_thread; + DWORD m_threadID; + DWORD m_targetID; + HDESK m_desk; + HWND m_window; + HWND m_foregroundWindow; + bool m_lowLevel; + }; + typedef std::map CDesks; + + // initialization and shutdown operations + void queryHookLibrary(HINSTANCE hookLibrary); + HCURSOR createBlankCursor() const; + void destroyCursor(HCURSOR cursor) const; + ATOM createDeskWindowClass(bool isPrimary) const; + void destroyClass(ATOM windowClass) const; + HWND createWindow(ATOM windowClass, const char* name) const; + void destroyWindow(HWND) const; + + // message handlers + void deskMouseMove(SInt32 x, SInt32 y) const; + void deskMouseRelativeMove(SInt32 dx, SInt32 dy) const; + void deskEnter(CDesk* desk); + void deskLeave(CDesk* desk, HKL keyLayout); + void deskThread(void* vdesk); + + // desk switch checking and handling + CDesk* addDesk(const CString& name, HDESK hdesk); + void removeDesks(); + void checkDesk(); + bool isDeskAccessible(const CDesk* desk) const; + void handleCheckDesk(const CEvent& event, void*); + + // communication with desk threads + void waitForDesk() const; + void sendMessage(UINT, WPARAM, LPARAM) const; + + // work around for messed up keyboard events from low-level hooks + HWND getForegroundWindow() const; + + // desk API wrappers + HDESK openInputDesktop(); + void closeDesktop(HDESK); + CString getDesktopName(HDESK); + + // our desk window procs + static LRESULT CALLBACK primaryDeskProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK secondaryDeskProc(HWND, UINT, WPARAM, LPARAM); + +private: + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if windows 95/98/me + bool m_is95Family; + + // true if windows 98/2k or higher (i.e. not 95/nt) + bool m_isModernFamily; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // our resources + ATOM m_deskClass; + HCURSOR m_cursor; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // true if system appears to have multiple monitors + bool m_multimon; + + // the timer used to check for desktop switching + CEventQueueTimer* m_timer; + + // screen saver stuff + DWORD m_threadID; + const IScreenSaver* m_screensaver; + bool m_screensaverNotify; + + // the current desk and it's name + CDesk* m_activeDesk; + CString m_activeDeskName; + + // one desk per desktop and a cond var to communicate with it + CMutex m_mutex; + CCondVar m_deskReady; + CDesks m_desks; + + // hook library stuff + InstallFunc m_install; + UninstallFunc m_uninstall; + InstallScreenSaverFunc m_installScreensaver; + UninstallScreenSaverFunc m_uninstallScreensaver; + + // keyboard stuff + IJob* m_updateKeys; + HKL m_keyLayout; + + // options + bool m_leaveForegroundOption; +}; + +#endif diff --git a/lib/platform/CMSWindowsEventQueueBuffer.cpp b/lib/platform/CMSWindowsEventQueueBuffer.cpp new file mode 100644 index 00000000..5bab2855 --- /dev/null +++ b/lib/platform/CMSWindowsEventQueueBuffer.cpp @@ -0,0 +1,138 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CMSWindowsEventQueueBuffer.h" +#include "CThread.h" +#include "IEventQueue.h" +#include "CArchMiscWindows.h" + +// +// CEventQueueTimer +// + +class CEventQueueTimer { }; + + +// +// CMSWindowsEventQueueBuffer +// + +CMSWindowsEventQueueBuffer::CMSWindowsEventQueueBuffer() +{ + // remember thread. we'll be posting messages to it. + m_thread = GetCurrentThreadId(); + + // create a message type for custom events + m_userEvent = RegisterWindowMessage("SYNERGY_USER_EVENT"); + + // get message type for daemon quit + m_daemonQuit = CArchMiscWindows::getDaemonQuitMessage(); + + // make sure this thread has a message queue + MSG dummy; + PeekMessage(&dummy, NULL, WM_USER, WM_USER, PM_NOREMOVE); +} + +CMSWindowsEventQueueBuffer::~CMSWindowsEventQueueBuffer() +{ + // do nothing +} + +void +CMSWindowsEventQueueBuffer::waitForEvent(double timeout) +{ + // check if messages are available first. if we don't do this then + // MsgWaitForMultipleObjects() will block even if the queue isn't + // empty if the messages in the queue were there before the last + // call to GetMessage()/PeekMessage(). + if (HIWORD(GetQueueStatus(QS_ALLINPUT)) != 0) { + return; + } + + // convert timeout + DWORD t; + if (timeout < 0.0) { + t = INFINITE; + } + else { + t = (DWORD)(1000.0 * timeout); + } + + // wait for a message. we cannot be interrupted by thread + // cancellation but that's okay because we're run in the main + // thread and we never cancel that thread. + HANDLE dummy[1]; + MsgWaitForMultipleObjects(0, dummy, FALSE, t, QS_ALLINPUT); +} + +IEventQueueBuffer::Type +CMSWindowsEventQueueBuffer::getEvent(CEvent& event, UInt32& dataID) +{ + // peek at messages first. waiting for QS_ALLINPUT will return + // if a message has been sent to our window but GetMessage will + // dispatch that message behind our backs and block. PeekMessage + // will also dispatch behind our backs but won't block. + if (!PeekMessage(&m_event, NULL, 0, 0, PM_NOREMOVE) && + !PeekMessage(&m_event, (HWND)-1, 0, 0, PM_NOREMOVE)) { + return kNone; + } + + // BOOL. yeah, right. + BOOL result = GetMessage(&m_event, NULL, 0, 0); + if (result == -1) { + return kNone; + } + else if (result == 0) { + event = CEvent(CEvent::kQuit); + return kSystem; + } + else if (m_daemonQuit != 0 && m_event.message == m_daemonQuit) { + event = CEvent(CEvent::kQuit); + return kSystem; + } + else if (m_event.message == m_userEvent) { + dataID = static_cast(m_event.wParam); + return kUser; + } + else { + event = CEvent(CEvent::kSystem, + IEventQueue::getSystemTarget(), &m_event); + return kSystem; + } +} + +bool +CMSWindowsEventQueueBuffer::addEvent(UInt32 dataID) +{ + return (PostThreadMessage(m_thread, m_userEvent, + static_cast(dataID), 0) != 0); +} + +bool +CMSWindowsEventQueueBuffer::isEmpty() const +{ + return (HIWORD(GetQueueStatus(QS_ALLINPUT)) == 0); +} + +CEventQueueTimer* +CMSWindowsEventQueueBuffer::newTimer(double, bool) const +{ + return new CEventQueueTimer; +} + +void +CMSWindowsEventQueueBuffer::deleteTimer(CEventQueueTimer* timer) const +{ + delete timer; +} diff --git a/lib/platform/CMSWindowsEventQueueBuffer.h b/lib/platform/CMSWindowsEventQueueBuffer.h new file mode 100644 index 00000000..28d8a2f6 --- /dev/null +++ b/lib/platform/CMSWindowsEventQueueBuffer.h @@ -0,0 +1,44 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSEVENTQUEUEBUFFER_H +#define CMSWINDOWSEVENTQUEUEBUFFER_H + +#include "IEventQueueBuffer.h" +#define WIN32_LEAN_AND_MEAN +#include + +//! Event queue buffer for Win32 +class CMSWindowsEventQueueBuffer : public IEventQueueBuffer { +public: + CMSWindowsEventQueueBuffer(); + virtual ~CMSWindowsEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void waitForEvent(double timeout); + virtual Type getEvent(CEvent& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(CEventQueueTimer*) const; + +private: + DWORD m_thread; + UINT m_userEvent; + MSG m_event; + UINT m_daemonQuit; +}; + +#endif diff --git a/lib/platform/CMSWindowsKeyState.cpp b/lib/platform/CMSWindowsKeyState.cpp new file mode 100644 index 00000000..ba105530 --- /dev/null +++ b/lib/platform/CMSWindowsKeyState.cpp @@ -0,0 +1,1420 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsKeyState.h" +#include "CMSWindowsDesks.h" +#include "CThread.h" +#include "CFunctionJob.h" +#include "CLog.h" +#include "CStringUtil.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include "CArchMiscWindows.h" + +// extended mouse buttons +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif + +// +// CMSWindowsKeyState +// + +// map virtual keys to synergy key enumeration +const KeyID CMSWindowsKeyState::s_virtualKey[] = +{ + /* 0x000 */ { kKeyNone }, // reserved + /* 0x001 */ { kKeyNone }, // VK_LBUTTON + /* 0x002 */ { kKeyNone }, // VK_RBUTTON + /* 0x003 */ { kKeyNone }, // VK_CANCEL + /* 0x004 */ { kKeyNone }, // VK_MBUTTON + /* 0x005 */ { kKeyNone }, // VK_XBUTTON1 + /* 0x006 */ { kKeyNone }, // VK_XBUTTON2 + /* 0x007 */ { kKeyNone }, // undefined + /* 0x008 */ { kKeyBackSpace }, // VK_BACK + /* 0x009 */ { kKeyTab }, // VK_TAB + /* 0x00a */ { kKeyNone }, // undefined + /* 0x00b */ { kKeyNone }, // undefined + /* 0x00c */ { kKeyClear }, // VK_CLEAR + /* 0x00d */ { kKeyReturn }, // VK_RETURN + /* 0x00e */ { kKeyNone }, // undefined + /* 0x00f */ { kKeyNone }, // undefined + /* 0x010 */ { kKeyShift_L }, // VK_SHIFT + /* 0x011 */ { kKeyControl_L }, // VK_CONTROL + /* 0x012 */ { kKeyAlt_L }, // VK_MENU + /* 0x013 */ { kKeyPause }, // VK_PAUSE + /* 0x014 */ { kKeyCapsLock }, // VK_CAPITAL + /* 0x015 */ { kKeyHangulKana }, // VK_HANGUL, VK_KANA + /* 0x016 */ { kKeyNone }, // undefined + /* 0x017 */ { kKeyNone }, // VK_JUNJA + /* 0x018 */ { kKeyNone }, // VK_FINAL + /* 0x019 */ { kKeyHanjaKanzi }, // VK_KANJI + /* 0x01a */ { kKeyNone }, // undefined + /* 0x01b */ { kKeyEscape }, // VK_ESCAPE + /* 0x01c */ { kKeyHenkan }, // VK_CONVERT + /* 0x01d */ { kKeyNone }, // VK_NONCONVERT + /* 0x01e */ { kKeyNone }, // VK_ACCEPT + /* 0x01f */ { kKeyNone }, // VK_MODECHANGE + /* 0x020 */ { kKeyNone }, // VK_SPACE + /* 0x021 */ { kKeyKP_PageUp }, // VK_PRIOR + /* 0x022 */ { kKeyKP_PageDown },// VK_NEXT + /* 0x023 */ { kKeyKP_End }, // VK_END + /* 0x024 */ { kKeyKP_Home }, // VK_HOME + /* 0x025 */ { kKeyKP_Left }, // VK_LEFT + /* 0x026 */ { kKeyKP_Up }, // VK_UP + /* 0x027 */ { kKeyKP_Right }, // VK_RIGHT + /* 0x028 */ { kKeyKP_Down }, // VK_DOWN + /* 0x029 */ { kKeySelect }, // VK_SELECT + /* 0x02a */ { kKeyNone }, // VK_PRINT + /* 0x02b */ { kKeyExecute }, // VK_EXECUTE + /* 0x02c */ { kKeyPrint }, // VK_SNAPSHOT + /* 0x02d */ { kKeyKP_Insert }, // VK_INSERT + /* 0x02e */ { kKeyKP_Delete }, // VK_DELETE + /* 0x02f */ { kKeyHelp }, // VK_HELP + /* 0x030 */ { kKeyNone }, // VK_0 + /* 0x031 */ { kKeyNone }, // VK_1 + /* 0x032 */ { kKeyNone }, // VK_2 + /* 0x033 */ { kKeyNone }, // VK_3 + /* 0x034 */ { kKeyNone }, // VK_4 + /* 0x035 */ { kKeyNone }, // VK_5 + /* 0x036 */ { kKeyNone }, // VK_6 + /* 0x037 */ { kKeyNone }, // VK_7 + /* 0x038 */ { kKeyNone }, // VK_8 + /* 0x039 */ { kKeyNone }, // VK_9 + /* 0x03a */ { kKeyNone }, // undefined + /* 0x03b */ { kKeyNone }, // undefined + /* 0x03c */ { kKeyNone }, // undefined + /* 0x03d */ { kKeyNone }, // undefined + /* 0x03e */ { kKeyNone }, // undefined + /* 0x03f */ { kKeyNone }, // undefined + /* 0x040 */ { kKeyNone }, // undefined + /* 0x041 */ { kKeyNone }, // VK_A + /* 0x042 */ { kKeyNone }, // VK_B + /* 0x043 */ { kKeyNone }, // VK_C + /* 0x044 */ { kKeyNone }, // VK_D + /* 0x045 */ { kKeyNone }, // VK_E + /* 0x046 */ { kKeyNone }, // VK_F + /* 0x047 */ { kKeyNone }, // VK_G + /* 0x048 */ { kKeyNone }, // VK_H + /* 0x049 */ { kKeyNone }, // VK_I + /* 0x04a */ { kKeyNone }, // VK_J + /* 0x04b */ { kKeyNone }, // VK_K + /* 0x04c */ { kKeyNone }, // VK_L + /* 0x04d */ { kKeyNone }, // VK_M + /* 0x04e */ { kKeyNone }, // VK_N + /* 0x04f */ { kKeyNone }, // VK_O + /* 0x050 */ { kKeyNone }, // VK_P + /* 0x051 */ { kKeyNone }, // VK_Q + /* 0x052 */ { kKeyNone }, // VK_R + /* 0x053 */ { kKeyNone }, // VK_S + /* 0x054 */ { kKeyNone }, // VK_T + /* 0x055 */ { kKeyNone }, // VK_U + /* 0x056 */ { kKeyNone }, // VK_V + /* 0x057 */ { kKeyNone }, // VK_W + /* 0x058 */ { kKeyNone }, // VK_X + /* 0x059 */ { kKeyNone }, // VK_Y + /* 0x05a */ { kKeyNone }, // VK_Z + /* 0x05b */ { kKeySuper_L }, // VK_LWIN + /* 0x05c */ { kKeySuper_R }, // VK_RWIN + /* 0x05d */ { kKeyMenu }, // VK_APPS + /* 0x05e */ { kKeyNone }, // undefined + /* 0x05f */ { kKeySleep }, // VK_SLEEP + /* 0x060 */ { kKeyKP_0 }, // VK_NUMPAD0 + /* 0x061 */ { kKeyKP_1 }, // VK_NUMPAD1 + /* 0x062 */ { kKeyKP_2 }, // VK_NUMPAD2 + /* 0x063 */ { kKeyKP_3 }, // VK_NUMPAD3 + /* 0x064 */ { kKeyKP_4 }, // VK_NUMPAD4 + /* 0x065 */ { kKeyKP_5 }, // VK_NUMPAD5 + /* 0x066 */ { kKeyKP_6 }, // VK_NUMPAD6 + /* 0x067 */ { kKeyKP_7 }, // VK_NUMPAD7 + /* 0x068 */ { kKeyKP_8 }, // VK_NUMPAD8 + /* 0x069 */ { kKeyKP_9 }, // VK_NUMPAD9 + /* 0x06a */ { kKeyKP_Multiply },// VK_MULTIPLY + /* 0x06b */ { kKeyKP_Add }, // VK_ADD + /* 0x06c */ { kKeyKP_Separator },// VK_SEPARATOR + /* 0x06d */ { kKeyKP_Subtract },// VK_SUBTRACT + /* 0x06e */ { kKeyKP_Decimal }, // VK_DECIMAL + /* 0x06f */ { kKeyNone }, // VK_DIVIDE + /* 0x070 */ { kKeyF1 }, // VK_F1 + /* 0x071 */ { kKeyF2 }, // VK_F2 + /* 0x072 */ { kKeyF3 }, // VK_F3 + /* 0x073 */ { kKeyF4 }, // VK_F4 + /* 0x074 */ { kKeyF5 }, // VK_F5 + /* 0x075 */ { kKeyF6 }, // VK_F6 + /* 0x076 */ { kKeyF7 }, // VK_F7 + /* 0x077 */ { kKeyF8 }, // VK_F8 + /* 0x078 */ { kKeyF9 }, // VK_F9 + /* 0x079 */ { kKeyF10 }, // VK_F10 + /* 0x07a */ { kKeyF11 }, // VK_F11 + /* 0x07b */ { kKeyF12 }, // VK_F12 + /* 0x07c */ { kKeyF13 }, // VK_F13 + /* 0x07d */ { kKeyF14 }, // VK_F14 + /* 0x07e */ { kKeyF15 }, // VK_F15 + /* 0x07f */ { kKeyF16 }, // VK_F16 + /* 0x080 */ { kKeyF17 }, // VK_F17 + /* 0x081 */ { kKeyF18 }, // VK_F18 + /* 0x082 */ { kKeyF19 }, // VK_F19 + /* 0x083 */ { kKeyF20 }, // VK_F20 + /* 0x084 */ { kKeyF21 }, // VK_F21 + /* 0x085 */ { kKeyF22 }, // VK_F22 + /* 0x086 */ { kKeyF23 }, // VK_F23 + /* 0x087 */ { kKeyF24 }, // VK_F24 + /* 0x088 */ { kKeyNone }, // unassigned + /* 0x089 */ { kKeyNone }, // unassigned + /* 0x08a */ { kKeyNone }, // unassigned + /* 0x08b */ { kKeyNone }, // unassigned + /* 0x08c */ { kKeyNone }, // unassigned + /* 0x08d */ { kKeyNone }, // unassigned + /* 0x08e */ { kKeyNone }, // unassigned + /* 0x08f */ { kKeyNone }, // unassigned + /* 0x090 */ { kKeyNumLock }, // VK_NUMLOCK + /* 0x091 */ { kKeyScrollLock }, // VK_SCROLL + /* 0x092 */ { kKeyNone }, // unassigned + /* 0x093 */ { kKeyNone }, // unassigned + /* 0x094 */ { kKeyNone }, // unassigned + /* 0x095 */ { kKeyNone }, // unassigned + /* 0x096 */ { kKeyNone }, // unassigned + /* 0x097 */ { kKeyNone }, // unassigned + /* 0x098 */ { kKeyNone }, // unassigned + /* 0x099 */ { kKeyNone }, // unassigned + /* 0x09a */ { kKeyNone }, // unassigned + /* 0x09b */ { kKeyNone }, // unassigned + /* 0x09c */ { kKeyNone }, // unassigned + /* 0x09d */ { kKeyNone }, // unassigned + /* 0x09e */ { kKeyNone }, // unassigned + /* 0x09f */ { kKeyNone }, // unassigned + /* 0x0a0 */ { kKeyShift_L }, // VK_LSHIFT + /* 0x0a1 */ { kKeyShift_R }, // VK_RSHIFT + /* 0x0a2 */ { kKeyControl_L }, // VK_LCONTROL + /* 0x0a3 */ { kKeyControl_R }, // VK_RCONTROL + /* 0x0a4 */ { kKeyAlt_L }, // VK_LMENU + /* 0x0a5 */ { kKeyAlt_R }, // VK_RMENU + /* 0x0a6 */ { kKeyNone }, // VK_BROWSER_BACK + /* 0x0a7 */ { kKeyNone }, // VK_BROWSER_FORWARD + /* 0x0a8 */ { kKeyNone }, // VK_BROWSER_REFRESH + /* 0x0a9 */ { kKeyNone }, // VK_BROWSER_STOP + /* 0x0aa */ { kKeyNone }, // VK_BROWSER_SEARCH + /* 0x0ab */ { kKeyNone }, // VK_BROWSER_FAVORITES + /* 0x0ac */ { kKeyNone }, // VK_BROWSER_HOME + /* 0x0ad */ { kKeyNone }, // VK_VOLUME_MUTE + /* 0x0ae */ { kKeyNone }, // VK_VOLUME_DOWN + /* 0x0af */ { kKeyNone }, // VK_VOLUME_UP + /* 0x0b0 */ { kKeyNone }, // VK_MEDIA_NEXT_TRACK + /* 0x0b1 */ { kKeyNone }, // VK_MEDIA_PREV_TRACK + /* 0x0b2 */ { kKeyNone }, // VK_MEDIA_STOP + /* 0x0b3 */ { kKeyNone }, // VK_MEDIA_PLAY_PAUSE + /* 0x0b4 */ { kKeyNone }, // VK_LAUNCH_MAIL + /* 0x0b5 */ { kKeyNone }, // VK_LAUNCH_MEDIA_SELECT + /* 0x0b6 */ { kKeyNone }, // VK_LAUNCH_APP1 + /* 0x0b7 */ { kKeyNone }, // VK_LAUNCH_APP2 + /* 0x0b8 */ { kKeyNone }, // unassigned + /* 0x0b9 */ { kKeyNone }, // unassigned + /* 0x0ba */ { kKeyNone }, // OEM specific + /* 0x0bb */ { kKeyNone }, // OEM specific + /* 0x0bc */ { kKeyNone }, // OEM specific + /* 0x0bd */ { kKeyNone }, // OEM specific + /* 0x0be */ { kKeyNone }, // OEM specific + /* 0x0bf */ { kKeyNone }, // OEM specific + /* 0x0c0 */ { kKeyNone }, // OEM specific + /* 0x0c1 */ { kKeyNone }, // unassigned + /* 0x0c2 */ { kKeyNone }, // unassigned + /* 0x0c3 */ { kKeyNone }, // unassigned + /* 0x0c4 */ { kKeyNone }, // unassigned + /* 0x0c5 */ { kKeyNone }, // unassigned + /* 0x0c6 */ { kKeyNone }, // unassigned + /* 0x0c7 */ { kKeyNone }, // unassigned + /* 0x0c8 */ { kKeyNone }, // unassigned + /* 0x0c9 */ { kKeyNone }, // unassigned + /* 0x0ca */ { kKeyNone }, // unassigned + /* 0x0cb */ { kKeyNone }, // unassigned + /* 0x0cc */ { kKeyNone }, // unassigned + /* 0x0cd */ { kKeyNone }, // unassigned + /* 0x0ce */ { kKeyNone }, // unassigned + /* 0x0cf */ { kKeyNone }, // unassigned + /* 0x0d0 */ { kKeyNone }, // unassigned + /* 0x0d1 */ { kKeyNone }, // unassigned + /* 0x0d2 */ { kKeyNone }, // unassigned + /* 0x0d3 */ { kKeyNone }, // unassigned + /* 0x0d4 */ { kKeyNone }, // unassigned + /* 0x0d5 */ { kKeyNone }, // unassigned + /* 0x0d6 */ { kKeyNone }, // unassigned + /* 0x0d7 */ { kKeyNone }, // unassigned + /* 0x0d8 */ { kKeyNone }, // unassigned + /* 0x0d9 */ { kKeyNone }, // unassigned + /* 0x0da */ { kKeyNone }, // unassigned + /* 0x0db */ { kKeyNone }, // OEM specific + /* 0x0dc */ { kKeyNone }, // OEM specific + /* 0x0dd */ { kKeyNone }, // OEM specific + /* 0x0de */ { kKeyNone }, // OEM specific + /* 0x0df */ { kKeyNone }, // OEM specific + /* 0x0e0 */ { kKeyNone }, // OEM specific + /* 0x0e1 */ { kKeyNone }, // OEM specific + /* 0x0e2 */ { kKeyNone }, // OEM specific + /* 0x0e3 */ { kKeyNone }, // OEM specific + /* 0x0e4 */ { kKeyNone }, // OEM specific + /* 0x0e5 */ { kKeyNone }, // unassigned + /* 0x0e6 */ { kKeyNone }, // OEM specific + /* 0x0e7 */ { kKeyNone }, // unassigned + /* 0x0e8 */ { kKeyNone }, // unassigned + /* 0x0e9 */ { kKeyNone }, // OEM specific + /* 0x0ea */ { kKeyNone }, // OEM specific + /* 0x0eb */ { kKeyNone }, // OEM specific + /* 0x0ec */ { kKeyNone }, // OEM specific + /* 0x0ed */ { kKeyNone }, // OEM specific + /* 0x0ee */ { kKeyNone }, // OEM specific + /* 0x0ef */ { kKeyNone }, // OEM specific + /* 0x0f0 */ { kKeyNone }, // OEM specific + /* 0x0f1 */ { kKeyNone }, // OEM specific + /* 0x0f2 */ { kKeyHiraganaKatakana }, // VK_OEM_COPY + /* 0x0f3 */ { kKeyZenkaku }, // VK_OEM_AUTO + /* 0x0f4 */ { kKeyZenkaku }, // VK_OEM_ENLW + /* 0x0f5 */ { kKeyNone }, // OEM specific + /* 0x0f6 */ { kKeyNone }, // VK_ATTN + /* 0x0f7 */ { kKeyNone }, // VK_CRSEL + /* 0x0f8 */ { kKeyNone }, // VK_EXSEL + /* 0x0f9 */ { kKeyNone }, // VK_EREOF + /* 0x0fa */ { kKeyNone }, // VK_PLAY + /* 0x0fb */ { kKeyNone }, // VK_ZOOM + /* 0x0fc */ { kKeyNone }, // reserved + /* 0x0fd */ { kKeyNone }, // VK_PA1 + /* 0x0fe */ { kKeyNone }, // VK_OEM_CLEAR + /* 0x0ff */ { kKeyNone }, // reserved + + /* 0x100 */ { kKeyNone }, // reserved + /* 0x101 */ { kKeyNone }, // VK_LBUTTON + /* 0x102 */ { kKeyNone }, // VK_RBUTTON + /* 0x103 */ { kKeyBreak }, // VK_CANCEL + /* 0x104 */ { kKeyNone }, // VK_MBUTTON + /* 0x105 */ { kKeyNone }, // VK_XBUTTON1 + /* 0x106 */ { kKeyNone }, // VK_XBUTTON2 + /* 0x107 */ { kKeyNone }, // undefined + /* 0x108 */ { kKeyNone }, // VK_BACK + /* 0x109 */ { kKeyNone }, // VK_TAB + /* 0x10a */ { kKeyNone }, // undefined + /* 0x10b */ { kKeyNone }, // undefined + /* 0x10c */ { kKeyClear }, // VK_CLEAR + /* 0x10d */ { kKeyKP_Enter }, // VK_RETURN + /* 0x10e */ { kKeyNone }, // undefined + /* 0x10f */ { kKeyNone }, // undefined + /* 0x110 */ { kKeyShift_R }, // VK_SHIFT + /* 0x111 */ { kKeyControl_R }, // VK_CONTROL + /* 0x112 */ { kKeyAlt_R }, // VK_MENU + /* 0x113 */ { kKeyNone }, // VK_PAUSE + /* 0x114 */ { kKeyNone }, // VK_CAPITAL + /* 0x115 */ { kKeyNone }, // VK_KANA + /* 0x116 */ { kKeyNone }, // VK_HANGUL + /* 0x117 */ { kKeyNone }, // VK_JUNJA + /* 0x118 */ { kKeyNone }, // VK_FINAL + /* 0x119 */ { kKeyNone }, // VK_KANJI + /* 0x11a */ { kKeyNone }, // undefined + /* 0x11b */ { kKeyNone }, // VK_ESCAPE + /* 0x11c */ { kKeyNone }, // VK_CONVERT + /* 0x11d */ { kKeyNone }, // VK_NONCONVERT + /* 0x11e */ { kKeyNone }, // VK_ACCEPT + /* 0x11f */ { kKeyNone }, // VK_MODECHANGE + /* 0x120 */ { kKeyNone }, // VK_SPACE + /* 0x121 */ { kKeyPageUp }, // VK_PRIOR + /* 0x122 */ { kKeyPageDown }, // VK_NEXT + /* 0x123 */ { kKeyEnd }, // VK_END + /* 0x124 */ { kKeyHome }, // VK_HOME + /* 0x125 */ { kKeyLeft }, // VK_LEFT + /* 0x126 */ { kKeyUp }, // VK_UP + /* 0x127 */ { kKeyRight }, // VK_RIGHT + /* 0x128 */ { kKeyDown }, // VK_DOWN + /* 0x129 */ { kKeySelect }, // VK_SELECT + /* 0x12a */ { kKeyNone }, // VK_PRINT + /* 0x12b */ { kKeyExecute }, // VK_EXECUTE + /* 0x12c */ { kKeyPrint }, // VK_SNAPSHOT + /* 0x12d */ { kKeyInsert }, // VK_INSERT + /* 0x12e */ { kKeyDelete }, // VK_DELETE + /* 0x12f */ { kKeyHelp }, // VK_HELP + /* 0x130 */ { kKeyNone }, // VK_0 + /* 0x131 */ { kKeyNone }, // VK_1 + /* 0x132 */ { kKeyNone }, // VK_2 + /* 0x133 */ { kKeyNone }, // VK_3 + /* 0x134 */ { kKeyNone }, // VK_4 + /* 0x135 */ { kKeyNone }, // VK_5 + /* 0x136 */ { kKeyNone }, // VK_6 + /* 0x137 */ { kKeyNone }, // VK_7 + /* 0x138 */ { kKeyNone }, // VK_8 + /* 0x139 */ { kKeyNone }, // VK_9 + /* 0x13a */ { kKeyNone }, // undefined + /* 0x13b */ { kKeyNone }, // undefined + /* 0x13c */ { kKeyNone }, // undefined + /* 0x13d */ { kKeyNone }, // undefined + /* 0x13e */ { kKeyNone }, // undefined + /* 0x13f */ { kKeyNone }, // undefined + /* 0x140 */ { kKeyNone }, // undefined + /* 0x141 */ { kKeyNone }, // VK_A + /* 0x142 */ { kKeyNone }, // VK_B + /* 0x143 */ { kKeyNone }, // VK_C + /* 0x144 */ { kKeyNone }, // VK_D + /* 0x145 */ { kKeyNone }, // VK_E + /* 0x146 */ { kKeyNone }, // VK_F + /* 0x147 */ { kKeyNone }, // VK_G + /* 0x148 */ { kKeyNone }, // VK_H + /* 0x149 */ { kKeyNone }, // VK_I + /* 0x14a */ { kKeyNone }, // VK_J + /* 0x14b */ { kKeyNone }, // VK_K + /* 0x14c */ { kKeyNone }, // VK_L + /* 0x14d */ { kKeyNone }, // VK_M + /* 0x14e */ { kKeyNone }, // VK_N + /* 0x14f */ { kKeyNone }, // VK_O + /* 0x150 */ { kKeyNone }, // VK_P + /* 0x151 */ { kKeyNone }, // VK_Q + /* 0x152 */ { kKeyNone }, // VK_R + /* 0x153 */ { kKeyNone }, // VK_S + /* 0x154 */ { kKeyNone }, // VK_T + /* 0x155 */ { kKeyNone }, // VK_U + /* 0x156 */ { kKeyNone }, // VK_V + /* 0x157 */ { kKeyNone }, // VK_W + /* 0x158 */ { kKeyNone }, // VK_X + /* 0x159 */ { kKeyNone }, // VK_Y + /* 0x15a */ { kKeyNone }, // VK_Z + /* 0x15b */ { kKeySuper_L }, // VK_LWIN + /* 0x15c */ { kKeySuper_R }, // VK_RWIN + /* 0x15d */ { kKeyMenu }, // VK_APPS + /* 0x15e */ { kKeyNone }, // undefined + /* 0x15f */ { kKeyNone }, // VK_SLEEP + /* 0x160 */ { kKeyNone }, // VK_NUMPAD0 + /* 0x161 */ { kKeyNone }, // VK_NUMPAD1 + /* 0x162 */ { kKeyNone }, // VK_NUMPAD2 + /* 0x163 */ { kKeyNone }, // VK_NUMPAD3 + /* 0x164 */ { kKeyNone }, // VK_NUMPAD4 + /* 0x165 */ { kKeyNone }, // VK_NUMPAD5 + /* 0x166 */ { kKeyNone }, // VK_NUMPAD6 + /* 0x167 */ { kKeyNone }, // VK_NUMPAD7 + /* 0x168 */ { kKeyNone }, // VK_NUMPAD8 + /* 0x169 */ { kKeyNone }, // VK_NUMPAD9 + /* 0x16a */ { kKeyNone }, // VK_MULTIPLY + /* 0x16b */ { kKeyNone }, // VK_ADD + /* 0x16c */ { kKeyKP_Separator },// VK_SEPARATOR + /* 0x16d */ { kKeyNone }, // VK_SUBTRACT + /* 0x16e */ { kKeyNone }, // VK_DECIMAL + /* 0x16f */ { kKeyKP_Divide }, // VK_DIVIDE + /* 0x170 */ { kKeyNone }, // VK_F1 + /* 0x171 */ { kKeyNone }, // VK_F2 + /* 0x172 */ { kKeyNone }, // VK_F3 + /* 0x173 */ { kKeyNone }, // VK_F4 + /* 0x174 */ { kKeyNone }, // VK_F5 + /* 0x175 */ { kKeyNone }, // VK_F6 + /* 0x176 */ { kKeyNone }, // VK_F7 + /* 0x177 */ { kKeyNone }, // VK_F8 + /* 0x178 */ { kKeyNone }, // VK_F9 + /* 0x179 */ { kKeyNone }, // VK_F10 + /* 0x17a */ { kKeyNone }, // VK_F11 + /* 0x17b */ { kKeyNone }, // VK_F12 + /* 0x17c */ { kKeyF13 }, // VK_F13 + /* 0x17d */ { kKeyF14 }, // VK_F14 + /* 0x17e */ { kKeyF15 }, // VK_F15 + /* 0x17f */ { kKeyF16 }, // VK_F16 + /* 0x180 */ { kKeyF17 }, // VK_F17 + /* 0x181 */ { kKeyF18 }, // VK_F18 + /* 0x182 */ { kKeyF19 }, // VK_F19 + /* 0x183 */ { kKeyF20 }, // VK_F20 + /* 0x184 */ { kKeyF21 }, // VK_F21 + /* 0x185 */ { kKeyF22 }, // VK_F22 + /* 0x186 */ { kKeyF23 }, // VK_F23 + /* 0x187 */ { kKeyF24 }, // VK_F24 + /* 0x188 */ { kKeyNone }, // unassigned + /* 0x189 */ { kKeyNone }, // unassigned + /* 0x18a */ { kKeyNone }, // unassigned + /* 0x18b */ { kKeyNone }, // unassigned + /* 0x18c */ { kKeyNone }, // unassigned + /* 0x18d */ { kKeyNone }, // unassigned + /* 0x18e */ { kKeyNone }, // unassigned + /* 0x18f */ { kKeyNone }, // unassigned + /* 0x190 */ { kKeyNumLock }, // VK_NUMLOCK + /* 0x191 */ { kKeyNone }, // VK_SCROLL + /* 0x192 */ { kKeyNone }, // unassigned + /* 0x193 */ { kKeyNone }, // unassigned + /* 0x194 */ { kKeyNone }, // unassigned + /* 0x195 */ { kKeyNone }, // unassigned + /* 0x196 */ { kKeyNone }, // unassigned + /* 0x197 */ { kKeyNone }, // unassigned + /* 0x198 */ { kKeyNone }, // unassigned + /* 0x199 */ { kKeyNone }, // unassigned + /* 0x19a */ { kKeyNone }, // unassigned + /* 0x19b */ { kKeyNone }, // unassigned + /* 0x19c */ { kKeyNone }, // unassigned + /* 0x19d */ { kKeyNone }, // unassigned + /* 0x19e */ { kKeyNone }, // unassigned + /* 0x19f */ { kKeyNone }, // unassigned + /* 0x1a0 */ { kKeyShift_L }, // VK_LSHIFT + /* 0x1a1 */ { kKeyShift_R }, // VK_RSHIFT + /* 0x1a2 */ { kKeyControl_L }, // VK_LCONTROL + /* 0x1a3 */ { kKeyControl_R }, // VK_RCONTROL + /* 0x1a4 */ { kKeyAlt_L }, // VK_LMENU + /* 0x1a5 */ { kKeyAlt_R }, // VK_RMENU + /* 0x1a6 */ { kKeyWWWBack }, // VK_BROWSER_BACK + /* 0x1a7 */ { kKeyWWWForward }, // VK_BROWSER_FORWARD + /* 0x1a8 */ { kKeyWWWRefresh }, // VK_BROWSER_REFRESH + /* 0x1a9 */ { kKeyWWWStop }, // VK_BROWSER_STOP + /* 0x1aa */ { kKeyWWWSearch }, // VK_BROWSER_SEARCH + /* 0x1ab */ { kKeyWWWFavorites },// VK_BROWSER_FAVORITES + /* 0x1ac */ { kKeyWWWHome }, // VK_BROWSER_HOME + /* 0x1ad */ { kKeyAudioMute }, // VK_VOLUME_MUTE + /* 0x1ae */ { kKeyAudioDown }, // VK_VOLUME_DOWN + /* 0x1af */ { kKeyAudioUp }, // VK_VOLUME_UP + /* 0x1b0 */ { kKeyAudioNext }, // VK_MEDIA_NEXT_TRACK + /* 0x1b1 */ { kKeyAudioPrev }, // VK_MEDIA_PREV_TRACK + /* 0x1b2 */ { kKeyAudioStop }, // VK_MEDIA_STOP + /* 0x1b3 */ { kKeyAudioPlay }, // VK_MEDIA_PLAY_PAUSE + /* 0x1b4 */ { kKeyAppMail }, // VK_LAUNCH_MAIL + /* 0x1b5 */ { kKeyAppMedia }, // VK_LAUNCH_MEDIA_SELECT + /* 0x1b6 */ { kKeyAppUser1 }, // VK_LAUNCH_APP1 + /* 0x1b7 */ { kKeyAppUser2 }, // VK_LAUNCH_APP2 + /* 0x1b8 */ { kKeyNone }, // unassigned + /* 0x1b9 */ { kKeyNone }, // unassigned + /* 0x1ba */ { kKeyNone }, // OEM specific + /* 0x1bb */ { kKeyNone }, // OEM specific + /* 0x1bc */ { kKeyNone }, // OEM specific + /* 0x1bd */ { kKeyNone }, // OEM specific + /* 0x1be */ { kKeyNone }, // OEM specific + /* 0x1bf */ { kKeyNone }, // OEM specific + /* 0x1c0 */ { kKeyNone }, // OEM specific + /* 0x1c1 */ { kKeyNone }, // unassigned + /* 0x1c2 */ { kKeyNone }, // unassigned + /* 0x1c3 */ { kKeyNone }, // unassigned + /* 0x1c4 */ { kKeyNone }, // unassigned + /* 0x1c5 */ { kKeyNone }, // unassigned + /* 0x1c6 */ { kKeyNone }, // unassigned + /* 0x1c7 */ { kKeyNone }, // unassigned + /* 0x1c8 */ { kKeyNone }, // unassigned + /* 0x1c9 */ { kKeyNone }, // unassigned + /* 0x1ca */ { kKeyNone }, // unassigned + /* 0x1cb */ { kKeyNone }, // unassigned + /* 0x1cc */ { kKeyNone }, // unassigned + /* 0x1cd */ { kKeyNone }, // unassigned + /* 0x1ce */ { kKeyNone }, // unassigned + /* 0x1cf */ { kKeyNone }, // unassigned + /* 0x1d0 */ { kKeyNone }, // unassigned + /* 0x1d1 */ { kKeyNone }, // unassigned + /* 0x1d2 */ { kKeyNone }, // unassigned + /* 0x1d3 */ { kKeyNone }, // unassigned + /* 0x1d4 */ { kKeyNone }, // unassigned + /* 0x1d5 */ { kKeyNone }, // unassigned + /* 0x1d6 */ { kKeyNone }, // unassigned + /* 0x1d7 */ { kKeyNone }, // unassigned + /* 0x1d8 */ { kKeyNone }, // unassigned + /* 0x1d9 */ { kKeyNone }, // unassigned + /* 0x1da */ { kKeyNone }, // unassigned + /* 0x1db */ { kKeyNone }, // OEM specific + /* 0x1dc */ { kKeyNone }, // OEM specific + /* 0x1dd */ { kKeyNone }, // OEM specific + /* 0x1de */ { kKeyNone }, // OEM specific + /* 0x1df */ { kKeyNone }, // OEM specific + /* 0x1e0 */ { kKeyNone }, // OEM specific + /* 0x1e1 */ { kKeyNone }, // OEM specific + /* 0x1e2 */ { kKeyNone }, // OEM specific + /* 0x1e3 */ { kKeyNone }, // OEM specific + /* 0x1e4 */ { kKeyNone }, // OEM specific + /* 0x1e5 */ { kKeyNone }, // unassigned + /* 0x1e6 */ { kKeyNone }, // OEM specific + /* 0x1e7 */ { kKeyNone }, // unassigned + /* 0x1e8 */ { kKeyNone }, // unassigned + /* 0x1e9 */ { kKeyNone }, // OEM specific + /* 0x1ea */ { kKeyNone }, // OEM specific + /* 0x1eb */ { kKeyNone }, // OEM specific + /* 0x1ec */ { kKeyNone }, // OEM specific + /* 0x1ed */ { kKeyNone }, // OEM specific + /* 0x1ee */ { kKeyNone }, // OEM specific + /* 0x1ef */ { kKeyNone }, // OEM specific + /* 0x1f0 */ { kKeyNone }, // OEM specific + /* 0x1f1 */ { kKeyNone }, // OEM specific + /* 0x1f2 */ { kKeyNone }, // VK_OEM_COPY + /* 0x1f3 */ { kKeyNone }, // VK_OEM_AUTO + /* 0x1f4 */ { kKeyNone }, // VK_OEM_ENLW + /* 0x1f5 */ { kKeyNone }, // OEM specific + /* 0x1f6 */ { kKeyNone }, // VK_ATTN + /* 0x1f7 */ { kKeyNone }, // VK_CRSEL + /* 0x1f8 */ { kKeyNone }, // VK_EXSEL + /* 0x1f9 */ { kKeyNone }, // VK_EREOF + /* 0x1fa */ { kKeyNone }, // VK_PLAY + /* 0x1fb */ { kKeyNone }, // VK_ZOOM + /* 0x1fc */ { kKeyNone }, // reserved + /* 0x1fd */ { kKeyNone }, // VK_PA1 + /* 0x1fe */ { kKeyNone }, // VK_OEM_CLEAR + /* 0x1ff */ { kKeyNone } // reserved +}; + +struct CWin32Modifiers { +public: + UINT m_vk; + KeyModifierMask m_mask; +}; + +static const CWin32Modifiers s_modifiers[] = +{ + { VK_SHIFT, KeyModifierShift }, + { VK_LSHIFT, KeyModifierShift }, + { VK_RSHIFT, KeyModifierShift }, + { VK_CONTROL, KeyModifierControl }, + { VK_LCONTROL, KeyModifierControl }, + { VK_RCONTROL, KeyModifierControl }, + { VK_MENU, KeyModifierAlt }, + { VK_LMENU, KeyModifierAlt }, + { VK_RMENU, KeyModifierAlt }, + { VK_LWIN, KeyModifierSuper }, + { VK_RWIN, KeyModifierSuper } +}; + +CMSWindowsKeyState::CMSWindowsKeyState(CMSWindowsDesks* desks, + void* eventTarget) : + m_is95Family(CArchMiscWindows::isWindows95Family()), + m_eventTarget(eventTarget), + m_desks(desks), + m_keyLayout(GetKeyboardLayout(0)), + m_fixTimer(NULL), + m_lastDown(0), + m_useSavedModifiers(false), + m_savedModifiers(0), + m_originalSavedModifiers(0) +{ + // look up symbol that's available on winNT family but not win95 + HMODULE userModule = GetModuleHandle("user32.dll"); + m_ToUnicodeEx = (ToUnicodeEx_t)GetProcAddress(userModule, "ToUnicodeEx"); +} + +CMSWindowsKeyState::~CMSWindowsKeyState() +{ + disable(); +} + +void +CMSWindowsKeyState::disable() +{ + if (m_fixTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_fixTimer); + EVENTQUEUE->deleteTimer(m_fixTimer); + m_fixTimer = NULL; + } + m_lastDown = 0; +} + +KeyButton +CMSWindowsKeyState::virtualKeyToButton(UINT virtualKey) const +{ + return m_virtualKeyToButton[virtualKey & 0xffu]; +} + +void +CMSWindowsKeyState::setKeyLayout(HKL keyLayout) +{ + m_keyLayout = keyLayout; +} + +bool +CMSWindowsKeyState::testAutoRepeat(bool press, bool isRepeat, KeyButton button) +{ + if (!isRepeat) { + isRepeat = (press && m_lastDown != 0 && button == m_lastDown); + } + if (press) { + m_lastDown = button; + } + else { + m_lastDown = 0; + } + return isRepeat; +} + +void +CMSWindowsKeyState::saveModifiers() +{ + m_savedModifiers = getActiveModifiers(); + m_originalSavedModifiers = m_savedModifiers; +} + +void +CMSWindowsKeyState::useSavedModifiers(bool enable) +{ + if (enable != m_useSavedModifiers) { + m_useSavedModifiers = enable; + if (!m_useSavedModifiers) { + // transfer any modifier state changes to CKeyState's state + KeyModifierMask mask = m_originalSavedModifiers ^ m_savedModifiers; + getActiveModifiersRValue() = + (getActiveModifiers() & ~mask) | (m_savedModifiers & mask); + } + } +} + +KeyID +CMSWindowsKeyState::mapKeyFromEvent(WPARAM charAndVirtKey, + LPARAM info, KeyModifierMask* maskOut) const +{ + static const KeyModifierMask s_controlAlt = + KeyModifierControl | KeyModifierAlt; + + // extract character, virtual key, and if we didn't use AltGr + char c = (char)((charAndVirtKey & 0xff00u) >> 8); + UINT vkCode = (charAndVirtKey & 0xffu); + bool noAltGr = ((charAndVirtKey & 0xff0000u) != 0); + + // handle some keys via table lookup + KeyID id = getKeyID(vkCode, (KeyButton)((info >> 16) & 0x1ffu)); + + // check if not in table; map character to key id + if (id == kKeyNone && c != 0) { + if ((c & 0x80u) == 0) { + // ASCII + id = static_cast(c) & 0xffu; + } + else { + // character is not really ASCII. instead it's some + // character in the current ANSI code page. try to + // convert that to a Unicode character. if we fail + // then use the single byte character as is. + char src = c; + wchar_t unicode; + if (MultiByteToWideChar(CP_THREAD_ACP, MB_PRECOMPOSED, + &src, 1, &unicode, 1) > 0) { + id = static_cast(unicode); + } + else { + id = static_cast(c) & 0xffu; + } + } + } + + // set modifier mask + if (maskOut != NULL) { + KeyModifierMask active = getActiveModifiers(); + if (!noAltGr && (active & s_controlAlt) == s_controlAlt) { + // if !noAltGr then we're only interested in matching the + // key, not the AltGr. AltGr is down (i.e. control and alt + // are down) but we don't want the client to have to match + // that so we clear it. + active &= ~s_controlAlt; + } + *maskOut = active; + } + + return id; +} + +bool +CMSWindowsKeyState::didGroupsChange() const +{ + GroupList groups; + return (getGroups(groups) && groups != m_groups); +} + +UINT +CMSWindowsKeyState::mapKeyToVirtualKey(KeyID key) const +{ + if (key == kKeyNone) { + return 0; + } + KeyToVKMap::const_iterator i = m_keyToVKMap.find(key); + if (i == m_keyToVKMap.end()) { + return 0; + } + else { + return i->second; + } +} + +void +CMSWindowsKeyState::onKey(KeyButton button, bool down, KeyModifierMask newState) +{ + // handle win32 brokenness and forward to superclass + fixKeys(); + CKeyState::onKey(button, down, newState); + fixKeys(); +} + +void +CMSWindowsKeyState::sendKeyEvent(void* target, + bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + if (press || isAutoRepeat) { + // send key + if (press && !isAutoRepeat) { + CKeyState::sendKeyEvent(target, true, false, + key, mask, 1, button); + if (count > 0) { + --count; + } + } + if (count >= 1) { + CKeyState::sendKeyEvent(target, true, true, + key, mask, count, button); + } + } + else { + // do key up + CKeyState::sendKeyEvent(target, false, false, key, mask, 1, button); + } +} + +void +CMSWindowsKeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) +{ + CKeyState::fakeKeyDown(id, mask, button); +} + +void +CMSWindowsKeyState::fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + CKeyState::fakeKeyRepeat(id, mask, count, button); +} + +bool +CMSWindowsKeyState::fakeCtrlAltDel() +{ + if (!m_is95Family) { + // to fake ctrl+alt+del on the NT family we broadcast a suitable + // hotkey to all windows on the winlogon desktop. however, the + // current thread must be on that desktop to do the broadcast + // and we can't switch just any thread because some own windows + // or hooks. so start a new thread to do the real work. + CThread cad(new CFunctionJob(&CMSWindowsKeyState::ctrlAltDelThread)); + cad.wait(); + } + else { + // simulate ctrl+alt+del + fakeKeyDown(kKeyDelete, KeyModifierControl | KeyModifierAlt, + virtualKeyToButton(VK_DELETE)); + } + return true; +} + +void +CMSWindowsKeyState::ctrlAltDelThread(void*) +{ + // get the Winlogon desktop at whatever privilege we can + HDESK desk = OpenDesktop("Winlogon", 0, FALSE, MAXIMUM_ALLOWED); + if (desk != NULL) { + if (SetThreadDesktop(desk)) { + PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, + MAKELPARAM(MOD_CONTROL | MOD_ALT, VK_DELETE)); + } + else { + LOG((CLOG_DEBUG "can't switch to Winlogon desk: %d", GetLastError())); + } + CloseDesktop(desk); + } + else { + LOG((CLOG_DEBUG "can't open Winlogon desk: %d", GetLastError())); + } +} + +KeyModifierMask +CMSWindowsKeyState::pollActiveModifiers() const +{ + KeyModifierMask state = 0; + + // get non-toggle modifiers from our own shadow key state + for (size_t i = 0; i < sizeof(s_modifiers) / sizeof(s_modifiers[0]); ++i) { + KeyButton button = virtualKeyToButton(s_modifiers[i].m_vk); + if (button != 0 && isKeyDown(button)) { + state |= s_modifiers[i].m_mask; + } + } + + // we can get toggle modifiers from the system + if ((GetKeyState(VK_CAPITAL) & 0x01) != 0) { + state |= KeyModifierCapsLock; + } + if ((GetKeyState(VK_NUMLOCK) & 0x01) != 0) { + state |= KeyModifierNumLock; + } + if ((GetKeyState(VK_SCROLL) & 0x01) != 0) { + state |= KeyModifierScrollLock; + } + + return state; +} + +SInt32 +CMSWindowsKeyState::pollActiveGroup() const +{ + // determine the thread that'll receive this event + HWND targetWindow = GetForegroundWindow(); + DWORD targetThread = GetWindowThreadProcessId(targetWindow, NULL); + + // get keyboard layout for the thread + HKL hkl = GetKeyboardLayout(targetThread); + + // get group + GroupMap::const_iterator i = m_groupMap.find(hkl); + if (i == m_groupMap.end()) { + LOG((CLOG_DEBUG1 "can't find keyboard layout %08x", hkl)); + return 0; + } + + return i->second; +} + +void +CMSWindowsKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + BYTE keyState[256]; + GetKeyboardState(keyState); + for (KeyButton i = 1; i < 256; ++i) { + if ((keyState[i] & 0x80) != 0) { + pressedKeys.insert(i); + } + } +} + +void +CMSWindowsKeyState::getKeyMap(CKeyMap& keyMap) +{ + // update keyboard groups + if (getGroups(m_groups)) { + m_groupMap.clear(); + SInt32 numGroups = (SInt32)m_groups.size(); + for (SInt32 g = 0; g < numGroups; ++g) { + m_groupMap[m_groups[g]] = g; + } + } + HKL activeLayout = GetKeyboardLayout(0); + + // clear table + memset(m_virtualKeyToButton, 0, sizeof(m_virtualKeyToButton)); + m_keyToVKMap.clear(); + + CKeyMap::KeyItem item; + SInt32 numGroups = (SInt32)m_groups.size(); + for (SInt32 g = 0; g < numGroups; ++g) { + item.m_group = g; + ActivateKeyboardLayout(m_groups[g], 0); + + // clear tables + memset(m_buttonToVK, 0, sizeof(m_buttonToVK)); + memset(m_buttonToNumpadVK, 0, sizeof(m_buttonToNumpadVK)); + + // map buttons (scancodes) to virtual keys + for (KeyButton i = 1; i < 256; ++i) { + UINT vk = MapVirtualKey(i, 1); + if (vk == 0) { + // unmapped + continue; + } + + // deal with certain virtual keys specially + switch (vk) { + case VK_SHIFT: + if (MapVirtualKey(VK_RSHIFT, 0) == i) { + vk = VK_RSHIFT; + } + else { + vk = VK_LSHIFT; + } + break; + + case VK_CONTROL: + vk = VK_LCONTROL; + break; + + case VK_MENU: + vk = VK_LMENU; + break; + + case VK_NUMLOCK: + vk = VK_PAUSE; + break; + + case VK_NUMPAD0: + case VK_NUMPAD1: + case VK_NUMPAD2: + case VK_NUMPAD3: + case VK_NUMPAD4: + case VK_NUMPAD5: + case VK_NUMPAD6: + case VK_NUMPAD7: + case VK_NUMPAD8: + case VK_NUMPAD9: + case VK_DECIMAL: + // numpad keys are saved in their own table + m_buttonToNumpadVK[i] = vk; + continue; + + case VK_LWIN: + case VK_RWIN: + // add extended key only for these on 95 family + if (m_is95Family) { + m_buttonToVK[i | 0x100u] = vk; + continue; + } + break; + + case VK_RETURN: + case VK_PRIOR: + case VK_NEXT: + case VK_END: + case VK_HOME: + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + case VK_INSERT: + case VK_DELETE: + // also add extended key for these + m_buttonToVK[i | 0x100u] = vk; + break; + } + + if (m_buttonToVK[i] == 0) { + m_buttonToVK[i] = vk; + } + } + + // now map virtual keys to buttons. multiple virtual keys may map + // to a single button. if the virtual key matches the one in + // m_buttonToVK then we use the button as is. if not then it's + // either a numpad key and we use the button as is or it's an + // extended button. + for (UINT i = 1; i < 255; ++i) { + // skip virtual keys we don't want + switch (i) { + case VK_LBUTTON: + case VK_RBUTTON: + case VK_MBUTTON: + case VK_XBUTTON1: + case VK_XBUTTON2: + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + continue; + } + + // get the button + KeyButton button = static_cast(MapVirtualKey(i, 0)); + if (button == 0) { + continue; + } + + // deal with certain virtual keys specially + switch (i) { + case VK_NUMPAD0: + case VK_NUMPAD1: + case VK_NUMPAD2: + case VK_NUMPAD3: + case VK_NUMPAD4: + case VK_NUMPAD5: + case VK_NUMPAD6: + case VK_NUMPAD7: + case VK_NUMPAD8: + case VK_NUMPAD9: + case VK_DECIMAL: + m_buttonToNumpadVK[button] = i; + break; + + default: + // add extended key if virtual keys don't match + if (m_buttonToVK[button] != i) { + m_buttonToVK[button | 0x100u] = i; + } + break; + } + } + + // add alt+printscreen + if (m_buttonToVK[0x54u] == 0) { + m_buttonToVK[0x54u] = VK_SNAPSHOT; + } + + // set virtual key to button table + if (GetKeyboardLayout(0) == m_groups[g]) { + for (KeyButton i = 0; i < 512; ++i) { + if (m_buttonToVK[i] != 0) { + if (m_virtualKeyToButton[m_buttonToVK[i]] == 0) { + m_virtualKeyToButton[m_buttonToVK[i]] = i; + } + } + if (m_buttonToNumpadVK[i] != 0) { + if (m_virtualKeyToButton[m_buttonToNumpadVK[i]] == 0) { + m_virtualKeyToButton[m_buttonToNumpadVK[i]] = i; + } + } + } + } + + // add numpad keys + for (KeyButton i = 0; i < 512; ++i) { + if (m_buttonToNumpadVK[i] != 0) { + item.m_id = getKeyID(m_buttonToNumpadVK[i], i); + item.m_button = i; + item.m_required = KeyModifierNumLock; + item.m_sensitive = KeyModifierNumLock | KeyModifierShift; + item.m_generates = 0; + item.m_client = m_buttonToNumpadVK[i]; + addKeyEntry(keyMap, item); + } + } + + // add other keys + BYTE keys[256]; + memset(keys, 0, sizeof(keys)); + for (KeyButton i = 0; i < 512; ++i) { + if (m_buttonToVK[i] != 0) { + // initialize item + item.m_id = getKeyID(m_buttonToVK[i], i); + item.m_button = i; + item.m_required = 0; + item.m_sensitive = 0; + item.m_client = m_buttonToVK[i]; + + // get flags for modifier keys + CKeyMap::initModifierKey(item); + + if (item.m_id == 0) { + // translate virtual key to a character with and without + // shift, caps lock, and AltGr. + struct Modifier { + UINT m_vk1; + UINT m_vk2; + BYTE m_state; + KeyModifierMask m_mask; + }; + static const Modifier modifiers[] = { + { VK_SHIFT, VK_SHIFT, 0x80u, KeyModifierShift }, + { VK_CAPITAL, VK_CAPITAL, 0x01u, KeyModifierCapsLock }, + { VK_CONTROL, VK_MENU, 0x80u, KeyModifierControl | + KeyModifierAlt } + }; + static const size_t s_numModifiers = + sizeof(modifiers) / sizeof(modifiers[0]); + static const size_t s_numCombinations = 1 << s_numModifiers; + KeyID id[s_numCombinations]; + + bool anyFound = false; + KeyButton button = static_cast(i & 0xffu); + for (size_t j = 0; j < s_numCombinations; ++j) { + for (size_t k = 0; k < s_numModifiers; ++k) { + if ((j & (1 << k)) != 0) { + keys[modifiers[k].m_vk1] = modifiers[k].m_state; + keys[modifiers[k].m_vk2] = modifiers[k].m_state; + } + else { + keys[modifiers[k].m_vk1] = 0; + keys[modifiers[k].m_vk2] = 0; + } + } + id[j] = getIDForKey(item, button, + m_buttonToVK[i], keys, m_groups[g]); + if (id[j] != 0) { + anyFound = true; + } + } + + if (anyFound) { + // determine what modifiers we're sensitive to. + // we're sensitive if the KeyID changes when the + // modifier does. + item.m_sensitive = 0; + for (size_t k = 0; k < s_numModifiers; ++k) { + for (size_t j = 0; j < s_numCombinations; ++j) { + if (id[j] != id[j ^ (1u << k)]) { + item.m_sensitive |= modifiers[k].m_mask; + break; + } + } + } + + // save each key. the map will automatically discard + // duplicates, like an unshift and shifted version of + // a key that's insensitive to shift. + for (size_t j = 0; j < s_numCombinations; ++j) { + item.m_id = id[j]; + item.m_required = 0; + for (size_t k = 0; k < s_numModifiers; ++k) { + if ((j & (1 << k)) != 0) { + item.m_required |= modifiers[k].m_mask; + } + } + addKeyEntry(keyMap, item); + } + } + } + else { + // found in table + switch (m_buttonToVK[i]) { + case VK_TAB: + // add kKeyLeftTab, too + item.m_id = kKeyLeftTab; + item.m_required |= KeyModifierShift; + item.m_sensitive |= KeyModifierShift; + addKeyEntry(keyMap, item); + item.m_id = kKeyTab; + item.m_required &= ~KeyModifierShift; + break; + + case VK_CANCEL: + item.m_required |= KeyModifierControl; + item.m_sensitive |= KeyModifierControl; + break; + + case VK_SNAPSHOT: + item.m_sensitive |= KeyModifierAlt; + if ((i & 0x100u) == 0) { + // non-extended snapshot key requires alt + item.m_required |= KeyModifierAlt; + } + break; + } + addKeyEntry(keyMap, item); + } + } + } + } + + // restore keyboard layout + ActivateKeyboardLayout(activeLayout, 0); +} + +void +CMSWindowsKeyState::fakeKey(const Keystroke& keystroke) +{ + switch (keystroke.m_type) { + case Keystroke::kButton: { + LOG((CLOG_DEBUG1 " %03x (%08x) %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, keystroke.m_data.m_button.m_press ? "down" : "up")); + KeyButton button = keystroke.m_data.m_button.m_button; + + // windows doesn't send key ups for key repeats + if (keystroke.m_data.m_button.m_repeat && + !keystroke.m_data.m_button.m_press) { + LOG((CLOG_DEBUG1 " discard key repeat release")); + break; + } + + // get the virtual key for the button + UINT vk = keystroke.m_data.m_button.m_client; + + // special handling of VK_SNAPSHOT + if (vk == VK_SNAPSHOT) { + if ((getActiveModifiers() & KeyModifierAlt) != 0) { + // snapshot active window + button = 1; + } + else { + // snapshot full screen + button = 0; + } + } + + // synthesize event + m_desks->fakeKeyEvent(button, vk, + keystroke.m_data.m_button.m_press, + keystroke.m_data.m_button.m_repeat); + break; + } + + case Keystroke::kGroup: + // we don't restore the group. we'd like to but we can't be + // sure the restoring group change will be processed after the + // key events. + if (!keystroke.m_data.m_group.m_restore) { + if (keystroke.m_data.m_group.m_absolute) { + LOG((CLOG_DEBUG1 " group %d", keystroke.m_data.m_group.m_group)); + setWindowGroup(keystroke.m_data.m_group.m_group); + } + else { + LOG((CLOG_DEBUG1 " group %+d", keystroke.m_data.m_group.m_group)); + setWindowGroup(getEffectiveGroup(pollActiveGroup(), + keystroke.m_data.m_group.m_group)); + } + } + break; + } +} + +KeyModifierMask& +CMSWindowsKeyState::getActiveModifiersRValue() +{ + if (m_useSavedModifiers) { + return m_savedModifiers; + } + else { + return CKeyState::getActiveModifiersRValue(); + } +} + +bool +CMSWindowsKeyState::getGroups(GroupList& groups) const +{ + // get keyboard layouts + UInt32 newNumLayouts = GetKeyboardLayoutList(0, NULL); + if (newNumLayouts == 0) { + LOG((CLOG_DEBUG1 "can't get keyboard layouts")); + return false; + } + HKL* newLayouts = new HKL[newNumLayouts]; + newNumLayouts = GetKeyboardLayoutList(newNumLayouts, newLayouts); + if (newNumLayouts == 0) { + LOG((CLOG_DEBUG1 "can't get keyboard layouts")); + delete[] newLayouts; + return false; + } + + groups.clear(); + groups.insert(groups.end(), newLayouts, newLayouts + newNumLayouts); + delete[] newLayouts; + return true; +} + +void +CMSWindowsKeyState::setWindowGroup(SInt32 group) +{ + HWND targetWindow = GetForegroundWindow(); + + bool sysCharSet = true; + // XXX -- determine if m_groups[group] can be used with the system + // character set. + + PostMessage(targetWindow, WM_INPUTLANGCHANGEREQUEST, + sysCharSet ? 1 : 0, (LPARAM)m_groups[group]); + + // XXX -- use a short delay to let the target window process the message + // before it sees the keyboard events. i'm not sure why this is + // necessary since the messages should arrive in order. if we don't + // delay, though, some of our keyboard events may disappear. + Sleep(100); +} + +void +CMSWindowsKeyState::fixKeys() +{ + // fake key releases for the windows keys if we think they're + // down but they're really up. we have to do this because if the + // user presses and releases a windows key without pressing any + // other key while it's down then the system will eat the key + // release. if we don't detect that and synthesize the release + // then the client won't take the usual windows key release action + // (which on windows is to show the start menu). + // + // only check on the windows 95 family since the NT family reports + // the key releases as usual. + if (!m_is95Family) { + return; + } + + KeyButton leftButton = virtualKeyToButton(VK_LWIN); + KeyButton rightButton = virtualKeyToButton(VK_RWIN); + bool leftDown = isKeyDown(leftButton); + bool rightDown = isKeyDown(rightButton); + bool fix = (leftDown || rightDown); + if (fix) { + // check if either button is not really down + bool leftAsyncDown = ((GetAsyncKeyState(VK_LWIN) & 0x8000) != 0); + bool rightAsyncDown = ((GetAsyncKeyState(VK_RWIN) & 0x8000) != 0); + + if (leftAsyncDown != leftDown || rightAsyncDown != rightDown) { + KeyModifierMask state = getActiveModifiers(); + if (!leftAsyncDown && !rightAsyncDown) { + // no win keys are down so remove super modifier + state &= ~KeyModifierSuper; + } + + // report up events + if (leftDown && !leftAsyncDown) { + LOG((CLOG_DEBUG1 "event: fake key release left windows key (0x%03x)", leftButton)); + CKeyState::onKey(leftButton, false, state); + CKeyState::sendKeyEvent(m_eventTarget, false, false, + kKeySuper_L, state, 1, leftButton); + } + if (rightDown && !rightAsyncDown) { + LOG((CLOG_DEBUG1 "event: fake key release right windows key (0x%03x)", rightButton)); + CKeyState::onKey(rightButton, false, state); + CKeyState::sendKeyEvent(m_eventTarget, false, false, + kKeySuper_R, state, 1, rightButton); + } + } + } + + if (fix && m_fixTimer == NULL) { + // schedule check + m_fixTimer = EVENTQUEUE->newTimer(0.1, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_fixTimer, + new TMethodEventJob( + this, &CMSWindowsKeyState::handleFixKeys)); + } + else if (!fix && m_fixTimer != NULL) { + // remove scheduled check + EVENTQUEUE->removeHandler(CEvent::kTimer, m_fixTimer); + EVENTQUEUE->deleteTimer(m_fixTimer); + m_fixTimer = NULL; + } +} + +void +CMSWindowsKeyState::handleFixKeys(const CEvent&, void*) +{ + fixKeys(); +} + +KeyID +CMSWindowsKeyState::getKeyID(UINT virtualKey, KeyButton button) +{ + if ((button & 0x100u) != 0) { + virtualKey += 0x100u; + } + return s_virtualKey[virtualKey]; +} + +KeyID +CMSWindowsKeyState::getIDForKey(CKeyMap::KeyItem& item, + KeyButton button, UINT virtualKey, + PBYTE keyState, HKL hkl) const +{ + int n; + KeyID id; + if (m_is95Family) { + // XXX -- how do we get characters not in Latin-1? + WORD ascii; + n = ToAsciiEx(virtualKey, button, keyState, &ascii, 0, hkl); + id = static_cast(ascii & 0xffu); + } + else { + WCHAR unicode[2]; + n = m_ToUnicodeEx(virtualKey, button, keyState, + unicode, sizeof(unicode) / sizeof(unicode[0]), + 0, hkl); + id = static_cast(unicode[0]); + } + switch (n) { + case -1: + return CKeyMap::getDeadKey(id); + + default: + case 0: + // unmapped + return kKeyNone; + + case 1: + return id; + + case 2: + // left over dead key in buffer; oops. + return getIDForKey(item, button, virtualKey, keyState, hkl); + } +} + +void +CMSWindowsKeyState::addKeyEntry(CKeyMap& keyMap, CKeyMap::KeyItem& item) +{ + keyMap.addKeyEntry(item); + if (item.m_group == 0) { + m_keyToVKMap[item.m_id] = static_cast(item.m_client); + } +} diff --git a/lib/platform/CMSWindowsKeyState.h b/lib/platform/CMSWindowsKeyState.h new file mode 100644 index 00000000..d8320a58 --- /dev/null +++ b/lib/platform/CMSWindowsKeyState.h @@ -0,0 +1,216 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSKEYSTATE_H +#define CMSWINDOWSKEYSTATE_H + +#include "CKeyState.h" +#include "CString.h" +#include "stdvector.h" +#define WIN32_LEAN_AND_MEAN +#include + +class CEvent; +class CEventQueueTimer; +class CMSWindowsDesks; + +//! Microsoft Windows key mapper +/*! +This class maps KeyIDs to keystrokes. +*/ +class CMSWindowsKeyState : public CKeyState { +public: + CMSWindowsKeyState(CMSWindowsDesks* desks, void* eventTarget); + virtual ~CMSWindowsKeyState(); + + //! @name manipulators + //@{ + + //! Handle screen disabling + /*! + Called when screen is disabled. This is needed to deal with platform + brokenness. + */ + void disable(); + + //! Set the active keyboard layout + /*! + Uses \p keyLayout when querying the keyboard. + */ + void setKeyLayout(HKL keyLayout); + + //! Test and set autorepeat state + /*! + Returns true if the given button is autorepeating and updates internal + state. + */ + bool testAutoRepeat(bool press, bool isRepeat, KeyButton); + + //! Remember modifier state + /*! + Records the current non-toggle modifier state. + */ + void saveModifiers(); + + //! Set effective modifier state + /*! + Temporarily sets the non-toggle modifier state to those saved by the + last call to \c saveModifiers if \p enable is \c true. Restores the + modifier state to the current modifier state if \p enable is \c false. + This is for synthesizing keystrokes on the primary screen when the + cursor is on a secondary screen. When on a secondary screen we capture + all non-toggle modifier state, track the state internally and do not + pass it on. So if Alt+F1 synthesizes Alt+X we need to synthesize + not just X but also Alt, despite the fact that our internal modifier + state indicates Alt is down, because local apps never saw the Alt down + event. + */ + void useSavedModifiers(bool enable); + + //@} + //! @name accessors + //@{ + + //! Map a virtual key to a button + /*! + Returns the button for the \p virtualKey. + */ + KeyButton virtualKeyToButton(UINT virtualKey) const; + + //! Map key event to a key + /*! + Converts a key event into a KeyID and the shadow modifier state + to a modifier mask. + */ + KeyID mapKeyFromEvent(WPARAM charAndVirtKey, + LPARAM info, KeyModifierMask* maskOut) const; + + //! Check if keyboard groups have changed + /*! + Returns true iff the number or order of the keyboard groups have + changed since the last call to updateKeys(). + */ + bool didGroupsChange() const; + + //! Map key to virtual key + /*! + Returns the virtual key for key \p key or 0 if there's no such virtual + key. + */ + UINT mapKeyToVirtualKey(KeyID key) const; + + //! Map virtual key and button to KeyID + /*! + Returns the KeyID for virtual key \p virtualKey and button \p button + (button should include the extended key bit), or kKeyNone if there is + no such key. + */ + static KeyID getKeyID(UINT virtualKey, KeyButton button); + + //@} + + // IKeyState overrides + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button); + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual bool fakeCtrlAltDel(); + virtual KeyModifierMask + pollActiveModifiers() const; + virtual SInt32 pollActiveGroup() const; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const; + + // CKeyState overrides + virtual void onKey(KeyButton button, bool down, + KeyModifierMask newState); + virtual void sendKeyEvent(void* target, + bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button); + +protected: + // CKeyState overrides + virtual void getKeyMap(CKeyMap& keyMap); + virtual void fakeKey(const Keystroke& keystroke); + virtual KeyModifierMask& + getActiveModifiersRValue(); + +private: + typedef std::vector GroupList; + + // send ctrl+alt+del hotkey event on NT family + static void ctrlAltDelThread(void*); + + bool getGroups(GroupList&) const; + void setWindowGroup(SInt32 group); + + void fixKeys(); + void handleFixKeys(const CEvent&, void*); + + KeyID getIDForKey(CKeyMap::KeyItem& item, + KeyButton button, UINT virtualKey, + PBYTE keyState, HKL hkl) const; + + void addKeyEntry(CKeyMap& keyMap, CKeyMap::KeyItem& item); + +private: + // not implemented + CMSWindowsKeyState(const CMSWindowsKeyState&); + CMSWindowsKeyState& operator=(const CMSWindowsKeyState&); + +private: + typedef std::map GroupMap; + typedef std::map KeyToVKMap; + + bool m_is95Family; + void* m_eventTarget; + CMSWindowsDesks* m_desks; + HKL m_keyLayout; + UINT m_buttonToVK[512]; + UINT m_buttonToNumpadVK[512]; + KeyButton m_virtualKeyToButton[256]; + KeyToVKMap m_keyToVKMap; + + // the timer used to check for fixing key state + CEventQueueTimer* m_fixTimer; + + // the groups (keyboard layouts) + GroupList m_groups; + GroupMap m_groupMap; + + // the last button that we generated a key down event for. this + // is zero if the last key event was a key up. we use this to + // synthesize key repeats since the low level keyboard hook can't + // tell us if an event is a key repeat. + KeyButton m_lastDown; + + // modifier tracking + bool m_useSavedModifiers; + KeyModifierMask m_savedModifiers; + KeyModifierMask m_originalSavedModifiers; + + // pointer to ToUnicodeEx. on win95 family this will be NULL. + typedef int (WINAPI *ToUnicodeEx_t)(UINT wVirtKey, + UINT wScanCode, + PBYTE lpKeyState, + LPWSTR pwszBuff, + int cchBuff, + UINT wFlags, + HKL dwhkl); + ToUnicodeEx_t m_ToUnicodeEx; + + static const KeyID s_virtualKey[]; +}; + +#endif diff --git a/lib/platform/CMSWindowsScreen.cpp b/lib/platform/CMSWindowsScreen.cpp new file mode 100644 index 00000000..b000b477 --- /dev/null +++ b/lib/platform/CMSWindowsScreen.cpp @@ -0,0 +1,1738 @@ +/* + * 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 "CMSWindowsScreen.h" +#include "CMSWindowsClipboard.h" +#include "CMSWindowsDesks.h" +#include "CMSWindowsEventQueueBuffer.h" +#include "CMSWindowsKeyState.h" +#include "CMSWindowsScreenSaver.h" +#include "CClipboard.h" +#include "CKeyMap.h" +#include "XScreen.h" +#include "CLock.h" +#include "CThread.h" +#include "CFunctionJob.h" +#include "CLog.h" +#include "CString.h" +#include "CStringUtil.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include "TMethodJob.h" +#include "CArch.h" +#include "CArchMiscWindows.h" +#include +#include + +// +// add backwards compatible multihead support (and suppress bogus warning). +// this isn't supported on MinGW yet AFAICT. +// +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4706) // assignment within conditional +#define COMPILE_MULTIMON_STUBS +#include +#pragma warning(pop) +#endif + +// X button stuff +#if !defined(WM_XBUTTONDOWN) +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define WM_NCXBUTTONDOWN 0x00AB +#define WM_NCXBUTTONUP 0x00AC +#define WM_NCXBUTTONDBLCLK 0x00AD +#define MOUSEEVENTF_XDOWN 0x0080 +#define MOUSEEVENTF_XUP 0x0100 +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif + +// WM_POWERBROADCAST stuff +#if !defined(PBT_APMRESUMEAUTOMATIC) +#define PBT_APMRESUMEAUTOMATIC 0x0012 +#endif + +// +// CMSWindowsScreen +// + +HINSTANCE CMSWindowsScreen::s_instance = NULL; +CMSWindowsScreen* CMSWindowsScreen::s_screen = NULL; + +CMSWindowsScreen::CMSWindowsScreen(bool isPrimary) : + m_isPrimary(isPrimary), + m_is95Family(CArchMiscWindows::isWindows95Family()), + m_isOnScreen(m_isPrimary), + m_class(0), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_multimon(false), + m_xCursor(0), m_yCursor(0), + m_sequenceNumber(0), + m_mark(0), + m_markReceived(0), + m_fixTimer(NULL), + m_keyLayout(NULL), + m_screensaver(NULL), + m_screensaverNotify(false), + m_screensaverActive(false), + m_window(NULL), + m_nextClipboardWindow(NULL), + m_ownClipboard(false), + m_desks(NULL), + m_hookLibrary(NULL), + m_init(NULL), + m_cleanup(NULL), + m_setSides(NULL), + m_setZone(NULL), + m_setMode(NULL), + m_keyState(NULL), + m_hasMouse(GetSystemMetrics(SM_MOUSEPRESENT) != 0), + m_showingMouse(false) +{ + assert(s_instance != NULL); + assert(s_screen == NULL); + + s_screen = this; + try { + if (m_isPrimary) { + m_hookLibrary = openHookLibrary("synrgyhk"); + } + m_screensaver = new CMSWindowsScreenSaver(); + m_desks = new CMSWindowsDesks(m_isPrimary, + m_hookLibrary, m_screensaver, + new TMethodJob(this, + &CMSWindowsScreen::updateKeysCB)); + m_keyState = new CMSWindowsKeyState(m_desks, getEventTarget()); + updateScreenShape(); + m_class = createWindowClass(); + m_window = createWindow(m_class, "Synergy"); + forceShowCursor(); + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : "")); + LOG((CLOG_DEBUG "window is 0x%08x", m_window)); + } + catch (...) { + delete m_keyState; + delete m_desks; + delete m_screensaver; + destroyWindow(m_window); + destroyClass(m_class); + closeHookLibrary(m_hookLibrary); + s_screen = NULL; + throw; + } + + // install event handlers + EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(), + new TMethodEventJob(this, + &CMSWindowsScreen::handleSystemEvent)); + + // install the platform event queue + EVENTQUEUE->adoptBuffer(new CMSWindowsEventQueueBuffer); +} + +CMSWindowsScreen::~CMSWindowsScreen() +{ + assert(s_screen != NULL); + + disable(); + EVENTQUEUE->adoptBuffer(NULL); + EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget()); + delete m_keyState; + delete m_desks; + delete m_screensaver; + destroyWindow(m_window); + destroyClass(m_class); + closeHookLibrary(m_hookLibrary); + s_screen = NULL; +} + +void +CMSWindowsScreen::init(HINSTANCE instance) +{ + assert(s_instance == NULL); + assert(instance != NULL); + + s_instance = instance; +} + +HINSTANCE +CMSWindowsScreen::getInstance() +{ + return s_instance; +} + +void +CMSWindowsScreen::enable() +{ + assert(m_isOnScreen == m_isPrimary); + + // we need to poll some things to fix them + m_fixTimer = EVENTQUEUE->newTimer(1.0, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_fixTimer, + new TMethodEventJob(this, + &CMSWindowsScreen::handleFixes)); + + // install our clipboard snooper + m_nextClipboardWindow = SetClipboardViewer(m_window); + + // track the active desk and (re)install the hooks + m_desks->enable(); + + if (m_isPrimary) { + // set jump zones + m_setZone(m_x, m_y, m_w, m_h, getJumpZoneSize()); + + // watch jump zones + m_setMode(kHOOK_WATCH_JUMP_ZONE); + } + else { + // prevent the system from entering power saving modes. if + // it did we'd be forced to disconnect from the server and + // the server would not be able to wake us up. + CArchMiscWindows::addBusyState(CArchMiscWindows::kSYSTEM); + } +} + +void +CMSWindowsScreen::disable() +{ + // stop tracking the active desk + m_desks->disable(); + + if (m_isPrimary) { + // disable hooks + m_setMode(kHOOK_DISABLE); + + // enable special key sequences on win95 family + enableSpecialKeys(true); + } + else { + // allow the system to enter power saving mode + CArchMiscWindows::removeBusyState(CArchMiscWindows::kSYSTEM | + CArchMiscWindows::kDISPLAY); + } + + // tell key state + m_keyState->disable(); + + // stop snooping the clipboard + ChangeClipboardChain(m_window, m_nextClipboardWindow); + m_nextClipboardWindow = NULL; + + // uninstall fix timer + if (m_fixTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_fixTimer); + EVENTQUEUE->deleteTimer(m_fixTimer); + m_fixTimer = NULL; + } + + m_isOnScreen = m_isPrimary; + forceShowCursor(); +} + +void +CMSWindowsScreen::enter() +{ + m_desks->enter(); + if (m_isPrimary) { + // enable special key sequences on win95 family + enableSpecialKeys(true); + + // watch jump zones + m_setMode(kHOOK_WATCH_JUMP_ZONE); + + // all messages prior to now are invalid + nextMark(); + } + + // now on screen + m_isOnScreen = true; + forceShowCursor(); +} + +bool +CMSWindowsScreen::leave() +{ + // get keyboard layout of foreground window. we'll use this + // keyboard layout for translating keys sent to clients. + HWND window = GetForegroundWindow(); + DWORD thread = GetWindowThreadProcessId(window, NULL); + m_keyLayout = GetKeyboardLayout(thread); + + // tell the key mapper about the keyboard layout + m_keyState->setKeyLayout(m_keyLayout); + + // tell desk that we're leaving and tell it the keyboard layout + m_desks->leave(m_keyLayout); + + if (m_isPrimary) { + // warp to center + warpCursor(m_xCenter, m_yCenter); + + // disable special key sequences on win95 family + enableSpecialKeys(false); + + // all messages prior to now are invalid + nextMark(); + + // remember the modifier state. this is the modifier state + // reflected in the internal keyboard state. + m_keyState->saveModifiers(); + + // capture events + m_setMode(kHOOK_RELAY_EVENTS); + } + + // now off screen + m_isOnScreen = false; + forceShowCursor(); + + return true; +} + +bool +CMSWindowsScreen::setClipboard(ClipboardID, const IClipboard* src) +{ + CMSWindowsClipboard dst(m_window); + if (src != NULL) { + // save clipboard data + return CClipboard::copy(&dst, src); + } + else { + // assert clipboard ownership + if (!dst.open(0)) { + return false; + } + dst.empty(); + dst.close(); + return true; + } +} + +void +CMSWindowsScreen::checkClipboards() +{ + // if we think we own the clipboard but we don't then somebody + // grabbed the clipboard on this screen without us knowing. + // tell the server that this screen grabbed the clipboard. + // + // this works around bugs in the clipboard viewer chain. + // sometimes NT will simply never send WM_DRAWCLIPBOARD + // messages for no apparent reason and rebooting fixes the + // problem. since we don't want a broken clipboard until the + // next reboot we do this double check. clipboard ownership + // won't be reflected on other screens until we leave but at + // least the clipboard itself will work. + if (m_ownClipboard && !CMSWindowsClipboard::isOwnedBySynergy()) { + LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received")); + m_ownClipboard = false; + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard); + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection); + } +} + +void +CMSWindowsScreen::openScreensaver(bool notify) +{ + assert(m_screensaver != NULL); + + m_screensaverNotify = notify; + if (m_screensaverNotify) { + m_desks->installScreensaverHooks(true); + } + else { + m_screensaver->disable(); + } +} + +void +CMSWindowsScreen::closeScreensaver() +{ + if (m_screensaver != NULL) { + if (m_screensaverNotify) { + m_desks->installScreensaverHooks(false); + } + else { + m_screensaver->enable(); + } + } + m_screensaverNotify = false; +} + +void +CMSWindowsScreen::screensaver(bool activate) +{ + assert(m_screensaver != NULL); + + if (activate) { + m_screensaver->activate(); + } + else { + m_screensaver->deactivate(); + } +} + +void +CMSWindowsScreen::resetOptions() +{ + m_desks->resetOptions(); +} + +void +CMSWindowsScreen::setOptions(const COptionsList& options) +{ + m_desks->setOptions(options); +} + +void +CMSWindowsScreen::setSequenceNumber(UInt32 seqNum) +{ + m_sequenceNumber = seqNum; +} + +bool +CMSWindowsScreen::isPrimary() const +{ + return m_isPrimary; +} + +void* +CMSWindowsScreen::getEventTarget() const +{ + return const_cast(this); +} + +bool +CMSWindowsScreen::getClipboard(ClipboardID, IClipboard* dst) const +{ + CMSWindowsClipboard src(m_window); + CClipboard::copy(dst, &src); + return true; +} + +void +CMSWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + assert(m_class != 0); + + x = m_x; + y = m_y; + w = m_w; + h = m_h; +} + +void +CMSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + m_desks->getCursorPos(x, y); +} + +void +CMSWindowsScreen::reconfigure(UInt32 activeSides) +{ + assert(m_isPrimary); + + LOG((CLOG_DEBUG "active sides: %x", activeSides)); + m_setSides(activeSides); +} + +void +CMSWindowsScreen::warpCursor(SInt32 x, SInt32 y) +{ + // warp mouse + warpCursorNoFlush(x, y); + + // remove all input events before and including warp + MSG msg; + while (PeekMessage(&msg, NULL, SYNERGY_MSG_INPUT_FIRST, + SYNERGY_MSG_INPUT_LAST, PM_REMOVE)) { + // do nothing + } + + // save position as last position + m_xCursor = x; + m_yCursor = y; +} + +UInt32 +CMSWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + // only allow certain modifiers + if ((mask & ~(KeyModifierShift | KeyModifierControl | + KeyModifierAlt | KeyModifierSuper)) != 0) { + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // fail if no keys + if (key == kKeyNone && mask == 0) { + return 0; + } + + // convert to win32 + UINT modifiers = 0; + if ((mask & KeyModifierShift) != 0) { + modifiers |= MOD_SHIFT; + } + if ((mask & KeyModifierControl) != 0) { + modifiers |= MOD_CONTROL; + } + if ((mask & KeyModifierAlt) != 0) { + modifiers |= MOD_ALT; + } + if ((mask & KeyModifierSuper) != 0) { + modifiers |= MOD_WIN; + } + UINT vk = m_keyState->mapKeyToVirtualKey(key); + if (key != kKeyNone && vk == 0) { + // can't map key + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // choose hotkey id + UInt32 id; + if (!m_oldHotKeyIDs.empty()) { + id = m_oldHotKeyIDs.back(); + m_oldHotKeyIDs.pop_back(); + } + else { + id = m_hotKeys.size() + 1; + } + + // if this hot key has modifiers only then we'll handle it specially + bool err; + if (key == kKeyNone) { + // check if already registered + err = (m_hotKeyToIDMap.count(CHotKeyItem(vk, modifiers)) > 0); + } + else { + // register with OS + err = (RegisterHotKey(NULL, id, modifiers, vk) == 0); + } + + if (!err) { + m_hotKeys.insert(std::make_pair(id, CHotKeyItem(vk, modifiers))); + m_hotKeyToIDMap[CHotKeyItem(vk, modifiers)] = id; + } + else { + m_oldHotKeyIDs.push_back(id); + m_hotKeys.erase(id); + LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", CKeyMap::formatKey(key, mask).c_str(), key, mask)); + return 0; + } + + LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", CKeyMap::formatKey(key, mask).c_str(), key, mask, id)); + return id; +} + +void +CMSWindowsScreen::unregisterHotKey(UInt32 id) +{ + // look up hotkey + HotKeyMap::iterator i = m_hotKeys.find(id); + if (i == m_hotKeys.end()) { + return; + } + + // unregister with OS + bool err; + if (i->second.getVirtualKey() != 0) { + err = !UnregisterHotKey(NULL, id); + } + else { + err = false; + } + if (err) { + LOG((CLOG_WARN "failed to unregister hotkey id=%d", id)); + } + else { + LOG((CLOG_DEBUG "unregistered hotkey id=%d", id)); + } + + // discard hot key from map and record old id for reuse + m_hotKeyToIDMap.erase(i->second); + m_hotKeys.erase(i); + m_oldHotKeyIDs.push_back(id); +} + +void +CMSWindowsScreen::fakeInputBegin() +{ + assert(m_isPrimary); + + if (!m_isOnScreen) { + m_keyState->useSavedModifiers(true); + } + m_desks->fakeInputBegin(); +} + +void +CMSWindowsScreen::fakeInputEnd() +{ + assert(m_isPrimary); + + m_desks->fakeInputEnd(); + if (!m_isOnScreen) { + m_keyState->useSavedModifiers(false); + } +} + +SInt32 +CMSWindowsScreen::getJumpZoneSize() const +{ + return 1; +} + +bool +CMSWindowsScreen::isAnyMouseButtonDown() const +{ + static const char* buttonToName[] = { + "", + "Left Button", + "Middle Button", + "Right Button", + "X Button 1", + "X Button 2" + }; + + for (UInt32 i = 1; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { + if (m_buttons[i]) { + LOG((CLOG_DEBUG "locked by \"%s\"", buttonToName[i])); + return true; + } + } + + return false; +} + +void +CMSWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + x = m_xCenter; + y = m_yCenter; +} + +void +CMSWindowsScreen::fakeMouseButton(ButtonID id, bool press) const +{ + m_desks->fakeMouseButton(id, press); +} + +void +CMSWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) const +{ + m_desks->fakeMouseMove(x, y); +} + +void +CMSWindowsScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + m_desks->fakeMouseRelativeMove(dx, dy); +} + +void +CMSWindowsScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + m_desks->fakeMouseWheel(xDelta, yDelta); +} + +void +CMSWindowsScreen::updateKeys() +{ + m_desks->updateKeys(); +} + +void +CMSWindowsScreen::fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) +{ + CPlatformScreen::fakeKeyDown(id, mask, button); + updateForceShowCursor(); +} + +void +CMSWindowsScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + CPlatformScreen::fakeKeyRepeat(id, mask, count, button); + updateForceShowCursor(); +} + +void +CMSWindowsScreen::fakeKeyUp(KeyButton button) +{ + CPlatformScreen::fakeKeyUp(button); + updateForceShowCursor(); +} + +void +CMSWindowsScreen::fakeAllKeysUp() +{ + CPlatformScreen::fakeAllKeysUp(); + updateForceShowCursor(); +} + +HINSTANCE +CMSWindowsScreen::openHookLibrary(const char* name) +{ + // load the hook library + HINSTANCE hookLibrary = LoadLibrary(name); + if (hookLibrary == NULL) { + LOG((CLOG_ERR "Failed to load hook library; %s.dll is missing", name)); + throw XScreenOpenFailure(); + } + + // look up functions + m_setSides = (SetSidesFunc)GetProcAddress(hookLibrary, "setSides"); + m_setZone = (SetZoneFunc)GetProcAddress(hookLibrary, "setZone"); + m_setMode = (SetModeFunc)GetProcAddress(hookLibrary, "setMode"); + m_init = (InitFunc)GetProcAddress(hookLibrary, "init"); + m_cleanup = (CleanupFunc)GetProcAddress(hookLibrary, "cleanup"); + if (m_setSides == NULL || + m_setZone == NULL || + m_setMode == NULL || + m_init == NULL || + m_cleanup == NULL) { + LOG((CLOG_ERR "Invalid hook library; use a newer %s.dll", name)); + throw XScreenOpenFailure(); + } + + // initialize hook library + if (m_init(GetCurrentThreadId()) == 0) { + LOG((CLOG_ERR "Cannot initialize hook library; is synergy already running?")); + throw XScreenOpenFailure(); + } + + return hookLibrary; +} + +void +CMSWindowsScreen::closeHookLibrary(HINSTANCE hookLibrary) const +{ + if (hookLibrary != NULL) { + m_cleanup(); + FreeLibrary(hookLibrary); + } +} + +HCURSOR +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)); + memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2)); + HCURSOR c = CreateCursor(s_instance, 0, 0, cw, ch, cursorAND, cursorXOR); + delete[] cursorXOR; + delete[] cursorAND; + return c; +} + +void +CMSWindowsScreen::destroyCursor(HCURSOR cursor) const +{ + if (cursor != NULL) { + DestroyCursor(cursor); + } +} + +ATOM +CMSWindowsScreen::createWindowClass() const +{ + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_DBLCLKS | CS_NOCLOSE; + classInfo.lpfnWndProc = &CMSWindowsScreen::wndProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = 0; + classInfo.hInstance = s_instance; + classInfo.hIcon = NULL; + classInfo.hCursor = NULL; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = "Synergy"; + classInfo.hIconSm = NULL; + return RegisterClassEx(&classInfo); +} + +void +CMSWindowsScreen::destroyClass(ATOM windowClass) const +{ + if (windowClass != 0) { + UnregisterClass(reinterpret_cast(windowClass), s_instance); + } +} + +HWND +CMSWindowsScreen::createWindow(ATOM windowClass, const char* name) const +{ + HWND window = CreateWindowEx(WS_EX_TOPMOST | + WS_EX_TRANSPARENT | + WS_EX_TOOLWINDOW, + reinterpret_cast(windowClass), + name, + WS_POPUP, + 0, 0, 1, 1, + NULL, NULL, + s_instance, + NULL); + if (window == NULL) { + LOG((CLOG_ERR "failed to create window: %d", GetLastError())); + throw XScreenOpenFailure(); + } + return window; +} + +void +CMSWindowsScreen::destroyWindow(HWND hwnd) const +{ + if (hwnd != NULL) { + DestroyWindow(hwnd); + } +} + +void +CMSWindowsScreen::sendEvent(CEvent::Type type, void* data) +{ + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data)); +} + +void +CMSWindowsScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id) +{ + CClipboardInfo* info = (CClipboardInfo*)malloc(sizeof(CClipboardInfo)); + info->m_id = id; + info->m_sequenceNumber = m_sequenceNumber; + sendEvent(type, info); +} + +void +CMSWindowsScreen::handleSystemEvent(const CEvent& event, void*) +{ + MSG* msg = reinterpret_cast(event.getData()); + assert(msg != NULL); + + if (CArchMiscWindows::processDialog(msg)) { + return; + } + if (onPreDispatch(msg->hwnd, msg->message, msg->wParam, msg->lParam)) { + return; + } + TranslateMessage(msg); + DispatchMessage(msg); +} + +void +CMSWindowsScreen::updateButtons() +{ + int numButtons = GetSystemMetrics(SM_CMOUSEBUTTONS); + m_buttons[kButtonNone] = false; + m_buttons[kButtonLeft] = (GetKeyState(VK_LBUTTON) < 0); + m_buttons[kButtonRight] = (GetKeyState(VK_RBUTTON) < 0); + m_buttons[kButtonMiddle] = (GetKeyState(VK_MBUTTON) < 0); + m_buttons[kButtonExtra0 + 0] = (numButtons >= 4) && + (GetKeyState(VK_XBUTTON1) < 0); + m_buttons[kButtonExtra0 + 1] = (numButtons >= 5) && + (GetKeyState(VK_XBUTTON2) < 0); +} + +IKeyState* +CMSWindowsScreen::getKeyState() const +{ + return m_keyState; +} + +bool +CMSWindowsScreen::onPreDispatch(HWND hwnd, + UINT message, WPARAM wParam, LPARAM lParam) +{ + // handle event + switch (message) { + case SYNERGY_MSG_SCREEN_SAVER: + return onScreensaver(wParam != 0); + + case SYNERGY_MSG_DEBUG: + LOG((CLOG_DEBUG1 "hook: 0x%08x 0x%08x", wParam, lParam)); + return true; + } + + if (m_isPrimary) { + return onPreDispatchPrimary(hwnd, message, wParam, lParam); + } + + return false; +} + +bool +CMSWindowsScreen::onPreDispatchPrimary(HWND, + UINT message, WPARAM wParam, LPARAM lParam) +{ + // handle event + switch (message) { + case SYNERGY_MSG_MARK: + return onMark(static_cast(wParam)); + + case SYNERGY_MSG_KEY: + return onKey(wParam, lParam); + + case SYNERGY_MSG_MOUSE_BUTTON: + return onMouseButton(wParam, lParam); + + case SYNERGY_MSG_MOUSE_MOVE: + return onMouseMove(static_cast(wParam), + static_cast(lParam)); + + case SYNERGY_MSG_MOUSE_WHEEL: + // XXX -- support x-axis scrolling + return onMouseWheel(0, static_cast(wParam)); + + case SYNERGY_MSG_PRE_WARP: + { + // save position to compute delta of next motion + m_xCursor = static_cast(wParam); + m_yCursor = static_cast(lParam); + + // we warped the mouse. discard events until we find the + // matching post warp event. see warpCursorNoFlush() for + // where the events are sent. we discard the matching + // post warp event and can be sure we've skipped the warp + // event. + MSG msg; + do { + GetMessage(&msg, NULL, SYNERGY_MSG_MOUSE_MOVE, + SYNERGY_MSG_POST_WARP); + } while (msg.message != SYNERGY_MSG_POST_WARP); + } + return true; + + case SYNERGY_MSG_POST_WARP: + LOG((CLOG_WARN "unmatched post warp")); + return true; + + case WM_HOTKEY: + // we discard these messages. we'll catch the hot key in the + // regular key event handling, where we can detect both key + // press and release. we only register the hot key so no other + // app will act on the key combination. + break; + } + + return false; +} + +bool +CMSWindowsScreen::onEvent(HWND, UINT msg, + WPARAM wParam, LPARAM lParam, LRESULT* result) +{ + switch (msg) { + case WM_QUERYENDSESSION: + if (m_is95Family) { + *result = TRUE; + return true; + } + break; + + case WM_ENDSESSION: + if (m_is95Family) { + if (wParam == TRUE && lParam == 0) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + return true; + } + break; + + case WM_DRAWCLIPBOARD: + // first pass on the message + if (m_nextClipboardWindow != NULL) { + SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + } + + // now handle the message + return onClipboardChange(); + + case WM_CHANGECBCHAIN: + if (m_nextClipboardWindow == (HWND)wParam) { + m_nextClipboardWindow = (HWND)lParam; + LOG((CLOG_DEBUG "clipboard chain: new next: 0x%08x", m_nextClipboardWindow)); + } + else if (m_nextClipboardWindow != NULL) { + SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + } + return true; + + case WM_DISPLAYCHANGE: + return onDisplayChange(); + + case WM_POWERBROADCAST: + switch (wParam) { + case PBT_APMRESUMEAUTOMATIC: + case PBT_APMRESUMECRITICAL: + case PBT_APMRESUMESUSPEND: + EVENTQUEUE->addEvent(CEvent(IScreen::getResumeEvent(), + getEventTarget(), NULL, + CEvent::kDeliverImmediately)); + break; + + case PBT_APMSUSPEND: + EVENTQUEUE->addEvent(CEvent(IScreen::getSuspendEvent(), + getEventTarget(), NULL, + CEvent::kDeliverImmediately)); + break; + } + *result = TRUE; + return true; + + case WM_DEVICECHANGE: + forceShowCursor(); + break; + + case WM_SETTINGCHANGE: + if (wParam == SPI_SETMOUSEKEYS) { + forceShowCursor(); + } + break; + } + + return false; +} + +bool +CMSWindowsScreen::onMark(UInt32 mark) +{ + m_markReceived = mark; + return true; +} + +bool +CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam) +{ + static const KeyModifierMask s_ctrlAlt = + KeyModifierControl | KeyModifierAlt; + + LOG((CLOG_DEBUG1 "event: Key char=%d, vk=0x%02x, nagr=%d, lParam=0x%08x", (wParam & 0xff00u) >> 8, wParam & 0xffu, (wParam & 0x10000u) ? 1 : 0, lParam)); + + // get event info + KeyButton button = (KeyButton)((lParam & 0x01ff0000) >> 16); + bool down = ((lParam & 0x80000000u) == 0x00000000u); + bool wasDown = isKeyDown(button); + KeyModifierMask oldState = pollActiveModifiers(); + + // check for autorepeat + if (m_keyState->testAutoRepeat(down, (lParam & 0x40000000u) == 1, button)) { + lParam |= 0x40000000u; + } + + // if the button is zero then guess what the button should be. + // these are badly synthesized key events and logitech software + // that maps mouse buttons to keys is known to do this. + // alternatively, we could just throw these events out. + if (button == 0) { + button = m_keyState->virtualKeyToButton(wParam & 0xffu); + if (button == 0) { + return true; + } + wasDown = isKeyDown(button); + } + + // record keyboard state + m_keyState->onKey(button, down, oldState); + + // windows doesn't tell us the modifier key state on mouse or key + // events so we have to figure it out. most apps would use + // GetKeyState() or even GetAsyncKeyState() for that but we can't + // because our hook doesn't pass on key events for several modifiers. + // it can't otherwise the system would interpret them normally on + // the primary screen even when on a secondary screen. so tapping + // alt would activate menus and tapping the windows key would open + // the start menu. if you don't pass those events on in the hook + // then GetKeyState() understandably doesn't reflect the effect of + // the event. curiously, neither does GetAsyncKeyState(), which is + // surprising. + // + // so anyway, we have to track the modifier state ourselves for + // at least those modifiers we don't pass on. pollActiveModifiers() + // does that but we have to update the keyboard state before calling + // pollActiveModifiers() to get the right answer. but the only way + // to set the modifier state or to set the up/down state of a key + // is via onKey(). so we have to call onKey() twice. + KeyModifierMask state = pollActiveModifiers(); + m_keyState->onKey(button, down, state); + + // check for hot keys + if (oldState != state) { + // modifier key was pressed/released + if (onHotKey(0, lParam)) { + return true; + } + } + else { + // non-modifier was pressed/released + if (onHotKey(wParam, lParam)) { + return true; + } + } + + // ignore message if posted prior to last mark change + if (!ignore()) { + // check for ctrl+alt+del. we do not want to pass that to the + // client. the user can use ctrl+alt+pause to emulate it. + UINT virtKey = (wParam & 0xffu); + if (virtKey == VK_DELETE && (state & s_ctrlAlt) == s_ctrlAlt) { + LOG((CLOG_DEBUG "discard ctrl+alt+del")); + return true; + } + + // check for ctrl+alt+del emulation + if ((virtKey == VK_PAUSE || virtKey == VK_CANCEL) && + (state & s_ctrlAlt) == s_ctrlAlt) { + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + // switch wParam and lParam to be as if VK_DELETE was + // pressed or released. when mapping the key we require that + // we not use AltGr (the 0x10000 flag in wParam) and we not + // use the keypad delete key (the 0x01000000 flag in lParam). + wParam = VK_DELETE | 0x00010000u; + lParam &= 0xfe000000; + lParam |= m_keyState->virtualKeyToButton(wParam & 0xffu) << 16; + lParam |= 0x01000001; + } + + // process key + KeyModifierMask mask; + KeyID key = m_keyState->mapKeyFromEvent(wParam, lParam, &mask); + button = static_cast((lParam & 0x01ff0000u) >> 16); + if (key != kKeyNone) { + // fix key up. if the key isn't down according to + // our table then we never got the key press event + // for it. if it's not a modifier key then we'll + // synthesize the press first. only do this on + // the windows 95 family, which eats certain special + // keys like alt+tab, ctrl+esc, etc. + if (m_is95Family && !wasDown && !down) { + switch (virtKey) { + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + case VK_LWIN: + case VK_RWIN: + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + break; + + default: + m_keyState->sendKeyEvent(getEventTarget(), + true, false, key, mask, 1, button); + break; + } + } + + // do it + m_keyState->sendKeyEvent(getEventTarget(), + ((lParam & 0x80000000u) == 0), + ((lParam & 0x40000000u) != 0), + key, mask, (SInt32)(lParam & 0xffff), button); + } + else { + LOG((CLOG_DEBUG1 "cannot map key")); + } + } + + return true; +} + +bool +CMSWindowsScreen::onHotKey(WPARAM wParam, LPARAM lParam) +{ + // get the key info + KeyModifierMask state = getActiveModifiers(); + UINT virtKey = (wParam & 0xffu); + UINT modifiers = 0; + if ((state & KeyModifierShift) != 0) { + modifiers |= MOD_SHIFT; + } + if ((state & KeyModifierControl) != 0) { + modifiers |= MOD_CONTROL; + } + if ((state & KeyModifierAlt) != 0) { + modifiers |= MOD_ALT; + } + if ((state & KeyModifierSuper) != 0) { + modifiers |= MOD_WIN; + } + + // find the hot key id + HotKeyToIDMap::const_iterator i = + m_hotKeyToIDMap.find(CHotKeyItem(virtKey, modifiers)); + if (i == m_hotKeyToIDMap.end()) { + return false; + } + + // find what kind of event + CEvent::Type type; + if ((lParam & 0x80000000u) == 0u) { + if ((lParam & 0x40000000u) != 0u) { + // ignore key repeats but it counts as a hot key + return true; + } + type = getHotKeyDownEvent(); + } + else { + type = getHotKeyUpEvent(); + } + + // generate event + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), + CHotKeyInfo::alloc(i->second))); + + return true; +} + +bool +CMSWindowsScreen::onMouseButton(WPARAM wParam, LPARAM lParam) +{ + // get which button + bool pressed = mapPressFromEvent(wParam, lParam); + ButtonID button = mapButtonFromEvent(wParam, lParam); + + // keep our shadow key state up to date + if (button >= kButtonLeft && button <= kButtonExtra0 + 1) { + if (pressed) { + m_buttons[button] = true; + } + else { + m_buttons[button] = false; + } + } + + // ignore message if posted prior to last mark change + if (!ignore()) { + KeyModifierMask mask = m_keyState->getActiveModifiers(); + if (pressed) { + LOG((CLOG_DEBUG1 "event: button press button=%d", button)); + if (button != kButtonNone) { + sendEvent(getButtonDownEvent(), + CButtonInfo::alloc(button, mask)); + } + } + else { + LOG((CLOG_DEBUG1 "event: button release button=%d", button)); + if (button != kButtonNone) { + sendEvent(getButtonUpEvent(), + CButtonInfo::alloc(button, mask)); + } + } + } + + return true; +} + +bool +CMSWindowsScreen::onMouseMove(SInt32 mx, SInt32 my) +{ + // compute motion delta (relative to the last known + // mouse position) + SInt32 x = mx - m_xCursor; + SInt32 y = my - m_yCursor; + + // ignore if the mouse didn't move or if message posted prior + // to last mark change. + if (ignore() || (x == 0 && y == 0)) { + return true; + } + + // save position to compute delta of next motion + m_xCursor = mx; + m_yCursor = my; + + if (m_isOnScreen) { + // motion on primary screen + sendEvent(getMotionOnPrimaryEvent(), + CMotionInfo::alloc(m_xCursor, m_yCursor)); + } + else { + // motion on secondary screen. warp mouse back to + // center. + warpCursorNoFlush(m_xCenter, m_yCenter); + + // examine the motion. if it's about the distance + // from the center of the screen to an edge then + // it's probably a bogus motion that we want to + // ignore (see warpCursorNoFlush() for a further + // description). + static SInt32 bogusZoneSize = 10; + if (-x + bogusZoneSize > m_xCenter - m_x || + x + bogusZoneSize > m_x + m_w - m_xCenter || + -y + bogusZoneSize > m_yCenter - m_y || + y + bogusZoneSize > m_y + m_h - m_yCenter) { + LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y)); + } + else { + // send motion + sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y)); + } + } + + return true; +} + +bool +CMSWindowsScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + // ignore message if posted prior to last mark change + if (!ignore()) { + LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta)); + sendEvent(getWheelEvent(), CWheelInfo::alloc(xDelta, yDelta)); + } + return true; +} + +bool +CMSWindowsScreen::onScreensaver(bool activated) +{ + // ignore this message if there are any other screen saver + // messages already in the queue. this is important because + // our checkStarted() function has a deliberate delay, so it + // can't respond to events at full CPU speed and will fall + // behind if a lot of screen saver events are generated. + // that can easily happen because windows will continually + // send SC_SCREENSAVE until the screen saver starts, even if + // the screen saver is disabled! + MSG msg; + if (PeekMessage(&msg, NULL, SYNERGY_MSG_SCREEN_SAVER, + SYNERGY_MSG_SCREEN_SAVER, PM_NOREMOVE)) { + return true; + } + + if (activated) { + if (!m_screensaverActive && + m_screensaver->checkStarted(SYNERGY_MSG_SCREEN_SAVER, FALSE, 0)) { + m_screensaverActive = true; + sendEvent(getScreensaverActivatedEvent()); + + // enable display power down + CArchMiscWindows::removeBusyState(CArchMiscWindows::kDISPLAY); + } + } + else { + if (m_screensaverActive) { + m_screensaverActive = false; + sendEvent(getScreensaverDeactivatedEvent()); + + // disable display power down + CArchMiscWindows::addBusyState(CArchMiscWindows::kDISPLAY); + } + } + + return true; +} + +bool +CMSWindowsScreen::onDisplayChange() +{ + // screen resolution may have changed. save old shape. + SInt32 xOld = m_x, yOld = m_y, wOld = m_w, hOld = m_h; + + // update shape + updateScreenShape(); + + // do nothing if resolution hasn't changed + if (xOld != m_x || yOld != m_y || wOld != m_w || hOld != m_h) { + if (m_isPrimary) { + // warp mouse to center if off screen + if (!m_isOnScreen) { + warpCursor(m_xCenter, m_yCenter); + } + + // tell hook about resize if on screen + else { + m_setZone(m_x, m_y, m_w, m_h, getJumpZoneSize()); + } + } + + // send new screen info + sendEvent(getShapeChangedEvent()); + + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : "")); + } + + return true; +} + +bool +CMSWindowsScreen::onClipboardChange() +{ + // now notify client that somebody changed the clipboard (unless + // we're the owner). + if (!CMSWindowsClipboard::isOwnedBySynergy()) { + if (m_ownClipboard) { + LOG((CLOG_DEBUG "clipboard changed: lost ownership")); + m_ownClipboard = false; + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard); + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection); + } + } + else if (!m_ownClipboard) { + LOG((CLOG_DEBUG "clipboard changed: synergy owned")); + m_ownClipboard = true; + } + + return true; +} + +void +CMSWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y) +{ + // send an event that we can recognize before the mouse warp + PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_PRE_WARP, x, y); + + // warp mouse. hopefully this inserts a mouse motion event + // between the previous message and the following message. + SetCursorPos(x, y); + + // yield the CPU. there's a race condition when warping: + // a hardware mouse event occurs + // the mouse hook is not called because that process doesn't have the CPU + // we send PRE_WARP, SetCursorPos(), send POST_WARP + // we process all of those events and update m_x, m_y + // we finish our time slice + // the hook is called + // the hook sends us a mouse event from the pre-warp position + // we get the CPU + // we compute a bogus warp + // we need the hook to process all mouse events that occur + // before we warp before we do the warp but i'm not sure how + // to guarantee that. yielding the CPU here may reduce the + // chance of undesired behavior. we'll also check for very + // large motions that look suspiciously like about half width + // or height of the screen. + ARCH->sleep(0.0); + + // send an event that we can recognize after the mouse warp + PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_POST_WARP, 0, 0); +} + +void +CMSWindowsScreen::nextMark() +{ + // next mark + ++m_mark; + + // mark point in message queue where the mark was changed + PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_MARK, m_mark, 0); +} + +bool +CMSWindowsScreen::ignore() const +{ + return (m_mark != m_markReceived); +} + +void +CMSWindowsScreen::updateScreenShape() +{ + // get shape + m_x = GetSystemMetrics(SM_XVIRTUALSCREEN); + m_y = GetSystemMetrics(SM_YVIRTUALSCREEN); + m_w = GetSystemMetrics(SM_CXVIRTUALSCREEN); + m_h = GetSystemMetrics(SM_CYVIRTUALSCREEN); + + // get center for cursor + m_xCenter = GetSystemMetrics(SM_CXSCREEN) >> 1; + m_yCenter = GetSystemMetrics(SM_CYSCREEN) >> 1; + + // check for multiple monitors + m_multimon = (m_w != GetSystemMetrics(SM_CXSCREEN) || + m_h != GetSystemMetrics(SM_CYSCREEN)); + + // tell the desks + m_desks->setShape(m_x, m_y, m_w, m_h, m_xCenter, m_yCenter, m_multimon); +} + +void +CMSWindowsScreen::handleFixes(const CEvent&, void*) +{ + // fix clipboard chain + fixClipboardViewer(); + + // update keys if keyboard layouts have changed + if (m_keyState->didGroupsChange()) { + updateKeys(); + } +} + +void +CMSWindowsScreen::fixClipboardViewer() +{ + // XXX -- disable this code for now. somehow it can cause an infinite + // recursion in the WM_DRAWCLIPBOARD handler. either we're sending + // the message to our own window or some window farther down the chain + // forwards the message to our window or a window farther up the chain. + // i'm not sure how that could happen. the m_nextClipboardWindow = NULL + // was not in the code that infinite loops and may fix the bug but i + // doubt it. +/* + ChangeClipboardChain(m_window, m_nextClipboardWindow); + m_nextClipboardWindow = NULL; + m_nextClipboardWindow = SetClipboardViewer(m_window); +*/ +} + +void +CMSWindowsScreen::enableSpecialKeys(bool enable) const +{ + // enable/disable ctrl+alt+del, alt+tab, etc on win95 family. + // since the win95 family doesn't support low-level hooks, we + // use this undocumented feature to suppress normal handling + // of certain key combinations. + if (m_is95Family) { + DWORD dummy = 0; + SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, + enable ? FALSE : TRUE, &dummy, 0); + } +} + +ButtonID +CMSWindowsScreen::mapButtonFromEvent(WPARAM msg, LPARAM button) const +{ + switch (msg) { + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_LBUTTONUP: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCLBUTTONUP: + return kButtonLeft; + + case WM_MBUTTONDOWN: + case WM_MBUTTONDBLCLK: + case WM_MBUTTONUP: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONDBLCLK: + case WM_NCMBUTTONUP: + return kButtonMiddle; + + case WM_RBUTTONDOWN: + case WM_RBUTTONDBLCLK: + case WM_RBUTTONUP: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONDBLCLK: + case WM_NCRBUTTONUP: + return kButtonRight; + + case WM_XBUTTONDOWN: + case WM_XBUTTONDBLCLK: + case WM_XBUTTONUP: + case WM_NCXBUTTONDOWN: + case WM_NCXBUTTONDBLCLK: + case WM_NCXBUTTONUP: + switch (button) { + case XBUTTON1: + if (GetSystemMetrics(SM_CMOUSEBUTTONS) >= 4) { + return kButtonExtra0 + 0; + } + break; + + case XBUTTON2: + if (GetSystemMetrics(SM_CMOUSEBUTTONS) >= 5) { + return kButtonExtra0 + 1; + } + break; + } + return kButtonNone; + + default: + return kButtonNone; + } +} + +bool +CMSWindowsScreen::mapPressFromEvent(WPARAM msg, LPARAM) const +{ + switch (msg) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_XBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_XBUTTONDBLCLK: + case WM_NCLBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCXBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCMBUTTONDBLCLK: + case WM_NCRBUTTONDBLCLK: + case WM_NCXBUTTONDBLCLK: + return true; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONUP: + case WM_NCRBUTTONUP: + case WM_NCXBUTTONUP: + return false; + + default: + return false; + } +} + +void +CMSWindowsScreen::updateKeysCB(void*) +{ + // record which keys we think are down + bool down[IKeyState::kNumButtons]; + bool sendFixes = (isPrimary() && !m_isOnScreen); + if (sendFixes) { + for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { + down[i] = m_keyState->isKeyDown(i); + } + } + + // update layouts if necessary + if (m_keyState->didGroupsChange()) { + CPlatformScreen::updateKeyMap(); + } + + // now update the keyboard state + CPlatformScreen::updateKeyState(); + + // now see which keys we thought were down but now think are up. + // send key releases for these keys to the active client. + if (sendFixes) { + KeyModifierMask mask = pollActiveModifiers(); + for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { + if (down[i] && !m_keyState->isKeyDown(i)) { + m_keyState->sendKeyEvent(getEventTarget(), + false, false, kKeyNone, mask, 1, i); + } + } + } +} + +void +CMSWindowsScreen::forceShowCursor() +{ + // check for mouse + m_hasMouse = (GetSystemMetrics(SM_MOUSEPRESENT) != 0); + + // decide if we should show the mouse + bool showMouse = (!m_hasMouse && !m_isPrimary && m_isOnScreen); + + // show/hide the mouse + if (showMouse != m_showingMouse) { + if (showMouse) { + m_oldMouseKeys.cbSize = sizeof(m_oldMouseKeys); + m_gotOldMouseKeys = + (SystemParametersInfo(SPI_GETMOUSEKEYS, + m_oldMouseKeys.cbSize, &m_oldMouseKeys, 0) != 0); + if (m_gotOldMouseKeys) { + m_mouseKeys = m_oldMouseKeys; + m_showingMouse = true; + updateForceShowCursor(); + } + } + else { + if (m_gotOldMouseKeys) { + SystemParametersInfo(SPI_SETMOUSEKEYS, + m_oldMouseKeys.cbSize, + &m_oldMouseKeys, SPIF_SENDCHANGE); + m_showingMouse = false; + } + } + } +} + +void +CMSWindowsScreen::updateForceShowCursor() +{ + DWORD oldFlags = m_mouseKeys.dwFlags; + + // turn on MouseKeys + m_mouseKeys.dwFlags = MKF_AVAILABLE | MKF_MOUSEKEYSON; + + // make sure MouseKeys is active in whatever state the NumLock is + // not currently in. + if ((m_keyState->getActiveModifiers() & KeyModifierNumLock) != 0) { + m_mouseKeys.dwFlags |= MKF_REPLACENUMBERS; + } + + // update MouseKeys + if (oldFlags != m_mouseKeys.dwFlags) { + SystemParametersInfo(SPI_SETMOUSEKEYS, + m_mouseKeys.cbSize, &m_mouseKeys, SPIF_SENDCHANGE); + } +} + +LRESULT CALLBACK +CMSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + assert(s_screen != NULL); + + LRESULT result = 0; + if (!s_screen->onEvent(hwnd, msg, wParam, lParam, &result)) { + result = DefWindowProc(hwnd, msg, wParam, lParam); + } + + return result; +} + + +// +// CMSWindowsScreen::CHotKeyItem +// + +CMSWindowsScreen::CHotKeyItem::CHotKeyItem(UINT keycode, UINT mask) : + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +UINT +CMSWindowsScreen::CHotKeyItem::getVirtualKey() const +{ + return m_keycode; +} + +bool +CMSWindowsScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const +{ + return (m_keycode < x.m_keycode || + (m_keycode == x.m_keycode && m_mask < x.m_mask)); +} diff --git a/lib/platform/CMSWindowsScreen.h b/lib/platform/CMSWindowsScreen.h new file mode 100644 index 00000000..eda3f554 --- /dev/null +++ b/lib/platform/CMSWindowsScreen.h @@ -0,0 +1,308 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSSCREEN_H +#define CMSWINDOWSSCREEN_H + +#include "CPlatformScreen.h" +#include "CSynergyHook.h" +#include "CCondVar.h" +#include "CMutex.h" +#include "CString.h" +#define WIN32_LEAN_AND_MEAN +#include + +class CEventQueueTimer; +class CMSWindowsDesks; +class CMSWindowsKeyState; +class CMSWindowsScreenSaver; +class CThread; + +//! Implementation of IPlatformScreen for Microsoft Windows +class CMSWindowsScreen : public CPlatformScreen { +public: + CMSWindowsScreen(bool isPrimary); + virtual ~CMSWindowsScreen(); + + //! @name manipulators + //@{ + + //! Initialize + /*! + Saves the application's HINSTANCE. This \b must be called by + WinMain with the HINSTANCE it was passed. + */ + static void init(HINSTANCE); + + //@} + //! @name accessors + //@{ + + //! Get instance + /*! + Returns the application instance handle passed to init(). + */ + static HINSTANCE getInstance(); + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides); + virtual void warpCursor(SInt32 x, SInt32 y); + virtual UInt32 registerHotKey(KeyID key, + KeyModifierMask mask); + virtual void unregisterHotKey(UInt32 id); + virtual void fakeInputBegin(); + virtual void fakeInputEnd(); + virtual SInt32 getJumpZoneSize() const; + virtual bool isAnyMouseButtonDown() const; + virtual void getCursorCenter(SInt32& x, SInt32& y) const; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press) const; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const; + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + // IKeyState overrides + virtual void updateKeys(); + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button); + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual void fakeKeyUp(KeyButton button); + virtual void fakeAllKeysUp(); + + // IPlatformScreen overrides + virtual void enable(); + virtual void disable(); + virtual void enter(); + virtual bool leave(); + virtual bool setClipboard(ClipboardID, const IClipboard*); + virtual void checkClipboards(); + virtual void openScreensaver(bool notify); + virtual void closeScreensaver(); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + virtual void setSequenceNumber(UInt32); + virtual bool isPrimary() const; + +protected: + // IPlatformScreen overrides + virtual void handleSystemEvent(const CEvent&, void*); + virtual void updateButtons(); + virtual IKeyState* getKeyState() const; + +private: + // initialization and shutdown operations + HINSTANCE openHookLibrary(const char* name); + void closeHookLibrary(HINSTANCE hookLibrary) const; + HCURSOR createBlankCursor() const; + void destroyCursor(HCURSOR cursor) const; + ATOM createWindowClass() const; + ATOM createDeskWindowClass(bool isPrimary) const; + void destroyClass(ATOM windowClass) const; + HWND createWindow(ATOM windowClass, const char* name) const; + void destroyWindow(HWND) const; + + // convenience function to send events + void sendEvent(CEvent::Type type, void* = NULL); + void sendClipboardEvent(CEvent::Type type, ClipboardID id); + + // handle message before it gets dispatched. returns true iff + // the message should not be dispatched. + bool onPreDispatch(HWND, UINT, WPARAM, LPARAM); + + // handle message before it gets dispatched. returns true iff + // the message should not be dispatched. + bool onPreDispatchPrimary(HWND, UINT, WPARAM, LPARAM); + + // handle message. returns true iff handled and optionally sets + // \c *result (which defaults to 0). + bool onEvent(HWND, UINT, WPARAM, LPARAM, LRESULT* result); + + // message handlers + bool onMark(UInt32 mark); + bool onKey(WPARAM, LPARAM); + bool onHotKey(WPARAM, LPARAM); + bool onMouseButton(WPARAM, LPARAM); + bool onMouseMove(SInt32 x, SInt32 y); + bool onMouseWheel(SInt32 xDelta, SInt32 yDelta); + bool onScreensaver(bool activated); + bool onDisplayChange(); + bool onClipboardChange(); + + // warp cursor without discarding queued events + void warpCursorNoFlush(SInt32 x, SInt32 y); + + // discard posted messages + void nextMark(); + + // test if event should be ignored + bool ignore() const; + + // update screen size cache + void updateScreenShape(); + + // fix timer callback + void handleFixes(const CEvent&, void*); + + // fix the clipboard viewer chain + void fixClipboardViewer(); + + // enable/disable special key combinations so we can catch/pass them + void enableSpecialKeys(bool) const; + + // map a button event to a button ID + ButtonID mapButtonFromEvent(WPARAM msg, LPARAM button) const; + + // map a button event to a press (true) or release (false) + bool mapPressFromEvent(WPARAM msg, LPARAM button) const; + + // job to update the key state + void updateKeysCB(void*); + + // determine whether the mouse is hidden by the system and force + // it to be displayed if user has entered this secondary screen. + void forceShowCursor(); + + // forceShowCursor uses MouseKeys to show the cursor. since we + // don't actually want MouseKeys behavior we have to make sure + // it applies when NumLock is in whatever state it's not in now. + // this method does that. + void updateForceShowCursor(); + + // our window proc + static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM); + +private: + struct CHotKeyItem { + public: + CHotKeyItem(UINT vk, UINT modifiers); + + UINT getVirtualKey() const; + + bool operator<(const CHotKeyItem&) const; + + private: + UINT m_keycode; + UINT m_mask; + }; + typedef std::map HotKeyMap; + typedef std::vector HotKeyIDList; + typedef std::map HotKeyToIDMap; + + static HINSTANCE s_instance; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if windows 95/98/me + bool m_is95Family; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // our resources + ATOM m_class; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // true if system appears to have multiple monitors + bool m_multimon; + + // last mouse position + SInt32 m_xCursor, m_yCursor; + + // last clipboard + UInt32 m_sequenceNumber; + + // used to discard queued messages that are no longer needed + UInt32 m_mark; + UInt32 m_markReceived; + + // the main loop's thread id + DWORD m_threadID; + + // timer for periodically checking stuff that requires polling + CEventQueueTimer* m_fixTimer; + + // the keyboard layout to use when off primary screen + HKL m_keyLayout; + + // screen saver stuff + CMSWindowsScreenSaver* m_screensaver; + bool m_screensaverNotify; + bool m_screensaverActive; + + // clipboard stuff. our window is used mainly as a clipboard + // owner and as a link in the clipboard viewer chain. + HWND m_window; + HWND m_nextClipboardWindow; + bool m_ownClipboard; + + // one desk per desktop and a cond var to communicate with it + CMSWindowsDesks* m_desks; + + // hook library stuff + HINSTANCE m_hookLibrary; + InitFunc m_init; + CleanupFunc m_cleanup; + SetSidesFunc m_setSides; + SetZoneFunc m_setZone; + SetModeFunc m_setMode; + + // keyboard stuff + CMSWindowsKeyState* m_keyState; + + // hot key stuff + HotKeyMap m_hotKeys; + HotKeyIDList m_oldHotKeyIDs; + HotKeyToIDMap m_hotKeyToIDMap; + + // map of button state + bool m_buttons[1 + kButtonExtra0 + 1]; + + // the system shows the mouse cursor when an internal display count + // is >= 0. this count is maintained per application but there's + // apparently a system wide count added to the application's count. + // this system count is 0 if there's a mouse attached to the system + // and -1 otherwise. the MouseKeys accessibility feature can modify + // this system count by making the system appear to have a mouse. + // + // m_hasMouse is true iff there's a mouse attached to the system or + // MouseKeys is simulating one. we track this so we can force the + // cursor to be displayed when the user has entered this screen. + // m_showingMouse is true when we're doing that. + bool m_hasMouse; + bool m_showingMouse; + bool m_gotOldMouseKeys; + MOUSEKEYS m_mouseKeys; + MOUSEKEYS m_oldMouseKeys; + + static CMSWindowsScreen* s_screen; +}; + +#endif diff --git a/lib/platform/CMSWindowsScreenSaver.cpp b/lib/platform/CMSWindowsScreenSaver.cpp new file mode 100644 index 00000000..7217338f --- /dev/null +++ b/lib/platform/CMSWindowsScreenSaver.cpp @@ -0,0 +1,498 @@ +/* + * 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 "CMSWindowsScreenSaver.h" +#include "CMSWindowsScreen.h" +#include "CThread.h" +#include "CLog.h" +#include "TMethodJob.h" +#include "CArch.h" +#include "CArchMiscWindows.h" +#include +#include + +#if !defined(SPI_GETSCREENSAVERRUNNING) +#define SPI_GETSCREENSAVERRUNNING 114 +#endif + +static const TCHAR* g_isSecureNT = "ScreenSaverIsSecure"; +static const TCHAR* g_isSecure9x = "ScreenSaveUsePassword"; +static const TCHAR* const g_pathScreenSaverIsSecure[] = { + "Control Panel", + "Desktop", + NULL +}; + +// +// CMSWindowsScreenSaver +// + +CMSWindowsScreenSaver::CMSWindowsScreenSaver() : + m_wasSecure(false), + m_wasSecureAnInt(false), + m_process(NULL), + m_watch(NULL), + m_threadID(0), + m_active(false) +{ + // detect OS + m_is95Family = false; + m_is95 = false; + m_isNT = false; + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(info); + if (GetVersionEx(&info)) { + m_is95Family = (info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); + if (info.dwPlatformId == VER_PLATFORM_WIN32_NT && + info.dwMajorVersion <= 4) { + m_isNT = true; + } + else if (info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS && + info.dwMajorVersion == 4 && + info.dwMinorVersion == 0) { + m_is95 = true; + } + } + + // check if screen saver is enabled + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); +} + +CMSWindowsScreenSaver::~CMSWindowsScreenSaver() +{ + unwatchProcess(); +} + +bool +CMSWindowsScreenSaver::checkStarted(UINT msg, WPARAM wParam, LPARAM lParam) +{ + // if already started then say it didn't just start + if (m_active) { + return false; + } + + // screen saver may have started. look for it and get + // the process. if we can't find it then assume it + // didn't really start. we wait a moment before + // looking to give the screen saver a chance to start. + // this shouldn't be a problem since we only get here + // if the screen saver wants to kick in, meaning that + // the system is idle or the user deliberately started + // the screen saver. + Sleep(250); + + // set parameters common to all screen saver handling + m_threadID = GetCurrentThreadId(); + m_msg = msg; + m_wParam = wParam; + m_lParam = lParam; + + // we handle the screen saver differently for the windows + // 95 and nt families. + if (m_is95Family) { + // on windows 95 we wait for the screen saver process + // to terminate. get the process. + DWORD processID = findScreenSaver(); + HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, processID); + if (process == NULL) { + // didn't start + LOG((CLOG_DEBUG2 "can't open screen saver process")); + return false; + } + + // watch for the process to exit + watchProcess(process); + } + else { + // on the windows nt family we wait for the desktop to + // change until it's neither the Screen-Saver desktop + // nor a desktop we can't open (the login desktop). + // since windows will send the request-to-start-screen- + // saver message even when the screen saver is disabled + // we first check that the screen saver is indeed active + // before watching for it to stop. + if (!isActive()) { + LOG((CLOG_DEBUG2 "can't open screen saver desktop")); + return false; + } + + watchDesktop(); + } + + return true; +} + +void +CMSWindowsScreenSaver::enable() +{ + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, m_wasEnabled, 0, 0); + + // restore password protection + if (m_wasSecure) { + setSecure(true, m_wasSecureAnInt); + } + + // restore display power down + CArchMiscWindows::removeBusyState(CArchMiscWindows::kDISPLAY); +} + +void +CMSWindowsScreenSaver::disable() +{ + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, 0, 0); + + // disable password protected screensaver + m_wasSecure = isSecure(&m_wasSecureAnInt); + if (m_wasSecure) { + setSecure(false, m_wasSecureAnInt); + } + + // disable display power down + CArchMiscWindows::addBusyState(CArchMiscWindows::kDISPLAY); +} + +void +CMSWindowsScreenSaver::activate() +{ + // don't activate if already active + if (!isActive()) { + // activate + HWND hwnd = GetForegroundWindow(); + if (hwnd != NULL) { + PostMessage(hwnd, WM_SYSCOMMAND, SC_SCREENSAVE, 0); + } + else { + // no foreground window. pretend we got the event instead. + DefWindowProc(NULL, WM_SYSCOMMAND, SC_SCREENSAVE, 0); + } + + // restore power save when screen saver activates + CArchMiscWindows::removeBusyState(CArchMiscWindows::kDISPLAY); + } +} + +void +CMSWindowsScreenSaver::deactivate() +{ + bool killed = false; + if (!m_is95Family) { + // NT runs screen saver in another desktop + HDESK desktop = OpenDesktop("Screen-saver", 0, FALSE, + DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS); + if (desktop != NULL) { + EnumDesktopWindows(desktop, + &CMSWindowsScreenSaver::killScreenSaverFunc, + reinterpret_cast(&killed)); + CloseDesktop(desktop); + } + } + + // if above failed or wasn't tried, try the windows 95 way + if (!killed) { + // find screen saver window and close it + HWND hwnd = FindWindow("WindowsScreenSaverClass", NULL); + if (hwnd == NULL) { + // win2k may use a different class + hwnd = FindWindow("Default Screen Saver", NULL); + } + if (hwnd != NULL) { + PostMessage(hwnd, WM_CLOSE, 0, 0); + } + } + + // force timer to restart + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, + !m_wasEnabled, 0, SPIF_SENDWININICHANGE); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, + m_wasEnabled, 0, SPIF_SENDWININICHANGE); + + // disable display power down + CArchMiscWindows::removeBusyState(CArchMiscWindows::kDISPLAY); +} + +bool +CMSWindowsScreenSaver::isActive() const +{ + if (m_is95) { + return (FindWindow("WindowsScreenSaverClass", NULL) != NULL); + } + else if (m_isNT) { + // screen saver runs on a separate desktop + HDESK desktop = OpenDesktop("Screen-saver", 0, FALSE, MAXIMUM_ALLOWED); + if (desktop == NULL && GetLastError() != ERROR_ACCESS_DENIED) { + // desktop doesn't exist so screen saver is not running + return false; + } + + // desktop exists. this should indicate that the screen saver + // is running but an OS bug can cause a valid handle to be + // returned even if the screen saver isn't running (Q230117). + // we'll try to enumerate the windows on the desktop and, if + // there are any, we assume the screen saver is running. (note + // that if we don't have permission to enumerate then we'll + // assume that the screen saver is not running.) that'd be + // easy enough except there's another OS bug (Q198590) that can + // cause EnumDesktopWindows() to enumerate the windows of + // another desktop if the requested desktop has no windows. to + // work around that we have to verify that the enumerated + // windows are, in fact, on the expected desktop. + CFindScreenSaverInfo info; + info.m_desktop = desktop; + info.m_window = NULL; + EnumDesktopWindows(desktop, + &CMSWindowsScreenSaver::findScreenSaverFunc, + reinterpret_cast(&info)); + + // done with desktop + CloseDesktop(desktop); + + // screen saver is running if a window was found + return (info.m_window != NULL); + } + else { + BOOL running; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, 0); + return (running != FALSE); + } +} + +BOOL CALLBACK +CMSWindowsScreenSaver::findScreenSaverFunc(HWND hwnd, LPARAM arg) +{ + CFindScreenSaverInfo* info = reinterpret_cast(arg); + + if (info->m_desktop != NULL) { + DWORD threadID = GetWindowThreadProcessId(hwnd, NULL); + HDESK desktop = GetThreadDesktop(threadID); + if (desktop != NULL && desktop != info->m_desktop) { + // stop enumerating -- wrong desktop + return FALSE; + } + } + + // found a window + info->m_window = hwnd; + + // don't need to enumerate further + return FALSE; +} + +BOOL CALLBACK +CMSWindowsScreenSaver::killScreenSaverFunc(HWND hwnd, LPARAM arg) +{ + if (IsWindowVisible(hwnd)) { + HINSTANCE instance = (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + if (instance != CMSWindowsScreen::getInstance()) { + PostMessage(hwnd, WM_CLOSE, 0, 0); + *reinterpret_cast(arg) = true; + } + } + return TRUE; +} + +DWORD +CMSWindowsScreenSaver::findScreenSaver() +{ + // try windows 95 way + HWND hwnd = FindWindow("WindowsScreenSaverClass", NULL); + + // get process ID of process that owns the window, if found + if (hwnd != NULL) { + DWORD processID; + GetWindowThreadProcessId(hwnd, &processID); + return processID; + } + + // not found + return 0; +} + +void +CMSWindowsScreenSaver::watchDesktop() +{ + // stop watching previous process/desktop + unwatchProcess(); + + // watch desktop in another thread + LOG((CLOG_DEBUG "watching screen saver desktop")); + m_active = true; + m_watch = new CThread(new TMethodJob(this, + &CMSWindowsScreenSaver::watchDesktopThread)); +} + +void +CMSWindowsScreenSaver::watchProcess(HANDLE process) +{ + // stop watching previous process/desktop + unwatchProcess(); + + // watch new process in another thread + if (process != NULL) { + LOG((CLOG_DEBUG "watching screen saver process")); + m_process = process; + m_active = true; + m_watch = new CThread(new TMethodJob(this, + &CMSWindowsScreenSaver::watchProcessThread)); + } +} + +void +CMSWindowsScreenSaver::unwatchProcess() +{ + if (m_watch != NULL) { + LOG((CLOG_DEBUG "stopped watching screen saver process/desktop")); + m_watch->cancel(); + m_watch->wait(); + delete m_watch; + m_watch = NULL; + m_active = false; + } + if (m_process != NULL) { + CloseHandle(m_process); + m_process = NULL; + } +} + +void +CMSWindowsScreenSaver::watchDesktopThread(void*) +{ + DWORD reserved = 0; + TCHAR* name = NULL; + + for (;;) { + // wait a bit + ARCH->sleep(0.2); + + if (m_isNT) { + // get current desktop + HDESK desk = OpenInputDesktop(0, FALSE, GENERIC_READ); + if (desk == NULL) { + // can't open desktop so keep waiting + continue; + } + + // get current desktop name length + DWORD size; + GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size); + + // allocate more space for the name, if necessary + if (size > reserved) { + reserved = size; + name = (TCHAR*)alloca(reserved + sizeof(TCHAR)); + } + + // get current desktop name + GetUserObjectInformation(desk, UOI_NAME, name, size, &size); + CloseDesktop(desk); + + // compare name to screen saver desktop name + if (_tcsicmp(name, TEXT("Screen-saver")) == 0) { + // still the screen saver desktop so keep waiting + continue; + } + } + else { + // 2000/XP have a sane way to detect a runnin screensaver. + BOOL running; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, 0); + if (running) { + continue; + } + } + + // send screen saver deactivation message + m_active = false; + PostThreadMessage(m_threadID, m_msg, m_wParam, m_lParam); + return; + } +} + +void +CMSWindowsScreenSaver::watchProcessThread(void*) +{ + for (;;) { + CThread::testCancel(); + if (WaitForSingleObject(m_process, 50) == WAIT_OBJECT_0) { + // process terminated + LOG((CLOG_DEBUG "screen saver died")); + + // send screen saver deactivation message + m_active = false; + PostThreadMessage(m_threadID, m_msg, m_wParam, m_lParam); + return; + } + } +} + +void +CMSWindowsScreenSaver::setSecure(bool secure, bool saveSecureAsInt) +{ + HKEY hkey = + CArchMiscWindows::addKey(HKEY_CURRENT_USER, g_pathScreenSaverIsSecure); + if (hkey == NULL) { + return; + } + + const TCHAR* isSecure = m_is95Family ? g_isSecure9x : g_isSecureNT; + if (saveSecureAsInt) { + CArchMiscWindows::setValue(hkey, isSecure, secure ? 1 : 0); + } + else { + CArchMiscWindows::setValue(hkey, isSecure, secure ? "1" : "0"); + } + + CArchMiscWindows::closeKey(hkey); +} + +bool +CMSWindowsScreenSaver::isSecure(bool* wasSecureFlagAnInt) const +{ + // get the password protection setting key + HKEY hkey = + CArchMiscWindows::openKey(HKEY_CURRENT_USER, g_pathScreenSaverIsSecure); + if (hkey == NULL) { + return false; + } + + // get the value. the value may be an int or a string, depending + // on the version of windows. + bool result; + const TCHAR* isSecure = m_is95Family ? g_isSecure9x : g_isSecureNT; + switch (CArchMiscWindows::typeOfValue(hkey, isSecure)) { + default: + result = false; + break; + + case CArchMiscWindows::kUINT: { + DWORD value = + CArchMiscWindows::readValueInt(hkey, isSecure); + *wasSecureFlagAnInt = true; + result = (value != 0); + break; + } + + case CArchMiscWindows::kSTRING: { + std::string value = + CArchMiscWindows::readValueString(hkey, isSecure); + *wasSecureFlagAnInt = false; + result = (value != "0"); + break; + } + } + + CArchMiscWindows::closeKey(hkey); + return result; +} diff --git a/lib/platform/CMSWindowsScreenSaver.h b/lib/platform/CMSWindowsScreenSaver.h new file mode 100644 index 00000000..bb43c703 --- /dev/null +++ b/lib/platform/CMSWindowsScreenSaver.h @@ -0,0 +1,92 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSSCREENSAVER_H +#define CMSWINDOWSSCREENSAVER_H + +#include "IScreenSaver.h" +#include "CString.h" +#define WIN32_LEAN_AND_MEAN +#include + +class CThread; + +//! Microsoft windows screen saver implementation +class CMSWindowsScreenSaver : public IScreenSaver { +public: + CMSWindowsScreenSaver(); + virtual ~CMSWindowsScreenSaver(); + + //! @name manipulators + //@{ + + //! Check if screen saver started + /*! + Check if the screen saver really started. Returns false if it + hasn't, true otherwise. When the screen saver stops, \c msg will + be posted to the current thread's message queue with the given + parameters. + */ + bool checkStarted(UINT msg, WPARAM, LPARAM); + + //@} + + // IScreenSaver overrides + virtual void enable(); + virtual void disable(); + virtual void activate(); + virtual void deactivate(); + virtual bool isActive() const; + +private: + class CFindScreenSaverInfo { + public: + HDESK m_desktop; + HWND m_window; + }; + + static BOOL CALLBACK findScreenSaverFunc(HWND hwnd, LPARAM lParam); + static BOOL CALLBACK killScreenSaverFunc(HWND hwnd, LPARAM lParam); + + DWORD findScreenSaver(); + void watchDesktop(); + void watchProcess(HANDLE process); + void unwatchProcess(); + void watchDesktopThread(void*); + void watchProcessThread(void*); + + void setSecure(bool secure, bool saveSecureAsInt); + bool isSecure(bool* wasSecureAnInt) const; + +private: + bool m_is95Family; + bool m_is95; + bool m_isNT; + BOOL m_wasEnabled; + bool m_wasSecure; + bool m_wasSecureAnInt; + + HANDLE m_process; + CThread* m_watch; + DWORD m_threadID; + UINT m_msg; + WPARAM m_wParam; + LPARAM m_lParam; + + // checkActive state. true if the screen saver is being watched + // for deactivation (and is therefore active). + bool m_active; +}; + +#endif diff --git a/lib/platform/CMSWindowsUtil.cpp b/lib/platform/CMSWindowsUtil.cpp new file mode 100644 index 00000000..4b3e3f4c --- /dev/null +++ b/lib/platform/CMSWindowsUtil.cpp @@ -0,0 +1,75 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CMSWindowsUtil.h" +#include "CStringUtil.h" +#include + +// +// CMSWindowsUtil +// + +CString +CMSWindowsUtil::getString(HINSTANCE instance, DWORD id) +{ + char buffer[1024]; + int size = static_cast(sizeof(buffer) / sizeof(buffer[0])); + char* msg = buffer; + + // load string + int n = LoadString(instance, id, msg, size); + msg[n] = '\0'; + if (n < size) { + return msg; + } + + // not enough buffer space. keep trying larger buffers until + // we get the whole string. + msg = NULL; + do { + size <<= 1; + delete[] msg; + char* msg = new char[size]; + n = LoadString(instance, id, msg, size); + } while (n == size); + msg[n] = '\0'; + + CString result(msg); + delete[] msg; + return result; +} + +CString +CMSWindowsUtil::getErrorString(HINSTANCE hinstance, DWORD error, DWORD id) +{ + char* buffer; + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM, + 0, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&buffer, + 0, + NULL) == 0) { + CString errorString = CStringUtil::print("%d", error); + return CStringUtil::format(getString(hinstance, id).c_str(), + errorString.c_str()); + } + else { + CString result(buffer); + LocalFree(buffer); + return result; + } +} diff --git a/lib/platform/CMSWindowsUtil.h b/lib/platform/CMSWindowsUtil.h new file mode 100644 index 00000000..5c4d14f5 --- /dev/null +++ b/lib/platform/CMSWindowsUtil.h @@ -0,0 +1,38 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSUTIL_H +#define CMSWINDOWSUTIL_H + +#include "CString.h" +#define WINDOWS_LEAN_AND_MEAN +#include + +class CMSWindowsUtil { +public: + //! Get message string + /*! + Gets a string for \p id from the string table of \p instance. + */ + static CString getString(HINSTANCE instance, DWORD id); + + //! Get error string + /*! + Gets a system error message for \p error. If the error cannot be + found return the string for \p id, replacing ${1} with \p error. + */ + static CString getErrorString(HINSTANCE, DWORD error, DWORD id); +}; + +#endif diff --git a/lib/platform/COSXClipboard.cpp b/lib/platform/COSXClipboard.cpp new file mode 100644 index 00000000..7d39eaad --- /dev/null +++ b/lib/platform/COSXClipboard.cpp @@ -0,0 +1,220 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "COSXClipboard.h" +#include "COSXClipboardUTF16Converter.h" +#include "COSXClipboardTextConverter.h" +#include "CLog.h" +#include "XArch.h" + +// +// COSXClipboard +// + +COSXClipboard::COSXClipboard() : + m_time(0), + m_scrap(NULL) +{ + m_converters.push_back(new COSXClipboardUTF16Converter); + m_converters.push_back(new COSXClipboardTextConverter); +} + +COSXClipboard::~COSXClipboard() +{ + clearConverters(); +} + +bool +COSXClipboard::empty() +{ + LOG((CLOG_DEBUG "empty clipboard")); + assert(m_scrap != NULL); + + OSStatus err = ClearScrap(&m_scrap); + if (err != noErr) { + LOG((CLOG_DEBUG "failed to grab clipboard")); + return false; + } + + // we own the clipboard + err = PutScrapFlavor( + m_scrap, + getOwnershipFlavor(), + kScrapFlavorMaskNone, + 0, + 0); + if (err != noErr) { + LOG((CLOG_DEBUG "failed to grab clipboard")); + return false; + } + + return true; +} + +void +COSXClipboard::add(EFormat format, const CString & data) +{ + LOG((CLOG_DEBUG "add %d bytes to clipboard format: %d", data.size(), format)); + + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + + IOSXClipboardConverter* converter = *index; + + // skip converters for other formats + if (converter->getFormat() == format) { + CString osXData = converter->fromIClipboard(data); + ScrapFlavorType flavorType = converter->getOSXFormat(); + + PutScrapFlavor( + m_scrap, + flavorType, + kScrapFlavorMaskNone, + osXData.size(), + osXData.data()); + } + } +} + +bool +COSXClipboard::open(Time time) const +{ + LOG((CLOG_DEBUG "open clipboard")); + m_time = time; + OSStatus err = GetCurrentScrap(&m_scrap); + return (err == noErr); +} + +void +COSXClipboard::close() const +{ + LOG((CLOG_DEBUG "close clipboard")); + m_scrap = NULL; +} + +IClipboard::Time +COSXClipboard::getTime() const +{ + return m_time; +} + +bool +COSXClipboard::has(EFormat format) const +{ + assert(m_scrap != NULL); + + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IOSXClipboardConverter* converter = *index; + if (converter->getFormat() == format) { + ScrapFlavorFlags flags; + ScrapFlavorType type = converter->getOSXFormat(); + + if (GetScrapFlavorFlags(m_scrap, type, &flags) == noErr) { + return true; + } + } + } + + return false; +} + +CString +COSXClipboard::get(EFormat format) const +{ + CString result; + + // find the converter for the first clipboard format we can handle + IOSXClipboardConverter* converter = NULL; + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + converter = *index; + + ScrapFlavorFlags flags; + ScrapFlavorType type = converter->getOSXFormat(); + + if (converter->getFormat() == format && + GetScrapFlavorFlags(m_scrap, type, &flags) == noErr) { + break; + } + converter = NULL; + } + + // if no converter then we don't recognize any formats + if (converter == NULL) { + return result; + } + + // get the clipboard data. + char* buffer = NULL; + try { + Size flavorSize; + OSStatus err = GetScrapFlavorSize(m_scrap, + converter->getOSXFormat(), &flavorSize); + if (err != noErr) { + throw err; + } + + buffer = new char[flavorSize]; + if (buffer == NULL) { + throw memFullErr; + } + + err = GetScrapFlavorData(m_scrap, + converter->getOSXFormat(), &flavorSize, buffer); + if (err != noErr) { + throw err; + } + + result = CString(buffer, flavorSize); + } + catch (OSStatus err) { + LOG((CLOG_DEBUG "exception thrown in COSXClipboard::get MacError (%d)", err)); + } + catch (...) { + LOG((CLOG_DEBUG "unknown exception in COSXClipboard::get")); + RETHROW_XTHREAD + } + delete[] buffer; + + return converter->toIClipboard(result); +} + +void +COSXClipboard::clearConverters() +{ + for (ConverterList::iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + delete *index; + } + m_converters.clear(); +} + +bool +COSXClipboard::isOwnedBySynergy() +{ + ScrapFlavorFlags flags; + ScrapRef scrap; + OSStatus err = GetCurrentScrap(&scrap); + if (err == noErr) { + err = GetScrapFlavorFlags(scrap, getOwnershipFlavor() , &flags); + } + return (err == noErr); +} + +ScrapFlavorType +COSXClipboard::getOwnershipFlavor() +{ + return 'Syne'; +} diff --git a/lib/platform/COSXClipboard.h b/lib/platform/COSXClipboard.h new file mode 100644 index 00000000..19f95ec7 --- /dev/null +++ b/lib/platform/COSXClipboard.h @@ -0,0 +1,94 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXCLIPBOARD_H +#define COSXCLIPBOARD_H + +#include "IClipboard.h" +#include +#include + +class IOSXClipboardConverter; + +//! OS X clipboard implementation +class COSXClipboard : public IClipboard { +public: + COSXClipboard(); + virtual ~COSXClipboard(); + + //! Test if clipboard is owned by synergy + static bool isOwnedBySynergy(); + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const CString& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual CString get(EFormat) const; + +private: + void clearConverters(); + static ScrapFlavorType + getOwnershipFlavor(); + +private: + typedef std::vector ConverterList; + + mutable Time m_time; + ConverterList m_converters; + mutable ScrapRef m_scrap; +}; + +//! Clipboard format converter interface +/*! +This interface defines the methods common to all Scrap book format +*/ +class IOSXClipboardConverter : public IInterface { +public: + //! @name accessors + //@{ + + //! Get clipboard format + /*! + Return the clipboard format this object converts from/to. + */ + virtual IClipboard::EFormat + getFormat() const = 0; + + //! returns the scrap flavor type that this object converts from/to + virtual ScrapFlavorType + getOSXFormat() const = 0; + + //! Convert from IClipboard format + /*! + Convert from the IClipboard format to the Carbon scrap format. + The input data must be in the IClipboard format returned by + getFormat(). The return data will be in the scrap + format returned by getOSXFormat(). + */ + virtual CString fromIClipboard(const CString&) const = 0; + + //! Convert to IClipboard format + /*! + Convert from the carbon scrap format to the IClipboard format + (i.e., the reverse of fromIClipboard()). + */ + virtual CString toIClipboard(const CString&) const = 0; + + //@} +}; + +#endif diff --git a/lib/platform/COSXClipboardAnyTextConverter.cpp b/lib/platform/COSXClipboardAnyTextConverter.cpp new file mode 100644 index 00000000..67fc3029 --- /dev/null +++ b/lib/platform/COSXClipboardAnyTextConverter.cpp @@ -0,0 +1,85 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "COSXClipboardAnyTextConverter.h" +#include + +// +// COSXClipboardAnyTextConverter +// + +COSXClipboardAnyTextConverter::COSXClipboardAnyTextConverter() +{ + // do nothing +} + +COSXClipboardAnyTextConverter::~COSXClipboardAnyTextConverter() +{ + // do nothing +} + +IClipboard::EFormat +COSXClipboardAnyTextConverter::getFormat() const +{ + return IClipboard::kText; +} + +CString +COSXClipboardAnyTextConverter::fromIClipboard(const CString& data) const +{ + // convert linefeeds and then convert to desired encoding + return doFromIClipboard(convertLinefeedToMacOS(data)); +} + +CString +COSXClipboardAnyTextConverter::toIClipboard(const CString& data) const +{ + // convert text then newlines + return convertLinefeedToUnix(doToIClipboard(data)); +} + +static +bool +isLF(char ch) +{ + return (ch == '\n'); +} + +static +bool +isCR(char ch) +{ + return (ch == '\r'); +} + +CString +COSXClipboardAnyTextConverter::convertLinefeedToMacOS(const CString& src) +{ + // note -- we assume src is a valid UTF-8 string + CString copy = src; + + std::replace_if(copy.begin(), copy.end(), isLF, '\r'); + + return copy; +} + +CString +COSXClipboardAnyTextConverter::convertLinefeedToUnix(const CString& src) +{ + CString copy = src; + + std::replace_if(copy.begin(), copy.end(), isCR, '\n'); + + return copy; +} diff --git a/lib/platform/COSXClipboardAnyTextConverter.h b/lib/platform/COSXClipboardAnyTextConverter.h new file mode 100644 index 00000000..5d0b79f3 --- /dev/null +++ b/lib/platform/COSXClipboardAnyTextConverter.h @@ -0,0 +1,52 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXCLIPBOARDANYTEXTCONVERTER_H +#define COSXCLIPBOARDANYTEXTCONVERTER_H + +#include "COSXClipboard.h" + +//! Convert to/from some text encoding +class COSXClipboardAnyTextConverter : public IOSXClipboardConverter { +public: + COSXClipboardAnyTextConverter(); + virtual ~COSXClipboardAnyTextConverter(); + + // IOSXClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual ScrapFlavorType + getOSXFormat() const = 0; + virtual CString fromIClipboard(const CString &) const; + virtual CString toIClipboard(const CString &) const; + +protected: + //! Convert from IClipboard format + /*! + Do UTF-8 conversion and linefeed conversion. + */ + virtual CString doFromIClipboard(const CString&) const = 0; + + //! Convert to IClipboard format + /*! + Do UTF-8 conversion and Linefeed conversion. + */ + virtual CString doToIClipboard(const CString&) const = 0; + +private: + static CString convertLinefeedToMacOS(const CString&); + static CString convertLinefeedToUnix(const CString&); +}; + +#endif diff --git a/lib/platform/COSXClipboardTextConverter.cpp b/lib/platform/COSXClipboardTextConverter.cpp new file mode 100644 index 00000000..bcbc228e --- /dev/null +++ b/lib/platform/COSXClipboardTextConverter.cpp @@ -0,0 +1,88 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "COSXClipboardTextConverter.h" +#include "CUnicode.h" + +// +// COSXClipboardTextConverter +// + +COSXClipboardTextConverter::COSXClipboardTextConverter() +{ + // do nothing +} + +COSXClipboardTextConverter::~COSXClipboardTextConverter() +{ + // do nothing +} + +ScrapFlavorType +COSXClipboardTextConverter::getOSXFormat() const +{ + return kScrapFlavorTypeText; +} + +CString +COSXClipboardTextConverter::convertString( + const CString& data, + CFStringEncoding fromEncoding, + CFStringEncoding toEncoding) +{ + CFStringRef stringRef = + CFStringCreateWithCString(kCFAllocatorDefault, + data.c_str(), fromEncoding); + + if (stringRef == NULL) { + return CString(); + } + + CFIndex buffSize; + CFRange entireString = CFRangeMake(0, CFStringGetLength(stringRef)); + + CFStringGetBytes(stringRef, entireString, toEncoding, + 0, false, NULL, 0, &buffSize); + + char* buffer = new char[buffSize]; + + if (buffer == NULL) { + CFRelease(stringRef); + return CString(); + } + + CFStringGetBytes(stringRef, entireString, toEncoding, + 0, false, (UInt8*)buffer, buffSize, NULL); + + CString result(buffer, buffSize); + + delete[] buffer; + CFRelease(stringRef); + + return result; +} + +CString +COSXClipboardTextConverter::doFromIClipboard(const CString& data) const +{ + return convertString(data, kCFStringEncodingUTF8, + CFStringGetSystemEncoding()); +} + +CString +COSXClipboardTextConverter::doToIClipboard(const CString& data) const +{ + return convertString(data, CFStringGetSystemEncoding(), + kCFStringEncodingUTF8); +} diff --git a/lib/platform/COSXClipboardTextConverter.h b/lib/platform/COSXClipboardTextConverter.h new file mode 100644 index 00000000..2a75f4f0 --- /dev/null +++ b/lib/platform/COSXClipboardTextConverter.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXCLIPBOARDTEXTCONVERTER_H +#define COSXCLIPBOARDTEXTCONVERTER_H + +#include "COSXClipboardAnyTextConverter.h" + +//! Convert to/from locale text encoding +class COSXClipboardTextConverter : public COSXClipboardAnyTextConverter { +public: + COSXClipboardTextConverter(); + virtual ~COSXClipboardTextConverter(); + + // IOSXClipboardAnyTextConverter overrides + virtual ScrapFlavorType + getOSXFormat() const; + +protected: + // COSXClipboardAnyTextConverter overrides + virtual CString doFromIClipboard(const CString&) const; + virtual CString doToIClipboard(const CString&) const; + + // generic encoding converter + static CString convertString(const CString& data, + CFStringEncoding fromEncoding, + CFStringEncoding toEncoding); +}; + +#endif diff --git a/lib/platform/COSXClipboardUTF16Converter.cpp b/lib/platform/COSXClipboardUTF16Converter.cpp new file mode 100644 index 00000000..10693c83 --- /dev/null +++ b/lib/platform/COSXClipboardUTF16Converter.cpp @@ -0,0 +1,50 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "COSXClipboardUTF16Converter.h" +#include "CUnicode.h" + +// +// COSXClipboardUTF16Converter +// + +COSXClipboardUTF16Converter::COSXClipboardUTF16Converter() +{ + // do nothing +} + +COSXClipboardUTF16Converter::~COSXClipboardUTF16Converter() +{ + // do nothing +} + +ScrapFlavorType +COSXClipboardUTF16Converter::getOSXFormat() const +{ + return kScrapFlavorTypeUnicode; +} + +CString +COSXClipboardUTF16Converter::doFromIClipboard(const CString& data) const +{ + // convert and add nul terminator + return CUnicode::UTF8ToUTF16(data); +} + +CString +COSXClipboardUTF16Converter::doToIClipboard(const CString& data) const +{ + // convert and strip nul terminator + return CUnicode::UTF16ToUTF8(data); +} diff --git a/lib/platform/COSXClipboardUTF16Converter.h b/lib/platform/COSXClipboardUTF16Converter.h new file mode 100644 index 00000000..1499a7ed --- /dev/null +++ b/lib/platform/COSXClipboardUTF16Converter.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXCLIPBOARDUTF16CONVERTER_H +#define COSXCLIPBOARDUTF16CONVERTER_H + +#include "COSXClipboardAnyTextConverter.h" + +//! Convert to/from UTF-16 encoding +class COSXClipboardUTF16Converter : public COSXClipboardAnyTextConverter { +public: + COSXClipboardUTF16Converter(); + virtual ~COSXClipboardUTF16Converter(); + + // IOSXClipboardAnyTextConverter overrides + virtual ScrapFlavorType + getOSXFormat() const; + +protected: + // COSXClipboardAnyTextConverter overrides + virtual CString doFromIClipboard(const CString&) const; + virtual CString doToIClipboard(const CString&) const; +}; + +#endif diff --git a/lib/platform/COSXEventQueueBuffer.cpp b/lib/platform/COSXEventQueueBuffer.cpp new file mode 100644 index 00000000..5bd0d747 --- /dev/null +++ b/lib/platform/COSXEventQueueBuffer.cpp @@ -0,0 +1,124 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "COSXEventQueueBuffer.h" +#include "CEvent.h" +#include "IEventQueue.h" + +// +// CEventQueueTimer +// + +class CEventQueueTimer { }; + +// +// COSXEventQueueBuffer +// + +COSXEventQueueBuffer::COSXEventQueueBuffer() : + m_event(NULL) +{ + // do nothing +} + +COSXEventQueueBuffer::~COSXEventQueueBuffer() +{ + // release the last event + if (m_event != NULL) { + ReleaseEvent(m_event); + } +} + +void +COSXEventQueueBuffer::waitForEvent(double timeout) +{ + EventRef event; + ReceiveNextEvent(0, NULL, timeout, false, &event); +} + +IEventQueueBuffer::Type +COSXEventQueueBuffer::getEvent(CEvent& event, UInt32& dataID) +{ + // release the previous event + if (m_event != NULL) { + ReleaseEvent(m_event); + m_event = NULL; + } + + // get the next event + OSStatus error = ReceiveNextEvent(0, NULL, 0.0, true, &m_event); + + // handle the event + if (error == eventLoopQuitErr) { + event = CEvent(CEvent::kQuit); + return kSystem; + } + else if (error != noErr) { + return kNone; + } + else { + UInt32 eventClass = GetEventClass(m_event); + switch (eventClass) { + case 'Syne': + dataID = GetEventKind(m_event); + return kUser; + + default: + event = CEvent(CEvent::kSystem, + IEventQueue::getSystemTarget(), &m_event); + return kSystem; + } + } +} + +bool +COSXEventQueueBuffer::addEvent(UInt32 dataID) +{ + EventRef event; + OSStatus error = CreateEvent( + kCFAllocatorDefault, + 'Syne', + dataID, + 0, + kEventAttributeNone, + &event); + + if (error == noErr) { + error = PostEventToQueue(GetMainEventQueue(), event, + kEventPriorityStandard); + ReleaseEvent(event); + } + + return (error == noErr); +} + +bool +COSXEventQueueBuffer::isEmpty() const +{ + EventRef event; + OSStatus status = ReceiveNextEvent(0, NULL, 0.0, false, &event); + return (status == eventLoopTimedOutErr); +} + +CEventQueueTimer* +COSXEventQueueBuffer::newTimer(double, bool) const +{ + return new CEventQueueTimer; +} + +void +COSXEventQueueBuffer::deleteTimer(CEventQueueTimer* timer) const +{ + delete timer; +} diff --git a/lib/platform/COSXEventQueueBuffer.h b/lib/platform/COSXEventQueueBuffer.h new file mode 100644 index 00000000..659b5940 --- /dev/null +++ b/lib/platform/COSXEventQueueBuffer.h @@ -0,0 +1,40 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXEVENTQUEUEBUFFER_H +#define COSXEVENTQUEUEBUFFER_H + +#include "IEventQueueBuffer.h" +#include + +//! Event queue buffer for OS X +class COSXEventQueueBuffer : public IEventQueueBuffer { +public: + COSXEventQueueBuffer(); + virtual ~COSXEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void waitForEvent(double timeout); + virtual Type getEvent(CEvent& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(CEventQueueTimer*) const; + +private: + EventRef m_event; +}; + +#endif diff --git a/lib/platform/COSXKeyState.cpp b/lib/platform/COSXKeyState.cpp new file mode 100644 index 00000000..e904d5e4 --- /dev/null +++ b/lib/platform/COSXKeyState.cpp @@ -0,0 +1,1237 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "COSXKeyState.h" +#include "CLog.h" +#include "CArch.h" + +// Hardcoded virtual key table. Oddly, Apple doesn't document the +// meaning of virtual key codes. The whole point of *virtual* key +// codes is to make them hardware independent so these codes should +// be constant across OS versions and hardware. Yet they don't +// tell us what codes map to what keys so we have to figure it out +// for ourselves. +// +// Note that some virtual keys codes appear more than once. The +// first instance of a virtual key code maps to the KeyID that we +// want to generate for that code. The others are for mapping +// different KeyIDs to a single key code. +static const UInt32 s_shiftVK = 56; +static const UInt32 s_controlVK = 59; +static const UInt32 s_altVK = 55; +static const UInt32 s_superVK = 58; +static const UInt32 s_capsLockVK = 57; +static const UInt32 s_numLockVK = 71; +static const UInt32 s_osxNumLock = 1 << 16; +struct CKeyEntry { +public: + KeyID m_keyID; + UInt32 m_virtualKey; +}; +static const CKeyEntry s_controlKeys[] = { + // cursor keys. if we don't do this we'll may still get these from + // the keyboard resource but they may not correspond to the arrow + // keys. + { kKeyLeft, 123 }, + { kKeyRight, 124 }, + { kKeyUp, 126 }, + { kKeyDown, 125 }, + { kKeyHome, 115 }, + { kKeyEnd, 119 }, + { kKeyPageUp, 116 }, + { kKeyPageDown, 121 }, + + // function keys + { kKeyF1, 122 }, + { kKeyF2, 120 }, + { kKeyF3, 99 }, + { kKeyF4, 118 }, + { kKeyF5, 96 }, + { kKeyF6, 97 }, + { kKeyF7, 98 }, + { kKeyF8, 100 }, + { kKeyF9, 101 }, + { kKeyF10, 109 }, + { kKeyF11, 103 }, + { kKeyF12, 111 }, + { kKeyF13, 105 }, + { kKeyF14, 107 }, + { kKeyF15, 113 }, + { kKeyF16, 106 }, + + { kKeyKP_0, 82 }, + { kKeyKP_1, 83 }, + { kKeyKP_2, 84 }, + { kKeyKP_3, 85 }, + { kKeyKP_4, 86 }, + { kKeyKP_5, 87 }, + { kKeyKP_6, 88 }, + { kKeyKP_7, 89 }, + { kKeyKP_8, 91 }, + { kKeyKP_9, 92 }, + { kKeyKP_Decimal, 65 }, + { kKeyKP_Equal, 81 }, + { kKeyKP_Multiply, 67 }, + { kKeyKP_Add, 69 }, + { kKeyKP_Divide, 75 }, + { kKeyKP_Subtract, 79 }, + { kKeyKP_Enter, 76 }, + + // virtual key 110 is fn+enter and i have no idea what that's supposed + // to map to. also the enter key with numlock on is a modifier but i + // don't know which. + + // modifier keys. OS X doesn't seem to support right handed versions + // of modifier keys so we map them to the left handed versions. + { kKeyShift_L, s_shiftVK }, + { kKeyShift_R, s_shiftVK }, // 60 + { kKeyControl_L, s_controlVK }, + { kKeyControl_R, s_controlVK }, // 62 + { kKeyAlt_L, s_altVK }, + { kKeyAlt_R, s_altVK }, + { kKeySuper_L, s_superVK }, + { kKeySuper_R, s_superVK }, // 61 + { kKeyMeta_L, s_superVK }, + { kKeyMeta_R, s_superVK }, // 61 + + // toggle modifiers + { kKeyNumLock, s_numLockVK }, + { kKeyCapsLock, s_capsLockVK } +}; + + +// +// COSXKeyState +// + +COSXKeyState::COSXKeyState() : + m_deadKeyState(0) +{ + // enable input in scripts other that roman + KeyScript(smKeyEnableKybds); + + // build virtual key map + for (size_t i = 0; i < sizeof(s_controlKeys) / + sizeof(s_controlKeys[0]); ++i) { + m_virtualKeyMap[s_controlKeys[i].m_virtualKey] = + s_controlKeys[i].m_keyID; + } +} + +COSXKeyState::~COSXKeyState() +{ + // do nothing +} + +KeyModifierMask +COSXKeyState::mapModifiersFromOSX(UInt32 mask) const +{ +LOG((CLOG_DEBUG1 "mask: %04x", mask)); + // convert + KeyModifierMask outMask = 0; + if ((mask & shiftKey) != 0) { + outMask |= KeyModifierShift; + } + if ((mask & rightShiftKey) != 0) { + outMask |= KeyModifierShift; + } + if ((mask & controlKey) != 0) { + outMask |= KeyModifierControl; + } + if ((mask & rightControlKey) != 0) { + outMask |= KeyModifierControl; + } + if ((mask & cmdKey) != 0) { + outMask |= KeyModifierAlt; + } + if ((mask & optionKey) != 0) { + outMask |= KeyModifierSuper; + } + if ((mask & rightOptionKey) != 0) { + outMask |= KeyModifierSuper; + } + if ((mask & alphaLock) != 0) { + outMask |= KeyModifierCapsLock; + } + if ((mask & s_osxNumLock) != 0) { + outMask |= KeyModifierNumLock; + } + + return outMask; +} + +KeyButton +COSXKeyState::mapKeyFromEvent(CKeyIDs& ids, + KeyModifierMask* maskOut, EventRef event) const +{ + ids.clear(); + + // map modifier key + if (maskOut != NULL) { + KeyModifierMask activeMask = getActiveModifiers(); + activeMask &= ~KeyModifierAltGr; + *maskOut = activeMask; + } + + // get virtual key + UInt32 vkCode; + GetEventParameter(event, kEventParamKeyCode, typeUInt32, + NULL, sizeof(vkCode), NULL, &vkCode); + + // handle up events + UInt32 eventKind = GetEventKind(event); + if (eventKind == kEventRawKeyUp) { + // the id isn't used. we just need the same button we used on + // the key press. note that we don't use or reset the dead key + // state; up events should not affect the dead key state. + ids.push_back(kKeyNone); + return mapVirtualKeyToKeyButton(vkCode); + } + + // check for special keys + CVirtualKeyMap::const_iterator i = m_virtualKeyMap.find(vkCode); + if (i != m_virtualKeyMap.end()) { + m_deadKeyState = 0; + ids.push_back(i->second); + return mapVirtualKeyToKeyButton(vkCode); + } + + // get keyboard info + KeyboardLayoutRef keyboardLayout; + OSStatus status = KLGetCurrentKeyboardLayout(&keyboardLayout); + if (status != noErr) { + return kKeyNone; + } + + // get the event modifiers and remove the command and control + // keys. note if we used them though. + UInt32 modifiers; + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, + NULL, sizeof(modifiers), NULL, &modifiers); + static const UInt32 s_commandModifiers = + cmdKey | controlKey | rightControlKey; + bool isCommand = ((modifiers & s_commandModifiers) != 0); + modifiers &= ~s_commandModifiers; + + // if we've used a command key then we want the glyph produced without + // the option key (i.e. the base glyph). + if (isCommand) { + modifiers &= ~optionKey; + } + + // translate via uchr resource + const void* resource; + if (KLGetKeyboardLayoutProperty(keyboardLayout, + kKLuchrData, &resource) == noErr) { + // choose action + UInt16 action; + switch (eventKind) { + case kEventRawKeyDown: + action = kUCKeyActionDown; + break; + + case kEventRawKeyRepeat: + action = kUCKeyActionAutoKey; + break; + + default: + return 0; + } + + // translate key + UniCharCount count; + UniChar chars[2]; + OSStatus status = UCKeyTranslate((const UCKeyboardLayout*)resource, + vkCode & 0xffu, action, + (modifiers >> 8) & 0xffu, + LMGetKbdType(), 0, &m_deadKeyState, + sizeof(chars) / sizeof(chars[0]), &count, chars); + + // get the characters + if (status == 0) { + if (count != 0 || m_deadKeyState == 0) { + m_deadKeyState = 0; + for (UniCharCount i = 0; i < count; ++i) { + ids.push_back(CKeyResource::unicharToKeyID(chars[i])); + } + adjustAltGrModifier(ids, maskOut, isCommand); + return mapVirtualKeyToKeyButton(vkCode); + } + return 0; + } + } + + // translate via KCHR resource + if (KLGetKeyboardLayoutProperty(keyboardLayout, + kKLKCHRData, &resource) == noErr) { + // build keycode + UInt16 keycode = + static_cast((modifiers & 0xff00u) | (vkCode & 0x00ffu)); + + // translate key + UInt32 result = KeyTranslate(resource, keycode, &m_deadKeyState); + + // get the characters + UInt8 c1 = static_cast((result >> 16) & 0xffu); + UInt8 c2 = static_cast( result & 0xffu); + if (c2 != 0) { + m_deadKeyState = 0; + if (c1 != 0) { + ids.push_back(CKeyResource::getKeyID(c1)); + } + ids.push_back(CKeyResource::getKeyID(c2)); + adjustAltGrModifier(ids, maskOut, isCommand); + return mapVirtualKeyToKeyButton(vkCode); + } + } + + return 0; +} + +bool +COSXKeyState::fakeCtrlAltDel() +{ + // pass keys through unchanged + return false; +} + +KeyModifierMask +COSXKeyState::pollActiveModifiers() const +{ + return mapModifiersFromOSX(GetCurrentKeyModifiers()); +} + +SInt32 +COSXKeyState::pollActiveGroup() const +{ + KeyboardLayoutRef keyboardLayout; + OSStatus status = KLGetCurrentKeyboardLayout(&keyboardLayout); + if (status == noErr) { + GroupMap::const_iterator i = m_groupMap.find(keyboardLayout); + if (i != m_groupMap.end()) { + return i->second; + } + } + return 0; +} + +void +COSXKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + KeyMap km; + GetKeys(km); + const UInt8* m = reinterpret_cast(km); + for (UInt32 i = 0; i < 16; ++i) { + for (UInt32 j = 0; j < 8; ++j) { + if ((m[i] & (1u << j)) != 0) { + pressedKeys.insert(mapVirtualKeyToKeyButton(8 * i + j)); + } + } + } +} + +void +COSXKeyState::getKeyMap(CKeyMap& keyMap) +{ + // update keyboard groups + if (getGroups(m_groups)) { + m_groupMap.clear(); + SInt32 numGroups = (SInt32)m_groups.size(); + for (SInt32 g = 0; g < numGroups; ++g) { + m_groupMap[m_groups[g]] = g; + } + } + + UInt32 keyboardType = LMGetKbdType(); + for (SInt32 g = 0, n = (SInt32)m_groups.size(); g < n; ++g) { + // add special keys + getKeyMapForSpecialKeys(keyMap, g); + + // add regular keys + + // try uchr resource first + const void* resource; + if (KLGetKeyboardLayoutProperty(m_groups[g], + kKLuchrData, &resource) == noErr) { + CUCHRKeyResource uchr(resource, keyboardType); + if (uchr.isValid()) { + LOG((CLOG_DEBUG1 "using uchr resource for group %d", g)); + getKeyMap(keyMap, g, uchr); + continue; + } + } + + // try KCHR resource + if (KLGetKeyboardLayoutProperty(m_groups[g], + kKLKCHRData, &resource) == noErr) { + CKCHRKeyResource kchr(resource); + if (kchr.isValid()) { + LOG((CLOG_DEBUG1 "using KCHR resource for group %d", g)); + getKeyMap(keyMap, g, kchr); + continue; + } + } + + LOG((CLOG_DEBUG1 "no keyboard resource for group %d", g)); + } +} + +void +COSXKeyState::fakeKey(const Keystroke& keystroke) +{ + switch (keystroke.m_type) { + case Keystroke::kButton: + LOG((CLOG_DEBUG1 " %03x (%08x) %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, keystroke.m_data.m_button.m_press ? "down" : "up")); + + // let system figure out character for us + CGPostKeyboardEvent(0, mapKeyButtonToVirtualKey( + keystroke.m_data.m_button.m_button), + keystroke.m_data.m_button.m_press); + + // add a delay if client data isn't zero + if (keystroke.m_data.m_button.m_client) { + ARCH->sleep(0.01); + } + break; + + case Keystroke::kGroup: + if (keystroke.m_data.m_group.m_absolute) { + LOG((CLOG_DEBUG1 " group %d", keystroke.m_data.m_group.m_group)); + setGroup(keystroke.m_data.m_group.m_group); + } + else { + LOG((CLOG_DEBUG1 " group %+d", keystroke.m_data.m_group.m_group)); + setGroup(getEffectiveGroup(pollActiveGroup(), + keystroke.m_data.m_group.m_group)); + } + break; + } +} + +void +COSXKeyState::getKeyMapForSpecialKeys(CKeyMap& keyMap, SInt32 group) const +{ + // special keys are insensitive to modifers and none are dead keys + CKeyMap::KeyItem item; + for (size_t i = 0; i < sizeof(s_controlKeys) / + sizeof(s_controlKeys[0]); ++i) { + const CKeyEntry& entry = s_controlKeys[i]; + item.m_id = entry.m_keyID; + item.m_group = group; + item.m_button = mapVirtualKeyToKeyButton(entry.m_virtualKey); + item.m_required = 0; + item.m_sensitive = 0; + item.m_dead = false; + item.m_client = 0; + CKeyMap::initModifierKey(item); + keyMap.addKeyEntry(item); + + if (item.m_lock) { + // all locking keys are half duplex on OS X + keyMap.addHalfDuplexButton(item.m_button); + } + } + + // note: we don't special case the number pad keys. querying the + // mac keyboard returns the non-keypad version of those keys but + // a CKeyState always provides a mapping from keypad keys to + // non-keypad keys so we'll be able to generate the characters + // anyway. +} + +bool +COSXKeyState::getKeyMap(CKeyMap& keyMap, + SInt32 group, const CKeyResource& r) const +{ + if (!r.isValid()) { + return false; + } + + // space for all possible modifier combinations + std::vector modifiers(r.getNumModifierCombinations()); + + // make space for the keys that any single button can synthesize + std::vector > buttonKeys(r.getNumTables()); + + // iterate over each button + CKeyMap::KeyItem item; + for (UInt32 i = 0; i < r.getNumButtons(); ++i) { + item.m_button = mapVirtualKeyToKeyButton(i); + + // the KeyIDs we've already handled + std::set keys; + + // convert the entry in each table for this button to a KeyID + for (UInt32 j = 0; j < r.getNumTables(); ++j) { + buttonKeys[j].first = r.getKey(j, i); + buttonKeys[j].second = CKeyMap::isDeadKey(buttonKeys[j].first); + } + + // iterate over each character table + for (UInt32 j = 0; j < r.getNumTables(); ++j) { + // get the KeyID for the button/table + KeyID id = buttonKeys[j].first; + if (id == kKeyNone) { + continue; + } + + // if we've already handled the KeyID in the table then + // move on to the next table + if (keys.count(id) > 0) { + continue; + } + keys.insert(id); + + // prepare item. the client state is 1 for dead keys. + item.m_id = id; + item.m_group = group; + item.m_dead = buttonKeys[j].second; + item.m_client = buttonKeys[j].second ? 1 : 0; + CKeyMap::initModifierKey(item); + if (item.m_lock) { + // all locking keys are half duplex on OS X + keyMap.addHalfDuplexButton(i); + } + + // collect the tables that map to the same KeyID. we know it + // can't be any earlier tables because of the check above. + std::set tables; + tables.insert(static_cast(j)); + for (UInt32 k = j + 1; k < r.getNumTables(); ++k) { + if (buttonKeys[k].first == id) { + tables.insert(static_cast(k)); + } + } + + // collect the modifier combinations that map to any of the + // tables we just collected + for (UInt32 k = 0; k < r.getNumModifierCombinations(); ++k) { + modifiers[k] = (tables.count(r.getTableForModifier(k)) > 0); + } + + // figure out which modifiers the key is sensitive to. the + // key is insensitive to a modifier if for every modifier mask + // with the modifier bit unset in the modifiers we also find + // the same mask with the bit set. + // + // we ignore a few modifiers that we know aren't important + // for generating characters. in fact, we want to ignore any + // characters generated by the control key. we don't map + // those and instead expect the control modifier plus a key. + UInt32 sensitive = 0; + for (UInt32 k = 0; (1u << k) < + r.getNumModifierCombinations(); ++k) { + UInt32 bit = (1u << k); + if ((bit << 8) == cmdKey || + (bit << 8) == controlKey || + (bit << 8) == rightControlKey) { + continue; + } + for (UInt32 m = 0; m < r.getNumModifierCombinations(); ++m) { + if (modifiers[m] != modifiers[m ^ bit]) { + sensitive |= bit; + break; + } + } + } + + // find each required modifier mask. the key can be synthesized + // using any of the masks. + std::set required; + for (UInt32 k = 0; k < r.getNumModifierCombinations(); ++k) { + if ((k & sensitive) == k && modifiers[k & sensitive]) { + required.insert(k); + } + } + + // now add a key entry for each key/required modifier pair. + item.m_sensitive = mapModifiersFromOSX(sensitive << 8); + for (std::set::iterator k = required.begin(); + k != required.end(); ++k) { + item.m_required = mapModifiersFromOSX(*k << 8); + keyMap.addKeyEntry(item); + } + } + } + + return true; +} + +bool +COSXKeyState::mapSynergyHotKeyToMac(KeyID key, KeyModifierMask mask, + UInt32 &macVirtualKey, UInt32 &macModifierMask) const +{ + // look up button for key + KeyButton button = getButton(key, pollActiveGroup()); + if (button == 0 && key != kKeyNone) { + return false; + } + macVirtualKey = mapKeyButtonToVirtualKey(button); + + // calculate modifier mask + macModifierMask = 0; + if ((mask & KeyModifierShift) != 0) { + macModifierMask |= shiftKey; + } + if ((mask & KeyModifierControl) != 0) { + macModifierMask |= controlKey; + } + if ((mask & KeyModifierAlt) != 0) { + macModifierMask |= cmdKey; + } + if ((mask & KeyModifierSuper) != 0) { + macModifierMask |= optionKey; + } + if ((mask & KeyModifierCapsLock) != 0) { + macModifierMask |= alphaLock; + } + if ((mask & KeyModifierNumLock) != 0) { + macModifierMask |= s_osxNumLock; + } + + return true; +} + +void +COSXKeyState::handleModifierKeys(void* target, + KeyModifierMask oldMask, KeyModifierMask newMask) +{ + // compute changed modifiers + KeyModifierMask changed = (oldMask ^ newMask); + + // synthesize changed modifier keys + if ((changed & KeyModifierShift) != 0) { + handleModifierKey(target, s_shiftVK, kKeyShift_L, + (newMask & KeyModifierShift) != 0, newMask); + } + if ((changed & KeyModifierControl) != 0) { + handleModifierKey(target, s_controlVK, kKeyControl_L, + (newMask & KeyModifierControl) != 0, newMask); + } + if ((changed & KeyModifierAlt) != 0) { + handleModifierKey(target, s_altVK, kKeyAlt_L, + (newMask & KeyModifierAlt) != 0, newMask); + } + if ((changed & KeyModifierSuper) != 0) { + handleModifierKey(target, s_superVK, kKeySuper_L, + (newMask & KeyModifierSuper) != 0, newMask); + } + if ((changed & KeyModifierCapsLock) != 0) { + handleModifierKey(target, s_capsLockVK, kKeyCapsLock, + (newMask & KeyModifierCapsLock) != 0, newMask); + } + if ((changed & KeyModifierNumLock) != 0) { + handleModifierKey(target, s_numLockVK, kKeyNumLock, + (newMask & KeyModifierNumLock) != 0, newMask); + } +} + +void +COSXKeyState::handleModifierKey(void* target, + UInt32 virtualKey, KeyID id, + bool down, KeyModifierMask newMask) +{ + KeyButton button = mapVirtualKeyToKeyButton(virtualKey); + onKey(button, down, newMask); + sendKeyEvent(target, down, false, id, newMask, 0, button); +} + +bool +COSXKeyState::getGroups(GroupList& groups) const +{ + // get number of layouts + CFIndex n; + OSStatus status = KLGetKeyboardLayoutCount(&n); + if (status != noErr) { + LOG((CLOG_DEBUG1 "can't get keyboard layouts")); + return false; + } + + // get each layout + groups.clear(); + for (CFIndex i = 0; i < n; ++i) { + KeyboardLayoutRef keyboardLayout; + status = KLGetKeyboardLayoutAtIndex(i, &keyboardLayout); + if (status == noErr) { + groups.push_back(keyboardLayout); + } + } + return true; +} + +void +COSXKeyState::setGroup(SInt32 group) +{ + KLSetCurrentKeyboardLayout(m_groups[group]); +} + +void +COSXKeyState::checkKeyboardLayout() +{ + // XXX -- should call this when notified that groups have changed. + // if no notification for that then we should poll. + GroupList groups; + if (getGroups(groups) && groups != m_groups) { + updateKeyMap(); + updateKeyState(); + } +} + +void +COSXKeyState::adjustAltGrModifier(const CKeyIDs& ids, + KeyModifierMask* mask, bool isCommand) const +{ + if (!isCommand) { + for (CKeyIDs::const_iterator i = ids.begin(); i != ids.end(); ++i) { + KeyID id = *i; + if (id != kKeyNone && + ((id < 0xe000u || id > 0xefffu) || + (id >= kKeyKP_Equal && id <= kKeyKP_9))) { + *mask |= KeyModifierAltGr; + return; + } + } + } +} + +KeyButton +COSXKeyState::mapVirtualKeyToKeyButton(UInt32 keyCode) +{ + // 'A' maps to 0 so shift every id + return static_cast(keyCode + KeyButtonOffset); +} + +UInt32 +COSXKeyState::mapKeyButtonToVirtualKey(KeyButton keyButton) +{ + return static_cast(keyButton - KeyButtonOffset); +} + + +// +// COSXKeyState::CKeyResource +// + +KeyID +COSXKeyState::CKeyResource::getKeyID(UInt8 c) +{ + if (c == 0) { + return kKeyNone; + } + else if (c >= 32 && c < 127) { + // ASCII + return static_cast(c); + } + else { + // handle special keys + switch (c) { + case 0x01: + return kKeyHome; + + case 0x02: + return kKeyKP_Enter; + + case 0x03: + return kKeyKP_Enter; + + case 0x04: + return kKeyEnd; + + case 0x05: + return kKeyHelp; + + case 0x08: + return kKeyBackSpace; + + case 0x09: + return kKeyTab; + + case 0x0b: + return kKeyPageUp; + + case 0x0c: + return kKeyPageDown; + + case 0x0d: + return kKeyReturn; + + case 0x10: + // OS X maps all the function keys (F1, etc) to this one key. + // we can't determine the right key here so we have to do it + // some other way. + return kKeyNone; + + case 0x1b: + return kKeyEscape; + + case 0x1c: + return kKeyLeft; + + case 0x1d: + return kKeyRight; + + case 0x1e: + return kKeyUp; + + case 0x1f: + return kKeyDown; + + case 0x7f: + return kKeyDelete; + + case 0x06: + case 0x07: + case 0x0a: + case 0x0e: + case 0x0f: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1a: + // discard other control characters + return kKeyNone; + + default: + // not special or unknown + break; + } + + // create string with character + char str[2]; + str[0] = static_cast(c); + str[1] = 0; + + // convert to unicode + CFStringRef cfString = + CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, + str, GetScriptManagerVariable(smKeyScript), + kCFAllocatorNull); + + // sometimes CFStringCreate...() returns NULL (e.g. Apple Korean + // encoding with char value 214). if it did then make no key, + // otherwise CFStringCreateMutableCopy() will crash. + if (cfString == NULL) { + return kKeyNone; + } + + // convert to precomposed + CFMutableStringRef mcfString = + CFStringCreateMutableCopy(kCFAllocatorDefault, 0, cfString); + CFRelease(cfString); + CFStringNormalize(mcfString, kCFStringNormalizationFormC); + + // check result + int unicodeLength = CFStringGetLength(mcfString); + if (unicodeLength == 0) { + CFRelease(mcfString); + return kKeyNone; + } + if (unicodeLength > 1) { + // FIXME -- more than one character, we should handle this + CFRelease(mcfString); + return kKeyNone; + } + + // get unicode character + UniChar uc = CFStringGetCharacterAtIndex(mcfString, 0); + CFRelease(mcfString); + + // convert to KeyID + return static_cast(uc); + } +} + +KeyID +COSXKeyState::CKeyResource::unicharToKeyID(UniChar c) +{ + switch (c) { + case 3: + return kKeyKP_Enter; + + case 8: + return kKeyBackSpace; + + case 9: + return kKeyTab; + + case 13: + return kKeyReturn; + + case 27: + return kKeyEscape; + + case 127: + return kKeyDelete; + + default: + if (c < 32) { + return kKeyNone; + } + return static_cast(c); + } +} + + +// +// COSXKeyState::CKCHRKeyResource +// + +COSXKeyState::CKCHRKeyResource::CKCHRKeyResource(const void* resource) +{ + m_resource = reinterpret_cast(resource); +} + +bool +COSXKeyState::CKCHRKeyResource::isValid() const +{ + return (m_resource != NULL); +} + +UInt32 +COSXKeyState::CKCHRKeyResource::getNumModifierCombinations() const +{ + // only 32 (not 256) because the righthanded modifier bits are ignored + return 32; +} + +UInt32 +COSXKeyState::CKCHRKeyResource::getNumTables() const +{ + return m_resource->m_numTables; +} + +UInt32 +COSXKeyState::CKCHRKeyResource::getNumButtons() const +{ + return 128; +} + +UInt32 +COSXKeyState::CKCHRKeyResource::getTableForModifier(UInt32 mask) const +{ + assert(mask < getNumModifierCombinations()); + + return m_resource->m_tableSelectionIndex[mask]; +} + +KeyID +COSXKeyState::CKCHRKeyResource::getKey(UInt32 table, UInt32 button) const +{ + assert(table < getNumTables()); + assert(button < getNumButtons()); + + UInt8 c = m_resource->m_characterTables[table][button]; + if (c == 0) { + // could be a dead key + const CKCHRDeadKeys* dkp = + reinterpret_cast( + m_resource->m_characterTables[getNumTables()]); + const CKCHRDeadKeyRecord* dkr = dkp->m_records; + for (SInt16 i = 0; i < dkp->m_numRecords; ++i) { + if (dkr->m_tableIndex == table && dkr->m_virtualKey == button) { + // get the no completion entry + c = dkr->m_completion[dkr->m_numCompletions][1]; + return CKeyMap::getDeadKey(getKeyID(c)); + } + + // next table. skip all the completions and the no match + // pair to get the next table. + dkr = reinterpret_cast( + dkr->m_completion[dkr->m_numCompletions + 1]); + } + } + + return getKeyID(c); +} + + +// +// COSXKeyState::CUCHRKeyResource +// + +COSXKeyState::CUCHRKeyResource::CUCHRKeyResource(const void* resource, + UInt32 keyboardType) : + m_m(NULL), + m_cti(NULL), + m_sdi(NULL), + m_sri(NULL), + m_st(NULL) +{ + m_resource = reinterpret_cast(resource); + if (m_resource == NULL) { + return; + } + + // find the keyboard info for the current keyboard type + const UCKeyboardTypeHeader* th = NULL; + const UCKeyboardLayout* r = m_resource; + for (ItemCount i = 0; i < r->keyboardTypeCount; ++i) { + if (keyboardType >= r->keyboardTypeList[i].keyboardTypeFirst && + keyboardType <= r->keyboardTypeList[i].keyboardTypeLast) { + th = r->keyboardTypeList + i; + break; + } + if (r->keyboardTypeList[i].keyboardTypeFirst == 0) { + // found the default. use it unless we find a match. + th = r->keyboardTypeList + i; + } + } + if (th == NULL) { + // cannot find a suitable keyboard type + return; + } + + // get tables for keyboard type + const UInt8* base = reinterpret_cast(m_resource); + m_m = reinterpret_cast(base + + th->keyModifiersToTableNumOffset); + m_cti = reinterpret_cast(base + + th->keyToCharTableIndexOffset); + m_sdi = reinterpret_cast(base + + th->keySequenceDataIndexOffset); + if (th->keyStateRecordsIndexOffset != 0) { + m_sri = reinterpret_cast(base + + th->keyStateRecordsIndexOffset); + } + if (th->keyStateTerminatorsOffset != 0) { + m_st = reinterpret_cast(base + + th->keyStateTerminatorsOffset); + } + + // find the space key, but only if it can combine with dead keys. + // a dead key followed by a space yields the non-dead version of + // the dead key. + m_spaceOutput = 0xffffu; + UInt32 table = getTableForModifier(0); + for (UInt32 button = 0, n = getNumButtons(); button < n; ++button) { + KeyID id = getKey(table, button); + if (id == 0x20) { + UCKeyOutput c = + reinterpret_cast(base + + m_cti->keyToCharTableOffsets[table])[button]; + if ((c & kUCKeyOutputTestForIndexMask) == + kUCKeyOutputStateIndexMask) { + m_spaceOutput = (c & kUCKeyOutputGetIndexMask); + break; + } + } + } +} + +bool +COSXKeyState::CUCHRKeyResource::isValid() const +{ + return (m_m != NULL); +} + +UInt32 +COSXKeyState::CUCHRKeyResource::getNumModifierCombinations() const +{ + // only 32 (not 256) because the righthanded modifier bits are ignored + return 32; +} + +UInt32 +COSXKeyState::CUCHRKeyResource::getNumTables() const +{ + return m_cti->keyToCharTableCount; +} + +UInt32 +COSXKeyState::CUCHRKeyResource::getNumButtons() const +{ + return m_cti->keyToCharTableSize; +} + +UInt32 +COSXKeyState::CUCHRKeyResource::getTableForModifier(UInt32 mask) const +{ + if (mask >= m_m->modifiersCount) { + return m_m->defaultTableNum; + } + else { + return m_m->tableNum[mask]; + } +} + +KeyID +COSXKeyState::CUCHRKeyResource::getKey(UInt32 table, UInt32 button) const +{ + assert(table < getNumTables()); + assert(button < getNumButtons()); + + const UInt8* base = reinterpret_cast(m_resource); + const UCKeyOutput c = reinterpret_cast(base + + m_cti->keyToCharTableOffsets[table])[button]; + + KeySequence keys; + switch (c & kUCKeyOutputTestForIndexMask) { + case kUCKeyOutputStateIndexMask: + if (!getDeadKey(keys, c & kUCKeyOutputGetIndexMask)) { + return kKeyNone; + } + break; + + case kUCKeyOutputSequenceIndexMask: + default: + if (!addSequence(keys, c)) { + return kKeyNone; + } + break; + } + + // XXX -- no support for multiple characters + if (keys.size() != 1) { + return kKeyNone; + } + + return keys.front(); +} + +bool +COSXKeyState::CUCHRKeyResource::getDeadKey( + KeySequence& keys, UInt16 index) const +{ + if (m_sri == NULL || index >= m_sri->keyStateRecordCount) { + // XXX -- should we be using some other fallback? + return false; + } + + UInt16 state = 0; + if (!getKeyRecord(keys, index, state)) { + return false; + } + if (state == 0) { + // not a dead key + return true; + } + + // no dead keys if we couldn't find the space key + if (m_spaceOutput == 0xffffu) { + return false; + } + + // the dead key should not have put anything in the key list + if (!keys.empty()) { + return false; + } + + // get the character generated by pressing the space key after the + // dead key. if we're still in a compose state afterwards then we're + // confused so we bail. + if (!getKeyRecord(keys, m_spaceOutput, state) || state != 0) { + return false; + } + + // convert keys to their dead counterparts + for (KeySequence::iterator i = keys.begin(); i != keys.end(); ++i) { + *i = CKeyMap::getDeadKey(*i); + } + + return true; +} + +bool +COSXKeyState::CUCHRKeyResource::getKeyRecord( + KeySequence& keys, UInt16 index, UInt16& state) const +{ + const UInt8* base = reinterpret_cast(m_resource); + const UCKeyStateRecord* sr = + reinterpret_cast(base + + m_sri->keyStateRecordOffsets[index]); + const UCKeyStateEntryTerminal* kset = + reinterpret_cast(sr->stateEntryData); + + UInt16 nextState = 0; + bool found = false; + if (state == 0) { + found = true; + nextState = sr->stateZeroNextState; + if (!addSequence(keys, sr->stateZeroCharData)) { + return false; + } + } + else { + // we have a next entry + switch (sr->stateEntryFormat) { + case kUCKeyStateEntryTerminalFormat: + for (UInt16 j = 0; j < sr->stateEntryCount; ++j) { + if (kset[j].curState == state) { + if (!addSequence(keys, kset[j].charData)) { + return false; + } + nextState = 0; + found = true; + break; + } + } + break; + + case kUCKeyStateEntryRangeFormat: + // XXX -- not supported yet + break; + + default: + // XXX -- unknown format + return false; + } + } + if (!found) { + // use a terminator + if (m_st != NULL && state < m_st->keyStateTerminatorCount) { + if (!addSequence(keys, m_st->keyStateTerminators[state - 1])) { + return false; + } + } + nextState = sr->stateZeroNextState; + if (!addSequence(keys, sr->stateZeroCharData)) { + return false; + } + } + + // next + state = nextState; + + return true; +} + +bool +COSXKeyState::CUCHRKeyResource::addSequence( + KeySequence& keys, UCKeyCharSeq c) const +{ + if ((c & kUCKeyOutputTestForIndexMask) == kUCKeyOutputSequenceIndexMask) { + UInt16 index = (c & kUCKeyOutputGetIndexMask); + if (index < m_sdi->charSequenceCount && + m_sdi->charSequenceOffsets[index] != + m_sdi->charSequenceOffsets[index + 1]) { + // XXX -- sequences not supported yet + return false; + } + } + + if (c != 0xfffe && c != 0xffff) { + KeyID id = unicharToKeyID(c); + if (id != kKeyNone) { + keys.push_back(id); + } + } + + return true; +} diff --git a/lib/platform/COSXKeyState.h b/lib/platform/COSXKeyState.h new file mode 100644 index 00000000..baf69713 --- /dev/null +++ b/lib/platform/COSXKeyState.h @@ -0,0 +1,235 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXKEYSTATE_H +#define COSXKEYSTATE_H + +#include "CKeyState.h" +#include "stdmap.h" +#include "stdset.h" +#include "stdvector.h" +#include + +//! OS X key state +/*! +A key state for OS X. +*/ +class COSXKeyState : public CKeyState { +public: + typedef std::vector CKeyIDs; + + COSXKeyState(); + virtual ~COSXKeyState(); + + //! @name modifiers + //@{ + + //! Handle modifier key change + /*! + Determines which modifier keys have changed and updates the modifier + state and sends key events as appropriate. + */ + void handleModifierKeys(void* target, + KeyModifierMask oldMask, KeyModifierMask newMask); + + //@} + //! @name accessors + //@{ + + //! Convert OS X modifier mask to synergy mask + /*! + Returns the synergy modifier mask corresponding to the OS X modifier + mask in \p mask. + */ + KeyModifierMask mapModifiersFromOSX(UInt32 mask) const; + + //! Map key event to keys + /*! + Converts a key event into a sequence of KeyIDs and the shadow modifier + state to a modifier mask. The KeyIDs list, in order, the characters + generated by the key press/release. It returns the id of the button + that was pressed or released, or 0 if the button doesn't map to a known + KeyID. + */ + KeyButton mapKeyFromEvent(CKeyIDs& ids, + KeyModifierMask* maskOut, EventRef event) const; + + //! Map key and mask to native values + /*! + Calculates mac virtual key and mask for a key \p key and modifiers + \p mask. Returns \c true if the key can be mapped, \c false otherwise. + */ + bool mapSynergyHotKeyToMac(KeyID key, KeyModifierMask mask, + UInt32& macVirtualKey, + UInt32& macModifierMask) const; + + //@} + + // IKeyState overrides + virtual bool fakeCtrlAltDel(); + virtual KeyModifierMask + pollActiveModifiers() const; + virtual SInt32 pollActiveGroup() const; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const; + +protected: + // CKeyState overrides + virtual void getKeyMap(CKeyMap& keyMap); + virtual void fakeKey(const Keystroke& keystroke); + +private: + class CKeyResource; + typedef std::vector GroupList; + + // Add hard coded special keys to a CKeyMap. + void getKeyMapForSpecialKeys( + CKeyMap& keyMap, SInt32 group) const; + + // Convert keyboard resource to a key map + bool getKeyMap(CKeyMap& keyMap, + SInt32 group, const CKeyResource& r) const; + + // Get the available keyboard groups + bool getGroups(GroupList&) const; + + // Change active keyboard group to group + void setGroup(SInt32 group); + + // Check if the keyboard layout has changed and update keyboard state + // if so. + void checkKeyboardLayout(); + + // Send an event for the given modifier key + void handleModifierKey(void* target, + UInt32 virtualKey, KeyID id, + bool down, KeyModifierMask newMask); + + // Checks if any in \p ids is a glyph key and if \p isCommand is false. + // If so it adds the AltGr modifier to \p mask. This allows OS X + // servers to use the option key both as AltGr and as a modifier. If + // option is acting as AltGr (i.e. it generates a glyph and there are + // no command modifiers active) then we don't send the super modifier + // to clients because they'd try to match it as a command modifier. + void adjustAltGrModifier(const CKeyIDs& ids, + KeyModifierMask* mask, bool isCommand) const; + + // Maps an OS X virtual key id to a KeyButton. This simply remaps + // the ids so we don't use KeyButton 0. + static KeyButton mapVirtualKeyToKeyButton(UInt32 keyCode); + + // Maps a KeyButton to an OS X key code. This is the inverse of + // mapVirtualKeyToKeyButton. + static UInt32 mapKeyButtonToVirtualKey(KeyButton keyButton); + +private: + class CKeyResource : public IInterface { + public: + virtual bool isValid() const = 0; + virtual UInt32 getNumModifierCombinations() const = 0; + virtual UInt32 getNumTables() const = 0; + virtual UInt32 getNumButtons() const = 0; + virtual UInt32 getTableForModifier(UInt32 mask) const = 0; + virtual KeyID getKey(UInt32 table, UInt32 button) const = 0; + + // Convert a character in the current script to the equivalent KeyID + static KeyID getKeyID(UInt8); + + // Convert a unicode character to the equivalent KeyID. + static KeyID unicharToKeyID(UniChar); + }; + + class CKCHRKeyResource : public CKeyResource { + public: + CKCHRKeyResource(const void*); + + // CKeyResource overrides + virtual bool isValid() const; + virtual UInt32 getNumModifierCombinations() const; + virtual UInt32 getNumTables() const; + virtual UInt32 getNumButtons() const; + virtual UInt32 getTableForModifier(UInt32 mask) const; + virtual KeyID getKey(UInt32 table, UInt32 button) const; + + private: + struct KCHRResource { + public: + SInt16 m_version; + UInt8 m_tableSelectionIndex[256]; + SInt16 m_numTables; + UInt8 m_characterTables[1][128]; + }; + struct CKCHRDeadKeyRecord { + public: + UInt8 m_tableIndex; + UInt8 m_virtualKey; + SInt16 m_numCompletions; + UInt8 m_completion[1][2]; + }; + struct CKCHRDeadKeys { + public: + SInt16 m_numRecords; + CKCHRDeadKeyRecord m_records[1]; + }; + + const KCHRResource* m_resource; + }; + + class CUCHRKeyResource : public CKeyResource { + public: + CUCHRKeyResource(const void*, UInt32 keyboardType); + + // CKeyResource overrides + virtual bool isValid() const; + virtual UInt32 getNumModifierCombinations() const; + virtual UInt32 getNumTables() const; + virtual UInt32 getNumButtons() const; + virtual UInt32 getTableForModifier(UInt32 mask) const; + virtual KeyID getKey(UInt32 table, UInt32 button) const; + + private: + typedef std::vector KeySequence; + + bool getDeadKey(KeySequence& keys, UInt16 index) const; + bool getKeyRecord(KeySequence& keys, + UInt16 index, UInt16& state) const; + bool addSequence(KeySequence& keys, UCKeyCharSeq c) const; + + private: + const UCKeyboardLayout* m_resource; + const UCKeyModifiersToTableNum* m_m; + const UCKeyToCharTableIndex* m_cti; + const UCKeySequenceDataIndex* m_sdi; + const UCKeyStateRecordsIndex* m_sri; + const UCKeyStateTerminators* m_st; + UInt16 m_spaceOutput; + }; + + // OS X uses a physical key if 0 for the 'A' key. synergy reserves + // KeyButton 0 so we offset all OS X physical key ids by this much + // when used as a KeyButton and by minus this much to map a KeyButton + // to a physical button. + enum { + KeyButtonOffset = 1 + }; + + typedef std::map GroupMap; + typedef std::map CVirtualKeyMap; + + CVirtualKeyMap m_virtualKeyMap; + mutable UInt32 m_deadKeyState; + GroupList m_groups; + GroupMap m_groupMap; +}; + +#endif diff --git a/lib/platform/COSXScreen.cpp b/lib/platform/COSXScreen.cpp new file mode 100644 index 00000000..82574cf4 --- /dev/null +++ b/lib/platform/COSXScreen.cpp @@ -0,0 +1,1699 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "COSXScreen.h" +#include "COSXClipboard.h" +#include "COSXEventQueueBuffer.h" +#include "COSXKeyState.h" +#include "COSXScreenSaver.h" +#include "CClipboard.h" +#include "CKeyMap.h" +#include "CCondVar.h" +#include "CLock.h" +#include "CMutex.h" +#include "CThread.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include "TMethodJob.h" +#include "XArch.h" + +#include +#include + +// Set some enums for fast user switching if we're building with an SDK +// from before such support was added. +#if !defined(MAC_OS_X_VERSION_10_3) || \ + (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_3) +enum { + kEventClassSystem = 'macs', + kEventSystemUserSessionActivated = 10, + kEventSystemUserSessionDeactivated = 11 +}; +#endif + +// This isn't in any Apple SDK that I know of as of yet. +enum { + kSynergyEventMouseScroll = 11, + kSynergyMouseScrollAxisX = 'saxx', + kSynergyMouseScrollAxisY = 'saxy' +}; + +// +// COSXScreen +// + +bool COSXScreen::s_testedForGHOM = false; +bool COSXScreen::s_hasGHOM = false; +CEvent::Type COSXScreen::s_confirmSleepEvent = CEvent::kUnknown; + +COSXScreen::COSXScreen(bool isPrimary) : + m_isPrimary(isPrimary), + m_isOnScreen(m_isPrimary), + m_cursorPosValid(false), + m_cursorHidden(false), + m_dragNumButtonsDown(0), + m_dragTimer(NULL), + m_keyState(NULL), + m_sequenceNumber(0), + m_screensaver(NULL), + m_screensaverNotify(false), + m_ownClipboard(false), + m_clipboardTimer(NULL), + m_hiddenWindow(NULL), + m_userInputWindow(NULL), + m_displayManagerNotificationUPP(NULL), + m_switchEventHandlerRef(0), + m_pmMutex(new CMutex), + m_pmWatchThread(NULL), + m_pmThreadReady(new CCondVar(m_pmMutex, false)), + m_activeModifierHotKey(0), + m_activeModifierHotKeyMask(0) +{ + try { + m_displayID = CGMainDisplayID(); + updateScreenShape(); + m_screensaver = new COSXScreenSaver(getEventTarget()); + m_keyState = new COSXKeyState(); + + if (m_isPrimary) { + // 1x1 window (to minimze the back buffer allocated for this + // window. + Rect bounds = { 100, 100, 101, 101 }; + + // m_hiddenWindow is a window meant to let us get mouse moves + // when the focus is on another computer. If you get your event + // from the application event target you'll get every mouse + // moves. On the other hand the Window event target will only + // get events when the mouse moves over the window. + + // The ignoreClicks attributes makes it impossible for the + // user to click on our invisible window. + CreateNewWindow(kUtilityWindowClass, + kWindowNoShadowAttribute | + kWindowIgnoreClicksAttribute | + kWindowNoActivatesAttribute, + &bounds, &m_hiddenWindow); + + // Make it invisible + SetWindowAlpha(m_hiddenWindow, 0); + ShowWindow(m_hiddenWindow); + + // m_userInputWindow is a window meant to let us get mouse moves + // when the focus is on this computer. + Rect inputBounds = { 100, 100, 200, 200 }; + CreateNewWindow(kUtilityWindowClass, + kWindowNoShadowAttribute | + kWindowOpaqueForEventsAttribute | + kWindowStandardHandlerAttribute, + &inputBounds, &m_userInputWindow); + + SetWindowAlpha(m_userInputWindow, 0); + } + + // install display manager notification handler + m_displayManagerNotificationUPP = + NewDMExtendedNotificationUPP(displayManagerCallback); + OSStatus err = GetCurrentProcess(&m_PSN); + err = DMRegisterExtendedNotifyProc(m_displayManagerNotificationUPP, + this, 0, &m_PSN); + + // install fast user switching event handler + EventTypeSpec switchEventTypes[2]; + switchEventTypes[0].eventClass = kEventClassSystem; + switchEventTypes[0].eventKind = kEventSystemUserSessionDeactivated; + switchEventTypes[1].eventClass = kEventClassSystem; + switchEventTypes[1].eventKind = kEventSystemUserSessionActivated; + EventHandlerUPP switchEventHandler = + NewEventHandlerUPP(userSwitchCallback); + InstallApplicationEventHandler(switchEventHandler, 2, switchEventTypes, + this, &m_switchEventHandlerRef); + DisposeEventHandlerUPP(switchEventHandler); + + // watch for requests to sleep + EVENTQUEUE->adoptHandler(COSXScreen::getConfirmSleepEvent(), + getEventTarget(), + new TMethodEventJob(this, + &COSXScreen::handleConfirmSleep)); + + // create thread for monitoring system power state. + LOG((CLOG_DEBUG "starting watchSystemPowerThread")); + m_pmWatchThread = new CThread(new TMethodJob + (this, &COSXScreen::watchSystemPowerThread)); + } + catch (...) { + EVENTQUEUE->removeHandler(COSXScreen::getConfirmSleepEvent(), + getEventTarget()); + if (m_switchEventHandlerRef != 0) { + RemoveEventHandler(m_switchEventHandlerRef); + } + if (m_displayManagerNotificationUPP != NULL) { + DMRemoveExtendedNotifyProc(m_displayManagerNotificationUPP, + NULL, &m_PSN, 0); + } + + if (m_hiddenWindow) { + ReleaseWindow(m_hiddenWindow); + m_hiddenWindow = NULL; + } + + if (m_userInputWindow) { + ReleaseWindow(m_userInputWindow); + m_userInputWindow = NULL; + } + delete m_keyState; + delete m_screensaver; + throw; + } + + // install event handlers + EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(), + new TMethodEventJob(this, + &COSXScreen::handleSystemEvent)); + + // install the platform event queue + EVENTQUEUE->adoptBuffer(new COSXEventQueueBuffer); +} + +COSXScreen::~COSXScreen() +{ + disable(); + EVENTQUEUE->adoptBuffer(NULL); + EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget()); + + if (m_pmWatchThread) { + // make sure the thread has setup the runloop. + { + CLock lock(m_pmMutex); + while (!(bool)*m_pmThreadReady) { + m_pmThreadReady->wait(); + } + } + + // now exit the thread's runloop and wait for it to exit + LOG((CLOG_DEBUG "stopping watchSystemPowerThread")); + CFRunLoopStop(m_pmRunloop); + m_pmWatchThread->wait(); + delete m_pmWatchThread; + m_pmWatchThread = NULL; + } + delete m_pmThreadReady; + delete m_pmMutex; + + EVENTQUEUE->removeHandler(COSXScreen::getConfirmSleepEvent(), + getEventTarget()); + + RemoveEventHandler(m_switchEventHandlerRef); + + DMRemoveExtendedNotifyProc(m_displayManagerNotificationUPP, + NULL, &m_PSN, 0); + + if (m_hiddenWindow) { + ReleaseWindow(m_hiddenWindow); + m_hiddenWindow = NULL; + } + + if (m_userInputWindow) { + ReleaseWindow(m_userInputWindow); + m_userInputWindow = NULL; + } + + delete m_keyState; + delete m_screensaver; +} + +void* +COSXScreen::getEventTarget() const +{ + return const_cast(this); +} + +bool +COSXScreen::getClipboard(ClipboardID, IClipboard* dst) const +{ + COSXClipboard src; + CClipboard::copy(dst, &src); + return true; +} + +void +COSXScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + x = m_x; + y = m_y; + w = m_w; + h = m_h; +} + +void +COSXScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + Point mouse; + GetGlobalMouse(&mouse); + x = mouse.h; + y = mouse.v; + m_cursorPosValid = true; + m_xCursor = x; + m_yCursor = y; +} + +void +COSXScreen::reconfigure(UInt32) +{ + // do nothing +} + +void +COSXScreen::warpCursor(SInt32 x, SInt32 y) +{ + // move cursor without generating events + CGPoint pos; + pos.x = x; + pos.y = y; + CGWarpMouseCursorPosition(pos); + + // save new cursor position + m_xCursor = x; + m_yCursor = y; + m_cursorPosValid = true; +} + +void +COSXScreen::fakeInputBegin() +{ + // FIXME -- not implemented +} + +void +COSXScreen::fakeInputEnd() +{ + // FIXME -- not implemented +} + +SInt32 +COSXScreen::getJumpZoneSize() const +{ + return 1; +} + +bool +COSXScreen::isAnyMouseButtonDown() const +{ + return (GetCurrentButtonState() != 0); +} + +void +COSXScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + x = m_xCenter; + y = m_yCenter; +} + +UInt32 +COSXScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + // get mac virtual key and modifier mask matching synergy key and mask + UInt32 macKey, macMask; + if (!m_keyState->mapSynergyHotKeyToMac(key, mask, macKey, macMask)) { + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // choose hotkey id + UInt32 id; + if (!m_oldHotKeyIDs.empty()) { + id = m_oldHotKeyIDs.back(); + m_oldHotKeyIDs.pop_back(); + } + else { + id = m_hotKeys.size() + 1; + } + + // if this hot key has modifiers only then we'll handle it specially + EventHotKeyRef ref = NULL; + bool okay; + if (key == kKeyNone) { + if (m_modifierHotKeys.count(mask) > 0) { + // already registered + okay = false; + } + else { + m_modifierHotKeys[mask] = id; + okay = true; + } + } + else { + EventHotKeyID hkid = { 'SNRG', (UInt32)id }; + OSStatus status = RegisterEventHotKey(macKey, macMask, hkid, + GetApplicationEventTarget(), 0, + &ref); + okay = (status == noErr); + m_hotKeyToIDMap[CHotKeyItem(macKey, macMask)] = id; + } + + if (!okay) { + m_oldHotKeyIDs.push_back(id); + m_hotKeyToIDMap.erase(CHotKeyItem(macKey, macMask)); + LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", CKeyMap::formatKey(key, mask).c_str(), key, mask)); + return 0; + } + + m_hotKeys.insert(std::make_pair(id, CHotKeyItem(ref, macKey, macMask))); + + LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", CKeyMap::formatKey(key, mask).c_str(), key, mask, id)); + return id; +} + +void +COSXScreen::unregisterHotKey(UInt32 id) +{ + // look up hotkey + HotKeyMap::iterator i = m_hotKeys.find(id); + if (i == m_hotKeys.end()) { + return; + } + + // unregister with OS + bool okay; + if (i->second.getRef() != NULL) { + okay = (UnregisterEventHotKey(i->second.getRef()) == noErr); + } + else { + okay = false; + // XXX -- this is inefficient + for (ModifierHotKeyMap::iterator j = m_modifierHotKeys.begin(); + j != m_modifierHotKeys.end(); ++j) { + if (j->second == id) { + m_modifierHotKeys.erase(j); + okay = true; + break; + } + } + } + if (!okay) { + LOG((CLOG_WARN "failed to unregister hotkey id=%d", id)); + } + else { + LOG((CLOG_DEBUG "unregistered hotkey id=%d", id)); + } + + // discard hot key from map and record old id for reuse + m_hotKeyToIDMap.erase(i->second); + m_hotKeys.erase(i); + m_oldHotKeyIDs.push_back(id); + if (m_activeModifierHotKey == id) { + m_activeModifierHotKey = 0; + m_activeModifierHotKeyMask = 0; + } +} + +void +COSXScreen::postMouseEvent(CGPoint& pos) const +{ + // check if cursor position is valid on the client display configuration + // stkamp@users.sourceforge.net + CGDisplayCount displayCount = 0; + CGGetDisplaysWithPoint(pos, 0, NULL, &displayCount); + if (displayCount == 0) { + // cursor position invalid - clamp to bounds of last valid display. + // find the last valid display using the last cursor position. + displayCount = 0; + CGDirectDisplayID displayID; + CGGetDisplaysWithPoint(CGPointMake(m_xCursor, m_yCursor), 1, + &displayID, &displayCount); + if (displayCount != 0) { + CGRect displayRect = CGDisplayBounds(displayID); + if (pos.x < displayRect.origin.x) { + pos.x = displayRect.origin.x; + } + else if (pos.x > displayRect.origin.x + + displayRect.size.width - 1) { + pos.x = displayRect.origin.x + displayRect.size.width - 1; + } + if (pos.y < displayRect.origin.y) { + pos.y = displayRect.origin.y; + } + else if (pos.y > displayRect.origin.y + + displayRect.size.height - 1) { + pos.y = displayRect.origin.y + displayRect.size.height - 1; + } + } + } + + // synthesize event. CGPostMouseEvent is a particularly good + // example of a bad API. we have to shadow the mouse state to + // use this API and if we want to support more buttons we have + // to recompile. + // + // the order of buttons on the mac is: + // 1 - Left + // 2 - Right + // 3 - Middle + // Whatever the USB device defined. + // + // It is a bit weird that the behaviour of buttons over 3 are dependent + // on currently plugged in USB devices. + CGPostMouseEvent(pos, true, sizeof(m_buttons) / sizeof(m_buttons[0]), + m_buttons[0], + m_buttons[2], + m_buttons[1], + m_buttons[3], + m_buttons[4]); +} + + +void +COSXScreen::fakeMouseButton(ButtonID id, bool press) const +{ + // get button index + UInt32 index = id - kButtonLeft; + if (index >= sizeof(m_buttons) / sizeof(m_buttons[0])) { + return; + } + + // update state + m_buttons[index] = press; + + CGPoint pos; + if (!m_cursorPosValid) { + SInt32 x, y; + getCursorPos(x, y); + } + pos.x = m_xCursor; + pos.y = m_yCursor; + postMouseEvent(pos); +} + +void +COSXScreen::fakeMouseMove(SInt32 x, SInt32 y) const +{ + // synthesize event + CGPoint pos; + pos.x = x; + pos.y = y; + postMouseEvent(pos); + + // save new cursor position + m_xCursor = static_cast(pos.x); + m_yCursor = static_cast(pos.y); + m_cursorPosValid = true; +} + +void +COSXScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + // OS X does not appear to have a fake relative mouse move function. + // simulate it by getting the current mouse position and adding to + // that. this can yield the wrong answer but there's not much else + // we can do. + + // get current position + Point oldPos; + GetGlobalMouse(&oldPos); + + // synthesize event + CGPoint pos; + m_xCursor = static_cast(oldPos.h); + m_yCursor = static_cast(oldPos.v); + pos.x = oldPos.h + dx; + pos.y = oldPos.v + dy; + postMouseEvent(pos); + + // we now assume we don't know the current cursor position + m_cursorPosValid = false; +} + +void +COSXScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + if (xDelta != 0 || yDelta != 0) { + CGPostScrollWheelEvent(2, mapScrollWheelFromSynergy(yDelta), + -mapScrollWheelFromSynergy(xDelta)); + } +} + +void +COSXScreen::enable() +{ + // watch the clipboard + m_clipboardTimer = EVENTQUEUE->newTimer(1.0, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_clipboardTimer, + new TMethodEventJob(this, + &COSXScreen::handleClipboardCheck)); + + if (m_isPrimary) { + // FIXME -- start watching jump zones + } + else { + // FIXME -- prevent system from entering power save mode + + // hide cursor + if (!m_cursorHidden) { +// CGDisplayHideCursor(m_displayID); + m_cursorHidden = true; + } + + // warp the mouse to the cursor center + fakeMouseMove(m_xCenter, m_yCenter); + + // FIXME -- prepare to show cursor if it moves + } +} + +void +COSXScreen::disable() +{ + if (m_isPrimary) { + // FIXME -- stop watching jump zones, stop capturing input + } + else { + // show cursor + if (m_cursorHidden) { +// CGDisplayShowCursor(m_displayID); + m_cursorHidden = false; + } + + // FIXME -- allow system to enter power saving mode + } + + // disable drag handling + m_dragNumButtonsDown = 0; + enableDragTimer(false); + + // uninstall clipboard timer + if (m_clipboardTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_clipboardTimer); + EVENTQUEUE->deleteTimer(m_clipboardTimer); + m_clipboardTimer = NULL; + } + + m_isOnScreen = m_isPrimary; +} + +void +COSXScreen::enter() +{ + if (m_isPrimary) { + // stop capturing input, watch jump zones + HideWindow( m_userInputWindow ); + ShowWindow( m_hiddenWindow ); + + SetMouseCoalescingEnabled(true, NULL); + + CGSetLocalEventsSuppressionInterval(0.0); + + // enable global hotkeys + setGlobalHotKeysEnabled(true); + } + else { + // show cursor + if (m_cursorHidden) { +// CGDisplayShowCursor(m_displayID); + m_cursorHidden = false; + } + + // reset buttons + for (UInt32 i = 0; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { + m_buttons[i] = false; + } + + // avoid suppression of local hardware events + // stkamp@users.sourceforge.net + CGSetLocalEventsFilterDuringSupressionState( + kCGEventFilterMaskPermitAllEvents, + kCGEventSupressionStateSupressionInterval); + CGSetLocalEventsFilterDuringSupressionState( + (kCGEventFilterMaskPermitLocalKeyboardEvents | + kCGEventFilterMaskPermitSystemDefinedEvents), + kCGEventSupressionStateRemoteMouseDrag); + } + + // now on screen + m_isOnScreen = true; +} + +bool +COSXScreen::leave() +{ + if (m_isPrimary) { + // warp to center + warpCursor(m_xCenter, m_yCenter); + + // capture events + HideWindow(m_hiddenWindow); + ShowWindow(m_userInputWindow); + RepositionWindow(m_userInputWindow, + m_userInputWindow, kWindowCenterOnMainScreen); + SetUserFocusWindow(m_userInputWindow); + + // The OS will coalesce some events if they are similar enough in a + // short period of time this is bad for us since we need every event + // to send it over to other machines. So disable it. + SetMouseCoalescingEnabled(false, NULL); + CGSetLocalEventsSuppressionInterval(0.0001); + + // disable global hotkeys + setGlobalHotKeysEnabled(false); + } + else { + // hide cursor + if (!m_cursorHidden) { +// CGDisplayHideCursor(m_displayID); + m_cursorHidden = true; + } + + // warp the mouse to the cursor center + fakeMouseMove(m_xCenter, m_yCenter); + + // FIXME -- prepare to show cursor if it moves + + // take keyboard focus + // FIXME + } + + // now off screen + m_isOnScreen = false; + + return true; +} + +bool +COSXScreen::setClipboard(ClipboardID, const IClipboard* src) +{ + COSXClipboard dst; + if (src != NULL) { + // save clipboard data + if (!CClipboard::copy(&dst, src)) { + return false; + } + } + else { + // assert clipboard ownership + if (!dst.open(0)) { + return false; + } + dst.empty(); + dst.close(); + } + checkClipboards(); + return true; +} + +void +COSXScreen::checkClipboards() +{ + // check if clipboard ownership changed + if (!COSXClipboard::isOwnedBySynergy()) { + if (m_ownClipboard) { + LOG((CLOG_DEBUG "clipboard changed: lost ownership")); + m_ownClipboard = false; + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard); + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection); + } + } + else if (!m_ownClipboard) { + LOG((CLOG_DEBUG "clipboard changed: synergy owned")); + m_ownClipboard = true; + } +} + +void +COSXScreen::openScreensaver(bool notify) +{ + m_screensaverNotify = notify; + if (!m_screensaverNotify) { + m_screensaver->disable(); + } +} + +void +COSXScreen::closeScreensaver() +{ + if (!m_screensaverNotify) { + m_screensaver->enable(); + } +} + +void +COSXScreen::screensaver(bool activate) +{ + if (activate) { + m_screensaver->activate(); + } + else { + m_screensaver->deactivate(); + } +} + +void +COSXScreen::resetOptions() +{ + // no options +} + +void +COSXScreen::setOptions(const COptionsList&) +{ + // no options +} + +void +COSXScreen::setSequenceNumber(UInt32 seqNum) +{ + m_sequenceNumber = seqNum; +} + +bool +COSXScreen::isPrimary() const +{ + return m_isPrimary; +} + +void +COSXScreen::sendEvent(CEvent::Type type, void* data) const +{ + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data)); +} + +void +COSXScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id) const +{ + CClipboardInfo* info = (CClipboardInfo*)malloc(sizeof(CClipboardInfo)); + info->m_id = id; + info->m_sequenceNumber = m_sequenceNumber; + sendEvent(type, info); +} + +void +COSXScreen::handleSystemEvent(const CEvent& event, void*) +{ + EventRef* carbonEvent = reinterpret_cast(event.getData()); + assert(carbonEvent != NULL); + + UInt32 eventClass = GetEventClass(*carbonEvent); + + switch (eventClass) { + case kEventClassMouse: + switch (GetEventKind(*carbonEvent)) { + case kEventMouseDown: + { + UInt16 myButton; + GetEventParameter(*carbonEvent, + kEventParamMouseButton, + typeMouseButton, + NULL, + sizeof(myButton), + NULL, + &myButton); + onMouseButton(true, myButton); + break; + } + + case kEventMouseUp: + { + UInt16 myButton; + GetEventParameter(*carbonEvent, + kEventParamMouseButton, + typeMouseButton, + NULL, + sizeof(myButton), + NULL, + &myButton); + onMouseButton(false, myButton); + break; + } + + case kEventMouseDragged: + case kEventMouseMoved: + { + HIPoint point; + GetEventParameter(*carbonEvent, + kEventParamMouseLocation, + typeHIPoint, + NULL, + sizeof(point), + NULL, + &point); + onMouseMove((SInt32)point.x, (SInt32)point.y); + break; + } + + case kEventMouseWheelMoved: + { + EventMouseWheelAxis axis; + SInt32 delta; + GetEventParameter(*carbonEvent, + kEventParamMouseWheelAxis, + typeMouseWheelAxis, + NULL, + sizeof(axis), + NULL, + &axis); + if (axis == kEventMouseWheelAxisX || + axis == kEventMouseWheelAxisY) { + GetEventParameter(*carbonEvent, + kEventParamMouseWheelDelta, + typeLongInteger, + NULL, + sizeof(delta), + NULL, + &delta); + if (axis == kEventMouseWheelAxisX) { + onMouseWheel(-mapScrollWheelToSynergy((SInt32)delta), 0); + } + else { + onMouseWheel(0, mapScrollWheelToSynergy((SInt32)delta)); + } + } + break; + } + + case kSynergyEventMouseScroll: + { + OSStatus r; + long xScroll; + long yScroll; + + // get scroll amount + r = GetEventParameter(*carbonEvent, + kSynergyMouseScrollAxisX, + typeLongInteger, + NULL, + sizeof(xScroll), + NULL, + &xScroll); + if (r != noErr) { + xScroll = 0; + } + r = GetEventParameter(*carbonEvent, + kSynergyMouseScrollAxisY, + typeLongInteger, + NULL, + sizeof(yScroll), + NULL, + &yScroll); + if (r != noErr) { + yScroll = 0; + } + + if (xScroll != 0 || yScroll != 0) { + onMouseWheel(-mapScrollWheelToSynergy(xScroll), + mapScrollWheelToSynergy(yScroll)); + } + } + } + break; + + case kEventClassKeyboard: + switch (GetEventKind(*carbonEvent)) { + case kEventRawKeyUp: + case kEventRawKeyDown: + case kEventRawKeyRepeat: + case kEventRawKeyModifiersChanged: + onKey(*carbonEvent); + break; + + case kEventHotKeyPressed: + case kEventHotKeyReleased: + onHotKey(*carbonEvent); + break; + } + + break; + + case kEventClassWindow: + SendEventToWindow(*carbonEvent, m_userInputWindow); + switch (GetEventKind(*carbonEvent)) { + case kEventWindowActivated: + LOG((CLOG_DEBUG1 "window activated")); + break; + + case kEventWindowDeactivated: + LOG((CLOG_DEBUG1 "window deactivated")); + break; + + case kEventWindowFocusAcquired: + LOG((CLOG_DEBUG1 "focus acquired")); + break; + + case kEventWindowFocusRelinquish: + LOG((CLOG_DEBUG1 "focus released")); + break; + } + break; + + default: + SendEventToEventTarget(*carbonEvent, GetEventDispatcherTarget()); + break; + } +} + +bool +COSXScreen::onMouseMove(SInt32 mx, SInt32 my) +{ + LOG((CLOG_DEBUG2 "mouse move %+d,%+d", mx, my)); + + SInt32 x = mx - m_xCursor; + SInt32 y = my - m_yCursor; + + if ((x == 0 && y == 0) || (mx == m_xCenter && mx == m_yCenter)) { + return true; + } + + // save position to compute delta of next motion + m_xCursor = mx; + m_yCursor = my; + + if (m_isOnScreen) { + // motion on primary screen + sendEvent(getMotionOnPrimaryEvent(), + CMotionInfo::alloc(m_xCursor, m_yCursor)); + } + else { + // motion on secondary screen. warp mouse back to + // center. + warpCursor(m_xCenter, m_yCenter); + + // examine the motion. if it's about the distance + // from the center of the screen to an edge then + // it's probably a bogus motion that we want to + // ignore (see warpCursorNoFlush() for a further + // description). + static SInt32 bogusZoneSize = 10; + if (-x + bogusZoneSize > m_xCenter - m_x || + x + bogusZoneSize > m_x + m_w - m_xCenter || + -y + bogusZoneSize > m_yCenter - m_y || + y + bogusZoneSize > m_y + m_h - m_yCenter) { + LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y)); + } + else { + // send motion + sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y)); + } + } + + return true; +} + +bool +COSXScreen::onMouseButton(bool pressed, UInt16 macButton) +{ + // Buttons 2 and 3 are inverted on the mac + ButtonID button = mapMacButtonToSynergy(macButton); + + if (pressed) { + LOG((CLOG_DEBUG1 "event: button press button=%d", button)); + if (button != kButtonNone) { + KeyModifierMask mask = m_keyState->getActiveModifiers(); + sendEvent(getButtonDownEvent(), CButtonInfo::alloc(button, mask)); + } + } + else { + LOG((CLOG_DEBUG1 "event: button release button=%d", button)); + if (button != kButtonNone) { + KeyModifierMask mask = m_keyState->getActiveModifiers(); + sendEvent(getButtonUpEvent(), CButtonInfo::alloc(button, mask)); + } + } + + // handle drags with any button other than button 1 or 2 + if (macButton > 2) { + if (pressed) { + // one more button + if (m_dragNumButtonsDown++ == 0) { + enableDragTimer(true); + } + } + else { + // one less button + if (--m_dragNumButtonsDown == 0) { + enableDragTimer(false); + } + } + } + + return true; +} + +bool +COSXScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta)); + sendEvent(getWheelEvent(), CWheelInfo::alloc(xDelta, yDelta)); + return true; +} + +void +COSXScreen::handleClipboardCheck(const CEvent&, void*) +{ + checkClipboards(); +} + +pascal void +COSXScreen::displayManagerCallback(void* inUserData, SInt16 inMessage, void*) +{ + COSXScreen* screen = (COSXScreen*)inUserData; + + if (inMessage == kDMNotifyEvent) { + screen->onDisplayChange(); + } +} + +bool +COSXScreen::onDisplayChange() +{ + // screen resolution may have changed. save old shape. + SInt32 xOld = m_x, yOld = m_y, wOld = m_w, hOld = m_h; + + // update shape + updateScreenShape(); + + // do nothing if resolution hasn't changed + if (xOld != m_x || yOld != m_y || wOld != m_w || hOld != m_h) { + if (m_isPrimary) { + // warp mouse to center if off screen + if (!m_isOnScreen) { + warpCursor(m_xCenter, m_yCenter); + } + } + + // send new screen info + sendEvent(getShapeChangedEvent()); + } + + return true; +} + +bool +COSXScreen::onKey(EventRef event) +{ + UInt32 eventKind = GetEventKind(event); + + // get the key and active modifiers + UInt32 virtualKey, macMask; + GetEventParameter(event, kEventParamKeyCode, typeUInt32, + NULL, sizeof(virtualKey), NULL, &virtualKey); + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, + NULL, sizeof(macMask), NULL, &macMask); + LOG((CLOG_DEBUG1 "event: Key event kind: %d, keycode=%d", eventKind, virtualKey)); + + // sadly, OS X doesn't report the virtualKey for modifier keys. + // virtualKey will be zero for modifier keys. since that's not good + // enough we'll have to figure out what the key was. + if (virtualKey == 0 && eventKind == kEventRawKeyModifiersChanged) { + // get old and new modifier state + KeyModifierMask oldMask = getActiveModifiers(); + KeyModifierMask newMask = m_keyState->mapModifiersFromOSX(macMask); + m_keyState->handleModifierKeys(getEventTarget(), oldMask, newMask); + + // if the current set of modifiers exactly matches a modifiers-only + // hot key then generate a hot key down event. + if (m_activeModifierHotKey == 0) { + if (m_modifierHotKeys.count(newMask) > 0) { + m_activeModifierHotKey = m_modifierHotKeys[newMask]; + m_activeModifierHotKeyMask = newMask; + EVENTQUEUE->addEvent(CEvent(getHotKeyDownEvent(), + getEventTarget(), + CHotKeyInfo::alloc(m_activeModifierHotKey))); + } + } + + // if a modifiers-only hot key is active and should no longer be + // then generate a hot key up event. + else if (m_activeModifierHotKey != 0) { + KeyModifierMask mask = (newMask & m_activeModifierHotKeyMask); + if (mask != m_activeModifierHotKeyMask) { + EVENTQUEUE->addEvent(CEvent(getHotKeyUpEvent(), + getEventTarget(), + CHotKeyInfo::alloc(m_activeModifierHotKey))); + m_activeModifierHotKey = 0; + m_activeModifierHotKeyMask = 0; + } + } + + return true; + } + + // check for hot key. when we're on a secondary screen we disable + // all hotkeys so we can capture the OS defined hot keys as regular + // keystrokes but that means we don't get our own hot keys either. + // so we check for a key/modifier match in our hot key map. + if (!m_isOnScreen) { + HotKeyToIDMap::const_iterator i = + m_hotKeyToIDMap.find(CHotKeyItem(virtualKey, macMask & 0xff00u)); + if (i != m_hotKeyToIDMap.end()) { + UInt32 id = i->second; + + // determine event type + CEvent::Type type; + UInt32 eventKind = GetEventKind(event); + if (eventKind == kEventRawKeyDown) { + type = getHotKeyDownEvent(); + } + else if (eventKind == kEventRawKeyUp) { + type = getHotKeyUpEvent(); + } + else { + return false; + } + + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), + CHotKeyInfo::alloc(id))); + + return true; + } + } + + // decode event type + bool down = (eventKind == kEventRawKeyDown); + bool up = (eventKind == kEventRawKeyUp); + bool isRepeat = (eventKind == kEventRawKeyRepeat); + + // map event to keys + KeyModifierMask mask; + COSXKeyState::CKeyIDs keys; + KeyButton button = m_keyState->mapKeyFromEvent(keys, &mask, event); + if (button == 0) { + return false; + } + + // check for AltGr in mask. if set we send neither the AltGr nor + // the super modifiers to clients then remove AltGr before passing + // the modifiers to onKey. + KeyModifierMask sendMask = (mask & ~KeyModifierAltGr); + if ((mask & KeyModifierAltGr) != 0) { + sendMask &= ~KeyModifierSuper; + } + mask &= ~KeyModifierAltGr; + + // update button state + if (down) { + m_keyState->onKey(button, true, mask); + } + else if (up) { + if (!m_keyState->isKeyDown(button)) { + // up event for a dead key. throw it away. + return false; + } + m_keyState->onKey(button, false, mask); + } + + // send key events + for (COSXKeyState::CKeyIDs::const_iterator i = keys.begin(); + i != keys.end(); ++i) { + m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat, + *i, sendMask, 1, button); + } + + return true; +} + +bool +COSXScreen::onHotKey(EventRef event) const +{ + // get the hotkey id + EventHotKeyID hkid; + GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, + NULL, sizeof(EventHotKeyID), NULL, &hkid); + UInt32 id = hkid.id; + + // determine event type + CEvent::Type type; + UInt32 eventKind = GetEventKind(event); + if (eventKind == kEventHotKeyPressed) { + type = getHotKeyDownEvent(); + } + else if (eventKind == kEventHotKeyReleased) { + type = getHotKeyUpEvent(); + } + else { + return false; + } + + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), + CHotKeyInfo::alloc(id))); + + return true; +} + +ButtonID +COSXScreen::mapMacButtonToSynergy(UInt16 macButton) const +{ + switch (macButton) { + case 1: + return kButtonLeft; + + case 2: + return kButtonRight; + + case 3: + return kButtonMiddle; + } + + return static_cast(macButton); +} + +SInt32 +COSXScreen::mapScrollWheelToSynergy(SInt32 x) const +{ + // return accelerated scrolling but not exponentially scaled as it is + // on the mac. + double d = (1.0 + getScrollSpeed()) * x / getScrollSpeedFactor(); + return static_cast(120.0 * d); +} + +SInt32 +COSXScreen::mapScrollWheelFromSynergy(SInt32 x) const +{ + // use server's acceleration with a little boost since other platforms + // take one wheel step as a larger step than the mac does. + return static_cast(3.0 * x / 120.0); +} + +double +COSXScreen::getScrollSpeed() const +{ + double scaling = 0.0; + + CFPropertyListRef pref = ::CFPreferencesCopyValue( + CFSTR("com.apple.scrollwheel.scaling") , + kCFPreferencesAnyApplication, + kCFPreferencesCurrentUser, + kCFPreferencesAnyHost); + if (pref != NULL) { + CFTypeID id = CFGetTypeID(pref); + if (id == CFNumberGetTypeID()) { + CFNumberRef value = static_cast(pref); + if (CFNumberGetValue(value, kCFNumberDoubleType, &scaling)) { + if (scaling < 0.0) { + scaling = 0.0; + } + } + } + CFRelease(pref); + } + + return scaling; +} + +double +COSXScreen::getScrollSpeedFactor() const +{ + return pow(10.0, getScrollSpeed()); +} + +void +COSXScreen::enableDragTimer(bool enable) +{ + if (enable && m_dragTimer == NULL) { + m_dragTimer = EVENTQUEUE->newTimer(0.01, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_dragTimer, + new TMethodEventJob(this, + &COSXScreen::handleDrag)); + GetMouse(&m_dragLastPoint); + } + else if (!enable && m_dragTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_dragTimer); + EVENTQUEUE->deleteTimer(m_dragTimer); + m_dragTimer = NULL; + } +} + +void +COSXScreen::handleDrag(const CEvent&, void*) +{ + Point p; + GetMouse(&p); + if (p.h != m_dragLastPoint.h || p.v != m_dragLastPoint.v) { + m_dragLastPoint = p; + onMouseMove((SInt32)p.h, (SInt32)p.v); + } +} + +void +COSXScreen::updateButtons() +{ + UInt32 buttons = GetCurrentButtonState(); + for (size_t i = 0; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { + m_buttons[i] = ((buttons & (1u << i)) != 0); + } +} + +IKeyState* +COSXScreen::getKeyState() const +{ + return m_keyState; +} + +void +COSXScreen::updateScreenShape() +{ + // get info for each display + CGDisplayCount displayCount = 0; + + if (CGGetActiveDisplayList(0, NULL, &displayCount) != CGDisplayNoErr) { + return; + } + + if (displayCount == 0) { + return; + } + + CGDirectDisplayID* displays = new CGDirectDisplayID[displayCount]; + if (displays == NULL) { + return; + } + + if (CGGetActiveDisplayList(displayCount, + displays, &displayCount) != CGDisplayNoErr) { + delete[] displays; + return; + } + + // get smallest rect enclosing all display rects + CGRect totalBounds = CGRectZero; + for (CGDisplayCount i = 0; i < displayCount; ++i) { + CGRect bounds = CGDisplayBounds(displays[i]); + totalBounds = CGRectUnion(totalBounds, bounds); + } + + // get shape of default screen + m_x = (SInt32)totalBounds.origin.x; + m_y = (SInt32)totalBounds.origin.y; + m_w = (SInt32)totalBounds.size.width; + m_h = (SInt32)totalBounds.size.height; + + // get center of default screen + GDHandle mainScreen = GetMainDevice(); + if (mainScreen != NULL) { + const Rect& rect = (*mainScreen)->gdRect; + m_xCenter = (rect.left + rect.right) / 2; + m_yCenter = (rect.top + rect.bottom) / 2; + } + else { + m_xCenter = m_x + (m_w >> 1); + m_yCenter = m_y + (m_h >> 1); + } + + delete[] displays; + + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d on %u %s", m_x, m_y, m_w, m_h, displayCount, (displayCount == 1) ? "display" : "displays")); +} + +#pragma mark - + +// +// FAST USER SWITCH NOTIFICATION SUPPORT +// +// COSXScreen::userSwitchCallback(void*) +// +// gets called if a fast user switch occurs +// + +pascal OSStatus +COSXScreen::userSwitchCallback(EventHandlerCallRef nextHandler, + EventRef theEvent, + void* inUserData) +{ + COSXScreen* screen = (COSXScreen*)inUserData; + UInt32 kind = GetEventKind(theEvent); + + if (kind == kEventSystemUserSessionDeactivated) { + LOG((CLOG_DEBUG "user session deactivated")); + EVENTQUEUE->addEvent(CEvent(IScreen::getSuspendEvent(), + screen->getEventTarget())); + } + else if (kind == kEventSystemUserSessionActivated) { + LOG((CLOG_DEBUG "user session activated")); + EVENTQUEUE->addEvent(CEvent(IScreen::getResumeEvent(), + screen->getEventTarget())); + } + return (CallNextEventHandler(nextHandler, theEvent)); +} + +#pragma mark - + +// +// SLEEP/WAKEUP NOTIFICATION SUPPORT +// +// COSXScreen::watchSystemPowerThread(void*) +// +// main of thread monitoring system power (sleep/wakup) using a CFRunLoop +// + +void +COSXScreen::watchSystemPowerThread(void*) +{ + io_object_t notifier; + IONotificationPortRef notificationPortRef; + CFRunLoopSourceRef runloopSourceRef = 0; + + m_pmRunloop = CFRunLoopGetCurrent(); + + // install system power change callback + m_pmRootPort = IORegisterForSystemPower(this, ¬ificationPortRef, + powerChangeCallback, ¬ifier); + if (m_pmRootPort == 0) { + LOG((CLOG_WARN "IORegisterForSystemPower failed")); + } + else { + runloopSourceRef = + IONotificationPortGetRunLoopSource(notificationPortRef); + CFRunLoopAddSource(m_pmRunloop, runloopSourceRef, + kCFRunLoopCommonModes); + } + + // thread is ready + { + CLock lock(m_pmMutex); + *m_pmThreadReady = true; + m_pmThreadReady->signal(); + } + + // if we were unable to initialize then exit. we must do this after + // setting m_pmThreadReady to true otherwise the parent thread will + // block waiting for it. + if (m_pmRootPort == 0) { + return; + } + + // start the run loop + LOG((CLOG_DEBUG "started watchSystemPowerThread")); + CFRunLoopRun(); + + // cleanup + if (notificationPortRef) { + CFRunLoopRemoveSource(m_pmRunloop, + runloopSourceRef, kCFRunLoopDefaultMode); + CFRunLoopSourceInvalidate(runloopSourceRef); + CFRelease(runloopSourceRef); + } + + CLock lock(m_pmMutex); + IODeregisterForSystemPower(¬ifier); + m_pmRootPort = 0; + LOG((CLOG_DEBUG "stopped watchSystemPowerThread")); +} + +void +COSXScreen::powerChangeCallback(void* refcon, io_service_t service, + natural_t messageType, void* messageArg) +{ + ((COSXScreen*)refcon)->handlePowerChangeRequest(messageType, messageArg); +} + +void +COSXScreen::handlePowerChangeRequest(natural_t messageType, void* messageArg) +{ + // we've received a power change notification + switch (messageType) { + case kIOMessageSystemWillSleep: + // COSXScreen has to handle this in the main thread so we have to + // queue a confirm sleep event here. we actually don't allow the + // system to sleep until the event is handled. + EVENTQUEUE->addEvent(CEvent(COSXScreen::getConfirmSleepEvent(), + getEventTarget(), messageArg, + CEvent::kDontFreeData)); + return; + + case kIOMessageSystemHasPoweredOn: + LOG((CLOG_DEBUG "system wakeup")); + EVENTQUEUE->addEvent(CEvent(IScreen::getResumeEvent(), + getEventTarget())); + break; + + default: + break; + } + + CLock lock(m_pmMutex); + if (m_pmRootPort != 0) { + IOAllowPowerChange(m_pmRootPort, (long)messageArg); + } +} + +CEvent::Type +COSXScreen::getConfirmSleepEvent() +{ + return CEvent::registerTypeOnce(s_confirmSleepEvent, + "COSXScreen::confirmSleep"); +} + +void +COSXScreen::handleConfirmSleep(const CEvent& event, void*) +{ + long messageArg = (long)event.getData(); + if (messageArg != 0) { + CLock lock(m_pmMutex); + if (m_pmRootPort != 0) { + // deliver suspend event immediately. + EVENTQUEUE->addEvent(CEvent(IScreen::getSuspendEvent(), + getEventTarget(), NULL, + CEvent::kDeliverImmediately)); + + LOG((CLOG_DEBUG "system will sleep")); + IOAllowPowerChange(m_pmRootPort, messageArg); + } + } +} + +#pragma mark - + +// +// GLOBAL HOTKEY OPERATING MODE SUPPORT (10.3) +// +// CoreGraphics private API (OSX 10.3) +// Source: http://ichiro.nnip.org/osx/Cocoa/GlobalHotkey.html +// +// We load the functions dynamically because they're not available in +// older SDKs. We don't use weak linking because we want users of +// older SDKs to build an app that works on newer systems and older +// SDKs will not provide the symbols. +// + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int CGSConnection; +typedef enum { + CGSGlobalHotKeyEnable = 0, + CGSGlobalHotKeyDisable = 1, +} CGSGlobalHotKeyOperatingMode; + +extern CGSConnection _CGSDefaultConnection(void) WEAK_IMPORT_ATTRIBUTE; +extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode) WEAK_IMPORT_ATTRIBUTE; +extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode) WEAK_IMPORT_ATTRIBUTE; + +typedef CGSConnection (*_CGSDefaultConnection_t)(void); +typedef CGError (*CGSGetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode); +typedef CGError (*CGSSetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode); + +static _CGSDefaultConnection_t s__CGSDefaultConnection; +static CGSGetGlobalHotKeyOperatingMode_t s_CGSGetGlobalHotKeyOperatingMode; +static CGSSetGlobalHotKeyOperatingMode_t s_CGSSetGlobalHotKeyOperatingMode; + +#ifdef __cplusplus +} +#endif + +#define LOOKUP(name_) \ + s_ ## name_ = NULL; \ + if (NSIsSymbolNameDefinedWithHint("_" #name_, "CoreGraphics")) { \ + s_ ## name_ = (name_ ## _t)NSAddressOfSymbol( \ + NSLookupAndBindSymbolWithHint( \ + "_" #name_, "CoreGraphics")); \ + } + +bool +COSXScreen::isGlobalHotKeyOperatingModeAvailable() +{ + if (!s_testedForGHOM) { + s_testedForGHOM = true; + LOOKUP(_CGSDefaultConnection); + LOOKUP(CGSGetGlobalHotKeyOperatingMode); + LOOKUP(CGSSetGlobalHotKeyOperatingMode); + s_hasGHOM = (s__CGSDefaultConnection != NULL && + s_CGSGetGlobalHotKeyOperatingMode != NULL && + s_CGSSetGlobalHotKeyOperatingMode != NULL); + } + return s_hasGHOM; +} + +void +COSXScreen::setGlobalHotKeysEnabled(bool enabled) +{ + if (isGlobalHotKeyOperatingModeAvailable()) { + CGSConnection conn = s__CGSDefaultConnection(); + + CGSGlobalHotKeyOperatingMode mode; + s_CGSGetGlobalHotKeyOperatingMode(conn, &mode); + + if (enabled && mode == CGSGlobalHotKeyDisable) { + s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyEnable); + } + else if (!enabled && mode == CGSGlobalHotKeyEnable) { + s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyDisable); + } + } +} + +bool +COSXScreen::getGlobalHotKeysEnabled() +{ + CGSGlobalHotKeyOperatingMode mode; + if (isGlobalHotKeyOperatingModeAvailable()) { + CGSConnection conn = s__CGSDefaultConnection(); + s_CGSGetGlobalHotKeyOperatingMode(conn, &mode); + } + else { + mode = CGSGlobalHotKeyEnable; + } + return (mode == CGSGlobalHotKeyEnable); +} + + +// +// COSXScreen::CHotKeyItem +// + +COSXScreen::CHotKeyItem::CHotKeyItem(UInt32 keycode, UInt32 mask) : + m_ref(NULL), + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +COSXScreen::CHotKeyItem::CHotKeyItem(EventHotKeyRef ref, + UInt32 keycode, UInt32 mask) : + m_ref(ref), + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +EventHotKeyRef +COSXScreen::CHotKeyItem::getRef() const +{ + return m_ref; +} + +bool +COSXScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const +{ + return (m_keycode < x.m_keycode || + (m_keycode == x.m_keycode && m_mask < x.m_mask)); +} diff --git a/lib/platform/COSXScreen.h b/lib/platform/COSXScreen.h new file mode 100644 index 00000000..f280d802 --- /dev/null +++ b/lib/platform/COSXScreen.h @@ -0,0 +1,252 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXSCREEN_H +#define COSXSCREEN_H + +#include "CPlatformScreen.h" +#include "stdmap.h" +#include "stdvector.h" +#include + +#include +#include +#include +#include +#include + +template +class CCondVar; +class CEventQueueTimer; +class CMutex; +class CThread; +class COSXKeyState; +class COSXScreenSaver; + +//! Implementation of IPlatformScreen for OS X +class COSXScreen : public CPlatformScreen { +public: + COSXScreen(bool isPrimary); + virtual ~COSXScreen(); + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides); + virtual void warpCursor(SInt32 x, SInt32 y); + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + virtual void unregisterHotKey(UInt32 id); + virtual void fakeInputBegin(); + virtual void fakeInputEnd(); + virtual SInt32 getJumpZoneSize() const; + virtual bool isAnyMouseButtonDown() const; + virtual void getCursorCenter(SInt32& x, SInt32& y) const; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press) const; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const; + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + // IPlatformScreen overrides + virtual void enable(); + virtual void disable(); + virtual void enter(); + virtual bool leave(); + virtual bool setClipboard(ClipboardID, const IClipboard*); + virtual void checkClipboards(); + virtual void openScreensaver(bool notify); + virtual void closeScreensaver(); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + virtual void setSequenceNumber(UInt32); + virtual bool isPrimary() const; + +protected: + // IPlatformScreen overrides + virtual void handleSystemEvent(const CEvent&, void*); + virtual void updateButtons(); + virtual IKeyState* getKeyState() const; + +private: + void updateScreenShape(); + void postMouseEvent(CGPoint&) const; + + // convenience function to send events + void sendEvent(CEvent::Type type, void* = NULL) const; + void sendClipboardEvent(CEvent::Type type, ClipboardID id) const; + + // message handlers + bool onMouseMove(SInt32 x, SInt32 y); + // mouse button handler. pressed is true if this is a mousedown + // event, false if it is a mouseup event. macButton is the index + // of the button pressed using the mac button mapping. + bool onMouseButton(bool pressed, UInt16 macButton); + bool onMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + bool onDisplayChange(); + + bool onKey(EventRef event); + bool onHotKey(EventRef event) const; + + // map mac mouse button to synergy buttons + ButtonID mapMacButtonToSynergy(UInt16) const; + + // map mac scroll wheel value to a synergy scroll wheel value + SInt32 mapScrollWheelToSynergy(SInt32) const; + + // map synergy scroll wheel value to a mac scroll wheel value + SInt32 mapScrollWheelFromSynergy(SInt32) const; + + // get the current scroll wheel speed + double getScrollSpeed() const; + + // get the current scroll wheel speed + double getScrollSpeedFactor() const; + + // enable/disable drag handling for buttons 3 and up + void enableDragTimer(bool enable); + + // drag timer handler + void handleDrag(const CEvent&, void*); + + // clipboard check timer handler + void handleClipboardCheck(const CEvent&, void*); + + // Resolution switch callback + static pascal void displayManagerCallback(void* inUserData, + SInt16 inMessage, void* inNotifyData); + + // fast user switch callback + static pascal OSStatus + userSwitchCallback(EventHandlerCallRef nextHandler, + EventRef theEvent, void* inUserData); + + // sleep / wakeup support + void watchSystemPowerThread(void*); + static void testCanceled(CFRunLoopTimerRef timer, void*info); + static void powerChangeCallback(void* refcon, io_service_t service, + natural_t messageType, void* messageArgument); + void handlePowerChangeRequest(natural_t messageType, + void* messageArgument); + + static CEvent::Type getConfirmSleepEvent(); + void handleConfirmSleep(const CEvent& event, void*); + + // global hotkey operating mode + static bool isGlobalHotKeyOperatingModeAvailable(); + static void setGlobalHotKeysEnabled(bool enabled); + static bool getGlobalHotKeysEnabled(); + +private: + struct CHotKeyItem { + public: + CHotKeyItem(UInt32, UInt32); + CHotKeyItem(EventHotKeyRef, UInt32, UInt32); + + EventHotKeyRef getRef() const; + + bool operator<(const CHotKeyItem&) const; + + private: + EventHotKeyRef m_ref; + UInt32 m_keycode; + UInt32 m_mask; + }; + typedef std::map HotKeyMap; + typedef std::vector HotKeyIDList; + typedef std::map ModifierHotKeyMap; + typedef std::map HotKeyToIDMap; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // the display + CGDirectDisplayID m_displayID; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // mouse state + mutable SInt32 m_xCursor, m_yCursor; + mutable bool m_cursorPosValid; + mutable boolean_t m_buttons[5]; + bool m_cursorHidden; + SInt32 m_dragNumButtonsDown; + Point m_dragLastPoint; + CEventQueueTimer* m_dragTimer; + + // keyboard stuff + COSXKeyState* m_keyState; + + // clipboards + UInt32 m_sequenceNumber; + + // screen saver stuff + COSXScreenSaver* m_screensaver; + bool m_screensaverNotify; + + // clipboard stuff + bool m_ownClipboard; + CEventQueueTimer* m_clipboardTimer; + + // window object that gets user input events when the server + // has focus. + WindowRef m_hiddenWindow; + // window object that gets user input events when the server + // does not have focus. + WindowRef m_userInputWindow; + + // display manager stuff (to get screen resolution switches). + DMExtendedNotificationUPP m_displayManagerNotificationUPP; + ProcessSerialNumber m_PSN; + + // fast user switching + EventHandlerRef m_switchEventHandlerRef; + + // sleep / wakeup + CMutex* m_pmMutex; + CThread* m_pmWatchThread; + CCondVar* m_pmThreadReady; + CFRunLoopRef m_pmRunloop; + io_connect_t m_pmRootPort; + + // hot key stuff + HotKeyMap m_hotKeys; + HotKeyIDList m_oldHotKeyIDs; + ModifierHotKeyMap m_modifierHotKeys; + UInt32 m_activeModifierHotKey; + KeyModifierMask m_activeModifierHotKeyMask; + HotKeyToIDMap m_hotKeyToIDMap; + + // global hotkey operating mode + static bool s_testedForGHOM; + static bool s_hasGHOM; + + // events + static CEvent::Type s_confirmSleepEvent; +}; + +#endif diff --git a/lib/platform/COSXScreenSaver.cpp b/lib/platform/COSXScreenSaver.cpp new file mode 100644 index 00000000..9cc6a678 --- /dev/null +++ b/lib/platform/COSXScreenSaver.cpp @@ -0,0 +1,175 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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. + */ + +#import "COSXScreenSaver.h" +#import "COSXScreenSaverUtil.h" +#import "CLog.h" +#import "IEventQueue.h" +#import "IPrimaryScreen.h" +#import + +// +// COSXScreenSaver +// + +COSXScreenSaver::COSXScreenSaver(void* eventTarget) : + m_eventTarget(eventTarget), + m_enabled(true) +{ + m_autoReleasePool = screenSaverUtilCreatePool(); + m_screenSaverController = screenSaverUtilCreateController(); + + // install launch/termination event handlers + EventTypeSpec launchEventTypes[2]; + launchEventTypes[0].eventClass = kEventClassApplication; + launchEventTypes[0].eventKind = kEventAppLaunched; + launchEventTypes[1].eventClass = kEventClassApplication; + launchEventTypes[1].eventKind = kEventAppTerminated; + + EventHandlerUPP launchTerminationEventHandler = + NewEventHandlerUPP(launchTerminationCallback); + InstallApplicationEventHandler(launchTerminationEventHandler, 2, + launchEventTypes, this, + &m_launchTerminationEventHandlerRef); + DisposeEventHandlerUPP(launchTerminationEventHandler); + + m_screenSaverPSN.highLongOfPSN = 0; + m_screenSaverPSN.lowLongOfPSN = 0; + + // test if screensaver is running and find process number + if (isActive()) { + ProcessInfoRec procInfo; + Str31 procName; // pascal string. first byte holds length. + memset(&procInfo, 0, sizeof(procInfo)); + procInfo.processName = procName; + procInfo.processInfoLength = sizeof(ProcessInfoRec); + + ProcessSerialNumber psn; + OSErr err = GetNextProcess(&psn); + while (err == 0) { + memset(procName, 0, sizeof(procName)); + err = GetProcessInformation(&psn, &procInfo); + if (err != 0) { + break; + } + if (strcmp("ScreenSaverEngine", (const char*)&procName[1]) == 0) { + m_screenSaverPSN = psn; + break; + } + err = GetNextProcess(&psn); + } + } +} + +COSXScreenSaver::~COSXScreenSaver() +{ + RemoveEventHandler(m_launchTerminationEventHandlerRef); +// screenSaverUtilReleaseController(m_screenSaverController); + screenSaverUtilReleasePool(m_autoReleasePool); +} + +void +COSXScreenSaver::enable() +{ + m_enabled = true; + screenSaverUtilEnable(m_screenSaverController); +} + +void +COSXScreenSaver::disable() +{ + m_enabled = false; + screenSaverUtilDisable(m_screenSaverController); +} + +void +COSXScreenSaver::activate() +{ + screenSaverUtilActivate(m_screenSaverController); +} + +void +COSXScreenSaver::deactivate() +{ + screenSaverUtilDeactivate(m_screenSaverController, m_enabled); +} + +bool +COSXScreenSaver::isActive() const +{ + return (screenSaverUtilIsActive(m_screenSaverController) != 0); +} + +void +COSXScreenSaver::processLaunched(ProcessSerialNumber psn) +{ + CFStringRef processName; + OSStatus err = CopyProcessName(&psn, &processName); + + if (err == 0 && CFEqual(CFSTR("ScreenSaverEngine"), processName)) { + m_screenSaverPSN = psn; + LOG((CLOG_DEBUG1 "ScreenSaverEngine launched. Enabled=%d", m_enabled)); + if (m_enabled) { + EVENTQUEUE->addEvent( + CEvent(IPrimaryScreen::getScreensaverActivatedEvent(), + m_eventTarget)); + } + } +} + +void +COSXScreenSaver::processTerminated(ProcessSerialNumber psn) +{ + if (m_screenSaverPSN.highLongOfPSN == psn.highLongOfPSN && + m_screenSaverPSN.lowLongOfPSN == psn.lowLongOfPSN) { + LOG((CLOG_DEBUG1 "ScreenSaverEngine terminated. Enabled=%d", m_enabled)); + if (m_enabled) { + EVENTQUEUE->addEvent( + CEvent(IPrimaryScreen::getScreensaverDeactivatedEvent(), + m_eventTarget)); + } + + m_screenSaverPSN.highLongOfPSN = 0; + m_screenSaverPSN.lowLongOfPSN = 0; + } +} + +pascal OSStatus +COSXScreenSaver::launchTerminationCallback( + EventHandlerCallRef nextHandler, + EventRef theEvent, void* userData) +{ + OSStatus result; + ProcessSerialNumber psn; + EventParamType actualType; + UInt32 actualSize; + + result = GetEventParameter(theEvent, kEventParamProcessID, + typeProcessSerialNumber, &actualType, + sizeof(psn), &actualSize, &psn); + + if ((result == noErr) && + (actualSize > 0) && + (actualType == typeProcessSerialNumber)) { + COSXScreenSaver* screenSaver = (COSXScreenSaver*)userData; + UInt32 eventKind = GetEventKind(theEvent); + if (eventKind == kEventAppLaunched) { + screenSaver->processLaunched(psn); + } + else if (eventKind == kEventAppTerminated) { + screenSaver->processTerminated(psn); + } + } + return (CallNextEventHandler(nextHandler, theEvent)); +} diff --git a/lib/platform/COSXScreenSaver.h b/lib/platform/COSXScreenSaver.h new file mode 100644 index 00000000..40c4e742 --- /dev/null +++ b/lib/platform/COSXScreenSaver.h @@ -0,0 +1,54 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXSCREENSAVER_H +#define COSXSCREENSAVER_H + +#include "IScreenSaver.h" +#include + +//! OSX screen saver implementation +class COSXScreenSaver : public IScreenSaver { +public: + COSXScreenSaver(void* eventTarget); + virtual ~COSXScreenSaver(); + + // IScreenSaver overrides + virtual void enable(); + virtual void disable(); + virtual void activate(); + virtual void deactivate(); + virtual bool isActive() const; + +private: + void processLaunched(ProcessSerialNumber psn); + void processTerminated(ProcessSerialNumber psn); + + static pascal OSStatus + launchTerminationCallback( + EventHandlerCallRef nextHandler, + EventRef theEvent, void* userData); + +private: + // the target for the events we generate + void* m_eventTarget; + + bool m_enabled; + void* m_screenSaverController; + void* m_autoReleasePool; + EventHandlerRef m_launchTerminationEventHandlerRef; + ProcessSerialNumber m_screenSaverPSN; +}; + +#endif diff --git a/lib/platform/COSXScreenSaverUtil.h b/lib/platform/COSXScreenSaverUtil.h new file mode 100644 index 00000000..d4124ab3 --- /dev/null +++ b/lib/platform/COSXScreenSaverUtil.h @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXSCREENSAVERUTIL_H +#define COSXSCREENSAVERUTIL_H + +#include "common.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +void* screenSaverUtilCreatePool(); +void screenSaverUtilReleasePool(void*); + +void* screenSaverUtilCreateController(); +void screenSaverUtilReleaseController(void*); +void screenSaverUtilEnable(void*); +void screenSaverUtilDisable(void*); +void screenSaverUtilActivate(void*); +void screenSaverUtilDeactivate(void*, int isEnabled); +int screenSaverUtilIsActive(void*); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/lib/platform/COSXScreenSaverUtil.m b/lib/platform/COSXScreenSaverUtil.m new file mode 100644 index 00000000..1376d1f2 --- /dev/null +++ b/lib/platform/COSXScreenSaverUtil.m @@ -0,0 +1,81 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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. + */ + +#import "COSXScreenSaverUtil.h" +#import "OSXScreenSaverControl.h" +#import + +// +// screenSaverUtil functions +// +// Note: these helper functions exist only so we can avoid using ObjC++. +// autoconf/automake don't know about ObjC++ and I don't know how to +// teach them about it. +// + +void* +screenSaverUtilCreatePool() +{ + return [[NSAutoreleasePool alloc] init]; +} + +void +screenSaverUtilReleasePool(void* pool) +{ + [(NSAutoreleasePool*)pool release]; +} + +void* +screenSaverUtilCreateController() +{ + return [[ScreenSaverController controller] retain]; +} + +void +screenSaverUtilReleaseController(void* controller) +{ + [(ScreenSaverController*)controller release]; +} + +void +screenSaverUtilEnable(void* controller) +{ + [(ScreenSaverController*)controller setScreenSaverCanRun:YES]; +} + +void +screenSaverUtilDisable(void* controller) +{ + [(ScreenSaverController*)controller setScreenSaverCanRun:NO]; +} + +void +screenSaverUtilActivate(void* controller) +{ + [(ScreenSaverController*)controller setScreenSaverCanRun:YES]; + [(ScreenSaverController*)controller screenSaverStartNow]; +} + +void +screenSaverUtilDeactivate(void* controller, int isEnabled) +{ + [(ScreenSaverController*)controller screenSaverStopNow]; + [(ScreenSaverController*)controller setScreenSaverCanRun:isEnabled]; +} + +int +screenSaverUtilIsActive(void* controller) +{ + return [(ScreenSaverController*)controller screenSaverIsRunning]; +} diff --git a/lib/platform/CSynergyHook.cpp b/lib/platform/CSynergyHook.cpp new file mode 100644 index 00000000..f4deb476 --- /dev/null +++ b/lib/platform/CSynergyHook.cpp @@ -0,0 +1,1110 @@ +/* + * 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 "CSynergyHook.h" +#include "ProtocolTypes.h" +#include +#include + +#if _MSC_VER >= 1400 +// VS2005 hack - we don't use assert here because we don't want to link with the CRT. +#undef assert +#if _DEBUG +#define assert(_X_) if (!(_X_)) __debugbreak() +#else +#define assert(_X_) __noop() +#endif +// VS2005 is a bit more smart than VC6 and optimize simple copy loop to +// intrinsic memcpy. +#pragma function(memcpy) +#endif + +// +// debugging compile flag. when not zero the server doesn't grab +// the keyboard when the mouse leaves the server screen. this +// makes it possible to use the debugger (via the keyboard) when +// all user input would normally be caught by the hook procedures. +// +#define NO_GRAB_KEYBOARD 0 + +// +// debugging compile flag. when not zero the server will not +// install low level hooks. +// +#define NO_LOWLEVEL_HOOKS 0 + +// +// extra mouse wheel stuff +// + +enum EWheelSupport { + kWheelNone, + kWheelOld, + kWheelWin2000, + kWheelModern +}; + +// declare extended mouse hook struct. useable on win2k +typedef struct tagMOUSEHOOKSTRUCTWin2000 { + MOUSEHOOKSTRUCT mhs; + DWORD mouseData; +} MOUSEHOOKSTRUCTWin2000; + +#if !defined(SM_MOUSEWHEELPRESENT) +#define SM_MOUSEWHEELPRESENT 75 +#endif + +// X button stuff +#if !defined(WM_XBUTTONDOWN) +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define WM_NCXBUTTONDOWN 0x00AB +#define WM_NCXBUTTONUP 0x00AC +#define WM_NCXBUTTONDBLCLK 0x00AD +#define MOUSEEVENTF_XDOWN 0x0080 +#define MOUSEEVENTF_XUP 0x0100 +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif + + +// +// globals +// + +#if defined(_MSC_VER) +#pragma comment(linker, "-section:shared,rws") +#pragma data_seg("shared") +#endif +// all data in this shared section *must* be initialized + +static HINSTANCE g_hinstance = NULL; +static DWORD g_processID = 0; +static EWheelSupport g_wheelSupport = kWheelNone; +static UINT g_wmMouseWheel = 0; +static DWORD g_threadID = 0; +static HHOOK g_keyboard = NULL; +static HHOOK g_mouse = NULL; +static HHOOK g_getMessage = NULL; +static HHOOK g_keyboardLL = NULL; +static HHOOK g_mouseLL = NULL; +static bool g_screenSaver = false; +static EHookMode g_mode = kHOOK_DISABLE; +static UInt32 g_zoneSides = 0; +static SInt32 g_zoneSize = 0; +static SInt32 g_xScreen = 0; +static SInt32 g_yScreen = 0; +static SInt32 g_wScreen = 0; +static SInt32 g_hScreen = 0; +static WPARAM g_deadVirtKey = 0; +static LPARAM g_deadLParam = 0; +static BYTE g_deadKeyState[256] = { 0 }; +static DWORD g_hookThread = 0; +static DWORD g_attachedThread = 0; +static bool g_fakeInput = false; + +#if defined(_MSC_VER) +#pragma data_seg() +#endif + +// keep linker quiet about floating point stuff. we don't use any +// floating point operations but our includes may define some +// (unused) floating point values. +#ifndef _DEBUG +extern "C" { +int _fltused=0; +} +#endif + + +// +// internal functions +// + +static +void +detachThread() +{ + if (g_attachedThread != 0 && g_hookThread != g_attachedThread) { + AttachThreadInput(g_hookThread, g_attachedThread, FALSE); + g_attachedThread = 0; + } +} + +static +bool +attachThreadToForeground() +{ + // only attach threads if using low level hooks. a low level hook + // runs in the thread that installed the hook but we have to make + // changes that require being attached to the target thread (which + // should be the foreground window). a regular hook runs in the + // thread that just removed the event from its queue so we're + // already in the right thread. + if (g_hookThread != 0) { + HWND window = GetForegroundWindow(); + DWORD threadID = GetWindowThreadProcessId(window, NULL); + // skip if no change + if (g_attachedThread != threadID) { + // detach from previous thread + detachThread(); + + // attach to new thread + if (threadID != 0 && threadID != g_hookThread) { + AttachThreadInput(g_hookThread, threadID, TRUE); + g_attachedThread = threadID; + } + return true; + } + } + return false; +} + +#if !NO_GRAB_KEYBOARD +static +WPARAM +makeKeyMsg(UINT virtKey, char c, bool noAltGr) +{ + return MAKEWPARAM(MAKEWORD(virtKey & 0xff, (BYTE)c), noAltGr ? 1 : 0); +} + +static +void +keyboardGetState(BYTE keys[256]) +{ + // we have to use GetAsyncKeyState() rather than GetKeyState() because + // we don't pass through most keys so the event synchronous state + // doesn't get updated. we do that because certain modifier keys have + // side effects, like alt and the windows key. + SHORT key; + for (int i = 0; i < 256; ++i) { + key = GetAsyncKeyState(i); + keys[i] = (BYTE)((key < 0) ? 0x80u : 0); + } + key = GetKeyState(VK_CAPITAL); + keys[VK_CAPITAL] = (BYTE)(((key < 0) ? 0x80 : 0) | (key & 1)); +} + +static +bool +doKeyboardHookHandler(WPARAM wParam, LPARAM lParam) +{ + // check for special events indicating if we should start or stop + // passing events through and not report them to the server. this + // is used to allow the server to synthesize events locally but + // not pick them up as user events. + if (wParam == SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY && + ((lParam >> 16) & 0xffu) == SYNERGY_HOOK_FAKE_INPUT_SCANCODE) { + // update flag + g_fakeInput = ((lParam & 0x80000000u) == 0); + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + 0xff000000u | wParam, lParam); + + // discard event + return true; + } + + // if we're expecting fake input then just pass the event through + // and do not forward to the server + if (g_fakeInput) { + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + 0xfe000000u | wParam, lParam); + return false; + } + + // VK_RSHIFT may be sent with an extended scan code but right shift + // is not an extended key so we reset that bit. + if (wParam == VK_RSHIFT) { + lParam &= ~0x01000000u; + } + + // tell server about event + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, wParam, lParam); + + // ignore dead key release + if (g_deadVirtKey == wParam && + (lParam & 0x80000000u) != 0) { + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | 0x04000000, lParam); + return false; + } + + // we need the keyboard state for ToAscii() + BYTE keys[256]; + keyboardGetState(keys); + + // ToAscii() maps ctrl+letter to the corresponding control code + // and ctrl+backspace to delete. we don't want those translations + // so clear the control modifier state. however, if we want to + // simulate AltGr (which is ctrl+alt) then we must not clear it. + UINT control = keys[VK_CONTROL] | keys[VK_LCONTROL] | keys[VK_RCONTROL]; + UINT menu = keys[VK_MENU] | keys[VK_LMENU] | keys[VK_RMENU]; + if ((control & 0x80) == 0 || (menu & 0x80) == 0) { + keys[VK_LCONTROL] = 0; + keys[VK_RCONTROL] = 0; + keys[VK_CONTROL] = 0; + } + else { + keys[VK_LCONTROL] = 0x80; + keys[VK_RCONTROL] = 0x80; + keys[VK_CONTROL] = 0x80; + keys[VK_LMENU] = 0x80; + keys[VK_RMENU] = 0x80; + keys[VK_MENU] = 0x80; + } + + // ToAscii() needs to know if a menu is active for some reason. + // we don't know and there doesn't appear to be any way to find + // out. so we'll just assume a menu is active if the menu key + // is down. + // FIXME -- figure out some way to check if a menu is active + UINT flags = 0; + if ((menu & 0x80) != 0) + flags |= 1; + + // if we're on the server screen then just pass numpad keys with alt + // key down as-is. we won't pick up the resulting character but the + // local app will. if on a client screen then grab keys as usual; + // if the client is a windows system it'll synthesize the expected + // character. if not then it'll probably just do nothing. + if (g_mode != kHOOK_RELAY_EVENTS) { + // we don't use virtual keys because we don't know what the + // state of the numlock key is. we'll hard code the scan codes + // instead. hopefully this works across all keyboards. + UINT sc = (lParam & 0x01ff0000u) >> 16; + if (menu && + (sc >= 0x47u && sc <= 0x52u && sc != 0x4au && sc != 0x4eu)) { + return false; + } + } + + // map the key event to a character. we have to put the dead + // key back first and this has the side effect of removing it. + if (g_deadVirtKey != 0) { + WORD c = 0; + ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, + g_deadKeyState, &c, flags); + } + WORD c = 0; + UINT scanCode = ((lParam & 0x10ff0000u) >> 16); + int n = ToAscii(wParam, scanCode, keys, &c, flags); + + // if mapping failed and ctrl and alt are pressed then try again + // with both not pressed. this handles the case where ctrl and + // alt are being used as individual modifiers rather than AltGr. + // we note that's the case in the message sent back to synergy + // because there's no simple way to deduce it after the fact. + // we have to put the dead key back first, if there was one. + bool noAltGr = false; + if (n == 0 && (control & 0x80) != 0 && (menu & 0x80) != 0) { + noAltGr = true; + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | 0x05000000, lParam); + if (g_deadVirtKey != 0) { + ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, + g_deadKeyState, &c, flags); + } + BYTE keys2[256]; + for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) { + keys2[i] = keys[i]; + } + keys2[VK_LCONTROL] = 0; + keys2[VK_RCONTROL] = 0; + keys2[VK_CONTROL] = 0; + keys2[VK_LMENU] = 0; + keys2[VK_RMENU] = 0; + keys2[VK_MENU] = 0; + n = ToAscii(wParam, scanCode, keys2, &c, flags); + } + + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | ((c & 0xff) << 8) | + ((n & 0xff) << 16) | 0x06000000, + lParam); + WPARAM charAndVirtKey = 0; + bool clearDeadKey = false; + switch (n) { + default: + // key is a dead key + g_deadVirtKey = wParam; + g_deadLParam = lParam; + for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) { + g_deadKeyState[i] = keys[i]; + } + break; + + case 0: + // key doesn't map to a character. this can happen if + // non-character keys are pressed after a dead key. + charAndVirtKey = makeKeyMsg(wParam, (char)0, noAltGr); + break; + + case 1: + // key maps to a character composed with dead key + charAndVirtKey = makeKeyMsg(wParam, (char)LOBYTE(c), noAltGr); + clearDeadKey = true; + break; + + case 2: { + // previous dead key not composed. send a fake key press + // and release for the dead key to our window. + WPARAM deadCharAndVirtKey = + makeKeyMsg(g_deadVirtKey, (char)LOBYTE(c), noAltGr); + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, + deadCharAndVirtKey, g_deadLParam & 0x7fffffffu); + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, + deadCharAndVirtKey, g_deadLParam | 0x80000000u); + + // use uncomposed character + charAndVirtKey = makeKeyMsg(wParam, (char)HIBYTE(c), noAltGr); + clearDeadKey = true; + break; + } + } + + // put back the dead key, if any, for the application to use + if (g_deadVirtKey != 0) { + ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, + g_deadKeyState, &c, flags); + } + + // clear out old dead key state + if (clearDeadKey) { + g_deadVirtKey = 0; + g_deadLParam = 0; + } + + // forward message to our window. do this whether or not we're + // forwarding events to clients because this'll keep our thread's + // key state table up to date. that's important for querying + // the scroll lock toggle state. + // XXX -- with hot keys for actions we may only need to do this when + // forwarding. + if (charAndVirtKey != 0) { + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + charAndVirtKey | 0x07000000, lParam); + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, charAndVirtKey, lParam); + } + + if (g_mode == kHOOK_RELAY_EVENTS) { + // let certain keys pass through + switch (wParam) { + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + // pass event on. we want to let these through to + // the window proc because otherwise the keyboard + // lights may not stay synchronized. + break; + + case VK_HANGUL: + // pass these modifiers if using a low level hook, discard + // them if not. + if (g_hookThread == 0) { + return true; + } + break; + + default: + // discard + return true; + } + } + + return false; +} + +static +bool +keyboardHookHandler(WPARAM wParam, LPARAM lParam) +{ + attachThreadToForeground(); + return doKeyboardHookHandler(wParam, lParam); +} +#endif + +static +bool +doMouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data) +{ + switch (wParam) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_XBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_XBUTTONDBLCLK: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + case WM_NCLBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCXBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCMBUTTONDBLCLK: + case WM_NCRBUTTONDBLCLK: + case WM_NCXBUTTONDBLCLK: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONUP: + case WM_NCRBUTTONUP: + case WM_NCXBUTTONUP: + // always relay the event. eat it if relaying. + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_BUTTON, wParam, data); + return (g_mode == kHOOK_RELAY_EVENTS); + + case WM_MOUSEWHEEL: + if (g_mode == kHOOK_RELAY_EVENTS) { + // relay event + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, data, 0); + } + return (g_mode == kHOOK_RELAY_EVENTS); + + case WM_NCMOUSEMOVE: + case WM_MOUSEMOVE: + if (g_mode == kHOOK_RELAY_EVENTS) { + // relay and eat event + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); + return true; + } + else if (g_mode == kHOOK_WATCH_JUMP_ZONE) { + // low level hooks can report bogus mouse positions that are + // outside of the screen. jeez. naturally we end up getting + // fake motion in the other direction to get the position back + // on the screen, which plays havoc with switch on double tap. + // CServer deals with that. we'll clamp positions onto the + // screen. also, if we discard events for positions outside + // of the screen then the mouse appears to get a bit jerky + // near the edge. we can either accept that or pass the bogus + // events. we'll try passing the events. + bool bogus = false; + if (x < g_xScreen) { + x = g_xScreen; + bogus = true; + } + else if (x >= g_xScreen + g_wScreen) { + x = g_xScreen + g_wScreen - 1; + bogus = true; + } + if (y < g_yScreen) { + y = g_yScreen; + bogus = true; + } + else if (y >= g_yScreen + g_hScreen) { + y = g_yScreen + g_hScreen - 1; + bogus = true; + } + + // check for mouse inside jump zone + bool inside = false; + if (!inside && (g_zoneSides & kLeftMask) != 0) { + inside = (x < g_xScreen + g_zoneSize); + } + if (!inside && (g_zoneSides & kRightMask) != 0) { + inside = (x >= g_xScreen + g_wScreen - g_zoneSize); + } + if (!inside && (g_zoneSides & kTopMask) != 0) { + inside = (y < g_yScreen + g_zoneSize); + } + if (!inside && (g_zoneSides & kBottomMask) != 0) { + inside = (y >= g_yScreen + g_hScreen - g_zoneSize); + } + + // relay the event + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); + + // if inside and not bogus then eat the event + return inside && !bogus; + } + } + + // pass the event + return false; +} + +static +bool +mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data) +{ +// attachThreadToForeground(); + return doMouseHookHandler(wParam, x, y, data); +} + +#if !NO_GRAB_KEYBOARD +static +LRESULT CALLBACK +keyboardHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + // handle the message + if (keyboardHookHandler(wParam, lParam)) { + return 1; + } + } + + return CallNextHookEx(g_keyboard, code, wParam, lParam); +} +#endif + +static +LRESULT CALLBACK +mouseHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + // decode message + const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam; + SInt32 x = (SInt32)info->pt.x; + SInt32 y = (SInt32)info->pt.y; + SInt32 w = 0; + if (wParam == WM_MOUSEWHEEL) { + // win2k and other systems supporting WM_MOUSEWHEEL in + // the mouse hook are gratuitously different (and poorly + // documented). if a low-level mouse hook is in place + // it should capture these events so we'll never see + // them. + switch (g_wheelSupport) { + case kWheelModern: + w = static_cast(LOWORD(info->dwExtraInfo)); + break; + + case kWheelWin2000: { + const MOUSEHOOKSTRUCTWin2000* info2k = + (const MOUSEHOOKSTRUCTWin2000*)lParam; + w = static_cast(HIWORD(info2k->mouseData)); + break; + } + + default: + break; + } + } + + // handle the message. note that we don't handle X buttons + // here. that's okay because they're only supported on + // win2k and winxp and up and on those platforms we'll get + // get the mouse events through the low level hook. + if (mouseHookHandler(wParam, x, y, w)) { + return 1; + } + } + + return CallNextHookEx(g_mouse, code, wParam, lParam); +} + +static +LRESULT CALLBACK +getMessageHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + if (g_screenSaver) { + MSG* msg = reinterpret_cast(lParam); + if (msg->message == WM_SYSCOMMAND && + msg->wParam == SC_SCREENSAVE) { + // broadcast screen saver started message + PostThreadMessage(g_threadID, + SYNERGY_MSG_SCREEN_SAVER, TRUE, 0); + } + } + if (g_mode == kHOOK_RELAY_EVENTS) { + MSG* msg = reinterpret_cast(lParam); + if (g_wheelSupport == kWheelOld && msg->message == g_wmMouseWheel) { + // post message to our window + PostThreadMessage(g_threadID, + SYNERGY_MSG_MOUSE_WHEEL, + static_cast(msg->wParam & 0xffffu), 0); + + // zero out the delta in the message so it's (hopefully) + // ignored + msg->wParam = 0; + } + } + } + + return CallNextHookEx(g_getMessage, code, wParam, lParam); +} + +#if (_WIN32_WINNT >= 0x0400) && defined(_MSC_VER) && !NO_LOWLEVEL_HOOKS + +// +// low-level keyboard hook -- this allows us to capture and handle +// alt+tab, alt+esc, ctrl+esc, and windows key hot keys. on the down +// side, key repeats are not reported to us. +// + +#if !NO_GRAB_KEYBOARD +static +LRESULT CALLBACK +keyboardLLHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + // decode the message + KBDLLHOOKSTRUCT* info = reinterpret_cast(lParam); + WPARAM wParam = info->vkCode; + LPARAM lParam = 1; // repeat code + lParam |= (info->scanCode << 16); // scan code + if (info->flags & LLKHF_EXTENDED) { + lParam |= (1lu << 24); // extended key + } + if (info->flags & LLKHF_ALTDOWN) { + lParam |= (1lu << 29); // context code + } + if (info->flags & LLKHF_UP) { + lParam |= (1lu << 31); // transition + } + // FIXME -- bit 30 should be set if key was already down but + // we don't know that info. as a result we'll never generate + // key repeat events. + + // handle the message + if (keyboardHookHandler(wParam, lParam)) { + return 1; + } + } + + return CallNextHookEx(g_keyboardLL, code, wParam, lParam); +} +#endif + +// +// low-level mouse hook -- this allows us to capture and handle mouse +// events very early. the earlier the better. +// + +static +LRESULT CALLBACK +mouseLLHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + // decode the message + MSLLHOOKSTRUCT* info = reinterpret_cast(lParam); + SInt32 x = static_cast(info->pt.x); + SInt32 y = static_cast(info->pt.y); + SInt32 w = static_cast(HIWORD(info->mouseData)); + + // handle the message + if (mouseHookHandler(wParam, x, y, w)) { + return 1; + } + } + + return CallNextHookEx(g_mouseLL, code, wParam, lParam); +} + +#endif + +static +EWheelSupport +getWheelSupport() +{ + // get operating system + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(info); + if (!GetVersionEx(&info)) { + return kWheelNone; + } + + // see if modern wheel is present + if (GetSystemMetrics(SM_MOUSEWHEELPRESENT)) { + // note if running on win2k + if (info.dwPlatformId == VER_PLATFORM_WIN32_NT && + info.dwMajorVersion == 5 && + info.dwMinorVersion == 0) { + return kWheelWin2000; + } + return kWheelModern; + } + + // not modern. see if we've got old-style support. +#if defined(MSH_WHEELSUPPORT) + UINT wheelSupportMsg = RegisterWindowMessage(MSH_WHEELSUPPORT); + HWND wheelSupportWindow = FindWindow(MSH_WHEELMODULE_CLASS, + MSH_WHEELMODULE_TITLE); + if (wheelSupportWindow != NULL && wheelSupportMsg != 0) { + if (SendMessage(wheelSupportWindow, wheelSupportMsg, 0, 0) != 0) { + g_wmMouseWheel = RegisterWindowMessage(MSH_MOUSEWHEEL); + if (g_wmMouseWheel != 0) { + return kWheelOld; + } + } + } +#endif + + // assume modern. we don't do anything special in this case + // except respond to WM_MOUSEWHEEL messages. GetSystemMetrics() + // can apparently return FALSE even if a mouse wheel is present + // though i'm not sure exactly when it does that (WinME returns + // FALSE for my logitech USB trackball). + return kWheelModern; +} + + +// +// external functions +// + +BOOL WINAPI +DllMain(HINSTANCE instance, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) { + DisableThreadLibraryCalls(instance); + if (g_processID == 0) { + g_hinstance = instance; + g_processID = GetCurrentProcessId(); + } + } + else if (reason == DLL_PROCESS_DETACH) { + if (g_processID == GetCurrentProcessId()) { + uninstall(); + uninstallScreenSaver(); + g_processID = 0; + g_hinstance = NULL; + } + } + return TRUE; +} + +extern "C" { + +// VS2005 hack to not link with the CRT +#if _MSC_VER >= 1400 +BOOL WINAPI _DllMainCRTStartup( + HINSTANCE instance, DWORD reason, LPVOID lpreserved) +{ + return DllMain(instance, reason, lpreserved); +} + +// VS2005 is a bit more bright than VC6 and optimize simple copy loop to +// intrinsic memcpy. +void * __cdecl memcpy(void * _Dst, const void * _Src, size_t _MaxCount) +{ + void * _DstBackup = _Dst; + switch (_MaxCount & 3) { + case 3: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + --_MaxCount; + case 2: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + --_MaxCount; + case 1: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + --_MaxCount; + break; + case 0: + break; + + default: + __assume(0); + break; + } + + // I think it's faster on intel to deference than modify the pointer. + const size_t max = _MaxCount / sizeof(UINT_PTR); + for (size_t i = 0; i < max; ++i) { + ((UINT_PTR*)_Dst)[i] = ((UINT_PTR*)_Src)[i]; + } + + (UINT_PTR*&)_Dst += max; + (UINT_PTR*&)_Src += max; + + switch (_MaxCount & 3) { + case 3: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + case 2: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + case 1: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + break; + case 0: + break; + + default: + __assume(0); + break; + } + + return _DstBackup; +} +#endif + +int +init(DWORD threadID) +{ + assert(g_hinstance != NULL); + + // try to open process that last called init() to see if it's + // still running or if it died without cleaning up. + if (g_processID != 0 && g_processID != GetCurrentProcessId()) { + HANDLE process = OpenProcess(STANDARD_RIGHTS_REQUIRED, + FALSE, g_processID); + if (process != NULL) { + // old process (probably) still exists so refuse to + // reinitialize this DLL (and thus steal it from the + // old process). + CloseHandle(process); + return 0; + } + + // clean up after old process. the system should've already + // removed the hooks so we just need to reset our state. + g_hinstance = GetModuleHandle(_T("synrgyhk")); + g_processID = GetCurrentProcessId(); + g_wheelSupport = kWheelNone; + g_threadID = 0; + g_keyboard = NULL; + g_mouse = NULL; + g_getMessage = NULL; + g_keyboardLL = NULL; + g_mouseLL = NULL; + g_screenSaver = false; + } + + // save thread id. we'll post messages to this thread's + // message queue. + g_threadID = threadID; + + // set defaults + g_mode = kHOOK_DISABLE; + g_zoneSides = 0; + g_zoneSize = 0; + g_xScreen = 0; + g_yScreen = 0; + g_wScreen = 0; + g_hScreen = 0; + + return 1; +} + +int +cleanup(void) +{ + assert(g_hinstance != NULL); + + if (g_processID == GetCurrentProcessId()) { + g_threadID = 0; + } + + return 1; +} + +EHookResult +install() +{ + assert(g_hinstance != NULL); + assert(g_keyboard == NULL); + assert(g_mouse == NULL); + assert(g_getMessage == NULL || g_screenSaver); + + // must be initialized + if (g_threadID == 0) { + return kHOOK_FAILED; + } + + // discard old dead keys + g_deadVirtKey = 0; + g_deadLParam = 0; + + // reset fake input flag + g_fakeInput = false; + + // check for mouse wheel support + g_wheelSupport = getWheelSupport(); + + // install GetMessage hook (unless already installed) + if (g_wheelSupport == kWheelOld && g_getMessage == NULL) { + g_getMessage = SetWindowsHookEx(WH_GETMESSAGE, + &getMessageHook, + g_hinstance, + 0); + } + + // install low-level hooks. we require that they both get installed. +#if (_WIN32_WINNT >= 0x0400) && defined(_MSC_VER) && !NO_LOWLEVEL_HOOKS + g_mouseLL = SetWindowsHookEx(WH_MOUSE_LL, + &mouseLLHook, + g_hinstance, + 0); +#if !NO_GRAB_KEYBOARD + g_keyboardLL = SetWindowsHookEx(WH_KEYBOARD_LL, + &keyboardLLHook, + g_hinstance, + 0); + if (g_mouseLL == NULL || g_keyboardLL == NULL) { + if (g_keyboardLL != NULL) { + UnhookWindowsHookEx(g_keyboardLL); + g_keyboardLL = NULL; + } + if (g_mouseLL != NULL) { + UnhookWindowsHookEx(g_mouseLL); + g_mouseLL = NULL; + } + } +#endif +#endif + + // install regular hooks + if (g_mouseLL == NULL) { + g_mouse = SetWindowsHookEx(WH_MOUSE, + &mouseHook, + g_hinstance, + 0); + } +#if !NO_GRAB_KEYBOARD + if (g_keyboardLL == NULL) { + g_keyboard = SetWindowsHookEx(WH_KEYBOARD, + &keyboardHook, + g_hinstance, + 0); + } +#endif + + // check that we got all the hooks we wanted + if ((g_getMessage == NULL && g_wheelSupport == kWheelOld) || +#if !NO_GRAB_KEYBOARD + (g_keyboardLL == NULL && g_keyboard == NULL) || +#endif + (g_mouseLL == NULL && g_mouse == NULL)) { + uninstall(); + return kHOOK_FAILED; + } + + if (g_keyboardLL != NULL || g_mouseLL != NULL) { + g_hookThread = GetCurrentThreadId(); + return kHOOK_OKAY_LL; + } + + return kHOOK_OKAY; +} + +int +uninstall(void) +{ + assert(g_hinstance != NULL); + + // discard old dead keys + g_deadVirtKey = 0; + g_deadLParam = 0; + + // detach from thread + detachThread(); + + // uninstall hooks + if (g_keyboardLL != NULL) { + UnhookWindowsHookEx(g_keyboardLL); + g_keyboardLL = NULL; + } + if (g_mouseLL != NULL) { + UnhookWindowsHookEx(g_mouseLL); + g_mouseLL = NULL; + } + if (g_keyboard != NULL) { + UnhookWindowsHookEx(g_keyboard); + g_keyboard = NULL; + } + if (g_mouse != NULL) { + UnhookWindowsHookEx(g_mouse); + g_mouse = NULL; + } + if (g_getMessage != NULL && !g_screenSaver) { + UnhookWindowsHookEx(g_getMessage); + g_getMessage = NULL; + } + g_wheelSupport = kWheelNone; + + return 1; +} + +int +installScreenSaver(void) +{ + assert(g_hinstance != NULL); + + // must be initialized + if (g_threadID == 0) { + return 0; + } + + // generate screen saver messages + g_screenSaver = true; + + // install hook unless it's already installed + if (g_getMessage == NULL) { + g_getMessage = SetWindowsHookEx(WH_GETMESSAGE, + &getMessageHook, + g_hinstance, + 0); + } + + return (g_getMessage != NULL) ? 1 : 0; +} + +int +uninstallScreenSaver(void) +{ + assert(g_hinstance != NULL); + + // uninstall hook unless the mouse wheel hook is installed + if (g_getMessage != NULL && g_wheelSupport != kWheelOld) { + UnhookWindowsHookEx(g_getMessage); + g_getMessage = NULL; + } + + // screen saver hook is no longer installed + g_screenSaver = false; + + return 1; +} + +void +setSides(UInt32 sides) +{ + g_zoneSides = sides; +} + +void +setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize) +{ + g_zoneSize = jumpZoneSize; + g_xScreen = x; + g_yScreen = y; + g_wScreen = w; + g_hScreen = h; +} + +void +setMode(EHookMode mode) +{ + if (mode == g_mode) { + // no change + return; + } + g_mode = mode; +} + +} diff --git a/lib/platform/CSynergyHook.h b/lib/platform/CSynergyHook.h new file mode 100644 index 00000000..e8661815 --- /dev/null +++ b/lib/platform/CSynergyHook.h @@ -0,0 +1,81 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSYNERGYHOOK_H +#define CSYNERGYHOOK_H + +#include "BasicTypes.h" +#define WIN32_LEAN_AND_MEAN +#include + +#if defined(SYNRGYHK_EXPORTS) +#define CSYNERGYHOOK_API __declspec(dllexport) +#else +#define CSYNERGYHOOK_API __declspec(dllimport) +#endif + +#define SYNERGY_MSG_MARK WM_APP + 0x0011 // mark id; +#define SYNERGY_MSG_KEY WM_APP + 0x0012 // vk code; key data +#define SYNERGY_MSG_MOUSE_BUTTON WM_APP + 0x0013 // button msg; +#define SYNERGY_MSG_MOUSE_WHEEL WM_APP + 0x0014 // delta; +#define SYNERGY_MSG_MOUSE_MOVE WM_APP + 0x0015 // x; y +#define SYNERGY_MSG_POST_WARP WM_APP + 0x0016 // ; +#define SYNERGY_MSG_PRE_WARP WM_APP + 0x0017 // x; y +#define SYNERGY_MSG_SCREEN_SAVER WM_APP + 0x0018 // activated; +#define SYNERGY_MSG_DEBUG WM_APP + 0x0019 // data, data +#define SYNERGY_MSG_INPUT_FIRST SYNERGY_MSG_KEY +#define SYNERGY_MSG_INPUT_LAST SYNERGY_MSG_PRE_WARP +#define SYNERGY_HOOK_LAST_MSG SYNERGY_MSG_DEBUG + +#define SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY VK_CANCEL +#define SYNERGY_HOOK_FAKE_INPUT_SCANCODE 0 + +extern "C" { + +enum EHookResult { + kHOOK_FAILED, + kHOOK_OKAY, + kHOOK_OKAY_LL +}; + +enum EHookMode { + kHOOK_DISABLE, + kHOOK_WATCH_JUMP_ZONE, + kHOOK_RELAY_EVENTS +}; + +typedef int (*InitFunc)(DWORD targetQueueThreadID); +typedef int (*CleanupFunc)(void); +typedef EHookResult (*InstallFunc)(void); +typedef int (*UninstallFunc)(void); +typedef int (*InstallScreenSaverFunc)(void); +typedef int (*UninstallScreenSaverFunc)(void); +typedef void (*SetSidesFunc)(UInt32); +typedef void (*SetZoneFunc)(SInt32, SInt32, SInt32, SInt32, SInt32); +typedef void (*SetModeFunc)(int); + +CSYNERGYHOOK_API int init(DWORD); +CSYNERGYHOOK_API int cleanup(void); +CSYNERGYHOOK_API EHookResult install(void); +CSYNERGYHOOK_API int uninstall(void); +CSYNERGYHOOK_API int installScreenSaver(void); +CSYNERGYHOOK_API int uninstallScreenSaver(void); +CSYNERGYHOOK_API void setSides(UInt32 sides); +CSYNERGYHOOK_API void setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, + SInt32 jumpZoneSize); +CSYNERGYHOOK_API void setMode(EHookMode mode); + +} + +#endif diff --git a/lib/platform/CXWindowsClipboard.cpp b/lib/platform/CXWindowsClipboard.cpp new file mode 100644 index 00000000..33252af9 --- /dev/null +++ b/lib/platform/CXWindowsClipboard.cpp @@ -0,0 +1,1507 @@ +/* + * 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 "CXWindowsClipboard.h" +#include "CXWindowsClipboardTextConverter.h" +#include "CXWindowsClipboardUCS2Converter.h" +#include "CXWindowsClipboardUTF8Converter.h" +#include "CXWindowsClipboardHTMLConverter.h" +#include "CXWindowsClipboardBMPConverter.h" +#include "CXWindowsUtil.h" +#include "CThread.h" +#include "CLog.h" +#include "CStopwatch.h" +#include "CArch.h" +#include "stdvector.h" +#include +#include + +// +// CXWindowsClipboard +// + +CXWindowsClipboard::CXWindowsClipboard(Display* display, + Window window, ClipboardID id) : + m_display(display), + m_window(window), + m_id(id), + m_open(false), + m_time(0), + m_owner(false), + m_timeOwned(0), + m_timeLost(0) +{ + // get some atoms + m_atomTargets = XInternAtom(m_display, "TARGETS", False); + m_atomMultiple = XInternAtom(m_display, "MULTIPLE", False); + m_atomTimestamp = XInternAtom(m_display, "TIMESTAMP", False); + m_atomInteger = XInternAtom(m_display, "INTEGER", False); + m_atomAtom = XInternAtom(m_display, "ATOM", False); + m_atomAtomPair = XInternAtom(m_display, "ATOM_PAIR", False); + m_atomData = XInternAtom(m_display, "CLIP_TEMPORARY", False); + m_atomINCR = XInternAtom(m_display, "INCR", False); + m_atomMotifClipLock = XInternAtom(m_display, "_MOTIF_CLIP_LOCK", False); + m_atomMotifClipHeader = XInternAtom(m_display, "_MOTIF_CLIP_HEADER", False); + m_atomMotifClipAccess = XInternAtom(m_display, + "_MOTIF_CLIP_LOCK_ACCESS_VALID", False); + m_atomGDKSelection = XInternAtom(m_display, "GDK_SELECTION", False); + + // set selection atom based on clipboard id + switch (id) { + case kClipboardClipboard: + m_selection = XInternAtom(m_display, "CLIPBOARD", False); + break; + + case kClipboardSelection: + default: + m_selection = XA_PRIMARY; + break; + } + + // add converters, most desired first + m_converters.push_back(new CXWindowsClipboardHTMLConverter(m_display, + "text/html")); + m_converters.push_back(new CXWindowsClipboardBMPConverter(m_display)); + m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display, + "text/plain;charset=UTF-8")); + m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display, + "UTF8_STRING")); + m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display, + "text/plain;charset=ISO-10646-UCS-2")); + m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display, + "text/unicode")); + m_converters.push_back(new CXWindowsClipboardTextConverter(m_display, + "text/plain")); + m_converters.push_back(new CXWindowsClipboardTextConverter(m_display, + "STRING")); + + // we have no data + clearCache(); +} + +CXWindowsClipboard::~CXWindowsClipboard() +{ + clearReplies(); + clearConverters(); +} + +void +CXWindowsClipboard::lost(Time time) +{ + LOG((CLOG_DEBUG "lost clipboard %d ownership at %d", m_id, time)); + if (m_owner) { + m_owner = false; + m_timeLost = time; + clearCache(); + } +} + +void +CXWindowsClipboard::addRequest(Window owner, Window requestor, + Atom target, ::Time time, Atom property) +{ + // must be for our window and we must have owned the selection + // at the given time. + bool success = false; + if (owner == m_window) { + LOG((CLOG_DEBUG1 "request for clipboard %d, target %s by 0x%08x (property=%s)", m_selection, CXWindowsUtil::atomToString(m_display, target).c_str(), requestor, CXWindowsUtil::atomToString(m_display, property).c_str())); + if (wasOwnedAtTime(time)) { + if (target == m_atomMultiple) { + // add a multiple request. property may not be None + // according to ICCCM. + if (property != None) { + success = insertMultipleReply(requestor, time, property); + } + } + else { + addSimpleRequest(requestor, target, time, property); + + // addSimpleRequest() will have already handled failure + success = true; + } + } + else { + LOG((CLOG_DEBUG1 "failed, not owned at time %d", time)); + } + } + + if (!success) { + // send failure + LOG((CLOG_DEBUG1 "failed")); + insertReply(new CReply(requestor, target, time)); + } + + // send notifications that are pending + pushReplies(); +} + +bool +CXWindowsClipboard::addSimpleRequest(Window requestor, + Atom target, ::Time time, Atom property) +{ + // obsolete requestors may supply a None property. in + // that case we use the target as the property to store + // the conversion. + if (property == None) { + property = target; + } + + // handle targets + CString data; + Atom type = None; + int format = 0; + if (target == m_atomTargets) { + type = getTargetsData(data, &format); + } + else if (target == m_atomTimestamp) { + type = getTimestampData(data, &format); + } + else { + IXWindowsClipboardConverter* converter = getConverter(target); + if (converter != NULL) { + IClipboard::EFormat clipboardFormat = converter->getFormat(); + if (m_added[clipboardFormat]) { + try { + data = converter->fromIClipboard(m_data[clipboardFormat]); + format = converter->getDataSize(); + type = converter->getAtom(); + } + catch (...) { + // ignore -- cannot convert + } + } + } + } + + if (type != None) { + // success + LOG((CLOG_DEBUG1 "success")); + insertReply(new CReply(requestor, target, time, + property, data, type, format)); + return true; + } + else { + // failure + LOG((CLOG_DEBUG1 "failed")); + insertReply(new CReply(requestor, target, time)); + return false; + } +} + +bool +CXWindowsClipboard::processRequest(Window requestor, + ::Time /*time*/, Atom property) +{ + CReplyMap::iterator index = m_replies.find(requestor); + if (index == m_replies.end()) { + // unknown requestor window + return false; + } + LOG((CLOG_DEBUG1 "received property %s delete from 0x08%x", CXWindowsUtil::atomToString(m_display, property).c_str(), requestor)); + + // find the property in the known requests. it should be the + // first property but we'll check 'em all if we have to. + CReplyList& replies = index->second; + for (CReplyList::iterator index2 = replies.begin(); + index2 != replies.end(); ++index2) { + CReply* reply = *index2; + if (reply->m_replied && reply->m_property == property) { + // if reply is complete then remove it and start the + // next one. + pushReplies(index, replies, index2); + return true; + } + } + + return false; +} + +bool +CXWindowsClipboard::destroyRequest(Window requestor) +{ + CReplyMap::iterator index = m_replies.find(requestor); + if (index == m_replies.end()) { + // unknown requestor window + return false; + } + + // destroy all replies for this window + clearReplies(index->second); + m_replies.erase(index); + + // note -- we don't stop watching the window for events because + // we're called in response to the window being destroyed. + + return true; +} + +Window +CXWindowsClipboard::getWindow() const +{ + return m_window; +} + +Atom +CXWindowsClipboard::getSelection() const +{ + return m_selection; +} + +bool +CXWindowsClipboard::empty() +{ + assert(m_open); + + LOG((CLOG_DEBUG "empty clipboard %d", m_id)); + + // assert ownership of clipboard + XSetSelectionOwner(m_display, m_selection, m_window, m_time); + if (XGetSelectionOwner(m_display, m_selection) != m_window) { + LOG((CLOG_DEBUG "failed to grab clipboard %d", m_id)); + return false; + } + + // clear all data. since we own the data now, the cache is up + // to date. + clearCache(); + m_cached = true; + + // FIXME -- actually delete motif clipboard items? + // FIXME -- do anything to motif clipboard properties? + + // save time + m_timeOwned = m_time; + m_timeLost = 0; + + // we're the owner now + m_owner = true; + LOG((CLOG_DEBUG "grabbed clipboard %d", m_id)); + + return true; +} + +void +CXWindowsClipboard::add(EFormat format, const CString& data) +{ + assert(m_open); + assert(m_owner); + + LOG((CLOG_DEBUG "add %d bytes to clipboard %d format: %d", data.size(), m_id, format)); + + m_data[format] = data; + m_added[format] = true; + + // FIXME -- set motif clipboard item? +} + +bool +CXWindowsClipboard::open(Time time) const +{ + assert(!m_open); + + LOG((CLOG_DEBUG "open clipboard %d", m_id)); + + // assume not motif + m_motif = false; + + // lock clipboard + if (m_id == kClipboardClipboard) { + if (!motifLockClipboard()) { + return false; + } + + // check if motif owns the selection. unlock motif clipboard + // if it does not. + m_motif = motifOwnsClipboard(); + LOG((CLOG_DEBUG1 "motif does %sown clipboard", m_motif ? "" : "not ")); + if (!m_motif) { + motifUnlockClipboard(); + } + } + + // now open + m_open = true; + m_time = time; + + // be sure to flush the cache later if it's dirty + m_checkCache = true; + + return true; +} + +void +CXWindowsClipboard::close() const +{ + assert(m_open); + + LOG((CLOG_DEBUG "close clipboard %d", m_id)); + + // unlock clipboard + if (m_motif) { + motifUnlockClipboard(); + } + + m_motif = false; + m_open = false; +} + +IClipboard::Time +CXWindowsClipboard::getTime() const +{ + checkCache(); + return m_timeOwned; +} + +bool +CXWindowsClipboard::has(EFormat format) const +{ + assert(m_open); + + fillCache(); + return m_added[format]; +} + +CString +CXWindowsClipboard::get(EFormat format) const +{ + assert(m_open); + + fillCache(); + return m_data[format]; +} + +void +CXWindowsClipboard::clearConverters() +{ + for (ConverterList::iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + delete *index; + } + m_converters.clear(); +} + +IXWindowsClipboardConverter* +CXWindowsClipboard::getConverter(Atom target, bool onlyIfNotAdded) const +{ + IXWindowsClipboardConverter* converter = NULL; + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + converter = *index; + if (converter->getAtom() == target) { + break; + } + } + if (converter == NULL) { + LOG((CLOG_DEBUG1 " no converter for target %s", CXWindowsUtil::atomToString(m_display, target).c_str())); + return NULL; + } + + // optionally skip already handled targets + if (onlyIfNotAdded) { + if (m_added[converter->getFormat()]) { + LOG((CLOG_DEBUG1 " skipping handled format %d", converter->getFormat())); + return NULL; + } + } + + return converter; +} + +void +CXWindowsClipboard::checkCache() const +{ + if (!m_checkCache) { + return; + } + m_checkCache = false; + + // get the time the clipboard ownership was taken by the current + // owner. + if (m_motif) { + m_timeOwned = motifGetTime(); + } + else { + m_timeOwned = icccmGetTime(); + } + + // if we can't get the time then use the time passed to us + if (m_timeOwned == 0) { + m_timeOwned = m_time; + } + + // if the cache is dirty then flush it + if (m_timeOwned != m_cacheTime) { + clearCache(); + } +} + +void +CXWindowsClipboard::clearCache() const +{ + const_cast(this)->doClearCache(); +} + +void +CXWindowsClipboard::doClearCache() +{ + m_checkCache = false; + m_cached = false; + for (SInt32 index = 0; index < kNumFormats; ++index) { + m_data[index] = ""; + m_added[index] = false; + } +} + +void +CXWindowsClipboard::fillCache() const +{ + // get the selection data if not already cached + checkCache(); + if (!m_cached) { + const_cast(this)->doFillCache(); + } +} + +void +CXWindowsClipboard::doFillCache() +{ + if (m_motif) { + motifFillCache(); + } + else { + icccmFillCache(); + } + m_checkCache = false; + m_cached = true; + m_cacheTime = m_timeOwned; +} + +void +CXWindowsClipboard::icccmFillCache() +{ + LOG((CLOG_DEBUG "ICCCM fill clipboard %d", m_id)); + + // see if we can get the list of available formats from the selection. + // if not then use a default list of formats. note that some clipboard + // owners are broken and report TARGETS as the type of the TARGETS data + // instead of the correct type ATOM; allow either. + const Atom atomTargets = m_atomTargets; + Atom target; + CString data; + if (!icccmGetSelection(atomTargets, &target, &data) || + (target != m_atomAtom && target != m_atomTargets)) { + LOG((CLOG_DEBUG1 "selection doesn't support TARGETS")); + data = ""; + CXWindowsUtil::appendAtomData(data, XA_STRING); + } + + CXWindowsUtil::convertAtomProperty(data); + const Atom* targets = reinterpret_cast(data.data()); + const UInt32 numTargets = data.size() / sizeof(Atom); + LOG((CLOG_DEBUG " available targets: %s", CXWindowsUtil::atomsToString(m_display, targets, numTargets).c_str())); + + // try each converter in order (because they're in order of + // preference). + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IXWindowsClipboardConverter* converter = *index; + + // skip already handled targets + if (m_added[converter->getFormat()]) { + continue; + } + + // see if atom is in target list + Atom target = None; + // XXX -- just ask for the converter's target to see if it's + // available rather than checking TARGETS. i've seen clipboard + // owners that don't report all the targets they support. + target = converter->getAtom(); + /* + for (UInt32 i = 0; i < numTargets; ++i) { + if (converter->getAtom() == targets[i]) { + target = targets[i]; + break; + } + } + */ + if (target == None) { + continue; + } + + // get the data + Atom actualTarget; + CString targetData; + if (!icccmGetSelection(target, &actualTarget, &targetData)) { + LOG((CLOG_DEBUG1 " no data for target %s", CXWindowsUtil::atomToString(m_display, target).c_str())); + continue; + } + + // add to clipboard and note we've done it + IClipboard::EFormat format = converter->getFormat(); + m_data[format] = converter->toIClipboard(targetData); + m_added[format] = true; + LOG((CLOG_DEBUG " added format %d for target %s (%u %s)", format, CXWindowsUtil::atomToString(m_display, target).c_str(), targetData.size(), targetData.size() == 1 ? "byte" : "bytes")); + } +} + +bool +CXWindowsClipboard::icccmGetSelection(Atom target, + Atom* actualTarget, CString* data) const +{ + assert(actualTarget != NULL); + assert(data != NULL); + + // request data conversion + CICCCMGetClipboard getter(m_window, m_time, m_atomData); + if (!getter.readClipboard(m_display, m_selection, + target, actualTarget, data)) { + LOG((CLOG_DEBUG1 "can't get data for selection target %s", CXWindowsUtil::atomToString(m_display, target).c_str())); + LOGC(getter.m_error, (CLOG_WARN "ICCCM violation by clipboard owner")); + return false; + } + else if (*actualTarget == None) { + LOG((CLOG_DEBUG1 "selection conversion failed for target %s", CXWindowsUtil::atomToString(m_display, target).c_str())); + return false; + } + return true; +} + +IClipboard::Time +CXWindowsClipboard::icccmGetTime() const +{ + Atom actualTarget; + CString data; + if (icccmGetSelection(m_atomTimestamp, &actualTarget, &data) && + actualTarget == m_atomInteger) { + Time time = *reinterpret_cast(data.data()); + LOG((CLOG_DEBUG1 "got ICCCM time %d", time)); + return time; + } + else { + // no timestamp + LOG((CLOG_DEBUG1 "can't get ICCCM time")); + return 0; + } +} + +bool +CXWindowsClipboard::motifLockClipboard() const +{ + // fail if anybody owns the lock (even us, so this is non-recursive) + Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != None) { + LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner)); + return false; + } + + // try to grab the lock + // FIXME -- is this right? there's a race condition here -- + // A grabs successfully, B grabs successfully, A thinks it + // still has the grab until it gets a SelectionClear. + Time time = CXWindowsUtil::getCurrentTime(m_display, m_window); + XSetSelectionOwner(m_display, m_atomMotifClipLock, m_window, time); + lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != m_window) { + LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner)); + return false; + } + + LOG((CLOG_DEBUG1 "locked motif clipboard")); + return true; +} + +void +CXWindowsClipboard::motifUnlockClipboard() const +{ + LOG((CLOG_DEBUG1 "unlocked motif clipboard")); + + // fail if we don't own the lock + Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != m_window) { + return; + } + + // release lock + Time time = CXWindowsUtil::getCurrentTime(m_display, m_window); + XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time); +} + +bool +CXWindowsClipboard::motifOwnsClipboard() const +{ + // get the current selection owner + // FIXME -- this can't be right. even if the window is destroyed + // Motif will still have a valid clipboard. how can we tell if + // some other client owns CLIPBOARD? + Window owner = XGetSelectionOwner(m_display, m_selection); + if (owner == None) { + return false; + } + + // get the Motif clipboard header property from the root window + Atom target; + SInt32 format; + CString data; + Window root = RootWindow(m_display, DefaultScreen(m_display)); + if (!CXWindowsUtil::getWindowProperty(m_display, root, + m_atomMotifClipHeader, + &data, &target, &format, False)) { + return false; + } + + // check the owner window against the current clipboard owner + const CMotifClipHeader* header = + reinterpret_cast(data.data()); + if (data.size() >= sizeof(CMotifClipHeader) && + header->m_id == kMotifClipHeader) { + if (static_cast(header->m_selectionOwner) == owner) { + return true; + } + } + + return false; +} + +void +CXWindowsClipboard::motifFillCache() +{ + LOG((CLOG_DEBUG "Motif fill clipboard %d", m_id)); + + // get the Motif clipboard header property from the root window + Atom target; + SInt32 format; + CString data; + Window root = RootWindow(m_display, DefaultScreen(m_display)); + if (!CXWindowsUtil::getWindowProperty(m_display, root, + m_atomMotifClipHeader, + &data, &target, &format, False)) { + return; + } + + // check that the header is okay + const CMotifClipHeader* header = + reinterpret_cast(data.data()); + if (data.size() < sizeof(CMotifClipHeader) || + header->m_id != kMotifClipHeader || + header->m_numItems < 1) { + return; + } + + // get the Motif item property from the root window + char name[18 + 20]; + sprintf(name, "_MOTIF_CLIP_ITEM_%d", header->m_item); + Atom atomItem = XInternAtom(m_display, name, False); + data = ""; + if (!CXWindowsUtil::getWindowProperty(m_display, root, + atomItem, &data, + &target, &format, False)) { + return; + } + + // check that the item is okay + const CMotifClipItem* item = + reinterpret_cast(data.data()); + if (data.size() < sizeof(CMotifClipItem) || + item->m_id != kMotifClipItem || + item->m_numFormats - item->m_numDeletedFormats < 1) { + return; + } + + // format list is after static item structure elements + const SInt32 numFormats = item->m_numFormats - item->m_numDeletedFormats; + const SInt32* formats = reinterpret_cast(item->m_size + + reinterpret_cast(data.data())); + + // get the available formats + typedef std::map CMotifFormatMap; + CMotifFormatMap motifFormats; + for (SInt32 i = 0; i < numFormats; ++i) { + // get Motif format property from the root window + sprintf(name, "_MOTIF_CLIP_ITEM_%d", formats[i]); + Atom atomFormat = XInternAtom(m_display, name, False); + CString data; + if (!CXWindowsUtil::getWindowProperty(m_display, root, + atomFormat, &data, + &target, &format, False)) { + continue; + } + + // check that the format is okay + const CMotifClipFormat* motifFormat = + reinterpret_cast(data.data()); + if (data.size() < sizeof(CMotifClipFormat) || + motifFormat->m_id != kMotifClipFormat || + motifFormat->m_length < 0 || + motifFormat->m_type == None || + motifFormat->m_deleted != 0) { + continue; + } + + // save it + motifFormats.insert(std::make_pair(motifFormat->m_type, data)); + } + //const UInt32 numMotifFormats = motifFormats.size(); + + // try each converter in order (because they're in order of + // preference). + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IXWindowsClipboardConverter* converter = *index; + + // skip already handled targets + if (m_added[converter->getFormat()]) { + continue; + } + + // see if atom is in target list + CMotifFormatMap::const_iterator index2 = + motifFormats.find(converter->getAtom()); + if (index2 == motifFormats.end()) { + continue; + } + + // get format + const CMotifClipFormat* motifFormat = + reinterpret_cast( + index2->second.data()); + const Atom target = motifFormat->m_type; + + // get the data (finally) + Atom actualTarget; + CString targetData; + if (!motifGetSelection(motifFormat, &actualTarget, &targetData)) { + LOG((CLOG_DEBUG1 " no data for target %s", CXWindowsUtil::atomToString(m_display, target).c_str())); + continue; + } + + // add to clipboard and note we've done it + IClipboard::EFormat format = converter->getFormat(); + m_data[format] = converter->toIClipboard(targetData); + m_added[format] = true; + LOG((CLOG_DEBUG " added format %d for target %s", format, CXWindowsUtil::atomToString(m_display, target).c_str())); + } +} + +bool +CXWindowsClipboard::motifGetSelection(const CMotifClipFormat* format, + Atom* actualTarget, CString* data) const +{ + // if the current clipboard owner and the owner indicated by the + // motif clip header are the same then transfer via a property on + // the root window, otherwise transfer as a normal ICCCM client. + if (!motifOwnsClipboard()) { + return icccmGetSelection(format->m_type, actualTarget, data); + } + + // use motif way + // FIXME -- this isn't right. it'll only work if the data is + // already stored on the root window and only if it fits in a + // property. motif has some scheme for transferring part by + // part that i don't know. + char name[18 + 20]; + sprintf(name, "_MOTIF_CLIP_ITEM_%d", format->m_data); + Atom target = XInternAtom(m_display, name, False); + Window root = RootWindow(m_display, DefaultScreen(m_display)); + return CXWindowsUtil::getWindowProperty(m_display, root, + target, data, + actualTarget, NULL, False); +} + +IClipboard::Time +CXWindowsClipboard::motifGetTime() const +{ + return icccmGetTime(); +} + +bool +CXWindowsClipboard::insertMultipleReply(Window requestor, + ::Time time, Atom property) +{ + // get the requested targets + Atom target; + SInt32 format; + CString data; + if (!CXWindowsUtil::getWindowProperty(m_display, requestor, + property, &data, &target, &format, False)) { + // can't get the requested targets + return false; + } + + // fail if the requested targets isn't of the correct form + if (format != 32 || target != m_atomAtomPair) { + return false; + } + + // data is a list of atom pairs: target, property + CXWindowsUtil::convertAtomProperty(data); + const Atom* targets = reinterpret_cast(data.data()); + const UInt32 numTargets = data.size() / sizeof(Atom); + + // add replies for each target + bool changed = false; + for (UInt32 i = 0; i < numTargets; i += 2) { + const Atom target = targets[i + 0]; + const Atom property = targets[i + 1]; + if (!addSimpleRequest(requestor, target, time, property)) { + // note that we can't perform the requested conversion + CXWindowsUtil::replaceAtomData(data, i, None); + changed = true; + } + } + + // update the targets property if we changed it + if (changed) { + CXWindowsUtil::setWindowProperty(m_display, requestor, + property, data.data(), data.size(), + target, format); + } + + // add reply for MULTIPLE request + insertReply(new CReply(requestor, m_atomMultiple, + time, property, CString(), None, 32)); + + return true; +} + +void +CXWindowsClipboard::insertReply(CReply* reply) +{ + assert(reply != NULL); + + // note -- we must respond to requests in order if requestor,target,time + // are the same, otherwise we can use whatever order we like with one + // exception: each reply in a MULTIPLE reply must be handled in order + // as well. those replies will almost certainly not share targets so + // we can't simply use requestor,target,time as map index. + // + // instead we'll use just the requestor. that's more restrictive than + // necessary but we're guaranteed to do things in the right order. + // note that we could also include the time in the map index and still + // ensure the right order. but since that'll just make it harder to + // find the right reply when handling property notify events we stick + // to just the requestor. + + const bool newWindow = (m_replies.count(reply->m_requestor) == 0); + m_replies[reply->m_requestor].push_back(reply); + + // adjust requestor's event mask if we haven't done so already. we + // want events in case the window is destroyed or any of its + // properties change. + if (newWindow) { + // note errors while we adjust event masks + bool error = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + + // get and save the current event mask + XWindowAttributes attr; + XGetWindowAttributes(m_display, reply->m_requestor, &attr); + m_eventMasks[reply->m_requestor] = attr.your_event_mask; + + // add the events we want + XSelectInput(m_display, reply->m_requestor, attr.your_event_mask | + StructureNotifyMask | PropertyChangeMask); + } + + // if we failed then the window has already been destroyed + if (error) { + m_replies.erase(reply->m_requestor); + delete reply; + } + } +} + +void +CXWindowsClipboard::pushReplies() +{ + // send the first reply for each window if that reply hasn't + // been sent yet. + for (CReplyMap::iterator index = m_replies.begin(); + index != m_replies.end(); ) { + assert(!index->second.empty()); + if (!index->second.front()->m_replied) { + pushReplies(index, index->second, index->second.begin()); + } + else { + ++index; + } + } +} + +void +CXWindowsClipboard::pushReplies(CReplyMap::iterator& mapIndex, + CReplyList& replies, CReplyList::iterator index) +{ + CReply* reply = *index; + while (sendReply(reply)) { + // reply is complete. discard it and send the next reply, + // if any. + index = replies.erase(index); + delete reply; + if (index == replies.end()) { + break; + } + reply = *index; + } + + // if there are no more replies in the list then remove the list + // and stop watching the requestor for events. + if (replies.empty()) { + CXWindowsUtil::CErrorLock lock(m_display); + Window requestor = mapIndex->first; + XSelectInput(m_display, requestor, m_eventMasks[requestor]); + m_replies.erase(mapIndex++); + m_eventMasks.erase(requestor); + } + else { + ++mapIndex; + } +} + +bool +CXWindowsClipboard::sendReply(CReply* reply) +{ + assert(reply != NULL); + + // bail out immediately if reply is done + if (reply->m_done) { + LOG((CLOG_DEBUG1 "clipboard: finished reply to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + return true; + } + + // start in failed state if property is None + bool failed = (reply->m_property == None); + if (!failed) { + LOG((CLOG_DEBUG1 "clipboard: setting property on 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + + // send using INCR if already sending incrementally or if reply + // is too large, otherwise just send it. + const UInt32 maxRequestSize = 3 * XMaxRequestSize(m_display); + const bool useINCR = (reply->m_data.size() > maxRequestSize); + + // send INCR reply if incremental and we haven't replied yet + if (useINCR && !reply->m_replied) { + UInt32 size = reply->m_data.size(); + if (!CXWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + &size, 4, m_atomINCR, 32)) { + failed = true; + } + } + + // send more INCR reply or entire non-incremental reply + else { + // how much more data should we send? + UInt32 size = reply->m_data.size() - reply->m_ptr; + if (size > maxRequestSize) + size = maxRequestSize; + + // send it + if (!CXWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + reply->m_data.data() + reply->m_ptr, + size, + reply->m_type, reply->m_format)) { + failed = true; + } + else { + reply->m_ptr += size; + + // we've finished the reply if we just sent the zero + // size incremental chunk or if we're not incremental. + reply->m_done = (size == 0 || !useINCR); + } + } + } + + // if we've failed then delete the property and say we're done. + // if we haven't replied yet then we can send a failure notify, + // otherwise we've failed in the middle of an incremental + // transfer; i don't know how to cancel that so i'll just send + // the final zero-length property. + // FIXME -- how do you gracefully cancel an incremental transfer? + if (failed) { + LOG((CLOG_DEBUG1 "clipboard: sending failure to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + reply->m_done = true; + if (reply->m_property != None) { + CXWindowsUtil::CErrorLock lock(m_display); + XDeleteProperty(m_display, reply->m_requestor, reply->m_property); + } + + if (!reply->m_replied) { + sendNotify(reply->m_requestor, m_selection, + reply->m_target, None, + reply->m_time); + + // don't wait for any reply (because we're not expecting one) + return true; + } + else { + static const char dummy = 0; + CXWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + &dummy, + 0, + reply->m_type, reply->m_format); + + // wait for delete notify + return false; + } + } + + // send notification if we haven't yet + if (!reply->m_replied) { + LOG((CLOG_DEBUG1 "clipboard: sending notify to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + reply->m_replied = true; + + // dump every property on the requestor window to the debug2 + // log. we've seen what appears to be a bug in lesstif and + // knowing the properties may help design a workaround, if + // it becomes necessary. + if (CLOG->getFilter() >= CLog::kDEBUG2) { + CXWindowsUtil::CErrorLock lock(m_display); + int n; + Atom* props = XListProperties(m_display, reply->m_requestor, &n); + LOG((CLOG_DEBUG2 "properties of 0x%08x:", reply->m_requestor)); + for (int i = 0; i < n; ++i) { + Atom target; + CString data; + char* name = XGetAtomName(m_display, props[i]); + if (!CXWindowsUtil::getWindowProperty(m_display, + reply->m_requestor, + props[i], &data, &target, NULL, False)) { + LOG((CLOG_DEBUG2 " %s: ", name)); + } + else { + // if there are any non-ascii characters in string + // then print the binary data. + static const char* hex = "0123456789abcdef"; + for (CString::size_type j = 0; j < data.size(); ++j) { + if (data[j] < 32 || data[j] > 126) { + CString tmp; + tmp.reserve(data.size() * 3); + for (j = 0; j < data.size(); ++j) { + unsigned char v = (unsigned char)data[j]; + tmp += hex[v >> 16]; + tmp += hex[v & 15]; + tmp += ' '; + } + data = tmp; + break; + } + } + char* type = XGetAtomName(m_display, target); + LOG((CLOG_DEBUG2 " %s (%s): %s", name, type, data.c_str())); + if (type != NULL) { + XFree(type); + } + } + if (name != NULL) { + XFree(name); + } + } + if (props != NULL) { + XFree(props); + } + } + + sendNotify(reply->m_requestor, m_selection, + reply->m_target, reply->m_property, + reply->m_time); + } + + // wait for delete notify + return false; +} + +void +CXWindowsClipboard::clearReplies() +{ + for (CReplyMap::iterator index = m_replies.begin(); + index != m_replies.end(); ++index) { + clearReplies(index->second); + } + m_replies.clear(); + m_eventMasks.clear(); +} + +void +CXWindowsClipboard::clearReplies(CReplyList& replies) +{ + for (CReplyList::iterator index = replies.begin(); + index != replies.end(); ++index) { + delete *index; + } + replies.clear(); +} + +void +CXWindowsClipboard::sendNotify(Window requestor, + Atom selection, Atom target, Atom property, Time time) +{ + XEvent event; + event.xselection.type = SelectionNotify; + event.xselection.display = m_display; + event.xselection.requestor = requestor; + event.xselection.selection = selection; + event.xselection.target = target; + event.xselection.property = property; + event.xselection.time = time; + CXWindowsUtil::CErrorLock lock(m_display); + XSendEvent(m_display, requestor, False, 0, &event); +} + +bool +CXWindowsClipboard::wasOwnedAtTime(::Time time) const +{ + // not owned if we've never owned the selection + checkCache(); + if (m_timeOwned == 0) { + return false; + } + + // if time is CurrentTime then return true if we still own the + // selection and false if we do not. else if we still own the + // selection then get the current time, otherwise use + // m_timeLost as the end time. + Time lost = m_timeLost; + if (m_timeLost == 0) { + if (time == CurrentTime) { + return true; + } + else { + lost = CXWindowsUtil::getCurrentTime(m_display, m_window); + } + } + else { + if (time == CurrentTime) { + return false; + } + } + + // compare time to range + Time duration = lost - m_timeOwned; + Time when = time - m_timeOwned; + return (/*when >= 0 &&*/ when <= duration); +} + +Atom +CXWindowsClipboard::getTargetsData(CString& data, int* format) const +{ + assert(format != NULL); + + // add standard targets + CXWindowsUtil::appendAtomData(data, m_atomTargets); + CXWindowsUtil::appendAtomData(data, m_atomMultiple); + CXWindowsUtil::appendAtomData(data, m_atomTimestamp); + + // add targets we can convert to + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IXWindowsClipboardConverter* converter = *index; + + // skip formats we don't have + if (m_added[converter->getFormat()]) { + CXWindowsUtil::appendAtomData(data, converter->getAtom()); + } + } + + *format = 32; + return m_atomAtom; +} + +Atom +CXWindowsClipboard::getTimestampData(CString& data, int* format) const +{ + assert(format != NULL); + + checkCache(); + CXWindowsUtil::appendTimeData(data, m_timeOwned); + *format = 32; + return m_atomInteger; +} + + +// +// CXWindowsClipboard::CICCCMGetClipboard +// + +CXWindowsClipboard::CICCCMGetClipboard::CICCCMGetClipboard( + Window requestor, Time time, Atom property) : + m_requestor(requestor), + m_time(time), + m_property(property), + m_incr(false), + m_failed(false), + m_done(false), + m_reading(false), + m_data(NULL), + m_actualTarget(NULL), + m_error(false) +{ + // do nothing +} + +CXWindowsClipboard::CICCCMGetClipboard::~CICCCMGetClipboard() +{ + // do nothing +} + +bool +CXWindowsClipboard::CICCCMGetClipboard::readClipboard(Display* display, + Atom selection, Atom target, Atom* actualTarget, CString* data) +{ + assert(actualTarget != NULL); + assert(data != NULL); + + LOG((CLOG_DEBUG1 "request selection=%s, target=%s, window=%x", CXWindowsUtil::atomToString(display, selection).c_str(), CXWindowsUtil::atomToString(display, target).c_str(), m_requestor)); + + m_atomNone = XInternAtom(display, "NONE", False); + m_atomIncr = XInternAtom(display, "INCR", False); + + // save output pointers + m_actualTarget = actualTarget; + m_data = data; + + // assume failure + *m_actualTarget = None; + *m_data = ""; + + // delete target property + XDeleteProperty(display, m_requestor, m_property); + + // select window for property changes + XWindowAttributes attr; + XGetWindowAttributes(display, m_requestor, &attr); + XSelectInput(display, m_requestor, + attr.your_event_mask | PropertyChangeMask); + + // request data conversion + XConvertSelection(display, selection, target, + m_property, m_requestor, m_time); + + // synchronize with server before we start following timeout countdown + XSync(display, False); + + // Xlib inexplicably omits the ability to wait for an event with + // a timeout. (it's inexplicable because there's no portable way + // to do it.) we'll poll until we have what we're looking for or + // a timeout expires. we use a timeout so we don't get locked up + // by badly behaved selection owners. + XEvent xevent; + std::vector events; + CStopwatch timeout(true); + static const double s_timeout = 0.25; // FIXME -- is this too short? + bool noWait = false; + while (!m_done && !m_failed) { + // fail if timeout has expired + if (timeout.getTime() >= s_timeout) { + m_failed = true; + break; + } + + // process events if any otherwise sleep + if (noWait || XPending(display) > 0) { + while (!m_done && !m_failed && (noWait || XPending(display) > 0)) { + XNextEvent(display, &xevent); + if (!processEvent(display, &xevent)) { + // not processed so save it + events.push_back(xevent); + } + else { + // reset timer since we've made some progress + timeout.reset(); + + // don't sleep anymore, just block waiting for events. + // we're assuming here that the clipboard owner will + // complete the protocol correctly. if we continue to + // sleep we'll get very bad performance. + noWait = true; + } + } + } + else { + ARCH->sleep(0.01); + } + } + + // put unprocessed events back + for (UInt32 i = events.size(); i > 0; --i) { + XPutBackEvent(display, &events[i - 1]); + } + + // restore mask + XSelectInput(display, m_requestor, attr.your_event_mask); + + // return success or failure + LOG((CLOG_DEBUG1 "request %s", m_failed ? "failed" : "succeeded")); + return !m_failed; +} + +bool +CXWindowsClipboard::CICCCMGetClipboard::processEvent( + Display* display, XEvent* xevent) +{ + // process event + switch (xevent->type) { + case DestroyNotify: + if (xevent->xdestroywindow.window == m_requestor) { + m_failed = true; + return true; + } + + // not interested + return false; + + case SelectionNotify: + if (xevent->xselection.requestor == m_requestor) { + // done if we can't convert + if (xevent->xselection.property == None || + xevent->xselection.property == m_atomNone) { + m_done = true; + return true; + } + + // proceed if conversion successful + else if (xevent->xselection.property == m_property) { + m_reading = true; + break; + } + } + + // otherwise not interested + return false; + + case PropertyNotify: + // proceed if conversion successful and we're receiving more data + if (xevent->xproperty.window == m_requestor && + xevent->xproperty.atom == m_property && + xevent->xproperty.state == PropertyNewValue) { + if (!m_reading) { + // we haven't gotten the SelectionNotify yet + return true; + } + break; + } + + // otherwise not interested + return false; + + default: + // not interested + return false; + } + + // get the data from the property + Atom target; + const CString::size_type oldSize = m_data->size(); + if (!CXWindowsUtil::getWindowProperty(display, m_requestor, + m_property, m_data, &target, NULL, True)) { + // unable to read property + m_failed = true; + return true; + } + + // note if incremental. if we're already incremental then the + // selection owner is busted. if the INCR property has no size + // then the selection owner is busted. + if (target == m_atomIncr) { + if (m_incr) { + m_failed = true; + m_error = true; + } + else if (m_data->size() == oldSize) { + m_failed = true; + m_error = true; + } + else { + m_incr = true; + + // discard INCR data + *m_data = ""; + } + } + + // handle incremental chunks + else if (m_incr) { + // if first incremental chunk then save target + if (oldSize == 0) { + LOG((CLOG_DEBUG1 " INCR first chunk, target %s", CXWindowsUtil::atomToString(display, target).c_str())); + *m_actualTarget = target; + } + + // secondary chunks must have the same target + else { + if (target != *m_actualTarget) { + LOG((CLOG_WARN " INCR target mismatch")); + m_failed = true; + m_error = true; + } + } + + // note if this is the final chunk + if (m_data->size() == oldSize) { + LOG((CLOG_DEBUG1 " INCR final chunk: %d bytes total", m_data->size())); + m_done = true; + } + } + + // not incremental; save the target. + else { + LOG((CLOG_DEBUG1 " target %s", CXWindowsUtil::atomToString(display, target).c_str())); + *m_actualTarget = target; + m_done = true; + } + + // this event has been processed + LOGC(!m_incr, (CLOG_DEBUG1 " got data, %d bytes", m_data->size())); + return true; +} + + +// +// CXWindowsClipboard::CReply +// + +CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time) : + m_requestor(requestor), + m_target(target), + m_time(time), + m_property(None), + m_replied(false), + m_done(false), + m_data(), + m_type(None), + m_format(32), + m_ptr(0) +{ + // do nothing +} + +CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time, + Atom property, const CString& data, Atom type, int format) : + m_requestor(requestor), + m_target(target), + m_time(time), + m_property(property), + m_replied(false), + m_done(false), + m_data(data), + m_type(type), + m_format(format), + m_ptr(0) +{ + // do nothing +} diff --git a/lib/platform/CXWindowsClipboard.h b/lib/platform/CXWindowsClipboard.h new file mode 100644 index 00000000..b36a6c8d --- /dev/null +++ b/lib/platform/CXWindowsClipboard.h @@ -0,0 +1,376 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARD_H +#define CXWINDOWSCLIPBOARD_H + +#include "IClipboard.h" +#include "ClipboardTypes.h" +#include "stdmap.h" +#include "stdlist.h" +#include "stdvector.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +#endif + +class IXWindowsClipboardConverter; + +//! X11 clipboard implementation +class CXWindowsClipboard : public IClipboard { +public: + /*! + Use \c window as the window that owns or interacts with the + clipboard identified by \c id. + */ + CXWindowsClipboard(Display*, Window window, ClipboardID id); + virtual ~CXWindowsClipboard(); + + //! Notify clipboard was lost + /*! + Tells clipboard it lost ownership at the given time. + */ + void lost(Time); + + //! Add clipboard request + /*! + Adds a selection request to the request list. If the given + owner window isn't this clipboard's window then this simply + sends a failure event to the requestor. + */ + void addRequest(Window owner, + Window requestor, Atom target, + ::Time time, Atom property); + + //! Process clipboard request + /*! + Continues processing a selection request. Returns true if the + request was handled, false if the request was unknown. + */ + bool processRequest(Window requestor, + ::Time time, Atom property); + + //! Cancel clipboard request + /*! + Terminate a selection request. Returns true iff the request + was known and handled. + */ + bool destroyRequest(Window requestor); + + //! Get window + /*! + Returns the clipboard's window (passed the c'tor). + */ + Window getWindow() const; + + //! Get selection atom + /*! + Returns the selection atom that identifies the clipboard to X11 + (e.g. XA_PRIMARY). + */ + Atom getSelection() const; + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const CString& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual CString get(EFormat) const; + +private: + // remove all converters from our list + void clearConverters(); + + // get the converter for a clipboard format. returns NULL if no + // suitable converter. iff onlyIfNotAdded is true then also + // return NULL if a suitable converter was found but we already + // have data of the converter's clipboard format. + IXWindowsClipboardConverter* + getConverter(Atom target, + bool onlyIfNotAdded = false) const; + + // convert target atom to clipboard format + EFormat getFormat(Atom target) const; + + // add a non-MULTIPLE request. does not verify that the selection + // was owned at the given time. returns true if the conversion + // could be performed, false otherwise. in either case, the + // reply is inserted. + bool addSimpleRequest( + Window requestor, Atom target, + ::Time time, Atom property); + + // if not already checked then see if the cache is stale and, if so, + // clear it. this has the side effect of updating m_timeOwned. + void checkCache() const; + + // clear the cache, resetting the cached flag and the added flag for + // each format. + void clearCache() const; + void doClearCache(); + + // cache all formats of the selection + void fillCache() const; + void doFillCache(); + + // + // helper classes + // + + // read an ICCCM conforming selection + class CICCCMGetClipboard { + public: + CICCCMGetClipboard(Window requestor, Time time, Atom property); + ~CICCCMGetClipboard(); + + // convert the given selection to the given type. returns + // true iff the conversion was successful or the conversion + // cannot be performed (in which case *actualTarget == None). + bool readClipboard(Display* display, + Atom selection, Atom target, + Atom* actualTarget, CString* data); + + private: + bool processEvent(Display* display, XEvent* event); + + private: + Window m_requestor; + Time m_time; + Atom m_property; + bool m_incr; + bool m_failed; + bool m_done; + + // atoms needed for the protocol + Atom m_atomNone; // NONE, not None + Atom m_atomIncr; + + // true iff we've received the selection notify + bool m_reading; + + // the converted selection data + CString* m_data; + + // the actual type of the data. if this is None then the + // selection owner cannot convert to the requested type. + Atom* m_actualTarget; + + public: + // true iff the selection owner didn't follow ICCCM conventions + bool m_error; + }; + + // Motif structure IDs + enum { kMotifClipFormat = 1, kMotifClipItem, kMotifClipHeader }; + + // _MOTIF_CLIP_HEADER structure + class CMotifClipHeader { + public: + SInt32 m_id; // kMotifClipHeader + SInt32 m_pad1[3]; + SInt32 m_item; + SInt32 m_pad2[4]; + SInt32 m_numItems; + SInt32 m_pad3[3]; + SInt32 m_selectionOwner; // a Window + SInt32 m_pad4[2]; + }; + + // Motif clip item structure + class CMotifClipItem { + public: + SInt32 m_id; // kMotifClipItem + SInt32 m_pad1[5]; + SInt32 m_size; + SInt32 m_numFormats; + SInt32 m_numDeletedFormats; + SInt32 m_pad2[6]; + }; + + // Motif clip format structure + class CMotifClipFormat { + public: + SInt32 m_id; // kMotifClipFormat + SInt32 m_pad1[6]; + SInt32 m_length; + SInt32 m_data; + SInt32 m_type; // an Atom + SInt32 m_pad2[1]; + SInt32 m_deleted; + SInt32 m_pad3[4]; + }; + + // stores data needed to respond to a selection request + class CReply { + public: + CReply(Window, Atom target, ::Time); + CReply(Window, Atom target, ::Time, Atom property, + const CString& data, Atom type, int format); + + public: + // information about the request + Window m_requestor; + Atom m_target; + ::Time m_time; + Atom m_property; + + // true iff we've sent the notification for this reply + bool m_replied; + + // true iff the reply has sent its last message + bool m_done; + + // the data to send and its type and format + CString m_data; + Atom m_type; + int m_format; + + // index of next byte in m_data to send + UInt32 m_ptr; + }; + typedef std::list CReplyList; + typedef std::map CReplyMap; + typedef std::map CReplyEventMask; + + // ICCCM interoperability methods + void icccmFillCache(); + bool icccmGetSelection(Atom target, + Atom* actualTarget, CString* data) const; + Time icccmGetTime() const; + + // motif interoperability methods + bool motifLockClipboard() const; + void motifUnlockClipboard() const; + bool motifOwnsClipboard() const; + void motifFillCache(); + bool motifGetSelection(const CMotifClipFormat*, + Atom* actualTarget, CString* data) const; + Time motifGetTime() const; + + // reply methods + bool insertMultipleReply(Window, ::Time, Atom); + void insertReply(CReply*); + void pushReplies(); + void pushReplies(CReplyMap::iterator&, + CReplyList&, CReplyList::iterator); + bool sendReply(CReply*); + void clearReplies(); + void clearReplies(CReplyList&); + void sendNotify(Window requestor, Atom selection, + Atom target, Atom property, Time time); + bool wasOwnedAtTime(::Time) const; + + // data conversion methods + Atom getTargetsData(CString&, int* format) const; + Atom getTimestampData(CString&, int* format) const; + +private: + typedef std::vector ConverterList; + + Display* m_display; + Window m_window; + ClipboardID m_id; + Atom m_selection; + mutable bool m_open; + mutable Time m_time; + bool m_owner; + mutable Time m_timeOwned; + Time m_timeLost; + + // true iff open and clipboard owned by a motif app + mutable bool m_motif; + + // the added/cached clipboard data + mutable bool m_checkCache; + bool m_cached; + Time m_cacheTime; + bool m_added[kNumFormats]; + CString m_data[kNumFormats]; + + // conversion request replies + CReplyMap m_replies; + CReplyEventMask m_eventMasks; + + // clipboard format converters + ConverterList m_converters; + + // atoms we'll need + Atom m_atomTargets; + Atom m_atomMultiple; + Atom m_atomTimestamp; + Atom m_atomInteger; + Atom m_atomAtom; + Atom m_atomAtomPair; + Atom m_atomData; + Atom m_atomINCR; + Atom m_atomMotifClipLock; + Atom m_atomMotifClipHeader; + Atom m_atomMotifClipAccess; + Atom m_atomGDKSelection; +}; + +//! Clipboard format converter interface +/*! +This interface defines the methods common to all X11 clipboard format +converters. +*/ +class IXWindowsClipboardConverter : public IInterface { +public: + //! @name accessors + //@{ + + //! Get clipboard format + /*! + Return the clipboard format this object converts from/to. + */ + virtual IClipboard::EFormat + getFormat() const = 0; + + //! Get X11 format atom + /*! + Return the atom representing the X selection format that + this object converts from/to. + */ + virtual Atom getAtom() const = 0; + + //! Get X11 property datum size + /*! + Return the size (in bits) of data elements returned by + toIClipboard(). + */ + virtual int getDataSize() const = 0; + + //! Convert from IClipboard format + /*! + Convert from the IClipboard format to the X selection format. + The input data must be in the IClipboard format returned by + getFormat(). The return data will be in the X selection + format returned by getAtom(). + */ + virtual CString fromIClipboard(const CString&) const = 0; + + //! Convert to IClipboard format + /*! + Convert from the X selection format to the IClipboard format + (i.e., the reverse of fromIClipboard()). + */ + virtual CString toIClipboard(const CString&) const = 0; + + //@} +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardAnyBitmapConverter.cpp b/lib/platform/CXWindowsClipboardAnyBitmapConverter.cpp new file mode 100644 index 00000000..ab6afae2 --- /dev/null +++ b/lib/platform/CXWindowsClipboardAnyBitmapConverter.cpp @@ -0,0 +1,187 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CXWindowsClipboardAnyBitmapConverter.h" + +// BMP info header structure +struct CBMPInfoHeader { +public: + UInt32 biSize; + SInt32 biWidth; + SInt32 biHeight; + UInt16 biPlanes; + UInt16 biBitCount; + UInt32 biCompression; + UInt32 biSizeImage; + SInt32 biXPelsPerMeter; + SInt32 biYPelsPerMeter; + UInt32 biClrUsed; + UInt32 biClrImportant; +}; + +// BMP is little-endian + +static +void +toLE(UInt8*& dst, UInt16 src) +{ + dst[0] = static_cast(src & 0xffu); + dst[1] = static_cast((src >> 8) & 0xffu); + dst += 2; +} + +static +void +toLE(UInt8*& dst, SInt32 src) +{ + dst[0] = static_cast(src & 0xffu); + dst[1] = static_cast((src >> 8) & 0xffu); + dst[2] = static_cast((src >> 16) & 0xffu); + dst[3] = static_cast((src >> 24) & 0xffu); + dst += 4; +} + +static +void +toLE(UInt8*& dst, UInt32 src) +{ + dst[0] = static_cast(src & 0xffu); + dst[1] = static_cast((src >> 8) & 0xffu); + dst[2] = static_cast((src >> 16) & 0xffu); + dst[3] = static_cast((src >> 24) & 0xffu); + dst += 4; +} + +static inline +UInt16 +fromLEU16(const UInt8* data) +{ + return static_cast(data[0]) | + (static_cast(data[1]) << 8); +} + +static inline +SInt32 +fromLES32(const UInt8* data) +{ + return static_cast(static_cast(data[0]) | + (static_cast(data[1]) << 8) | + (static_cast(data[2]) << 16) | + (static_cast(data[3]) << 24)); +} + +static inline +UInt32 +fromLEU32(const UInt8* data) +{ + return static_cast(data[0]) | + (static_cast(data[1]) << 8) | + (static_cast(data[2]) << 16) | + (static_cast(data[3]) << 24); +} + + +// +// CXWindowsClipboardAnyBitmapConverter +// + +CXWindowsClipboardAnyBitmapConverter::CXWindowsClipboardAnyBitmapConverter() +{ + // do nothing +} + +CXWindowsClipboardAnyBitmapConverter::~CXWindowsClipboardAnyBitmapConverter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardAnyBitmapConverter::getFormat() const +{ + return IClipboard::kBitmap; +} + +int +CXWindowsClipboardAnyBitmapConverter::getDataSize() const +{ + return 8; +} + +CString +CXWindowsClipboardAnyBitmapConverter::fromIClipboard(const CString& bmp) const +{ + // fill BMP info header with native-endian data + CBMPInfoHeader infoHeader; + const UInt8* rawBMPInfoHeader = reinterpret_cast(bmp.data()); + infoHeader.biSize = fromLEU32(rawBMPInfoHeader + 0); + infoHeader.biWidth = fromLES32(rawBMPInfoHeader + 4); + infoHeader.biHeight = fromLES32(rawBMPInfoHeader + 8); + infoHeader.biPlanes = fromLEU16(rawBMPInfoHeader + 12); + infoHeader.biBitCount = fromLEU16(rawBMPInfoHeader + 14); + infoHeader.biCompression = fromLEU32(rawBMPInfoHeader + 16); + infoHeader.biSizeImage = fromLEU32(rawBMPInfoHeader + 20); + infoHeader.biXPelsPerMeter = fromLES32(rawBMPInfoHeader + 24); + infoHeader.biYPelsPerMeter = fromLES32(rawBMPInfoHeader + 28); + infoHeader.biClrUsed = fromLEU32(rawBMPInfoHeader + 32); + infoHeader.biClrImportant = fromLEU32(rawBMPInfoHeader + 36); + + // check that format is acceptable + if (infoHeader.biSize != 40 || + infoHeader.biWidth == 0 || infoHeader.biHeight == 0 || + infoHeader.biPlanes != 0 || infoHeader.biCompression != 0 || + (infoHeader.biBitCount != 24 && infoHeader.biBitCount != 32)) { + return CString(); + } + + // convert to image format + const UInt8* rawBMPPixels = rawBMPInfoHeader + 40; + if (infoHeader.biBitCount == 24) { + return doBGRFromIClipboard(rawBMPPixels, + infoHeader.biWidth, infoHeader.biHeight); + } + else { + return doBGRAFromIClipboard(rawBMPPixels, + infoHeader.biWidth, infoHeader.biHeight); + } +} + +CString +CXWindowsClipboardAnyBitmapConverter::toIClipboard(const CString& image) const +{ + // convert to raw BMP data + UInt32 w, h, depth; + CString rawBMP = doToIClipboard(image, w, h, depth); + if (rawBMP.empty() || w == 0 || h == 0 || (depth != 24 && depth != 32)) { + return CString(); + } + + // fill BMP info header with little-endian data + UInt8 infoHeader[40]; + UInt8* dst = infoHeader; + toLE(dst, static_cast(40)); + toLE(dst, static_cast(w)); + toLE(dst, static_cast(h)); + toLE(dst, static_cast(1)); + toLE(dst, static_cast(depth)); + toLE(dst, static_cast(0)); // BI_RGB + toLE(dst, static_cast(image.size())); + toLE(dst, static_cast(2834)); // 72 dpi + toLE(dst, static_cast(2834)); // 72 dpi + toLE(dst, static_cast(0)); + toLE(dst, static_cast(0)); + + // construct image + return CString(reinterpret_cast(infoHeader), + sizeof(infoHeader)) + rawBMP; +} diff --git a/lib/platform/CXWindowsClipboardAnyBitmapConverter.h b/lib/platform/CXWindowsClipboardAnyBitmapConverter.h new file mode 100644 index 00000000..6a153422 --- /dev/null +++ b/lib/platform/CXWindowsClipboardAnyBitmapConverter.h @@ -0,0 +1,59 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDANYBITMAPCONVERTER_H +#define CXWINDOWSCLIPBOARDANYBITMAPCONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from some text encoding +class CXWindowsClipboardAnyBitmapConverter : + public IXWindowsClipboardConverter { +public: + CXWindowsClipboardAnyBitmapConverter(); + virtual ~CXWindowsClipboardAnyBitmapConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const = 0; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +protected: + //! Convert from IClipboard format + /*! + Convert raw BGR pixel data to another image format. + */ + virtual CString doBGRFromIClipboard(const UInt8* bgrData, + UInt32 w, UInt32 h) const = 0; + + //! Convert from IClipboard format + /*! + Convert raw BGRA pixel data to another image format. + */ + virtual CString doBGRAFromIClipboard(const UInt8* bgrData, + UInt32 w, UInt32 h) const = 0; + + //! Convert to IClipboard format + /*! + Convert an image into raw BGR or BGRA image data and store the + width, height, and image depth (24 or 32). + */ + virtual CString doToIClipboard(const CString&, + UInt32& w, UInt32& h, UInt32& depth) const = 0; +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardBMPConverter.cpp b/lib/platform/CXWindowsClipboardBMPConverter.cpp new file mode 100644 index 00000000..747d6850 --- /dev/null +++ b/lib/platform/CXWindowsClipboardBMPConverter.cpp @@ -0,0 +1,139 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CXWindowsClipboardBMPConverter.h" + +// BMP file header structure +struct CBMPHeader { +public: + UInt16 type; + UInt32 size; + UInt16 reserved1; + UInt16 reserved2; + UInt32 offset; +}; + +// BMP is little-endian +static inline +UInt32 +fromLEU32(const UInt8* data) +{ + return static_cast(data[0]) | + (static_cast(data[1]) << 8) | + (static_cast(data[2]) << 16) | + (static_cast(data[3]) << 24); +} + +static +void +toLE(UInt8*& dst, char src) +{ + dst[0] = static_cast(src); + dst += 1; +} + +static +void +toLE(UInt8*& dst, UInt16 src) +{ + dst[0] = static_cast(src & 0xffu); + dst[1] = static_cast((src >> 8) & 0xffu); + dst += 2; +} + +static +void +toLE(UInt8*& dst, UInt32 src) +{ + dst[0] = static_cast(src & 0xffu); + dst[1] = static_cast((src >> 8) & 0xffu); + dst[2] = static_cast((src >> 16) & 0xffu); + dst[3] = static_cast((src >> 24) & 0xffu); + dst += 4; +} + +// +// CXWindowsClipboardBMPConverter +// + +CXWindowsClipboardBMPConverter::CXWindowsClipboardBMPConverter( + Display* display) : + m_atom(XInternAtom(display, "image/bmp", False)) +{ + // do nothing +} + +CXWindowsClipboardBMPConverter::~CXWindowsClipboardBMPConverter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardBMPConverter::getFormat() const +{ + return IClipboard::kBitmap; +} + +Atom +CXWindowsClipboardBMPConverter::getAtom() const +{ + return m_atom; +} + +int +CXWindowsClipboardBMPConverter::getDataSize() const +{ + return 8; +} + +CString +CXWindowsClipboardBMPConverter::fromIClipboard(const CString& bmp) const +{ + // create BMP image + UInt8 header[14]; + UInt8* dst = header; + toLE(dst, 'B'); + toLE(dst, 'M'); + toLE(dst, static_cast(14 + bmp.size())); + toLE(dst, static_cast(0)); + toLE(dst, static_cast(0)); + toLE(dst, static_cast(14 + 40)); + return CString(reinterpret_cast(header), 14) + bmp; +} + +CString +CXWindowsClipboardBMPConverter::toIClipboard(const CString& bmp) const +{ + // make sure data is big enough for a BMP file + if (bmp.size() <= 14 + 40) { + return CString(); + } + + // check BMP file header + const UInt8* rawBMPHeader = reinterpret_cast(bmp.data()); + if (rawBMPHeader[0] != 'B' || rawBMPHeader[1] != 'M') { + return CString(); + } + + // get offset to image data + UInt32 offset = fromLEU32(rawBMPHeader + 10); + + // construct BMP + if (offset == 14 + 40) { + return bmp.substr(14); + } + else { + return bmp.substr(14, 40) + bmp.substr(offset, bmp.size() - offset); + } +} diff --git a/lib/platform/CXWindowsClipboardBMPConverter.h b/lib/platform/CXWindowsClipboardBMPConverter.h new file mode 100644 index 00000000..a7d44549 --- /dev/null +++ b/lib/platform/CXWindowsClipboardBMPConverter.h @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDBMPCONVERTER_H +#define CXWINDOWSCLIPBOARDBMPCONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from some text encoding +class CXWindowsClipboardBMPConverter : + public IXWindowsClipboardConverter { +public: + CXWindowsClipboardBMPConverter(Display* display); + virtual ~CXWindowsClipboardBMPConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +private: + Atom m_atom; +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardHTMLConverter.cpp b/lib/platform/CXWindowsClipboardHTMLConverter.cpp new file mode 100644 index 00000000..fafca54e --- /dev/null +++ b/lib/platform/CXWindowsClipboardHTMLConverter.cpp @@ -0,0 +1,62 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CXWindowsClipboardHTMLConverter.h" +#include "CUnicode.h" + +// +// CXWindowsClipboardHTMLConverter +// + +CXWindowsClipboardHTMLConverter::CXWindowsClipboardHTMLConverter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +CXWindowsClipboardHTMLConverter::~CXWindowsClipboardHTMLConverter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardHTMLConverter::getFormat() const +{ + return IClipboard::kHTML; +} + +Atom +CXWindowsClipboardHTMLConverter::getAtom() const +{ + return m_atom; +} + +int +CXWindowsClipboardHTMLConverter::getDataSize() const +{ + return 8; +} + +CString +CXWindowsClipboardHTMLConverter::fromIClipboard(const CString& data) const +{ + return CUnicode::UTF8ToUTF16(data); +} + +CString +CXWindowsClipboardHTMLConverter::toIClipboard(const CString& data) const +{ + return CUnicode::UTF16ToUTF8(data); +} diff --git a/lib/platform/CXWindowsClipboardHTMLConverter.h b/lib/platform/CXWindowsClipboardHTMLConverter.h new file mode 100644 index 00000000..7f761f20 --- /dev/null +++ b/lib/platform/CXWindowsClipboardHTMLConverter.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDHTMLCONVERTER_H +#define CXWINDOWSCLIPBOARDHTMLCONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from HTML encoding +class CXWindowsClipboardHTMLConverter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + CXWindowsClipboardHTMLConverter(Display* display, const char* name); + virtual ~CXWindowsClipboardHTMLConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +private: + Atom m_atom; +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardTextConverter.cpp b/lib/platform/CXWindowsClipboardTextConverter.cpp new file mode 100644 index 00000000..bd1a520c --- /dev/null +++ b/lib/platform/CXWindowsClipboardTextConverter.cpp @@ -0,0 +1,74 @@ +/* + * 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 "CXWindowsClipboardTextConverter.h" +#include "CUnicode.h" + +// +// CXWindowsClipboardTextConverter +// + +CXWindowsClipboardTextConverter::CXWindowsClipboardTextConverter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +CXWindowsClipboardTextConverter::~CXWindowsClipboardTextConverter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardTextConverter::getFormat() const +{ + return IClipboard::kText; +} + +Atom +CXWindowsClipboardTextConverter::getAtom() const +{ + return m_atom; +} + +int +CXWindowsClipboardTextConverter::getDataSize() const +{ + return 8; +} + +CString +CXWindowsClipboardTextConverter::fromIClipboard(const CString& data) const +{ + return CUnicode::UTF8ToText(data); +} + +CString +CXWindowsClipboardTextConverter::toIClipboard(const CString& data) const +{ + // convert to UTF-8 + bool errors; + CString utf8 = CUnicode::textToUTF8(data, &errors); + + // if there were decoding errors then, to support old applications + // that don't understand UTF-8 but can report the exact binary + // UTF-8 representation, see if the data appears to be UTF-8. if + // so then use it as is. + if (errors && CUnicode::isUTF8(data)) { + return data; + } + + return utf8; +} diff --git a/lib/platform/CXWindowsClipboardTextConverter.h b/lib/platform/CXWindowsClipboardTextConverter.h new file mode 100644 index 00000000..3840b7df --- /dev/null +++ b/lib/platform/CXWindowsClipboardTextConverter.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDTEXTCONVERTER_H +#define CXWINDOWSCLIPBOARDTEXTCONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from locale text encoding +class CXWindowsClipboardTextConverter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + CXWindowsClipboardTextConverter(Display* display, const char* name); + virtual ~CXWindowsClipboardTextConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +private: + Atom m_atom; +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardUCS2Converter.cpp b/lib/platform/CXWindowsClipboardUCS2Converter.cpp new file mode 100644 index 00000000..86b8d13f --- /dev/null +++ b/lib/platform/CXWindowsClipboardUCS2Converter.cpp @@ -0,0 +1,62 @@ +/* + * 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 "CXWindowsClipboardUCS2Converter.h" +#include "CUnicode.h" + +// +// CXWindowsClipboardUCS2Converter +// + +CXWindowsClipboardUCS2Converter::CXWindowsClipboardUCS2Converter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +CXWindowsClipboardUCS2Converter::~CXWindowsClipboardUCS2Converter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardUCS2Converter::getFormat() const +{ + return IClipboard::kText; +} + +Atom +CXWindowsClipboardUCS2Converter::getAtom() const +{ + return m_atom; +} + +int +CXWindowsClipboardUCS2Converter::getDataSize() const +{ + return 16; +} + +CString +CXWindowsClipboardUCS2Converter::fromIClipboard(const CString& data) const +{ + return CUnicode::UTF8ToUCS2(data); +} + +CString +CXWindowsClipboardUCS2Converter::toIClipboard(const CString& data) const +{ + return CUnicode::UCS2ToUTF8(data); +} diff --git a/lib/platform/CXWindowsClipboardUCS2Converter.h b/lib/platform/CXWindowsClipboardUCS2Converter.h new file mode 100644 index 00000000..e848e302 --- /dev/null +++ b/lib/platform/CXWindowsClipboardUCS2Converter.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDUCS2CONVERTER_H +#define CXWINDOWSCLIPBOARDUCS2CONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from UCS-2 encoding +class CXWindowsClipboardUCS2Converter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + CXWindowsClipboardUCS2Converter(Display* display, const char* name); + virtual ~CXWindowsClipboardUCS2Converter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +private: + Atom m_atom; +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardUTF8Converter.cpp b/lib/platform/CXWindowsClipboardUTF8Converter.cpp new file mode 100644 index 00000000..7edc850f --- /dev/null +++ b/lib/platform/CXWindowsClipboardUTF8Converter.cpp @@ -0,0 +1,61 @@ +/* + * 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 "CXWindowsClipboardUTF8Converter.h" + +// +// CXWindowsClipboardUTF8Converter +// + +CXWindowsClipboardUTF8Converter::CXWindowsClipboardUTF8Converter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +CXWindowsClipboardUTF8Converter::~CXWindowsClipboardUTF8Converter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardUTF8Converter::getFormat() const +{ + return IClipboard::kText; +} + +Atom +CXWindowsClipboardUTF8Converter::getAtom() const +{ + return m_atom; +} + +int +CXWindowsClipboardUTF8Converter::getDataSize() const +{ + return 8; +} + +CString +CXWindowsClipboardUTF8Converter::fromIClipboard(const CString& data) const +{ + return data; +} + +CString +CXWindowsClipboardUTF8Converter::toIClipboard(const CString& data) const +{ + return data; +} diff --git a/lib/platform/CXWindowsClipboardUTF8Converter.h b/lib/platform/CXWindowsClipboardUTF8Converter.h new file mode 100644 index 00000000..5ac8b153 --- /dev/null +++ b/lib/platform/CXWindowsClipboardUTF8Converter.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDUTF8CONVERTER_H +#define CXWINDOWSCLIPBOARDUTF8CONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from UTF-8 encoding +class CXWindowsClipboardUTF8Converter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + CXWindowsClipboardUTF8Converter(Display* display, const char* name); + virtual ~CXWindowsClipboardUTF8Converter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +private: + Atom m_atom; +}; + +#endif diff --git a/lib/platform/CXWindowsEventQueueBuffer.cpp b/lib/platform/CXWindowsEventQueueBuffer.cpp new file mode 100644 index 00000000..170e5bc6 --- /dev/null +++ b/lib/platform/CXWindowsEventQueueBuffer.cpp @@ -0,0 +1,208 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CXWindowsEventQueueBuffer.h" +#include "CLock.h" +#include "CThread.h" +#include "CEvent.h" +#include "IEventQueue.h" +#if HAVE_POLL +# include +#else +# if HAVE_SYS_SELECT_H +# include +# endif +# if HAVE_SYS_TIME_H +# include +# endif +# if HAVE_SYS_TYPES_H +# include +# endif +# if HAVE_UNISTD_H +# include +# endif +#endif + +// +// CEventQueueTimer +// + +class CEventQueueTimer { }; + + +// +// CXWindowsEventQueueBuffer +// + +CXWindowsEventQueueBuffer::CXWindowsEventQueueBuffer( + Display* display, Window window) : + m_display(display), + m_window(window), + m_waiting(false) +{ + assert(m_display != NULL); + assert(m_window != None); + + m_userEvent = XInternAtom(m_display, "SYNERGY_USER_EVENT", False); +} + +CXWindowsEventQueueBuffer::~CXWindowsEventQueueBuffer() +{ + // do nothing +} + +void +CXWindowsEventQueueBuffer::waitForEvent(double dtimeout) +{ + CThread::testCancel(); + + { + CLock lock(&m_mutex); + // we're now waiting for events + m_waiting = true; + + // push out pending events + flush(); + } + + // use poll() to wait for a message from the X server or for timeout. + // this is a good deal more efficient than polling and sleeping. +#if HAVE_POLL + struct pollfd pfds[1]; + pfds[0].fd = ConnectionNumber(m_display); + pfds[0].events = POLLIN; + int timeout = (dtimeout < 0.0) ? -1 : + static_cast(1000.0 * dtimeout); +#else + struct timeval timeout; + struct timeval* timeoutPtr; + if (dtimeout < 0.0) { + timeoutPtr = NULL; + } + else { + timeout.tv_sec = static_cast(dtimeout); + timeout.tv_usec = static_cast(1.0e+6 * + (dtimeout - timeout.tv_sec)); + timeoutPtr = &timeout; + } + + // initialize file descriptor sets + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(ConnectionNumber(m_display), &rfds); +#endif + + // wait for message from X server or for timeout. also check + // if the thread has been cancelled. poll() should return -1 + // with EINTR when the thread is cancelled. +#if HAVE_POLL + poll(pfds, 1, timeout); +#else + select(ConnectionNumber(m_display) + 1, + SELECT_TYPE_ARG234 &rfds, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG5 timeoutPtr); +#endif + + { + // we're no longer waiting for events + CLock lock(&m_mutex); + m_waiting = false; + } + + CThread::testCancel(); +} + +IEventQueueBuffer::Type +CXWindowsEventQueueBuffer::getEvent(CEvent& event, UInt32& dataID) +{ + CLock lock(&m_mutex); + + // push out pending events + flush(); + + // get next event + XNextEvent(m_display, &m_event); + + // process event + if (m_event.xany.type == ClientMessage && + m_event.xclient.message_type == m_userEvent) { + dataID = static_cast(m_event.xclient.data.l[0]); + return kUser; + } + else { + event = CEvent(CEvent::kSystem, + IEventQueue::getSystemTarget(), &m_event); + return kSystem; + } +} + +bool +CXWindowsEventQueueBuffer::addEvent(UInt32 dataID) +{ + // prepare a message + XEvent xevent; + xevent.xclient.type = ClientMessage; + xevent.xclient.window = m_window; + xevent.xclient.message_type = m_userEvent; + xevent.xclient.format = 32; + xevent.xclient.data.l[0] = static_cast(dataID); + + // save the message + CLock lock(&m_mutex); + m_postedEvents.push_back(xevent); + + // if we're currently waiting for an event then send saved events to + // the X server now. if we're not waiting then some other thread + // might be using the display connection so we can't safely use it + // too. + if (m_waiting) { + flush(); + } + + return true; +} + +bool +CXWindowsEventQueueBuffer::isEmpty() const +{ + CLock lock(&m_mutex); + return (XPending(m_display) == 0); +} + +CEventQueueTimer* +CXWindowsEventQueueBuffer::newTimer(double, bool) const +{ + return new CEventQueueTimer; +} + +void +CXWindowsEventQueueBuffer::deleteTimer(CEventQueueTimer* timer) const +{ + delete timer; +} + +void +CXWindowsEventQueueBuffer::flush() +{ + // note -- m_mutex must be locked on entry + + // flush the posted event list to the X server + for (size_t i = 0; i < m_postedEvents.size(); ++i) { + XSendEvent(m_display, m_window, False, 0, &m_postedEvents[i]); + } + XFlush(m_display); + m_postedEvents.clear(); +} diff --git a/lib/platform/CXWindowsEventQueueBuffer.h b/lib/platform/CXWindowsEventQueueBuffer.h new file mode 100644 index 00000000..5d9b6dd4 --- /dev/null +++ b/lib/platform/CXWindowsEventQueueBuffer.h @@ -0,0 +1,57 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSEVENTQUEUEBUFFER_H +#define CXWINDOWSEVENTQUEUEBUFFER_H + +#include "IEventQueueBuffer.h" +#include "CMutex.h" +#include "stdvector.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +#endif + +//! Event queue buffer for X11 +class CXWindowsEventQueueBuffer : public IEventQueueBuffer { +public: + CXWindowsEventQueueBuffer(Display*, Window); + virtual ~CXWindowsEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void waitForEvent(double timeout); + virtual Type getEvent(CEvent& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(CEventQueueTimer*) const; + +private: + void flush(); + +private: + typedef std::vector CEventList; + + CMutex m_mutex; + Display* m_display; + Window m_window; + Atom m_userEvent; + XEvent m_event; + CEventList m_postedEvents; + bool m_waiting; +}; + +#endif diff --git a/lib/platform/CXWindowsKeyState.cpp b/lib/platform/CXWindowsKeyState.cpp new file mode 100644 index 00000000..9f98ded4 --- /dev/null +++ b/lib/platform/CXWindowsKeyState.cpp @@ -0,0 +1,826 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsKeyState.h" +#include "CXWindowsUtil.h" +#include "CLog.h" +#include "CStringUtil.h" +#include "stdmap.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +# include +# define XK_MISCELLANY +# define XK_XKB_KEYS +# include +#if HAVE_XKB_EXTENSION +# include +#endif +#endif + +CXWindowsKeyState::CXWindowsKeyState(Display* display, bool useXKB) : + m_display(display) +{ + XGetKeyboardControl(m_display, &m_keyboardState); +#if HAVE_XKB_EXTENSION + if (useXKB) { + m_xkb = XkbGetMap(m_display, XkbKeyActionsMask | XkbKeyBehaviorsMask | + XkbAllClientInfoMask, XkbUseCoreKbd); + } + else { + m_xkb = NULL; + } +#endif + setActiveGroup(kGroupPollAndSet); +} + +CXWindowsKeyState::~CXWindowsKeyState() +{ +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbFreeKeyboard(m_xkb, 0, True); + } +#endif +} + +void +CXWindowsKeyState::setActiveGroup(SInt32 group) +{ + if (group == kGroupPollAndSet) { + m_group = -1; + m_group = pollActiveGroup(); + } + else if (group == kGroupPoll) { + m_group = -1; + } + else { + assert(group >= 0); + m_group = group; + } +} + +void +CXWindowsKeyState::setAutoRepeat(const XKeyboardState& state) +{ + m_keyboardState = state; +} + +KeyModifierMask +CXWindowsKeyState::mapModifiersFromX(unsigned int state) const +{ + UInt32 offset = 8 * getGroupFromState(state); + KeyModifierMask mask = 0; + for (int i = 0; i < 8; ++i) { + if ((state & (1u << i)) != 0) { + mask |= m_modifierFromX[offset + i]; + } + } + return mask; +} + +bool +CXWindowsKeyState::mapModifiersToX(KeyModifierMask mask, + unsigned int& modifiers) const +{ + modifiers = 0; + + for (SInt32 i = 0; i < kKeyModifierNumBits; ++i) { + KeyModifierMask bit = (1u << i); + if ((mask & bit) != 0) { + KeyModifierToXMask::const_iterator j = m_modifierToX.find(bit); + if (j == m_modifierToX.end()) { + return false; + } + else { + modifiers |= j->second; + } + } + } + + return true; +} + +void +CXWindowsKeyState::mapKeyToKeycodes(KeyID key, CKeycodeList& keycodes) const +{ + keycodes.clear(); + std::pair range = + m_keyCodeFromKey.equal_range(key); + for (KeyToKeyCodeMap::const_iterator i = range.first; + i != range.second; ++i) { + keycodes.push_back(i->second); + } +} + +bool +CXWindowsKeyState::fakeCtrlAltDel() +{ + // pass keys through unchanged + return false; +} + +KeyModifierMask +CXWindowsKeyState::pollActiveModifiers() const +{ + Window root = DefaultRootWindow(m_display), window; + int xRoot, yRoot, xWindow, yWindow; + unsigned int state; + if (!XQueryPointer(m_display, root, &root, &window, + &xRoot, &yRoot, &xWindow, &yWindow, &state)) { + state = 0; + } + return mapModifiersFromX(state); +} + +SInt32 +CXWindowsKeyState::pollActiveGroup() const +{ + if (m_group != -1) { + assert(m_group >= 0); + return m_group; + } + +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbStateRec state; + if (XkbGetState(m_display, XkbUseCoreKbd, &state)) { + return state.group; + } + } +#endif + return 0; +} + +void +CXWindowsKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + char keys[32]; + XQueryKeymap(m_display, keys); + for (UInt32 i = 0; i < 32; ++i) { + for (UInt32 j = 0; j < 8; ++j) { + if ((keys[i] & (1u << j)) != 0) { + pressedKeys.insert(8 * i + j); + } + } + } +} + +void +CXWindowsKeyState::getKeyMap(CKeyMap& keyMap) +{ + // get autorepeat info. we must use the global_auto_repeat told to + // us because it may have modified by synergy. + int oldGlobalAutoRepeat = m_keyboardState.global_auto_repeat; + XGetKeyboardControl(m_display, &m_keyboardState); + m_keyboardState.global_auto_repeat = oldGlobalAutoRepeat; + +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbGetUpdatedMap(m_display, XkbKeyActionsMask | XkbKeyBehaviorsMask | + XkbAllClientInfoMask, m_xkb); + updateKeysymMapXKB(keyMap); + } + else +#endif + { + updateKeysymMap(keyMap); + } +} + +void +CXWindowsKeyState::fakeKey(const Keystroke& keystroke) +{ + switch (keystroke.m_type) { + case Keystroke::kButton: + LOG((CLOG_DEBUG1 " %03x (%08x) %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, keystroke.m_data.m_button.m_press ? "down" : "up")); + if (keystroke.m_data.m_button.m_repeat) { + int c = keystroke.m_data.m_button.m_button; + int i = (c >> 3); + int b = 1 << (c & 7); + if (m_keyboardState.global_auto_repeat == AutoRepeatModeOff || + (m_keyboardState.auto_repeats[i] & b) == 0) { + LOG((CLOG_DEBUG1 " discard autorepeat")); + break; + } + } + XTestFakeKeyEvent(m_display, keystroke.m_data.m_button.m_button, + keystroke.m_data.m_button.m_press ? True : False, + CurrentTime); + break; + + case Keystroke::kGroup: + if (keystroke.m_data.m_group.m_absolute) { + LOG((CLOG_DEBUG1 " group %d", keystroke.m_data.m_group.m_group)); +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbLockGroup(m_display, XkbUseCoreKbd, + keystroke.m_data.m_group.m_group); + } + else +#endif + { + LOG((CLOG_DEBUG1 " ignored")); + } + } + else { + LOG((CLOG_DEBUG1 " group %+d", keystroke.m_data.m_group.m_group)); +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbLockGroup(m_display, XkbUseCoreKbd, + getEffectiveGroup(pollActiveGroup(), + keystroke.m_data.m_group.m_group)); + } + else +#endif + { + LOG((CLOG_DEBUG1 " ignored")); + } + } + break; + } + XFlush(m_display); +} + +void +CXWindowsKeyState::updateKeysymMap(CKeyMap& keyMap) +{ + // there are up to 4 keysyms per keycode + static const int maxKeysyms = 4; + + LOG((CLOG_DEBUG1 "non-XKB mapping")); + + // prepare map from X modifier to KeyModifierMask. certain bits + // are predefined. + m_modifierFromX.clear(); + m_modifierFromX.resize(8); + m_modifierFromX[ShiftMapIndex] = KeyModifierShift; + m_modifierFromX[LockMapIndex] = KeyModifierCapsLock; + m_modifierFromX[ControlMapIndex] = KeyModifierControl; + m_modifierToX.clear(); + m_modifierToX[KeyModifierShift] = ShiftMask; + m_modifierToX[KeyModifierCapsLock] = LockMask; + m_modifierToX[KeyModifierControl] = ControlMask; + + // prepare map from KeyID to KeyCode + m_keyCodeFromKey.clear(); + + // get the number of keycodes + int minKeycode, maxKeycode; + XDisplayKeycodes(m_display, &minKeycode, &maxKeycode); + int numKeycodes = maxKeycode - minKeycode + 1; + + // get the keyboard mapping for all keys + int keysymsPerKeycode; + KeySym* allKeysyms = XGetKeyboardMapping(m_display, + minKeycode, numKeycodes, + &keysymsPerKeycode); + + // it's more convenient to always have maxKeysyms KeySyms per key + { + KeySym* tmpKeysyms = new KeySym[maxKeysyms * numKeycodes]; + for (int i = 0; i < numKeycodes; ++i) { + for (int j = 0; j < maxKeysyms; ++j) { + if (j < keysymsPerKeycode) { + tmpKeysyms[maxKeysyms * i + j] = + allKeysyms[keysymsPerKeycode * i + j]; + } + else { + tmpKeysyms[maxKeysyms * i + j] = NoSymbol; + } + } + } + XFree(allKeysyms); + allKeysyms = tmpKeysyms; + } + + // get the buttons assigned to modifiers. X11 does not predefine + // the meaning of any modifiers except shift, caps lock, and the + // control key. the meaning of a modifier bit (other than those) + // depends entirely on the KeySyms mapped to that bit. unfortunately + // you cannot map a bit back to the KeySym used to produce it. + // for example, let's say button 1 maps to Alt_L without shift and + // Meta_L with shift. now if mod1 is mapped to button 1 that could + // mean the user used Alt or Meta to turn on that modifier and there's + // no way to know which. it's also possible for one button to be + // mapped to multiple bits so both mod1 and mod2 could be generated + // by button 1. + // + // we're going to ignore any modifier for a button except the first. + // with the above example, that means we'll ignore the mod2 modifier + // bit unless it's also mapped to some other button. we're also + // going to ignore all KeySyms except the first modifier KeySym, + // which means button 1 above won't map to Meta, just Alt. + std::map modifierButtons; + XModifierKeymap* modifiers = XGetModifierMapping(m_display); + for (unsigned int i = 0; i < 8; ++i) { + const KeyCode* buttons = + modifiers->modifiermap + i * modifiers->max_keypermod; + for (int j = 0; j < modifiers->max_keypermod; ++j) { + modifierButtons.insert(std::make_pair(buttons[j], i)); + } + } + XFreeModifiermap(modifiers); + modifierButtons.erase(0); + + // Hack to deal with VMware. When a VMware client grabs input the + // player clears out the X modifier map for whatever reason. We're + // notified of the change and arrive here to discover that there + // are no modifiers at all. Since this prevents the modifiers from + // working in the VMware client we'll use the last known good set + // of modifiers when there are no modifiers. If there are modifiers + // we update the last known good set. + if (!modifierButtons.empty()) { + m_lastGoodNonXKBModifiers = modifierButtons; + } + else { + modifierButtons = m_lastGoodNonXKBModifiers; + } + + // add entries for each keycode + CKeyMap::KeyItem item; + for (int i = 0; i < numKeycodes; ++i) { + KeySym* keysyms = allKeysyms + maxKeysyms * i; + KeyCode keycode = static_cast(i + minKeycode); + item.m_button = static_cast(keycode); + item.m_client = 0; + + // determine modifier sensitivity + item.m_sensitive = 0; + + // if the keysyms in levels 2 or 3 exist and differ from levels + // 0 and 1 then the key is sensitive AltGr (Mode_switch) + if ((keysyms[2] != NoSymbol && keysyms[2] != keysyms[0]) || + (keysyms[3] != NoSymbol && keysyms[2] != keysyms[1])) { + item.m_sensitive |= KeyModifierAltGr; + } + + // check if the key is caps-lock sensitive. some systems only + // provide one keysym for keys sensitive to caps-lock. if we + // find that then fill in the missing keysym. + if (keysyms[0] != NoSymbol && keysyms[1] == NoSymbol && + keysyms[2] == NoSymbol && keysyms[3] == NoSymbol) { + KeySym lKeysym, uKeysym; + XConvertCase(keysyms[0], &lKeysym, &uKeysym); + if (lKeysym != uKeysym) { + keysyms[0] = lKeysym; + keysyms[1] = uKeysym; + item.m_sensitive |= KeyModifierCapsLock; + } + } + else if (keysyms[0] != NoSymbol && keysyms[1] != NoSymbol) { + KeySym lKeysym, uKeysym; + XConvertCase(keysyms[0], &lKeysym, &uKeysym); + if (lKeysym != uKeysym && + lKeysym == keysyms[0] && + uKeysym == keysyms[1]) { + item.m_sensitive |= KeyModifierCapsLock; + } + else if (keysyms[2] != NoSymbol && keysyms[3] != NoSymbol) { + XConvertCase(keysyms[2], &lKeysym, &uKeysym); + if (lKeysym != uKeysym && + lKeysym == keysyms[2] && + uKeysym == keysyms[3]) { + item.m_sensitive |= KeyModifierCapsLock; + } + } + } + + // key is sensitive to shift if keysyms in levels 0 and 1 or + // levels 2 and 3 don't match. it's also sensitive to shift + // if it's sensitive to caps-lock. + if ((item.m_sensitive & KeyModifierCapsLock) != 0) { + item.m_sensitive |= KeyModifierShift; + } + else if ((keysyms[0] != NoSymbol && keysyms[1] != NoSymbol && + keysyms[0] != keysyms[1]) || + (keysyms[2] != NoSymbol && keysyms[3] != NoSymbol && + keysyms[2] != keysyms[3])) { + item.m_sensitive |= KeyModifierShift; + } + + // key is sensitive to numlock if any keysym on it is + if (IsKeypadKey(keysyms[0]) || IsPrivateKeypadKey(keysyms[0]) || + IsKeypadKey(keysyms[1]) || IsPrivateKeypadKey(keysyms[1]) || + IsKeypadKey(keysyms[2]) || IsPrivateKeypadKey(keysyms[2]) || + IsKeypadKey(keysyms[3]) || IsPrivateKeypadKey(keysyms[3])) { + item.m_sensitive |= KeyModifierNumLock; + } + + // do each keysym (shift level) + for (int j = 0; j < maxKeysyms; ++j) { + item.m_id = CXWindowsUtil::mapKeySymToKeyID(keysyms[j]); + if (item.m_id == kKeyNone) { + if (j != 0 && modifierButtons.count(keycode) > 0) { + // pretend the modifier works in other shift levels + // because it probably does. + if (keysyms[1] == NoSymbol || j != 3) { + item.m_id = CXWindowsUtil::mapKeySymToKeyID(keysyms[0]); + } + else { + item.m_id = CXWindowsUtil::mapKeySymToKeyID(keysyms[1]); + } + } + if (item.m_id == kKeyNone) { + continue; + } + } + + // group is 0 for levels 0 and 1 and 1 for levels 2 and 3 + item.m_group = (j >= 2) ? 1 : 0; + + // compute required modifiers + item.m_required = 0; + if ((j & 1) != 0) { + item.m_required |= KeyModifierShift; + } + if ((j & 2) != 0) { + item.m_required |= KeyModifierAltGr; + } + + item.m_generates = 0; + item.m_lock = false; + if (modifierButtons.count(keycode) > 0) { + // get flags for modifier keys + CKeyMap::initModifierKey(item); + + // add mapping from X (unless we already have) + if (item.m_generates != 0) { + unsigned int bit = modifierButtons[keycode]; + if (m_modifierFromX[bit] == 0) { + m_modifierFromX[bit] = item.m_generates; + m_modifierToX[item.m_generates] = (1u << bit); + } + } + } + + // add key + keyMap.addKeyEntry(item); + m_keyCodeFromKey.insert(std::make_pair(item.m_id, keycode)); + + // add other ways to synthesize the key + if ((j & 1) != 0) { + // add capslock version of key is sensitive to capslock + KeySym lKeysym, uKeysym; + XConvertCase(keysyms[j], &lKeysym, &uKeysym); + if (lKeysym != uKeysym && + lKeysym == keysyms[j - 1] && + uKeysym == keysyms[j]) { + item.m_required &= ~KeyModifierShift; + item.m_required |= KeyModifierCapsLock; + keyMap.addKeyEntry(item); + item.m_required |= KeyModifierShift; + item.m_required &= ~KeyModifierCapsLock; + } + + // add numlock version of key if sensitive to numlock + if (IsKeypadKey(keysyms[j]) || IsPrivateKeypadKey(keysyms[j])) { + item.m_required &= ~KeyModifierShift; + item.m_required |= KeyModifierNumLock; + keyMap.addKeyEntry(item); + item.m_required |= KeyModifierShift; + item.m_required &= ~KeyModifierNumLock; + } + } + } + } + + delete[] allKeysyms; +} + +#if HAVE_XKB_EXTENSION +void +CXWindowsKeyState::updateKeysymMapXKB(CKeyMap& keyMap) +{ + static const XkbKTMapEntryRec defMapEntry = { + True, // active + 0, // level + { + 0, // mods.mask + 0, // mods.real_mods + 0 // mods.vmods + } + }; + + LOG((CLOG_DEBUG1 "XKB mapping")); + + // find the number of groups + int maxNumGroups = 0; + for (int i = m_xkb->min_key_code; i <= m_xkb->max_key_code; ++i) { + int numGroups = XkbKeyNumGroups(m_xkb, static_cast(i)); + if (numGroups > maxNumGroups) { + maxNumGroups = numGroups; + } + } + + // prepare map from X modifier to KeyModifierMask + std::vector modifierLevel(maxNumGroups * 8, 4); + m_modifierFromX.clear(); + m_modifierFromX.resize(maxNumGroups * 8); + m_modifierToX.clear(); + + // prepare map from KeyID to KeyCode + m_keyCodeFromKey.clear(); + + // Hack to deal with VMware. When a VMware client grabs input the + // player clears out the X modifier map for whatever reason. We're + // notified of the change and arrive here to discover that there + // are no modifiers at all. Since this prevents the modifiers from + // working in the VMware client we'll use the last known good set + // of modifiers when there are no modifiers. If there are modifiers + // we update the last known good set. + bool useLastGoodModifiers = !hasModifiersXKB(); + if (!useLastGoodModifiers) { + m_lastGoodXKBModifiers.clear(); + } + + // check every button. on this pass we save all modifiers as native + // X modifier masks. + CKeyMap::KeyItem item; + for (int i = m_xkb->min_key_code; i <= m_xkb->max_key_code; ++i) { + KeyCode keycode = static_cast(i); + item.m_button = static_cast(keycode); + item.m_client = 0; + + // skip keys with no groups (they generate no symbols) + if (XkbKeyNumGroups(m_xkb, keycode) == 0) { + continue; + } + + // note half-duplex keys + const XkbBehavior& b = m_xkb->server->behaviors[keycode]; + if ((b.type & XkbKB_OpMask) == XkbKB_Lock) { + keyMap.addHalfDuplexButton(item.m_button); + } + + // iterate over all groups + for (int group = 0; group < maxNumGroups; ++group) { + item.m_group = group; + int eGroup = getEffectiveGroup(keycode, group); + + // get key info + XkbKeyTypePtr type = XkbKeyKeyType(m_xkb, keycode, eGroup); + + // set modifiers the item is sensitive to + item.m_sensitive = type->mods.mask; + + // iterate over all shift levels for the button (including none) + for (int j = -1; j < type->map_count; ++j) { + const XkbKTMapEntryRec* mapEntry = + ((j == -1) ? &defMapEntry : type->map + j); + if (!mapEntry->active) { + continue; + } + int level = mapEntry->level; + + // set required modifiers for this item + item.m_required = mapEntry->mods.mask; + if ((item.m_required & LockMask) != 0 && + j != -1 && type->preserve != NULL && + (type->preserve[j].mask & LockMask) != 0) { + // sensitive caps lock and we preserve caps-lock. + // preserving caps-lock means we Xlib functions would + // yield the capitialized KeySym so we'll adjust the + // level accordingly. + if ((level ^ 1) < type->num_levels) { + level ^= 1; + } + } + + // get the keysym for this item + KeySym keysym = XkbKeySymEntry(m_xkb, keycode, level, eGroup); + + // check for group change actions, locking modifiers, and + // modifier masks. + item.m_lock = false; + bool isModifier = false; + UInt32 modifierMask = m_xkb->map->modmap[keycode]; + if (XkbKeyHasActions(m_xkb, keycode)) { + XkbAction* action = + XkbKeyActionEntry(m_xkb, keycode, level, eGroup); + if (action->type == XkbSA_SetMods || + action->type == XkbSA_LockMods) { + isModifier = true; + + // note toggles + item.m_lock = (action->type == XkbSA_LockMods); + + // maybe use action's mask + if ((action->mods.flags & XkbSA_UseModMapMods) == 0) { + modifierMask = action->mods.mask; + } + } + else if (action->type == XkbSA_SetGroup || + action->type == XkbSA_LatchGroup || + action->type == XkbSA_LockGroup) { + // ignore group change key + continue; + } + } + level = mapEntry->level; + + // VMware modifier hack + if (useLastGoodModifiers) { + XKBModifierMap::const_iterator k = + m_lastGoodXKBModifiers.find(eGroup * 256 + keycode); + if (k != m_lastGoodXKBModifiers.end()) { + // Use last known good modifier + isModifier = true; + level = k->second.m_level; + modifierMask = k->second.m_mask; + item.m_lock = k->second.m_lock; + } + } + else if (isModifier) { + // Save known good modifier + XKBModifierInfo& info = + m_lastGoodXKBModifiers[eGroup * 256 + keycode]; + info.m_level = level; + info.m_mask = modifierMask; + info.m_lock = item.m_lock; + } + + // record the modifier mask for this key. don't bother + // for keys that change the group. + item.m_generates = 0; + UInt32 modifierBit = + CXWindowsUtil::getModifierBitForKeySym(keysym); + if (isModifier && modifierBit != kKeyModifierBitNone) { + item.m_generates = (1u << modifierBit); + for (SInt32 j = 0; j < 8; ++j) { + // skip modifiers this key doesn't generate + if ((modifierMask & (1u << j)) == 0) { + continue; + } + + // skip keys that map to a modifier that we've + // already seen using fewer modifiers. that is + // if this key must combine with other modifiers + // and we know of a key that combines with fewer + // modifiers (or no modifiers) then prefer the + // other key. + if (level >= modifierLevel[8 * group + j]) { + continue; + } + modifierLevel[8 * group + j] = level; + + // save modifier + m_modifierFromX[8 * group + j] |= (1u << modifierBit); + m_modifierToX.insert(std::make_pair( + 1u << modifierBit, 1u << j)); + } + } + + // handle special cases of just one keysym for the keycode + if (type->num_levels == 1) { + // if there are upper- and lowercase versions of the + // keysym then add both. + KeySym lKeysym, uKeysym; + XConvertCase(keysym, &lKeysym, &uKeysym); + if (lKeysym != uKeysym) { + if (j != -1) { + continue; + } + + item.m_sensitive |= ShiftMask | LockMask; + + KeyID lKeyID = CXWindowsUtil::mapKeySymToKeyID(lKeysym); + KeyID uKeyID = CXWindowsUtil::mapKeySymToKeyID(uKeysym); + if (lKeyID == kKeyNone || uKeyID == kKeyNone) { + continue; + } + + item.m_id = lKeyID; + item.m_required = 0; + keyMap.addKeyEntry(item); + + item.m_id = uKeyID; + item.m_required = ShiftMask; + keyMap.addKeyEntry(item); + item.m_required = LockMask; + keyMap.addKeyEntry(item); + + if (group == 0) { + m_keyCodeFromKey.insert( + std::make_pair(lKeyID, keycode)); + m_keyCodeFromKey.insert( + std::make_pair(uKeyID, keycode)); + } + continue; + } + } + + // add entry + item.m_id = CXWindowsUtil::mapKeySymToKeyID(keysym); + keyMap.addKeyEntry(item); + if (group == 0) { + m_keyCodeFromKey.insert(std::make_pair(item.m_id, keycode)); + } + } + } + } + + // change all modifier masks to synergy masks from X masks + keyMap.foreachKey(&CXWindowsKeyState::remapKeyModifiers, this); + + // allow composition across groups + keyMap.allowGroupSwitchDuringCompose(); +} +#endif + +void +CXWindowsKeyState::remapKeyModifiers(KeyID id, SInt32 group, + CKeyMap::KeyItem& item, void* vself) +{ + CXWindowsKeyState* self = reinterpret_cast(vself); + item.m_required = + self->mapModifiersFromX(XkbBuildCoreState(item.m_required, group)); + item.m_sensitive = + self->mapModifiersFromX(XkbBuildCoreState(item.m_sensitive, group)); +} + +bool +CXWindowsKeyState::hasModifiersXKB() const +{ +#if HAVE_XKB_EXTENSION + // iterate over all keycodes + for (int i = m_xkb->min_key_code; i <= m_xkb->max_key_code; ++i) { + KeyCode keycode = static_cast(i); + if (XkbKeyHasActions(m_xkb, keycode)) { + // iterate over all groups + int numGroups = XkbKeyNumGroups(m_xkb, keycode); + for (int group = 0; group < numGroups; ++group) { + // iterate over all shift levels for the button (including none) + XkbKeyTypePtr type = XkbKeyKeyType(m_xkb, keycode, group); + for (int j = -1; j < type->map_count; ++j) { + if (j != -1 && !type->map[j].active) { + continue; + } + int level = ((j == -1) ? 0 : type->map[j].level); + XkbAction* action = + XkbKeyActionEntry(m_xkb, keycode, level, group); + if (action->type == XkbSA_SetMods || + action->type == XkbSA_LockMods) { + return true; + } + } + } + } + } +#endif + return false; +} + +int +CXWindowsKeyState::getEffectiveGroup(KeyCode keycode, int group) const +{ + (void)keycode; +#if HAVE_XKB_EXTENSION + // get effective group for key + int numGroups = XkbKeyNumGroups(m_xkb, keycode); + if (group >= numGroups) { + unsigned char groupInfo = XkbKeyGroupInfo(m_xkb, keycode); + switch (XkbOutOfRangeGroupAction(groupInfo)) { + case XkbClampIntoRange: + group = numGroups - 1; + break; + + case XkbRedirectIntoRange: + group = XkbOutOfRangeGroupNumber(groupInfo); + if (group >= numGroups) { + group = 0; + } + break; + + default: + // wrap + group %= numGroups; + break; + } + } +#endif + return group; +} + +UInt32 +CXWindowsKeyState::getGroupFromState(unsigned int state) const +{ +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + return XkbGroupForCoreState(state); + } +#endif + return 0; +} diff --git a/lib/platform/CXWindowsKeyState.h b/lib/platform/CXWindowsKeyState.h new file mode 100644 index 00000000..02ed4520 --- /dev/null +++ b/lib/platform/CXWindowsKeyState.h @@ -0,0 +1,155 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSKEYSTATE_H +#define CXWINDOWSKEYSTATE_H + +#include "CKeyState.h" +#include "stdmap.h" +#include "stdvector.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +# if HAVE_X11_EXTENSIONS_XTEST_H +# include +# else +# error The XTest extension is required to build synergy +# endif +# if HAVE_XKB_EXTENSION +# include +# endif +#endif + +//! X Windows key state +/*! +A key state for X Windows. +*/ +class CXWindowsKeyState : public CKeyState { +public: + typedef std::vector CKeycodeList; + enum { + kGroupPoll = -1, + kGroupPollAndSet = -2 + }; + + CXWindowsKeyState(Display*, bool useXKB); + ~CXWindowsKeyState(); + + //! @name modifiers + //@{ + + //! Set active group + /*! + Sets the active group to \p group. This is the group returned by + \c pollActiveGroup(). If \p group is \c kGroupPoll then + \c pollActiveGroup() will really poll, but that's a slow operation + on X11. If \p group is \c kGroupPollAndSet then this will poll the + active group now and use it for future calls to \c pollActiveGroup(). + */ + void setActiveGroup(SInt32 group); + + //! Set the auto-repeat state + /*! + Sets the auto-repeat state. + */ + void setAutoRepeat(const XKeyboardState&); + + //@} + //! @name accessors + //@{ + + //! Convert X modifier mask to synergy mask + /*! + Returns the synergy modifier mask corresponding to the X modifier + mask in \p state. + */ + KeyModifierMask mapModifiersFromX(unsigned int state) const; + + //! Convert synergy modifier mask to X mask + /*! + Converts the synergy modifier mask to the corresponding X modifier + mask. Returns \c true if successful and \c false if any modifier + could not be converted. + */ + bool mapModifiersToX(KeyModifierMask, unsigned int&) const; + + //! Convert synergy key to all corresponding X keycodes + /*! + Converts the synergy key \p key to all of the keycodes that map to + that key. + */ + void mapKeyToKeycodes(KeyID key, + CKeycodeList& keycodes) const; + + //@} + + // IKeyState overrides + virtual bool fakeCtrlAltDel(); + virtual KeyModifierMask + pollActiveModifiers() const; + virtual SInt32 pollActiveGroup() const; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const; + +protected: + // CKeyState overrides + virtual void getKeyMap(CKeyMap& keyMap); + virtual void fakeKey(const Keystroke& keystroke); + +private: + void updateKeysymMap(CKeyMap&); + void updateKeysymMapXKB(CKeyMap&); + bool hasModifiersXKB() const; + int getEffectiveGroup(KeyCode, int group) const; + UInt32 getGroupFromState(unsigned int state) const; + + static void remapKeyModifiers(KeyID, SInt32, + CKeyMap::KeyItem&, void*); + +private: + struct XKBModifierInfo { + public: + unsigned char m_level; + UInt32 m_mask; + bool m_lock; + }; + + typedef std::vector KeyModifierMaskList; + typedef std::map KeyModifierToXMask; + typedef std::multimap KeyToKeyCodeMap; + typedef std::map NonXKBModifierMap; + typedef std::map XKBModifierMap; + + Display* m_display; +#if HAVE_XKB_EXTENSION + XkbDescPtr m_xkb; +#endif + SInt32 m_group; + XKBModifierMap m_lastGoodXKBModifiers; + NonXKBModifierMap m_lastGoodNonXKBModifiers; + + // X modifier (bit number) to synergy modifier (mask) mapping + KeyModifierMaskList m_modifierFromX; + + // synergy modifier (mask) to X modifier (mask) + KeyModifierToXMask m_modifierToX; + + // map KeyID to all keycodes that can synthesize that KeyID + KeyToKeyCodeMap m_keyCodeFromKey; + + // autorepeat state + XKeyboardState m_keyboardState; +}; + +#endif diff --git a/lib/platform/CXWindowsScreen.cpp b/lib/platform/CXWindowsScreen.cpp new file mode 100644 index 00000000..9c4ab134 --- /dev/null +++ b/lib/platform/CXWindowsScreen.cpp @@ -0,0 +1,1901 @@ +/* + * 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 "CXWindowsScreen.h" +#include "CXWindowsClipboard.h" +#include "CXWindowsEventQueueBuffer.h" +#include "CXWindowsKeyState.h" +#include "CXWindowsScreenSaver.h" +#include "CXWindowsUtil.h" +#include "CClipboard.h" +#include "CKeyMap.h" +#include "XScreen.h" +#include "CLog.h" +#include "CStopwatch.h" +#include "CStringUtil.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +# include +# define XK_MISCELLANY +# define XK_XKB_KEYS +# include +# if HAVE_X11_EXTENSIONS_XTEST_H +# include +# else +# error The XTest extension is required to build synergy +# endif +# if HAVE_X11_EXTENSIONS_XINERAMA_H + // Xinerama.h may lack extern "C" for inclusion by C++ + extern "C" { +# include + } +# endif +# if HAVE_XKB_EXTENSION +# include +# endif +#endif +#include "CArch.h" + + +// +// CXWindowsScreen +// + +// NOTE -- the X display is shared among several objects but is owned +// by the CXWindowsScreen. Xlib is not reentrant so we must ensure +// that no two objects can simultaneously call Xlib with the display. +// this is easy since we only make X11 calls from the main thread. +// we must also ensure that these objects do not use the display in +// their destructors or, if they do, we can tell them not to. This +// is to handle unexpected disconnection of the X display, when any +// call on the display is invalid. In that situation we discard the +// display and the X11 event queue buffer, ignore any calls that try +// to use the display, and wait to be destroyed. + +CXWindowsScreen* CXWindowsScreen::s_screen = NULL; + +CXWindowsScreen::CXWindowsScreen(const char* displayName, bool isPrimary) : + m_isPrimary(isPrimary), + m_display(NULL), + m_root(None), + m_window(None), + m_isOnScreen(m_isPrimary), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_xCursor(0), m_yCursor(0), + m_keyState(NULL), + m_lastFocus(None), + m_lastFocusRevert(RevertToNone), + m_im(NULL), + m_ic(NULL), + m_lastKeycode(0), + m_sequenceNumber(0), + m_screensaver(NULL), + m_screensaverNotify(false), + m_xtestIsXineramaUnaware(true), + m_xkb(false) +{ + assert(s_screen == NULL); + + s_screen = this; + + // set the X I/O error handler so we catch the display disconnecting + XSetIOErrorHandler(&CXWindowsScreen::ioErrorHandler); + + try { + m_display = openDisplay(displayName); + m_root = DefaultRootWindow(m_display); + saveShape(); + m_window = openWindow(); + m_screensaver = new CXWindowsScreenSaver(m_display, + m_window, getEventTarget()); + m_keyState = new CXWindowsKeyState(m_display, m_xkb); + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_xinerama ? "(xinerama)" : "")); + LOG((CLOG_DEBUG "window is 0x%08x", m_window)); + } + catch (...) { + if (m_display != NULL) { + XCloseDisplay(m_display); + } + throw; + } + + // primary/secondary screen only initialization + if (m_isPrimary) { + // start watching for events on other windows + selectEvents(m_root); + + // prepare to use input methods + openIM(); + } + else { + // become impervious to server grabs + XTestGrabControl(m_display, True); + } + + // initialize the clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_clipboard[id] = new CXWindowsClipboard(m_display, m_window, id); + } + + // install event handlers + EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(), + new TMethodEventJob(this, + &CXWindowsScreen::handleSystemEvent)); + + // install the platform event queue + EVENTQUEUE->adoptBuffer(new CXWindowsEventQueueBuffer(m_display, m_window)); +} + +CXWindowsScreen::~CXWindowsScreen() +{ + assert(s_screen != NULL); + assert(m_display != NULL); + + EVENTQUEUE->adoptBuffer(NULL); + EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget()); + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + delete m_clipboard[id]; + } + delete m_keyState; + delete m_screensaver; + m_keyState = NULL; + m_screensaver = NULL; + if (m_display != NULL) { + // FIXME -- is it safe to clean up the IC and IM without a display? + if (m_ic != NULL) { + XDestroyIC(m_ic); + } + if (m_im != NULL) { + XCloseIM(m_im); + } + XDestroyWindow(m_display, m_window); + XCloseDisplay(m_display); + } + XSetIOErrorHandler(NULL); + + s_screen = NULL; +} + +void +CXWindowsScreen::enable() +{ + if (!m_isPrimary) { + // get the keyboard control state + XKeyboardState keyControl; + XGetKeyboardControl(m_display, &keyControl); + m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn); + m_keyState->setAutoRepeat(keyControl); + + // move hider window under the cursor center + XMoveWindow(m_display, m_window, m_xCenter, m_yCenter); + + // raise and show the window + // FIXME -- take focus? + XMapRaised(m_display, m_window); + + // warp the mouse to the cursor center + fakeMouseMove(m_xCenter, m_yCenter); + } +} + +void +CXWindowsScreen::disable() +{ + // release input context focus + if (m_ic != NULL) { + XUnsetICFocus(m_ic); + } + + // unmap the hider/grab window. this also ungrabs the mouse and + // keyboard if they're grabbed. + XUnmapWindow(m_display, m_window); + + // restore auto-repeat state + if (!m_isPrimary && m_autoRepeat) { + XAutoRepeatOn(m_display); + } +} + +void +CXWindowsScreen::enter() +{ + // release input context focus + if (m_ic != NULL) { + XUnsetICFocus(m_ic); + } + + // set the input focus to what it had been when we took it + if (m_lastFocus != None) { + // the window may not exist anymore so ignore errors + CXWindowsUtil::CErrorLock lock(m_display); + XSetInputFocus(m_display, m_lastFocus, m_lastFocusRevert, CurrentTime); + } + + // unmap the hider/grab window. this also ungrabs the mouse and + // keyboard if they're grabbed. + XUnmapWindow(m_display, m_window); + +/* maybe call this if entering for the screensaver + // set keyboard focus to root window. the screensaver should then + // pick up key events for when the user enters a password to unlock. + XSetInputFocus(m_display, PointerRoot, PointerRoot, CurrentTime); +*/ + + if (!m_isPrimary) { + // get the keyboard control state + XKeyboardState keyControl; + XGetKeyboardControl(m_display, &keyControl); + m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn); + m_keyState->setAutoRepeat(keyControl); + + // turn off auto-repeat. we do this so fake key press events don't + // cause the local server to generate their own auto-repeats of + // those keys. + XAutoRepeatOff(m_display); + } + + // now on screen + m_isOnScreen = true; +} + +bool +CXWindowsScreen::leave() +{ + if (!m_isPrimary) { + // restore the previous keyboard auto-repeat state. if the user + // changed the auto-repeat configuration while on the client then + // that state is lost. that's because we can't get notified by + // the X server when the auto-repeat configuration is changed so + // we can't track the desired configuration. + if (m_autoRepeat) { + XAutoRepeatOn(m_display); + } + + // move hider window under the cursor center + XMoveWindow(m_display, m_window, m_xCenter, m_yCenter); + } + + // raise and show the window + XMapRaised(m_display, m_window); + + // grab the mouse and keyboard, if primary and possible + if (m_isPrimary && !grabMouseAndKeyboard()) { + XUnmapWindow(m_display, m_window); + return false; + } + + // save current focus + XGetInputFocus(m_display, &m_lastFocus, &m_lastFocusRevert); + + // take focus + XSetInputFocus(m_display, m_window, RevertToPointerRoot, CurrentTime); + + // now warp the mouse. we warp after showing the window so we're + // guaranteed to get the mouse leave event and to prevent the + // keyboard focus from changing under point-to-focus policies. + if (m_isPrimary) { + warpCursor(m_xCenter, m_yCenter); + } + else { + fakeMouseMove(m_xCenter, m_yCenter); + } + + // set input context focus to our window + if (m_ic != NULL) { + XmbResetIC(m_ic); + XSetICFocus(m_ic); + m_filtered.clear(); + } + + // now off screen + m_isOnScreen = false; + + return true; +} + +bool +CXWindowsScreen::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + // fail if we don't have the requested clipboard + if (m_clipboard[id] == NULL) { + return false; + } + + // get the actual time. ICCCM does not allow CurrentTime. + Time timestamp = CXWindowsUtil::getCurrentTime( + m_display, m_clipboard[id]->getWindow()); + + if (clipboard != NULL) { + // save clipboard data + return CClipboard::copy(m_clipboard[id], clipboard, timestamp); + } + else { + // assert clipboard ownership + if (!m_clipboard[id]->open(timestamp)) { + return false; + } + m_clipboard[id]->empty(); + m_clipboard[id]->close(); + return true; + } +} + +void +CXWindowsScreen::checkClipboards() +{ + // do nothing, we're always up to date +} + +void +CXWindowsScreen::openScreensaver(bool notify) +{ + m_screensaverNotify = notify; + if (!m_screensaverNotify) { + m_screensaver->disable(); + } +} + +void +CXWindowsScreen::closeScreensaver() +{ + if (!m_screensaverNotify) { + m_screensaver->enable(); + } +} + +void +CXWindowsScreen::screensaver(bool activate) +{ + if (activate) { + m_screensaver->activate(); + } + else { + m_screensaver->deactivate(); + } +} + +void +CXWindowsScreen::resetOptions() +{ + m_xtestIsXineramaUnaware = true; +} + +void +CXWindowsScreen::setOptions(const COptionsList& options) +{ + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + if (options[i] == kOptionXTestXineramaUnaware) { + m_xtestIsXineramaUnaware = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "XTest is Xinerama unaware %s", m_xtestIsXineramaUnaware ? "true" : "false")); + } + } +} + +void +CXWindowsScreen::setSequenceNumber(UInt32 seqNum) +{ + m_sequenceNumber = seqNum; +} + +bool +CXWindowsScreen::isPrimary() const +{ + return m_isPrimary; +} + +void* +CXWindowsScreen::getEventTarget() const +{ + return const_cast(this); +} + +bool +CXWindowsScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + assert(clipboard != NULL); + + // fail if we don't have the requested clipboard + if (m_clipboard[id] == NULL) { + return false; + } + + // get the actual time. ICCCM does not allow CurrentTime. + Time timestamp = CXWindowsUtil::getCurrentTime( + m_display, m_clipboard[id]->getWindow()); + + // copy the clipboard + return CClipboard::copy(clipboard, m_clipboard[id], timestamp); +} + +void +CXWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + x = m_x; + y = m_y; + w = m_w; + h = m_h; +} + +void +CXWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + Window root, window; + int mx, my, xWindow, yWindow; + unsigned int mask; + if (XQueryPointer(m_display, m_root, &root, &window, + &mx, &my, &xWindow, &yWindow, &mask)) { + x = mx; + y = my; + } + else { + x = m_xCenter; + y = m_yCenter; + } +} + +void +CXWindowsScreen::reconfigure(UInt32) +{ + // do nothing +} + +void +CXWindowsScreen::warpCursor(SInt32 x, SInt32 y) +{ + // warp mouse + warpCursorNoFlush(x, y); + + // remove all input events before and including warp + XEvent event; + while (XCheckMaskEvent(m_display, PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask, + &event)) { + // do nothing + } + + // save position as last position + m_xCursor = x; + m_yCursor = y; +} + +UInt32 +CXWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + // only allow certain modifiers + if ((mask & ~(KeyModifierShift | KeyModifierControl | + KeyModifierAlt | KeyModifierSuper)) != 0) { + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // fail if no keys + if (key == kKeyNone && mask == 0) { + return 0; + } + + // convert to X + unsigned int modifiers; + if (!m_keyState->mapModifiersToX(mask, modifiers)) { + // can't map all modifiers + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + CXWindowsKeyState::CKeycodeList keycodes; + m_keyState->mapKeyToKeycodes(key, keycodes); + if (key != kKeyNone && keycodes.empty()) { + // can't map key + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // choose hotkey id + UInt32 id; + if (!m_oldHotKeyIDs.empty()) { + id = m_oldHotKeyIDs.back(); + m_oldHotKeyIDs.pop_back(); + } + else { + id = m_hotKeys.size() + 1; + } + HotKeyList& hotKeys = m_hotKeys[id]; + + // all modifier hotkey must be treated specially. for each modifier + // we need to grab the modifier key in combination with all the other + // requested modifiers. + bool err = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &err); + if (key == kKeyNone) { + static const KeyModifierMask s_hotKeyModifiers[] = { + KeyModifierShift, + KeyModifierControl, + KeyModifierAlt, + KeyModifierMeta, + KeyModifierSuper + }; + + XModifierKeymap* modKeymap = XGetModifierMapping(m_display); + for (size_t j = 0; j < sizeof(s_hotKeyModifiers) / + sizeof(s_hotKeyModifiers[0]) && !err; ++j) { + // skip modifier if not in mask + if ((mask & s_hotKeyModifiers[j]) == 0) { + continue; + } + + // skip with error if we can't map remaining modifiers + unsigned int modifiers2; + KeyModifierMask mask2 = (mask & ~s_hotKeyModifiers[j]); + if (!m_keyState->mapModifiersToX(mask2, modifiers2)) { + err = true; + continue; + } + + // compute modifier index for modifier. there should be + // exactly one X modifier missing + int index; + switch (modifiers ^ modifiers2) { + case ShiftMask: + index = ShiftMapIndex; + break; + + case LockMask: + index = LockMapIndex; + break; + + case ControlMask: + index = ControlMapIndex; + break; + + case Mod1Mask: + index = Mod1MapIndex; + break; + + case Mod2Mask: + index = Mod2MapIndex; + break; + + case Mod3Mask: + index = Mod3MapIndex; + break; + + case Mod4Mask: + index = Mod4MapIndex; + break; + + case Mod5Mask: + index = Mod5MapIndex; + break; + + default: + err = true; + continue; + } + + // grab each key for the modifier + const KeyCode* modifiermap = + modKeymap->modifiermap + index * modKeymap->max_keypermod; + for (int k = 0; k < modKeymap->max_keypermod && !err; ++k) { + KeyCode code = modifiermap[k]; + if (modifiermap[k] != 0) { + XGrabKey(m_display, code, modifiers2, m_root, + False, GrabModeAsync, GrabModeAsync); + if (!err) { + hotKeys.push_back(std::make_pair(code, modifiers2)); + m_hotKeyToIDMap[CHotKeyItem(code, modifiers2)] = id; + } + } + } + } + XFreeModifiermap(modKeymap); + } + + // a non-modifier key must be insensitive to CapsLock, NumLock and + // ScrollLock, so we have to grab the key with every combination of + // those. + else { + // collect available toggle modifiers + unsigned int modifier; + unsigned int toggleModifiers[3]; + size_t numToggleModifiers = 0; + if (m_keyState->mapModifiersToX(KeyModifierCapsLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + if (m_keyState->mapModifiersToX(KeyModifierNumLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + if (m_keyState->mapModifiersToX(KeyModifierScrollLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + + + for (CXWindowsKeyState::CKeycodeList::iterator j = keycodes.begin(); + j != keycodes.end() && !err; ++j) { + for (size_t i = 0; i < (1u << numToggleModifiers); ++i) { + // add toggle modifiers for index i + unsigned int tmpModifiers = modifiers; + if ((i & 1) != 0) { + tmpModifiers |= toggleModifiers[0]; + } + if ((i & 2) != 0) { + tmpModifiers |= toggleModifiers[1]; + } + if ((i & 4) != 0) { + tmpModifiers |= toggleModifiers[2]; + } + + // add grab + XGrabKey(m_display, *j, tmpModifiers, m_root, + False, GrabModeAsync, GrabModeAsync); + if (!err) { + hotKeys.push_back(std::make_pair(*j, tmpModifiers)); + m_hotKeyToIDMap[CHotKeyItem(*j, tmpModifiers)] = id; + } + } + } + } + } + + if (err) { + // if any failed then unregister any we did get + for (HotKeyList::iterator j = hotKeys.begin(); + j != hotKeys.end(); ++j) { + XUngrabKey(m_display, j->first, j->second, m_root); + m_hotKeyToIDMap.erase(CHotKeyItem(j->first, j->second)); + } + + m_oldHotKeyIDs.push_back(id); + m_hotKeys.erase(id); + LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", CKeyMap::formatKey(key, mask).c_str(), key, mask)); + return 0; + } + + LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", CKeyMap::formatKey(key, mask).c_str(), key, mask, id)); + return id; +} + +void +CXWindowsScreen::unregisterHotKey(UInt32 id) +{ + // look up hotkey + HotKeyMap::iterator i = m_hotKeys.find(id); + if (i == m_hotKeys.end()) { + return; + } + + // unregister with OS + bool err = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &err); + HotKeyList& hotKeys = i->second; + for (HotKeyList::iterator j = hotKeys.begin(); + j != hotKeys.end(); ++j) { + XUngrabKey(m_display, j->first, j->second, m_root); + m_hotKeyToIDMap.erase(CHotKeyItem(j->first, j->second)); + } + } + if (err) { + LOG((CLOG_WARN "failed to unregister hotkey id=%d", id)); + } + else { + LOG((CLOG_DEBUG "unregistered hotkey id=%d", id)); + } + + // discard hot key from map and record old id for reuse + m_hotKeys.erase(i); + m_oldHotKeyIDs.push_back(id); +} + +void +CXWindowsScreen::fakeInputBegin() +{ + // FIXME -- not implemented +} + +void +CXWindowsScreen::fakeInputEnd() +{ + // FIXME -- not implemented +} + +SInt32 +CXWindowsScreen::getJumpZoneSize() const +{ + return 1; +} + +bool +CXWindowsScreen::isAnyMouseButtonDown() const +{ + // query the pointer to get the button state + Window root, window; + int xRoot, yRoot, xWindow, yWindow; + unsigned int state; + if (XQueryPointer(m_display, m_root, &root, &window, + &xRoot, &yRoot, &xWindow, &yWindow, &state)) { + return ((state & (Button1Mask | Button2Mask | Button3Mask | + Button4Mask | Button5Mask)) != 0); + } + + return false; +} + +void +CXWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + x = m_xCenter; + y = m_yCenter; +} + +void +CXWindowsScreen::fakeMouseButton(ButtonID button, bool press) const +{ + const unsigned int xButton = mapButtonToX(button); + if (xButton != 0) { + XTestFakeButtonEvent(m_display, xButton, + press ? True : False, CurrentTime); + XFlush(m_display); + } +} + +void +CXWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) const +{ + if (m_xinerama && m_xtestIsXineramaUnaware) { + XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + } + else { + XTestFakeMotionEvent(m_display, DefaultScreen(m_display), + x, y, CurrentTime); + } + XFlush(m_display); +} + +void +CXWindowsScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + // FIXME -- ignore xinerama for now + if (false && m_xinerama && m_xtestIsXineramaUnaware) { +// XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + } + else { + XTestFakeRelativeMotionEvent(m_display, dx, dy, CurrentTime); + } + XFlush(m_display); +} + +void +CXWindowsScreen::fakeMouseWheel(SInt32, SInt32 yDelta) const +{ + // XXX -- support x-axis scrolling + if (yDelta == 0) { + return; + } + + // choose button depending on rotation direction + const unsigned int xButton = mapButtonToX(static_cast( + (yDelta >= 0) ? -1 : -2)); + if (xButton == 0) { + // If we get here, then the XServer does not support the scroll + // wheel buttons, so send PageUp/PageDown keystrokes instead. + // Patch by Tom Chadwick. + KeyCode keycode = 0; + if (yDelta >= 0) { + keycode = XKeysymToKeycode(m_display, XK_Page_Up); + } + else { + keycode = XKeysymToKeycode(m_display, XK_Page_Down); + } + if (keycode != 0) { + XTestFakeKeyEvent(m_display, keycode, True, CurrentTime); + XTestFakeKeyEvent(m_display, keycode, False, CurrentTime); + } + return; + } + + // now use absolute value of delta + if (yDelta < 0) { + yDelta = -yDelta; + } + + // send as many clicks as necessary + for (; yDelta >= 120; yDelta -= 120) { + XTestFakeButtonEvent(m_display, xButton, True, CurrentTime); + XTestFakeButtonEvent(m_display, xButton, False, CurrentTime); + } + XFlush(m_display); +} + +Display* +CXWindowsScreen::openDisplay(const char* displayName) +{ + // get the DISPLAY + if (displayName == NULL) { + displayName = getenv("DISPLAY"); + if (displayName == NULL) { + displayName = ":0.0"; + } + } + + // open the display + LOG((CLOG_DEBUG "XOpenDisplay(\"%s\")", displayName)); + Display* display = XOpenDisplay(displayName); + if (display == NULL) { + throw XScreenUnavailable(60.0); + } + + // verify the availability of the XTest extension + if (!m_isPrimary) { + int majorOpcode, firstEvent, firstError; + if (!XQueryExtension(display, XTestExtensionName, + &majorOpcode, &firstEvent, &firstError)) { + LOG((CLOG_ERR "XTEST extension not available")); + XCloseDisplay(display); + throw XScreenOpenFailure(); + } + } + +#if HAVE_XKB_EXTENSION + { + m_xkb = false; + int major = XkbMajorVersion, minor = XkbMinorVersion; + if (XkbLibraryVersion(&major, &minor)) { + int opcode, firstError; + if (XkbQueryExtension(display, &opcode, &m_xkbEventBase, + &firstError, &major, &minor)) { + m_xkb = true; + XkbSelectEvents(display, XkbUseCoreKbd, + XkbMapNotifyMask, XkbMapNotifyMask); + XkbSelectEventDetails(display, XkbUseCoreKbd, + XkbStateNotifyMask, + XkbGroupStateMask, XkbGroupStateMask); + } + } + } +#endif + + return display; +} + +void +CXWindowsScreen::saveShape() +{ + // get shape of default screen + m_x = 0; + m_y = 0; + m_w = WidthOfScreen(DefaultScreenOfDisplay(m_display)); + m_h = HeightOfScreen(DefaultScreenOfDisplay(m_display)); + + // get center of default screen + m_xCenter = m_x + (m_w >> 1); + m_yCenter = m_y + (m_h >> 1); + + // check if xinerama is enabled and there is more than one screen. + // get center of first Xinerama screen. Xinerama appears to have + // a bug when XWarpPointer() is used in combination with + // XGrabPointer(). in that case, the warp is successful but the + // next pointer motion warps the pointer again, apparently to + // constrain it to some unknown region, possibly the region from + // 0,0 to Wm,Hm where Wm (Hm) is the minimum width (height) over + // all physical screens. this warp only seems to happen if the + // pointer wasn't in that region before the XWarpPointer(). the + // second (unexpected) warp causes synergy to think the pointer + // has been moved when it hasn't. to work around the problem, + // we warp the pointer to the center of the first physical + // screen instead of the logical screen. + m_xinerama = false; +#if HAVE_X11_EXTENSIONS_XINERAMA_H + int eventBase, errorBase; + if (XineramaQueryExtension(m_display, &eventBase, &errorBase) && + XineramaIsActive(m_display)) { + int numScreens; + XineramaScreenInfo* screens; + screens = XineramaQueryScreens(m_display, &numScreens); + if (screens != NULL) { + if (numScreens > 1) { + m_xinerama = true; + m_xCenter = screens[0].x_org + (screens[0].width >> 1); + m_yCenter = screens[0].y_org + (screens[0].height >> 1); + } + XFree(screens); + } + } +#endif +} + +Window +CXWindowsScreen::openWindow() const +{ + // default window attributes. we don't want the window manager + // messing with our window and we don't want the cursor to be + // visible inside the window. + XSetWindowAttributes attr; + attr.do_not_propagate_mask = 0; + attr.override_redirect = True; + attr.cursor = createBlankCursor(); + + // adjust attributes and get size and shape + SInt32 x, y, w, h; + if (m_isPrimary) { + // grab window attributes. this window is used to capture user + // input when the user is focused on another client. it covers + // the whole screen. + attr.event_mask = PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask | PropertyChangeMask; + x = m_x; + y = m_y; + w = m_w; + h = m_h; + } + else { + // cursor hider window attributes. this window is used to hide the + // cursor when it's not on the screen. the window is hidden as soon + // as the cursor enters the screen or the display's real mouse is + // moved. we'll reposition the window as necessary so its + // position here doesn't matter. it only needs to be 1x1 because + // it only needs to contain the cursor's hotspot. + attr.event_mask = LeaveWindowMask; + x = 0; + y = 0; + w = 1; + h = 1; + } + + // create and return the window + Window window = XCreateWindow(m_display, m_root, x, y, w, h, 0, 0, + InputOnly, CopyFromParent, + CWDontPropagate | CWEventMask | + CWOverrideRedirect | CWCursor, + &attr); + if (window == None) { + throw XScreenOpenFailure(); + } + return window; +} + +void +CXWindowsScreen::openIM() +{ + // open the input methods + XIM im = XOpenIM(m_display, NULL, NULL, NULL); + if (im == NULL) { + LOG((CLOG_INFO "no support for IM")); + return; + } + + // find the appropriate style. synergy supports XIMPreeditNothing + // only at the moment. + XIMStyles* styles; + if (XGetIMValues(im, XNQueryInputStyle, &styles, NULL) != NULL || + styles == NULL) { + LOG((CLOG_WARN "cannot get IM styles")); + XCloseIM(im); + return; + } + XIMStyle style = 0; + for (unsigned short i = 0; i < styles->count_styles; ++i) { + style = styles->supported_styles[i]; + if ((style & XIMPreeditNothing) != 0) { + if ((style & (XIMStatusNothing | XIMStatusNone)) != 0) { + break; + } + } + } + XFree(styles); + if (style == 0) { + LOG((CLOG_INFO "no supported IM styles")); + XCloseIM(im); + return; + } + + // create an input context for the style and tell it about our window + XIC ic = XCreateIC(im, XNInputStyle, style, XNClientWindow, m_window, NULL); + if (ic == NULL) { + LOG((CLOG_WARN "cannot create IC")); + XCloseIM(im); + return; + } + + // find out the events we must select for and do so + unsigned long mask; + if (XGetICValues(ic, XNFilterEvents, &mask, NULL) != NULL) { + LOG((CLOG_WARN "cannot get IC filter events")); + XDestroyIC(ic); + XCloseIM(im); + return; + } + + // we have IM + m_im = im; + m_ic = ic; + m_lastKeycode = 0; + + // select events on our window that IM requires + XWindowAttributes attr; + XGetWindowAttributes(m_display, m_window, &attr); + XSelectInput(m_display, m_window, attr.your_event_mask | mask); +} + +void +CXWindowsScreen::sendEvent(CEvent::Type type, void* data) +{ + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data)); +} + +void +CXWindowsScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id) +{ + CClipboardInfo* info = (CClipboardInfo*)malloc(sizeof(CClipboardInfo)); + info->m_id = id; + info->m_sequenceNumber = m_sequenceNumber; + sendEvent(type, info); +} + +IKeyState* +CXWindowsScreen::getKeyState() const +{ + return m_keyState; +} + +Bool +CXWindowsScreen::findKeyEvent(Display*, XEvent* xevent, XPointer arg) +{ + CKeyEventFilter* filter = reinterpret_cast(arg); + return (xevent->type == filter->m_event && + xevent->xkey.window == filter->m_window && + xevent->xkey.time == filter->m_time && + xevent->xkey.keycode == filter->m_keycode) ? True : False; +} + +void +CXWindowsScreen::handleSystemEvent(const CEvent& event, void*) +{ + XEvent* xevent = reinterpret_cast(event.getData()); + assert(xevent != NULL); + + // update key state + bool isRepeat = false; + if (m_isPrimary) { + if (xevent->type == KeyRelease) { + // check if this is a key repeat by getting the next + // KeyPress event that has the same key and time as + // this release event, if any. first prepare the + // filter info. + CKeyEventFilter filter; + filter.m_event = KeyPress; + filter.m_window = xevent->xkey.window; + filter.m_time = xevent->xkey.time; + filter.m_keycode = xevent->xkey.keycode; + XEvent xevent2; + isRepeat = (XCheckIfEvent(m_display, &xevent2, + &CXWindowsScreen::findKeyEvent, + (XPointer)&filter) == True); + } + + if (xevent->type == KeyPress || xevent->type == KeyRelease) { + if (xevent->xkey.window == m_root) { + // this is a hot key + onHotKey(xevent->xkey, isRepeat); + return; + } + else if (!m_isOnScreen) { + // this might be a hot key + if (onHotKey(xevent->xkey, isRepeat)) { + return; + } + } + + bool down = (isRepeat || xevent->type == KeyPress); + KeyModifierMask state = + m_keyState->mapModifiersFromX(xevent->xkey.state); + m_keyState->onKey(xevent->xkey.keycode, down, state); + } + } + + // let input methods try to handle event first + if (m_ic != NULL) { + // XFilterEvent() may eat the event and generate a new KeyPress + // event with a keycode of 0 because there isn't an actual key + // associated with the keysym. but the KeyRelease may pass + // through XFilterEvent() and keep its keycode. this means + // there's a mismatch between KeyPress and KeyRelease keycodes. + // since we use the keycode on the client to detect when a key + // is released this won't do. so we remember the keycode on + // the most recent KeyPress (and clear it on a matching + // KeyRelease) so we have a keycode for a synthesized KeyPress. + if (xevent->type == KeyPress && xevent->xkey.keycode != 0) { + m_lastKeycode = xevent->xkey.keycode; + } + else if (xevent->type == KeyRelease && + xevent->xkey.keycode == m_lastKeycode) { + m_lastKeycode = 0; + } + + // now filter the event + if (XFilterEvent(xevent, None)) { + if (xevent->type == KeyPress) { + // add filtered presses to the filtered list + m_filtered.insert(m_lastKeycode); + } + return; + } + + // discard matching key releases for key presses that were + // filtered and remove them from our filtered list. + else if (xevent->type == KeyRelease && + m_filtered.count(xevent->xkey.keycode) > 0) { + m_filtered.erase(xevent->xkey.keycode); + return; + } + } + + // let screen saver have a go + if (m_screensaver->handleXEvent(xevent)) { + // screen saver handled it + return; + } + + // handle the event ourself + switch (xevent->type) { + case CreateNotify: + if (m_isPrimary) { + // select events on new window + selectEvents(xevent->xcreatewindow.window); + } + break; + + case MappingNotify: + refreshKeyboard(xevent); + break; + + case LeaveNotify: + if (!m_isPrimary) { + // mouse moved out of hider window somehow. hide the window. + XUnmapWindow(m_display, m_window); + } + break; + + case SelectionClear: + { + // we just lost the selection. that means someone else + // grabbed the selection so this screen is now the + // selection owner. report that to the receiver. + ClipboardID id = getClipboardID(xevent->xselectionclear.selection); + if (id != kClipboardEnd) { + LOG((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time)); + m_clipboard[id]->lost(xevent->xselectionclear.time); + sendClipboardEvent(getClipboardGrabbedEvent(), id); + return; + } + } + break; + + case SelectionNotify: + // notification of selection transferred. we shouldn't + // get this here because we handle them in the selection + // retrieval methods. we'll just delete the property + // with the data (satisfying the usual ICCCM protocol). + if (xevent->xselection.property != None) { + XDeleteProperty(m_display, + xevent->xselection.requestor, + xevent->xselection.property); + } + break; + + case SelectionRequest: + { + // somebody is asking for clipboard data + ClipboardID id = getClipboardID( + xevent->xselectionrequest.selection); + if (id != kClipboardEnd) { + m_clipboard[id]->addRequest( + xevent->xselectionrequest.owner, + xevent->xselectionrequest.requestor, + xevent->xselectionrequest.target, + xevent->xselectionrequest.time, + xevent->xselectionrequest.property); + return; + } + } + break; + + case PropertyNotify: + // property delete may be part of a selection conversion + if (xevent->xproperty.state == PropertyDelete) { + processClipboardRequest(xevent->xproperty.window, + xevent->xproperty.time, + xevent->xproperty.atom); + } + break; + + case DestroyNotify: + // looks like one of the windows that requested a clipboard + // transfer has gone bye-bye. + destroyClipboardRequest(xevent->xdestroywindow.window); + break; + + case KeyPress: + if (m_isPrimary) { + onKeyPress(xevent->xkey); + } + return; + + case KeyRelease: + if (m_isPrimary) { + onKeyRelease(xevent->xkey, isRepeat); + } + return; + + case ButtonPress: + if (m_isPrimary) { + onMousePress(xevent->xbutton); + } + return; + + case ButtonRelease: + if (m_isPrimary) { + onMouseRelease(xevent->xbutton); + } + return; + + case MotionNotify: + if (m_isPrimary) { + onMouseMove(xevent->xmotion); + } + return; + + default: +#if HAVE_XKB_EXTENSION + if (m_xkb && xevent->type == m_xkbEventBase) { + XkbEvent* xkbEvent = reinterpret_cast(xevent); + switch (xkbEvent->any.xkb_type) { + case XkbMapNotify: + refreshKeyboard(xevent); + return; + + case XkbStateNotify: + LOG((CLOG_INFO "group change: %d", xkbEvent->state.group)); + m_keyState->setActiveGroup((SInt32)xkbEvent->state.group); + return; + } + } +#endif + break; + } +} + +void +CXWindowsScreen::onKeyPress(XKeyEvent& xkey) +{ + LOG((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xkey.keycode, xkey.state)); + const KeyModifierMask mask = m_keyState->mapModifiersFromX(xkey.state); + KeyID key = mapKeyFromX(&xkey); + if (key != kKeyNone) { + // check for ctrl+alt+del emulation + if ((key == kKeyPause || key == kKeyBreak) && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + // pretend it's ctrl+alt+del + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + key = kKeyDelete; + } + + // get which button. see call to XFilterEvent() in onEvent() + // for more info. + bool isFake = false; + KeyButton keycode = static_cast(xkey.keycode); + if (keycode == 0) { + isFake = true; + keycode = static_cast(m_lastKeycode); + if (keycode == 0) { + // no keycode + return; + } + } + + // handle key + m_keyState->sendKeyEvent(getEventTarget(), + true, false, key, mask, 1, keycode); + + // do fake release if this is a fake press + if (isFake) { + m_keyState->sendKeyEvent(getEventTarget(), + false, false, key, mask, 1, keycode); + } + } +} + +void +CXWindowsScreen::onKeyRelease(XKeyEvent& xkey, bool isRepeat) +{ + const KeyModifierMask mask = m_keyState->mapModifiersFromX(xkey.state); + KeyID key = mapKeyFromX(&xkey); + if (key != kKeyNone) { + // check for ctrl+alt+del emulation + if ((key == kKeyPause || key == kKeyBreak) && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + // pretend it's ctrl+alt+del and ignore autorepeat + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + key = kKeyDelete; + isRepeat = false; + } + + KeyButton keycode = static_cast(xkey.keycode); + if (!isRepeat) { + // no press event follows so it's a plain release + LOG((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", keycode, xkey.state)); + m_keyState->sendKeyEvent(getEventTarget(), + false, false, key, mask, 1, keycode); + } + else { + // found a press event following so it's a repeat. + // we could attempt to count the already queued + // repeats but we'll just send a repeat of 1. + // note that we discard the press event. + LOG((CLOG_DEBUG1 "event: repeat code=%d, state=0x%04x", keycode, xkey.state)); + m_keyState->sendKeyEvent(getEventTarget(), + false, true, key, mask, 1, keycode); + } + } +} + +bool +CXWindowsScreen::onHotKey(XKeyEvent& xkey, bool isRepeat) +{ + // find the hot key id + HotKeyToIDMap::const_iterator i = + m_hotKeyToIDMap.find(CHotKeyItem(xkey.keycode, xkey.state)); + if (i == m_hotKeyToIDMap.end()) { + return false; + } + + // find what kind of event + CEvent::Type type; + if (xkey.type == KeyPress) { + type = getHotKeyDownEvent(); + } + else if (xkey.type == KeyRelease) { + type = getHotKeyUpEvent(); + } + else { + return false; + } + + // generate event (ignore key repeats) + if (!isRepeat) { + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), + CHotKeyInfo::alloc(i->second))); + } + return true; +} + +void +CXWindowsScreen::onMousePress(const XButtonEvent& xbutton) +{ + LOG((CLOG_DEBUG1 "event: ButtonPress button=%d", xbutton.button)); + ButtonID button = mapButtonFromX(&xbutton); + KeyModifierMask mask = m_keyState->mapModifiersFromX(xbutton.state); + if (button != kButtonNone) { + sendEvent(getButtonDownEvent(), CButtonInfo::alloc(button, mask)); + } +} + +void +CXWindowsScreen::onMouseRelease(const XButtonEvent& xbutton) +{ + LOG((CLOG_DEBUG1 "event: ButtonRelease button=%d", xbutton.button)); + ButtonID button = mapButtonFromX(&xbutton); + KeyModifierMask mask = m_keyState->mapModifiersFromX(xbutton.state); + if (button != kButtonNone) { + sendEvent(getButtonUpEvent(), CButtonInfo::alloc(button, mask)); + } + else if (xbutton.button == 4) { + // wheel forward (away from user) + sendEvent(getWheelEvent(), CWheelInfo::alloc(0, 120)); + } + else if (xbutton.button == 5) { + // wheel backward (toward user) + sendEvent(getWheelEvent(), CWheelInfo::alloc(0, -120)); + } + // XXX -- support x-axis scrolling +} + +void +CXWindowsScreen::onMouseMove(const XMotionEvent& xmotion) +{ + LOG((CLOG_DEBUG2 "event: MotionNotify %d,%d", xmotion.x_root, xmotion.y_root)); + + // compute motion delta (relative to the last known + // mouse position) + SInt32 x = xmotion.x_root - m_xCursor; + SInt32 y = xmotion.y_root - m_yCursor; + + // save position to compute delta of next motion + m_xCursor = xmotion.x_root; + m_yCursor = xmotion.y_root; + + if (xmotion.send_event) { + // we warped the mouse. discard events until we + // find the matching sent event. see + // warpCursorNoFlush() for where the events are + // sent. we discard the matching sent event and + // can be sure we've skipped the warp event. + XEvent xevent; + do { + XMaskEvent(m_display, PointerMotionMask, &xevent); + } while (!xevent.xany.send_event); + } + else if (m_isOnScreen) { + // motion on primary screen + sendEvent(getMotionOnPrimaryEvent(), + CMotionInfo::alloc(m_xCursor, m_yCursor)); + } + else { + // motion on secondary screen. warp mouse back to + // center. + // + // my lombard (powerbook g3) running linux and + // using the adbmouse driver has two problems: + // first, the driver only sends motions of +/-2 + // pixels and, second, it seems to discard some + // physical input after a warp. the former isn't a + // big deal (we're just limited to every other + // pixel) but the latter is a PITA. to work around + // it we only warp when the mouse has moved more + // than s_size pixels from the center. + static const SInt32 s_size = 32; + if (xmotion.x_root - m_xCenter < -s_size || + xmotion.x_root - m_xCenter > s_size || + xmotion.y_root - m_yCenter < -s_size || + xmotion.y_root - m_yCenter > s_size) { + warpCursorNoFlush(m_xCenter, m_yCenter); + } + + // send event if mouse moved. do this after warping + // back to center in case the motion takes us onto + // the primary screen. if we sent the event first + // in that case then the warp would happen after + // warping to the primary screen's enter position, + // effectively overriding it. + if (x != 0 || y != 0) { + sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y)); + } + } +} + +Cursor +CXWindowsScreen::createBlankCursor() const +{ + // this seems just a bit more complicated than really necessary + + // get the closet cursor size to 1x1 + unsigned int w, h; + XQueryBestCursor(m_display, m_root, 1, 1, &w, &h); + + // make bitmap data for cursor of closet size. since the cursor + // is blank we can use the same bitmap for shape and mask: all + // zeros. + const int size = ((w + 7) >> 3) * h; + char* data = new char[size]; + memset(data, 0, size); + + // make bitmap + Pixmap bitmap = XCreateBitmapFromData(m_display, m_root, data, w, h); + + // need an arbitrary color for the cursor + XColor color; + color.pixel = 0; + color.red = color.green = color.blue = 0; + color.flags = DoRed | DoGreen | DoBlue; + + // make cursor from bitmap + Cursor cursor = XCreatePixmapCursor(m_display, bitmap, bitmap, + &color, &color, 0, 0); + + // don't need bitmap or the data anymore + delete[] data; + XFreePixmap(m_display, bitmap); + + return cursor; +} + +ClipboardID +CXWindowsScreen::getClipboardID(Atom selection) const +{ + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->getSelection() == selection) { + return id; + } + } + return kClipboardEnd; +} + +void +CXWindowsScreen::processClipboardRequest(Window requestor, + Time time, Atom property) +{ + // check every clipboard until one returns success + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->processRequest(requestor, time, property)) { + break; + } + } +} + +void +CXWindowsScreen::destroyClipboardRequest(Window requestor) +{ + // check every clipboard until one returns success + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->destroyRequest(requestor)) { + break; + } + } +} + +void +CXWindowsScreen::onError() +{ + // prevent further access to the X display + EVENTQUEUE->adoptBuffer(NULL); + m_screensaver->destroy(); + m_screensaver = NULL; + m_display = NULL; + + // notify of failure + sendEvent(getErrorEvent(), NULL); + + // FIXME -- should ensure that we ignore operations that involve + // m_display from now on. however, Xlib will simply exit the + // application in response to the X I/O error so there's no + // point in trying to really handle the error. if we did want + // to handle the error, it'd probably be easiest to delegate to + // one of two objects. one object would take the implementation + // from this class. the other object would be stub methods that + // don't use X11. on error, we'd switch to the latter. +} + +int +CXWindowsScreen::ioErrorHandler(Display*) +{ + // the display has disconnected, probably because X is shutting + // down. X forces us to exit at this point which is annoying. + // we'll pretend as if we won't exit so we try to make sure we + // don't access the display anymore. + LOG((CLOG_CRIT "X display has unexpectedly disconnected")); + s_screen->onError(); + return 0; +} + +void +CXWindowsScreen::selectEvents(Window w) const +{ + // ignore errors while we adjust event masks. windows could be + // destroyed at any time after the XQueryTree() in doSelectEvents() + // so we must ignore BadWindow errors. + CXWindowsUtil::CErrorLock lock(m_display); + + // adjust event masks + doSelectEvents(w); +} + +void +CXWindowsScreen::doSelectEvents(Window w) const +{ + // we want to track the mouse everywhere on the display. to achieve + // that we select PointerMotionMask on every window. we also select + // SubstructureNotifyMask in order to get CreateNotify events so we + // select events on new windows too. + // + // note that this can break certain clients due a design flaw of X. + // X will deliver a PointerMotion event to the deepest window in the + // hierarchy that contains the pointer and has PointerMotionMask + // selected by *any* client. if another client doesn't select + // motion events in a subwindow so the parent window will get them + // then by selecting for motion events on the subwindow we break + // that client because the parent will no longer get the events. + + // FIXME -- should provide some workaround for event selection + // design flaw. perhaps only select for motion events on windows + // that already do or are top-level windows or don't propagate + // pointer events. or maybe an option to simply poll the mouse. + + // we don't want to adjust our grab window + if (w == m_window) { + return; + } + + // select events of interest. do this before querying the tree so + // we'll get notifications of children created after the XQueryTree() + // so we won't miss them. + XSelectInput(m_display, w, PointerMotionMask | SubstructureNotifyMask); + + // recurse on child windows + Window rw, pw, *cw; + unsigned int nc; + if (XQueryTree(m_display, w, &rw, &pw, &cw, &nc)) { + for (unsigned int i = 0; i < nc; ++i) { + doSelectEvents(cw[i]); + } + XFree(cw); + } +} + +KeyID +CXWindowsScreen::mapKeyFromX(XKeyEvent* event) const +{ + // convert to a keysym + KeySym keysym; + if (event->type == KeyPress && m_ic != NULL) { + // do multibyte lookup. can only call XmbLookupString with a + // key press event and a valid XIC so we checked those above. + char scratch[32]; + int n = sizeof(scratch) / sizeof(scratch[0]); + char* buffer = scratch; + int status; + n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status); + if (status == XBufferOverflow) { + // not enough space. grow buffer and try again. + buffer = new char[n]; + n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status); + delete[] buffer; + } + + // see what we got. since we don't care about the string + // we'll just look for a keysym. + switch (status) { + default: + case XLookupNone: + case XLookupChars: + keysym = 0; + break; + + case XLookupKeySym: + case XLookupBoth: + break; + } + } + else { + // plain old lookup + char dummy[1]; + XLookupString(event, dummy, 0, &keysym, NULL); + } + + // convert key + return CXWindowsUtil::mapKeySymToKeyID(keysym); +} + +ButtonID +CXWindowsScreen::mapButtonFromX(const XButtonEvent* event) const +{ + unsigned int button = event->button; + + // first three buttons map to 1, 2, 3 (kButtonLeft, Middle, Right) + if (button >= 1 && button <= 3) { + return static_cast(button); + } + + // buttons 4 and 5 are ignored here. they're used for the wheel. + // buttons 6, 7, etc and up map to 4, 5, etc. + else if (button >= 6) { + return static_cast(button - 2); + } + + // unknown button + else { + return kButtonNone; + } +} + +unsigned int +CXWindowsScreen::mapButtonToX(ButtonID id) const +{ + // map button -1 to button 4 (+wheel) + if (id == static_cast(-1)) { + id = 4; + } + + // map button -2 to button 5 (-wheel) + else if (id == static_cast(-2)) { + id = 5; + } + + // map buttons 4, 5, etc. to 6, 7, etc. to make room for buttons + // 4 and 5 used to simulate the mouse wheel. + else if (id >= 4) { + id += 2; + } + + // check button is in legal range + if (id < 1 || id > m_buttons.size()) { + // out of range + return 0; + } + + // map button + return static_cast(id); +} + +void +CXWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y) +{ + assert(m_window != None); + + // send an event that we can recognize before the mouse warp + XEvent eventBefore; + eventBefore.type = MotionNotify; + eventBefore.xmotion.display = m_display; + eventBefore.xmotion.window = m_window; + eventBefore.xmotion.root = m_root; + eventBefore.xmotion.subwindow = m_window; + eventBefore.xmotion.time = CurrentTime; + eventBefore.xmotion.x = x; + eventBefore.xmotion.y = y; + eventBefore.xmotion.x_root = x; + eventBefore.xmotion.y_root = y; + eventBefore.xmotion.state = 0; + eventBefore.xmotion.is_hint = NotifyNormal; + eventBefore.xmotion.same_screen = True; + XEvent eventAfter = eventBefore; + XSendEvent(m_display, m_window, False, 0, &eventBefore); + + // warp mouse + XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + + // send an event that we can recognize after the mouse warp + XSendEvent(m_display, m_window, False, 0, &eventAfter); + XSync(m_display, False); + + LOG((CLOG_DEBUG2 "warped to %d,%d", x, y)); +} + +void +CXWindowsScreen::updateButtons() +{ + // query the button mapping + UInt32 numButtons = XGetPointerMapping(m_display, NULL, 0); + unsigned char* tmpButtons = new unsigned char[numButtons]; + XGetPointerMapping(m_display, tmpButtons, numButtons); + + // find the largest logical button id + unsigned char maxButton = 0; + for (UInt32 i = 0; i < numButtons; ++i) { + if (tmpButtons[i] > maxButton) { + maxButton = tmpButtons[i]; + } + } + + // allocate button array + m_buttons.resize(maxButton); + + // fill in button array values. m_buttons[i] is the physical + // button number for logical button i+1. + for (UInt32 i = 0; i < numButtons; ++i) { + m_buttons[i] = 0; + } + for (UInt32 i = 0; i < numButtons; ++i) { + m_buttons[tmpButtons[i] - 1] = i + 1; + } + + // clean up + delete[] tmpButtons; +} + +bool +CXWindowsScreen::grabMouseAndKeyboard() +{ + // grab the mouse and keyboard. keep trying until we get them. + // if we can't grab one after grabbing the other then ungrab + // and wait before retrying. give up after s_timeout seconds. + static const double s_timeout = 1.0; + int result; + CStopwatch timer; + do { + // keyboard first + do { + result = XGrabKeyboard(m_display, m_window, True, + GrabModeAsync, GrabModeAsync, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + LOG((CLOG_DEBUG2 "waiting to grab keyboard")); + ARCH->sleep(0.05); + if (timer.getTime() >= s_timeout) { + LOG((CLOG_DEBUG2 "grab keyboard timed out")); + return false; + } + } + } while (result != GrabSuccess); + LOG((CLOG_DEBUG2 "grabbed keyboard")); + + // now the mouse + result = XGrabPointer(m_display, m_window, True, 0, + GrabModeAsync, GrabModeAsync, + m_window, None, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + // back off to avoid grab deadlock + XUngrabKeyboard(m_display, CurrentTime); + LOG((CLOG_DEBUG2 "ungrabbed keyboard, waiting to grab pointer")); + ARCH->sleep(0.05); + if (timer.getTime() >= s_timeout) { + LOG((CLOG_DEBUG2 "grab pointer timed out")); + return false; + } + } + } while (result != GrabSuccess); + + LOG((CLOG_DEBUG1 "grabbed pointer and keyboard")); + return true; +} + +void +CXWindowsScreen::refreshKeyboard(XEvent* event) +{ + if (XPending(m_display) > 0) { + XEvent tmpEvent; + XPeekEvent(m_display, &tmpEvent); + if (tmpEvent.type == MappingNotify) { + // discard this event since another follows. + // we tend to get a bunch of these in a row. + return; + } + } + + // keyboard mapping changed +#if HAVE_XKB_EXTENSION + if (m_xkb && event->type == m_xkbEventBase) { + XkbRefreshKeyboardMapping((XkbMapNotifyEvent*)event); + } + else +#else + { + XRefreshKeyboardMapping(&event->xmapping); + } +#endif + m_keyState->updateKeyMap(); + m_keyState->updateKeyState(); +} + + +// +// CXWindowsScreen::CHotKeyItem +// + +CXWindowsScreen::CHotKeyItem::CHotKeyItem(int keycode, unsigned int mask) : + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +bool +CXWindowsScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const +{ + return (m_keycode < x.m_keycode || + (m_keycode == x.m_keycode && m_mask < x.m_mask)); +} diff --git a/lib/platform/CXWindowsScreen.h b/lib/platform/CXWindowsScreen.h new file mode 100644 index 00000000..99c45576 --- /dev/null +++ b/lib/platform/CXWindowsScreen.h @@ -0,0 +1,229 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSSCREEN_H +#define CXWINDOWSSCREEN_H + +#include "CPlatformScreen.h" +#include "stdset.h" +#include "stdvector.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +#endif + +class CXWindowsClipboard; +class CXWindowsKeyState; +class CXWindowsScreenSaver; + +//! Implementation of IPlatformScreen for X11 +class CXWindowsScreen : public CPlatformScreen { +public: + CXWindowsScreen(const char* displayName, bool isPrimary); + virtual ~CXWindowsScreen(); + + //! @name manipulators + //@{ + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides); + virtual void warpCursor(SInt32 x, SInt32 y); + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + virtual void unregisterHotKey(UInt32 id); + virtual void fakeInputBegin(); + virtual void fakeInputEnd(); + virtual SInt32 getJumpZoneSize() const; + virtual bool isAnyMouseButtonDown() const; + virtual void getCursorCenter(SInt32& x, SInt32& y) const; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press) const; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const; + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + // IPlatformScreen overrides + virtual void enable(); + virtual void disable(); + virtual void enter(); + virtual bool leave(); + virtual bool setClipboard(ClipboardID, const IClipboard*); + virtual void checkClipboards(); + virtual void openScreensaver(bool notify); + virtual void closeScreensaver(); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + virtual void setSequenceNumber(UInt32); + virtual bool isPrimary() const; + +protected: + // IPlatformScreen overrides + virtual void handleSystemEvent(const CEvent&, void*); + virtual void updateButtons(); + virtual IKeyState* getKeyState() const; + +private: + // event sending + void sendEvent(CEvent::Type, void* = NULL); + void sendClipboardEvent(CEvent::Type, ClipboardID); + + // create the transparent cursor + Cursor createBlankCursor() const; + + // determine the clipboard from the X selection. returns + // kClipboardEnd if no such clipboard. + ClipboardID getClipboardID(Atom selection) const; + + // continue processing a selection request + void processClipboardRequest(Window window, + Time time, Atom property); + + // terminate a selection request + void destroyClipboardRequest(Window window); + + // X I/O error handler + void onError(); + static int ioErrorHandler(Display*); + +private: + class CKeyEventFilter { + public: + int m_event; + Window m_window; + Time m_time; + KeyCode m_keycode; + }; + + Display* openDisplay(const char* displayName); + void saveShape(); + Window openWindow() const; + void openIM(); + + bool grabMouseAndKeyboard(); + void onKeyPress(XKeyEvent&); + void onKeyRelease(XKeyEvent&, bool isRepeat); + bool onHotKey(XKeyEvent&, bool isRepeat); + void onMousePress(const XButtonEvent&); + void onMouseRelease(const XButtonEvent&); + void onMouseMove(const XMotionEvent&); + + void selectEvents(Window) const; + void doSelectEvents(Window) const; + + KeyID mapKeyFromX(XKeyEvent*) const; + ButtonID mapButtonFromX(const XButtonEvent*) const; + unsigned int mapButtonToX(ButtonID id) const; + + void warpCursorNoFlush(SInt32 x, SInt32 y); + + void refreshKeyboard(XEvent*); + + static Bool findKeyEvent(Display*, XEvent* xevent, XPointer arg); + +private: + struct CHotKeyItem { + public: + CHotKeyItem(int, unsigned int); + + bool operator<(const CHotKeyItem&) const; + + private: + int m_keycode; + unsigned int m_mask; + }; + typedef std::set CFilteredKeycodes; + typedef std::vector > HotKeyList; + typedef std::map HotKeyMap; + typedef std::vector HotKeyIDList; + typedef std::map HotKeyToIDMap; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + Display* m_display; + Window m_root; + Window m_window; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // last mouse position + SInt32 m_xCursor, m_yCursor; + + // keyboard stuff + CXWindowsKeyState* m_keyState; + + // hot key stuff + HotKeyMap m_hotKeys; + HotKeyIDList m_oldHotKeyIDs; + HotKeyToIDMap m_hotKeyToIDMap; + + // input focus stuff + Window m_lastFocus; + int m_lastFocusRevert; + + // input method stuff + XIM m_im; + XIC m_ic; + KeyCode m_lastKeycode; + CFilteredKeycodes m_filtered; + + // clipboards + CXWindowsClipboard* m_clipboard[kClipboardEnd]; + UInt32 m_sequenceNumber; + + // screen saver stuff + CXWindowsScreenSaver* m_screensaver; + bool m_screensaverNotify; + + // logical to physical button mapping. m_buttons[i] gives the + // physical button for logical button i+1. + std::vector m_buttons; + + // true if global auto-repeat was enabled before we turned it off + bool m_autoRepeat; + + // stuff to workaround xtest being xinerama unaware. attempting + // to fake a mouse motion under xinerama may behave strangely, + // especially if screen 0 is not at 0,0 or if faking a motion on + // a screen other than screen 0. + bool m_xtestIsXineramaUnaware; + bool m_xinerama; + + // XKB extension stuff + bool m_xkb; + int m_xkbEventBase; + + // pointer to (singleton) screen. this is only needed by + // ioErrorHandler(). + static CXWindowsScreen* s_screen; +}; + +#endif diff --git a/lib/platform/CXWindowsScreenSaver.cpp b/lib/platform/CXWindowsScreenSaver.cpp new file mode 100644 index 00000000..65af34e1 --- /dev/null +++ b/lib/platform/CXWindowsScreenSaver.cpp @@ -0,0 +1,598 @@ +/* + * 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 "CXWindowsScreenSaver.h" +#include "CXWindowsUtil.h" +#include "IPlatformScreen.h" +#include "CLog.h" +#include "CEvent.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include +#if HAVE_X11_EXTENSIONS_XTEST_H +# include +#else +# error The XTest extension is required to build synergy +#endif +#if HAVE_X11_EXTENSIONS_DPMS_H +extern "C" { +# include +# include +# if !HAVE_DPMS_PROTOTYPES +# undef DPMSModeOn +# undef DPMSModeStandby +# undef DPMSModeSuspend +# undef DPMSModeOff +# define DPMSModeOn 0 +# define DPMSModeStandby 1 +# define DPMSModeSuspend 2 +# define DPMSModeOff 3 +extern Bool DPMSQueryExtension(Display *, int *, int *); +extern Bool DPMSCapable(Display *); +extern Status DPMSEnable(Display *); +extern Status DPMSDisable(Display *); +extern Status DPMSForceLevel(Display *, CARD16); +extern Status DPMSInfo(Display *, CARD16 *, BOOL *); +# endif +} +#endif + +// +// CXWindowsScreenSaver +// + +CXWindowsScreenSaver::CXWindowsScreenSaver( + Display* display, Window window, void* eventTarget) : + m_display(display), + m_xscreensaverSink(window), + m_eventTarget(eventTarget), + m_xscreensaver(None), + m_xscreensaverActive(false), + m_dpms(false), + m_disabled(false), + m_suppressDisable(false), + m_disableTimer(NULL), + m_disablePos(0) +{ + // get atoms + m_atomScreenSaver = XInternAtom(m_display, + "SCREENSAVER", False); + m_atomScreenSaverVersion = XInternAtom(m_display, + "_SCREENSAVER_VERSION", False); + m_atomScreenSaverActivate = XInternAtom(m_display, + "ACTIVATE", False); + m_atomScreenSaverDeactivate = XInternAtom(m_display, + "DEACTIVATE", False); + + // check for DPMS extension. this is an alternative screen saver + // that powers down the display. +#if HAVE_X11_EXTENSIONS_DPMS_H + int eventBase, errorBase; + if (DPMSQueryExtension(m_display, &eventBase, &errorBase)) { + if (DPMSCapable(m_display)) { + // we have DPMS + m_dpms = true; + } + } +#endif + + // watch top-level windows for changes + bool error = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + Window root = DefaultRootWindow(m_display); + XWindowAttributes attr; + XGetWindowAttributes(m_display, root, &attr); + m_rootEventMask = attr.your_event_mask; + XSelectInput(m_display, root, m_rootEventMask | SubstructureNotifyMask); + } + if (error) { + LOG((CLOG_DEBUG "didn't set root event mask")); + m_rootEventMask = 0; + } + + // get the built-in settings + XGetScreenSaver(m_display, &m_timeout, &m_interval, + &m_preferBlanking, &m_allowExposures); + + // get the DPMS settings + m_dpmsEnabled = isDPMSEnabled(); + + // get the xscreensaver window, if any + if (!findXScreenSaver()) { + setXScreenSaver(None); + } + + // install disable timer event handler + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CXWindowsScreenSaver::handleDisableTimer)); +} + +CXWindowsScreenSaver::~CXWindowsScreenSaver() +{ + // done with disable job + if (m_disableTimer != NULL) { + EVENTQUEUE->deleteTimer(m_disableTimer); + } + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + + if (m_display != NULL) { + enableDPMS(m_dpmsEnabled); + XSetScreenSaver(m_display, m_timeout, m_interval, + m_preferBlanking, m_allowExposures); + clearWatchForXScreenSaver(); + CXWindowsUtil::CErrorLock lock(m_display); + XSelectInput(m_display, DefaultRootWindow(m_display), m_rootEventMask); + } +} + +void +CXWindowsScreenSaver::destroy() +{ + m_display = NULL; + delete this; +} + +bool +CXWindowsScreenSaver::handleXEvent(const XEvent* xevent) +{ + switch (xevent->type) { + case CreateNotify: + if (m_xscreensaver == None) { + if (isXScreenSaver(xevent->xcreatewindow.window)) { + // found the xscreensaver + setXScreenSaver(xevent->xcreatewindow.window); + } + else { + // another window to watch. to detect the xscreensaver + // window we look for a property but that property may + // not yet exist by the time we get this event so we + // have to watch the window for property changes. + // this would be so much easier if xscreensaver did the + // smart thing and stored its window in a property on + // the root window. + addWatchXScreenSaver(xevent->xcreatewindow.window); + } + } + break; + + case DestroyNotify: + if (xevent->xdestroywindow.window == m_xscreensaver) { + // xscreensaver is gone + LOG((CLOG_DEBUG "xscreensaver died")); + setXScreenSaver(None); + return true; + } + break; + + case PropertyNotify: + if (xevent->xproperty.state == PropertyNewValue) { + if (isXScreenSaver(xevent->xproperty.window)) { + // found the xscreensaver + setXScreenSaver(xevent->xcreatewindow.window); + } + } + break; + + case MapNotify: + if (xevent->xmap.window == m_xscreensaver) { + // xscreensaver has activated + setXScreenSaverActive(true); + return true; + } + break; + + case UnmapNotify: + if (xevent->xunmap.window == m_xscreensaver) { + // xscreensaver has deactivated + setXScreenSaverActive(false); + return true; + } + break; + } + + return false; +} + +void +CXWindowsScreenSaver::enable() +{ + // for xscreensaver + m_disabled = false; + updateDisableTimer(); + + // for built-in X screen saver + XSetScreenSaver(m_display, m_timeout, m_interval, + m_preferBlanking, m_allowExposures); + + // for DPMS + enableDPMS(m_dpmsEnabled); +} + +void +CXWindowsScreenSaver::disable() +{ + // for xscreensaver + m_disabled = true; + updateDisableTimer(); + + // use built-in X screen saver + XGetScreenSaver(m_display, &m_timeout, &m_interval, + &m_preferBlanking, &m_allowExposures); + XSetScreenSaver(m_display, 0, m_interval, + m_preferBlanking, m_allowExposures); + + // for DPMS + m_dpmsEnabled = isDPMSEnabled(); + enableDPMS(false); + + // FIXME -- now deactivate? +} + +void +CXWindowsScreenSaver::activate() +{ + // remove disable job timer + m_suppressDisable = true; + updateDisableTimer(); + + // enable DPMS if it was enabled + enableDPMS(m_dpmsEnabled); + + // try xscreensaver + findXScreenSaver(); + if (m_xscreensaver != None) { + sendXScreenSaverCommand(m_atomScreenSaverActivate); + return; + } + + // try built-in X screen saver + if (m_timeout != 0) { + XForceScreenSaver(m_display, ScreenSaverActive); + } + + // try DPMS + activateDPMS(true); +} + +void +CXWindowsScreenSaver::deactivate() +{ + // reinstall disable job timer + m_suppressDisable = false; + updateDisableTimer(); + + // try DPMS + activateDPMS(false); + + // disable DPMS if screen saver is disabled + if (m_disabled) { + enableDPMS(false); + } + + // try xscreensaver + findXScreenSaver(); + if (m_xscreensaver != None) { + sendXScreenSaverCommand(m_atomScreenSaverDeactivate); + return; + } + + // use built-in X screen saver + XForceScreenSaver(m_display, ScreenSaverReset); +} + +bool +CXWindowsScreenSaver::isActive() const +{ + // check xscreensaver + if (m_xscreensaver != None) { + return m_xscreensaverActive; + } + + // check DPMS + if (isDPMSActivated()) { + return true; + } + + // can't check built-in X screen saver activity + return false; +} + +bool +CXWindowsScreenSaver::findXScreenSaver() +{ + // do nothing if we've already got the xscreensaver window + if (m_xscreensaver == None) { + // find top-level window xscreensaver window + Window root = DefaultRootWindow(m_display); + Window rw, pw, *cw; + unsigned int nc; + if (XQueryTree(m_display, root, &rw, &pw, &cw, &nc)) { + for (unsigned int i = 0; i < nc; ++i) { + if (isXScreenSaver(cw[i])) { + setXScreenSaver(cw[i]); + break; + } + } + XFree(cw); + } + } + + return (m_xscreensaver != None); +} + +void +CXWindowsScreenSaver::setXScreenSaver(Window window) +{ + LOG((CLOG_DEBUG "xscreensaver window: 0x%08x", window)); + + // save window + m_xscreensaver = window; + + if (m_xscreensaver != None) { + // clear old watch list + clearWatchForXScreenSaver(); + + // see if xscreensaver is active + bool error = false; + XWindowAttributes attr; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + XGetWindowAttributes(m_display, m_xscreensaver, &attr); + } + setXScreenSaverActive(!error && attr.map_state != IsUnmapped); + + // save current DPMS state; xscreensaver may have changed it. + m_dpmsEnabled = isDPMSEnabled(); + } + else { + // screen saver can't be active if it doesn't exist + setXScreenSaverActive(false); + + // start watching for xscreensaver + watchForXScreenSaver(); + } +} + +bool +CXWindowsScreenSaver::isXScreenSaver(Window w) const +{ + // check for m_atomScreenSaverVersion string property + Atom type; + return (CXWindowsUtil::getWindowProperty(m_display, w, + m_atomScreenSaverVersion, + NULL, &type, NULL, False) && + type == XA_STRING); +} + +void +CXWindowsScreenSaver::setXScreenSaverActive(bool activated) +{ + if (m_xscreensaverActive != activated) { + LOG((CLOG_DEBUG "xscreensaver %s on window 0x%08x", activated ? "activated" : "deactivated", m_xscreensaver)); + m_xscreensaverActive = activated; + + // if screen saver was activated forcefully (i.e. against + // our will) then just accept it. don't try to keep it + // from activating since that'll just pop up the password + // dialog if locking is enabled. + m_suppressDisable = activated; + updateDisableTimer(); + + if (activated) { + EVENTQUEUE->addEvent(CEvent( + IPlatformScreen::getScreensaverActivatedEvent(), + m_eventTarget)); + } + else { + EVENTQUEUE->addEvent(CEvent( + IPlatformScreen::getScreensaverDeactivatedEvent(), + m_eventTarget)); + } + } +} + +void +CXWindowsScreenSaver::sendXScreenSaverCommand(Atom cmd, long arg1, long arg2) +{ + XEvent event; + event.xclient.type = ClientMessage; + event.xclient.display = m_display; + event.xclient.window = m_xscreensaverSink; + event.xclient.message_type = m_atomScreenSaver; + event.xclient.format = 32; + event.xclient.data.l[0] = static_cast(cmd); + event.xclient.data.l[1] = arg1; + event.xclient.data.l[2] = arg2; + event.xclient.data.l[3] = 0; + event.xclient.data.l[4] = 0; + + LOG((CLOG_DEBUG "send xscreensaver command: %d %d %d", (long)cmd, arg1, arg2)); + bool error = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + XSendEvent(m_display, m_xscreensaver, False, 0, &event); + } + if (error) { + findXScreenSaver(); + } +} + +void +CXWindowsScreenSaver::watchForXScreenSaver() +{ + // clear old watch list + clearWatchForXScreenSaver(); + + // add every child of the root to the list of windows to watch + Window root = DefaultRootWindow(m_display); + Window rw, pw, *cw; + unsigned int nc; + if (XQueryTree(m_display, root, &rw, &pw, &cw, &nc)) { + for (unsigned int i = 0; i < nc; ++i) { + addWatchXScreenSaver(cw[i]); + } + XFree(cw); + } + + // now check for xscreensaver window in case it set the property + // before we could request property change events. + if (findXScreenSaver()) { + // found it so clear out our watch list + clearWatchForXScreenSaver(); + } +} + +void +CXWindowsScreenSaver::clearWatchForXScreenSaver() +{ + // stop watching all windows + CXWindowsUtil::CErrorLock lock(m_display); + for (CWatchList::iterator index = m_watchWindows.begin(); + index != m_watchWindows.end(); ++index) { + XSelectInput(m_display, index->first, index->second); + } + m_watchWindows.clear(); +} + +void +CXWindowsScreenSaver::addWatchXScreenSaver(Window window) +{ + // get window attributes + bool error = false; + XWindowAttributes attr; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + XGetWindowAttributes(m_display, window, &attr); + } + + // if successful and window uses override_redirect (like xscreensaver + // does) then watch it for property changes. + if (!error && attr.override_redirect == True) { + error = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + XSelectInput(m_display, window, + attr.your_event_mask | PropertyChangeMask); + } + if (!error) { + // if successful then add the window to our list + m_watchWindows.insert(std::make_pair(window, attr.your_event_mask)); + } + } +} + +void +CXWindowsScreenSaver::updateDisableTimer() +{ + if (m_disabled && !m_suppressDisable && m_disableTimer == NULL) { + // 5 seconds should be plenty often to suppress the screen saver + m_disableTimer = EVENTQUEUE->newTimer(5.0, this); + } + else if ((!m_disabled || m_suppressDisable) && m_disableTimer != NULL) { + EVENTQUEUE->deleteTimer(m_disableTimer); + m_disableTimer = NULL; + } +} + +void +CXWindowsScreenSaver::handleDisableTimer(const CEvent&, void*) +{ + // send fake mouse motion directly to xscreensaver + if (m_xscreensaver != None) { + XEvent event; + event.xmotion.type = MotionNotify; + event.xmotion.display = m_display; + event.xmotion.window = m_xscreensaver; + event.xmotion.root = DefaultRootWindow(m_display); + event.xmotion.subwindow = None; + event.xmotion.time = CurrentTime; + event.xmotion.x = m_disablePos; + event.xmotion.y = 0; + event.xmotion.x_root = m_disablePos; + event.xmotion.y_root = 0; + event.xmotion.state = 0; + event.xmotion.is_hint = NotifyNormal; + event.xmotion.same_screen = True; + + CXWindowsUtil::CErrorLock lock(m_display); + XSendEvent(m_display, m_xscreensaver, False, 0, &event); + + m_disablePos = 20 - m_disablePos; + } +} + +void +CXWindowsScreenSaver::activateDPMS(bool activate) +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + // DPMSForceLevel will generate a BadMatch if DPMS is disabled + CXWindowsUtil::CErrorLock lock(m_display); + DPMSForceLevel(m_display, activate ? DPMSModeStandby : DPMSModeOn); + } +#endif +} + +void +CXWindowsScreenSaver::enableDPMS(bool enable) +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + if (enable) { + DPMSEnable(m_display); + } + else { + DPMSDisable(m_display); + } + } +#endif +} + +bool +CXWindowsScreenSaver::isDPMSEnabled() const +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + CARD16 level; + BOOL state; + DPMSInfo(m_display, &level, &state); + return (state != False); + } + else { + return false; + } +#else + return false; +#endif +} + +bool +CXWindowsScreenSaver::isDPMSActivated() const +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + CARD16 level; + BOOL state; + DPMSInfo(m_display, &level, &state); + return (level != DPMSModeOn); + } + else { + return false; + } +#else + return false; +#endif +} diff --git a/lib/platform/CXWindowsScreenSaver.h b/lib/platform/CXWindowsScreenSaver.h new file mode 100644 index 00000000..991b20e1 --- /dev/null +++ b/lib/platform/CXWindowsScreenSaver.h @@ -0,0 +1,164 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSSCREENSAVER_H +#define CXWINDOWSSCREENSAVER_H + +#include "IScreenSaver.h" +#include "stdmap.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +#endif + +class CEvent; +class CEventQueueTimer; + +//! X11 screen saver implementation +class CXWindowsScreenSaver : public IScreenSaver { +public: + CXWindowsScreenSaver(Display*, Window, void* eventTarget); + virtual ~CXWindowsScreenSaver(); + + //! @name manipulators + //@{ + + //! Event filtering + /*! + Should be called for each system event before event translation and + dispatch. Returns true to skip translation and dispatch. + */ + bool handleXEvent(const XEvent*); + + //! Destroy without the display + /*! + Tells this object to delete itself without using the X11 display. + It may leak some resources as a result. + */ + void destroy(); + + //@} + + // IScreenSaver overrides + virtual void enable(); + virtual void disable(); + virtual void activate(); + virtual void deactivate(); + virtual bool isActive() const; + +private: + // find and set the running xscreensaver's window. returns true iff + // found. + bool findXScreenSaver(); + + // set the xscreensaver's window, updating the activation state flag + void setXScreenSaver(Window); + + // returns true if the window appears to be the xscreensaver window + bool isXScreenSaver(Window) const; + + // set xscreensaver's activation state flag. sends notification + // if the state has changed. + void setXScreenSaverActive(bool activated); + + // send a command to xscreensaver + void sendXScreenSaverCommand(Atom, long = 0, long = 0); + + // watch all windows that could potentially be the xscreensaver for + // the events that will confirm it. + void watchForXScreenSaver(); + + // stop watching all watched windows + void clearWatchForXScreenSaver(); + + // add window to the watch list + void addWatchXScreenSaver(Window window); + + // install/uninstall the job used to suppress the screensaver + void updateDisableTimer(); + + // called periodically to prevent the screen saver from starting + void handleDisableTimer(const CEvent&, void*); + + // force DPMS to activate or deactivate + void activateDPMS(bool activate); + + // enable/disable DPMS screen saver + void enableDPMS(bool); + + // check if DPMS is enabled + bool isDPMSEnabled() const; + + // check if DPMS is activate + bool isDPMSActivated() const; + +private: + typedef std::map CWatchList; + + // the X display + Display* m_display; + + // window to receive xscreensaver repsonses + Window m_xscreensaverSink; + + // the target for the events we generate + void* m_eventTarget; + + // xscreensaver's window + Window m_xscreensaver; + + // xscreensaver activation state + bool m_xscreensaverActive; + + // old event mask on root window + long m_rootEventMask; + + // potential xscreensaver windows being watched + CWatchList m_watchWindows; + + // atoms used to communicate with xscreensaver's window + Atom m_atomScreenSaver; + Atom m_atomScreenSaverVersion; + Atom m_atomScreenSaverActivate; + Atom m_atomScreenSaverDeactivate; + + // built-in screen saver settings + int m_timeout; + int m_interval; + int m_preferBlanking; + int m_allowExposures; + + // DPMS screen saver settings + bool m_dpms; + bool m_dpmsEnabled; + + // true iff the client wants the screen saver suppressed + bool m_disabled; + + // true iff we're ignoring m_disabled. this is true, for example, + // when the client has called activate() and so presumably wants + // to activate the screen saver even if disabled. + bool m_suppressDisable; + + // the disable timer (NULL if not installed) + CEventQueueTimer* m_disableTimer; + + // fake mouse motion position for suppressing the screen saver. + // xscreensaver since 2.21 requires the mouse to move more than 10 + // pixels to be considered significant. + SInt32 m_disablePos; +}; + +#endif diff --git a/lib/platform/CXWindowsUtil.cpp b/lib/platform/CXWindowsUtil.cpp new file mode 100644 index 00000000..a33fc4ee --- /dev/null +++ b/lib/platform/CXWindowsUtil.cpp @@ -0,0 +1,1766 @@ +/* + * 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 "CXWindowsUtil.h" +#include "KeyTypes.h" +#include "CThread.h" +#include "CLog.h" +#include "CStringUtil.h" +#include +#define XK_APL +#define XK_ARABIC +#define XK_ARMENIAN +#define XK_CAUCASUS +#define XK_CURRENCY +#define XK_CYRILLIC +#define XK_GEORGIAN +#define XK_GREEK +#define XK_HEBREW +#define XK_KATAKANA +#define XK_KOREAN +#define XK_LATIN1 +#define XK_LATIN2 +#define XK_LATIN3 +#define XK_LATIN4 +#define XK_LATIN8 +#define XK_LATIN9 +#define XK_MISCELLANY +#define XK_PUBLISHING +#define XK_SPECIAL +#define XK_TECHNICAL +#define XK_THAI +#define XK_VIETNAMESE +#define XK_XKB_KEYS +#include + +#if !defined(XK_OE) +#define XK_OE 0x13bc +#endif +#if !defined(XK_oe) +#define XK_oe 0x13bd +#endif +#if !defined(XK_Ydiaeresis) +#define XK_Ydiaeresis 0x13be +#endif + +/* + * This table maps keysym values into the corresponding ISO 10646 + * (UCS, Unicode) values. + * + * The array keysymtab[] contains pairs of X11 keysym values for graphical + * characters and the corresponding Unicode value. + * + * Author: Markus G. Kuhn , + * University of Cambridge, April 2001 + * + * Special thanks to Richard Verhoeven for preparing + * an initial draft of the mapping table. + * + * This software is in the public domain. Share and enjoy! + */ + +struct codepair { + KeySym keysym; + UInt32 ucs4; +} s_keymap[] = { +{ XK_Aogonek, 0x0104 }, /* LATIN CAPITAL LETTER A WITH OGONEK */ +{ XK_breve, 0x02d8 }, /* BREVE */ +{ XK_Lstroke, 0x0141 }, /* LATIN CAPITAL LETTER L WITH STROKE */ +{ XK_Lcaron, 0x013d }, /* LATIN CAPITAL LETTER L WITH CARON */ +{ XK_Sacute, 0x015a }, /* LATIN CAPITAL LETTER S WITH ACUTE */ +{ XK_Scaron, 0x0160 }, /* LATIN CAPITAL LETTER S WITH CARON */ +{ XK_Scedilla, 0x015e }, /* LATIN CAPITAL LETTER S WITH CEDILLA */ +{ XK_Tcaron, 0x0164 }, /* LATIN CAPITAL LETTER T WITH CARON */ +{ XK_Zacute, 0x0179 }, /* LATIN CAPITAL LETTER Z WITH ACUTE */ +{ XK_Zcaron, 0x017d }, /* LATIN CAPITAL LETTER Z WITH CARON */ +{ XK_Zabovedot, 0x017b }, /* LATIN CAPITAL LETTER Z WITH DOT ABOVE */ +{ XK_aogonek, 0x0105 }, /* LATIN SMALL LETTER A WITH OGONEK */ +{ XK_ogonek, 0x02db }, /* OGONEK */ +{ XK_lstroke, 0x0142 }, /* LATIN SMALL LETTER L WITH STROKE */ +{ XK_lcaron, 0x013e }, /* LATIN SMALL LETTER L WITH CARON */ +{ XK_sacute, 0x015b }, /* LATIN SMALL LETTER S WITH ACUTE */ +{ XK_caron, 0x02c7 }, /* CARON */ +{ XK_scaron, 0x0161 }, /* LATIN SMALL LETTER S WITH CARON */ +{ XK_scedilla, 0x015f }, /* LATIN SMALL LETTER S WITH CEDILLA */ +{ XK_tcaron, 0x0165 }, /* LATIN SMALL LETTER T WITH CARON */ +{ XK_zacute, 0x017a }, /* LATIN SMALL LETTER Z WITH ACUTE */ +{ XK_doubleacute, 0x02dd }, /* DOUBLE ACUTE ACCENT */ +{ XK_zcaron, 0x017e }, /* LATIN SMALL LETTER Z WITH CARON */ +{ XK_zabovedot, 0x017c }, /* LATIN SMALL LETTER Z WITH DOT ABOVE */ +{ XK_Racute, 0x0154 }, /* LATIN CAPITAL LETTER R WITH ACUTE */ +{ XK_Abreve, 0x0102 }, /* LATIN CAPITAL LETTER A WITH BREVE */ +{ XK_Lacute, 0x0139 }, /* LATIN CAPITAL LETTER L WITH ACUTE */ +{ XK_Cacute, 0x0106 }, /* LATIN CAPITAL LETTER C WITH ACUTE */ +{ XK_Ccaron, 0x010c }, /* LATIN CAPITAL LETTER C WITH CARON */ +{ XK_Eogonek, 0x0118 }, /* LATIN CAPITAL LETTER E WITH OGONEK */ +{ XK_Ecaron, 0x011a }, /* LATIN CAPITAL LETTER E WITH CARON */ +{ XK_Dcaron, 0x010e }, /* LATIN CAPITAL LETTER D WITH CARON */ +{ XK_Dstroke, 0x0110 }, /* LATIN CAPITAL LETTER D WITH STROKE */ +{ XK_Nacute, 0x0143 }, /* LATIN CAPITAL LETTER N WITH ACUTE */ +{ XK_Ncaron, 0x0147 }, /* LATIN CAPITAL LETTER N WITH CARON */ +{ XK_Odoubleacute, 0x0150 }, /* LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ +{ XK_Rcaron, 0x0158 }, /* LATIN CAPITAL LETTER R WITH CARON */ +{ XK_Uring, 0x016e }, /* LATIN CAPITAL LETTER U WITH RING ABOVE */ +{ XK_Udoubleacute, 0x0170 }, /* LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ +{ XK_Tcedilla, 0x0162 }, /* LATIN CAPITAL LETTER T WITH CEDILLA */ +{ XK_racute, 0x0155 }, /* LATIN SMALL LETTER R WITH ACUTE */ +{ XK_abreve, 0x0103 }, /* LATIN SMALL LETTER A WITH BREVE */ +{ XK_lacute, 0x013a }, /* LATIN SMALL LETTER L WITH ACUTE */ +{ XK_cacute, 0x0107 }, /* LATIN SMALL LETTER C WITH ACUTE */ +{ XK_ccaron, 0x010d }, /* LATIN SMALL LETTER C WITH CARON */ +{ XK_eogonek, 0x0119 }, /* LATIN SMALL LETTER E WITH OGONEK */ +{ XK_ecaron, 0x011b }, /* LATIN SMALL LETTER E WITH CARON */ +{ XK_dcaron, 0x010f }, /* LATIN SMALL LETTER D WITH CARON */ +{ XK_dstroke, 0x0111 }, /* LATIN SMALL LETTER D WITH STROKE */ +{ XK_nacute, 0x0144 }, /* LATIN SMALL LETTER N WITH ACUTE */ +{ XK_ncaron, 0x0148 }, /* LATIN SMALL LETTER N WITH CARON */ +{ XK_odoubleacute, 0x0151 }, /* LATIN SMALL LETTER O WITH DOUBLE ACUTE */ +{ XK_rcaron, 0x0159 }, /* LATIN SMALL LETTER R WITH CARON */ +{ XK_uring, 0x016f }, /* LATIN SMALL LETTER U WITH RING ABOVE */ +{ XK_udoubleacute, 0x0171 }, /* LATIN SMALL LETTER U WITH DOUBLE ACUTE */ +{ XK_tcedilla, 0x0163 }, /* LATIN SMALL LETTER T WITH CEDILLA */ +{ XK_abovedot, 0x02d9 }, /* DOT ABOVE */ +{ XK_Hstroke, 0x0126 }, /* LATIN CAPITAL LETTER H WITH STROKE */ +{ XK_Hcircumflex, 0x0124 }, /* LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ +{ XK_Iabovedot, 0x0130 }, /* LATIN CAPITAL LETTER I WITH DOT ABOVE */ +{ XK_Gbreve, 0x011e }, /* LATIN CAPITAL LETTER G WITH BREVE */ +{ XK_Jcircumflex, 0x0134 }, /* LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ +{ XK_hstroke, 0x0127 }, /* LATIN SMALL LETTER H WITH STROKE */ +{ XK_hcircumflex, 0x0125 }, /* LATIN SMALL LETTER H WITH CIRCUMFLEX */ +{ XK_idotless, 0x0131 }, /* LATIN SMALL LETTER DOTLESS I */ +{ XK_gbreve, 0x011f }, /* LATIN SMALL LETTER G WITH BREVE */ +{ XK_jcircumflex, 0x0135 }, /* LATIN SMALL LETTER J WITH CIRCUMFLEX */ +{ XK_Cabovedot, 0x010a }, /* LATIN CAPITAL LETTER C WITH DOT ABOVE */ +{ XK_Ccircumflex, 0x0108 }, /* LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ +{ XK_Gabovedot, 0x0120 }, /* LATIN CAPITAL LETTER G WITH DOT ABOVE */ +{ XK_Gcircumflex, 0x011c }, /* LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ +{ XK_Ubreve, 0x016c }, /* LATIN CAPITAL LETTER U WITH BREVE */ +{ XK_Scircumflex, 0x015c }, /* LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ +{ XK_cabovedot, 0x010b }, /* LATIN SMALL LETTER C WITH DOT ABOVE */ +{ XK_ccircumflex, 0x0109 }, /* LATIN SMALL LETTER C WITH CIRCUMFLEX */ +{ XK_gabovedot, 0x0121 }, /* LATIN SMALL LETTER G WITH DOT ABOVE */ +{ XK_gcircumflex, 0x011d }, /* LATIN SMALL LETTER G WITH CIRCUMFLEX */ +{ XK_ubreve, 0x016d }, /* LATIN SMALL LETTER U WITH BREVE */ +{ XK_scircumflex, 0x015d }, /* LATIN SMALL LETTER S WITH CIRCUMFLEX */ +{ XK_kra, 0x0138 }, /* LATIN SMALL LETTER KRA */ +{ XK_Rcedilla, 0x0156 }, /* LATIN CAPITAL LETTER R WITH CEDILLA */ +{ XK_Itilde, 0x0128 }, /* LATIN CAPITAL LETTER I WITH TILDE */ +{ XK_Lcedilla, 0x013b }, /* LATIN CAPITAL LETTER L WITH CEDILLA */ +{ XK_Emacron, 0x0112 }, /* LATIN CAPITAL LETTER E WITH MACRON */ +{ XK_Gcedilla, 0x0122 }, /* LATIN CAPITAL LETTER G WITH CEDILLA */ +{ XK_Tslash, 0x0166 }, /* LATIN CAPITAL LETTER T WITH STROKE */ +{ XK_rcedilla, 0x0157 }, /* LATIN SMALL LETTER R WITH CEDILLA */ +{ XK_itilde, 0x0129 }, /* LATIN SMALL LETTER I WITH TILDE */ +{ XK_lcedilla, 0x013c }, /* LATIN SMALL LETTER L WITH CEDILLA */ +{ XK_emacron, 0x0113 }, /* LATIN SMALL LETTER E WITH MACRON */ +{ XK_gcedilla, 0x0123 }, /* LATIN SMALL LETTER G WITH CEDILLA */ +{ XK_tslash, 0x0167 }, /* LATIN SMALL LETTER T WITH STROKE */ +{ XK_ENG, 0x014a }, /* LATIN CAPITAL LETTER ENG */ +{ XK_eng, 0x014b }, /* LATIN SMALL LETTER ENG */ +{ XK_Amacron, 0x0100 }, /* LATIN CAPITAL LETTER A WITH MACRON */ +{ XK_Iogonek, 0x012e }, /* LATIN CAPITAL LETTER I WITH OGONEK */ +{ XK_Eabovedot, 0x0116 }, /* LATIN CAPITAL LETTER E WITH DOT ABOVE */ +{ XK_Imacron, 0x012a }, /* LATIN CAPITAL LETTER I WITH MACRON */ +{ XK_Ncedilla, 0x0145 }, /* LATIN CAPITAL LETTER N WITH CEDILLA */ +{ XK_Omacron, 0x014c }, /* LATIN CAPITAL LETTER O WITH MACRON */ +{ XK_Kcedilla, 0x0136 }, /* LATIN CAPITAL LETTER K WITH CEDILLA */ +{ XK_Uogonek, 0x0172 }, /* LATIN CAPITAL LETTER U WITH OGONEK */ +{ XK_Utilde, 0x0168 }, /* LATIN CAPITAL LETTER U WITH TILDE */ +{ XK_Umacron, 0x016a }, /* LATIN CAPITAL LETTER U WITH MACRON */ +{ XK_amacron, 0x0101 }, /* LATIN SMALL LETTER A WITH MACRON */ +{ XK_iogonek, 0x012f }, /* LATIN SMALL LETTER I WITH OGONEK */ +{ XK_eabovedot, 0x0117 }, /* LATIN SMALL LETTER E WITH DOT ABOVE */ +{ XK_imacron, 0x012b }, /* LATIN SMALL LETTER I WITH MACRON */ +{ XK_ncedilla, 0x0146 }, /* LATIN SMALL LETTER N WITH CEDILLA */ +{ XK_omacron, 0x014d }, /* LATIN SMALL LETTER O WITH MACRON */ +{ XK_kcedilla, 0x0137 }, /* LATIN SMALL LETTER K WITH CEDILLA */ +{ XK_uogonek, 0x0173 }, /* LATIN SMALL LETTER U WITH OGONEK */ +{ XK_utilde, 0x0169 }, /* LATIN SMALL LETTER U WITH TILDE */ +{ XK_umacron, 0x016b }, /* LATIN SMALL LETTER U WITH MACRON */ +#if defined(XK_Babovedot) +{ XK_Babovedot, 0x1e02 }, /* LATIN CAPITAL LETTER B WITH DOT ABOVE */ +{ XK_babovedot, 0x1e03 }, /* LATIN SMALL LETTER B WITH DOT ABOVE */ +{ XK_Dabovedot, 0x1e0a }, /* LATIN CAPITAL LETTER D WITH DOT ABOVE */ +{ XK_Wgrave, 0x1e80 }, /* LATIN CAPITAL LETTER W WITH GRAVE */ +{ XK_Wacute, 0x1e82 }, /* LATIN CAPITAL LETTER W WITH ACUTE */ +{ XK_dabovedot, 0x1e0b }, /* LATIN SMALL LETTER D WITH DOT ABOVE */ +{ XK_Ygrave, 0x1ef2 }, /* LATIN CAPITAL LETTER Y WITH GRAVE */ +{ XK_Fabovedot, 0x1e1e }, /* LATIN CAPITAL LETTER F WITH DOT ABOVE */ +{ XK_fabovedot, 0x1e1f }, /* LATIN SMALL LETTER F WITH DOT ABOVE */ +{ XK_Mabovedot, 0x1e40 }, /* LATIN CAPITAL LETTER M WITH DOT ABOVE */ +{ XK_mabovedot, 0x1e41 }, /* LATIN SMALL LETTER M WITH DOT ABOVE */ +{ XK_Pabovedot, 0x1e56 }, /* LATIN CAPITAL LETTER P WITH DOT ABOVE */ +{ XK_wgrave, 0x1e81 }, /* LATIN SMALL LETTER W WITH GRAVE */ +{ XK_pabovedot, 0x1e57 }, /* LATIN SMALL LETTER P WITH DOT ABOVE */ +{ XK_wacute, 0x1e83 }, /* LATIN SMALL LETTER W WITH ACUTE */ +{ XK_Sabovedot, 0x1e60 }, /* LATIN CAPITAL LETTER S WITH DOT ABOVE */ +{ XK_ygrave, 0x1ef3 }, /* LATIN SMALL LETTER Y WITH GRAVE */ +{ XK_Wdiaeresis, 0x1e84 }, /* LATIN CAPITAL LETTER W WITH DIAERESIS */ +{ XK_wdiaeresis, 0x1e85 }, /* LATIN SMALL LETTER W WITH DIAERESIS */ +{ XK_sabovedot, 0x1e61 }, /* LATIN SMALL LETTER S WITH DOT ABOVE */ +{ XK_Wcircumflex, 0x0174 }, /* LATIN CAPITAL LETTER W WITH CIRCUMFLEX */ +{ XK_Tabovedot, 0x1e6a }, /* LATIN CAPITAL LETTER T WITH DOT ABOVE */ +{ XK_Ycircumflex, 0x0176 }, /* LATIN CAPITAL LETTER Y WITH CIRCUMFLEX */ +{ XK_wcircumflex, 0x0175 }, /* LATIN SMALL LETTER W WITH CIRCUMFLEX */ +{ XK_tabovedot, 0x1e6b }, /* LATIN SMALL LETTER T WITH DOT ABOVE */ +{ XK_ycircumflex, 0x0177 }, /* LATIN SMALL LETTER Y WITH CIRCUMFLEX */ +#endif // defined(XK_Babovedot) +#if defined(XK_overline) +{ XK_overline, 0x203e }, /* OVERLINE */ +{ XK_kana_fullstop, 0x3002 }, /* IDEOGRAPHIC FULL STOP */ +{ XK_kana_openingbracket, 0x300c }, /* LEFT CORNER BRACKET */ +{ XK_kana_closingbracket, 0x300d }, /* RIGHT CORNER BRACKET */ +{ XK_kana_comma, 0x3001 }, /* IDEOGRAPHIC COMMA */ +{ XK_kana_conjunctive, 0x30fb }, /* KATAKANA MIDDLE DOT */ +{ XK_kana_WO, 0x30f2 }, /* KATAKANA LETTER WO */ +{ XK_kana_a, 0x30a1 }, /* KATAKANA LETTER SMALL A */ +{ XK_kana_i, 0x30a3 }, /* KATAKANA LETTER SMALL I */ +{ XK_kana_u, 0x30a5 }, /* KATAKANA LETTER SMALL U */ +{ XK_kana_e, 0x30a7 }, /* KATAKANA LETTER SMALL E */ +{ XK_kana_o, 0x30a9 }, /* KATAKANA LETTER SMALL O */ +{ XK_kana_ya, 0x30e3 }, /* KATAKANA LETTER SMALL YA */ +{ XK_kana_yu, 0x30e5 }, /* KATAKANA LETTER SMALL YU */ +{ XK_kana_yo, 0x30e7 }, /* KATAKANA LETTER SMALL YO */ +{ XK_kana_tsu, 0x30c3 }, /* KATAKANA LETTER SMALL TU */ +{ XK_prolongedsound, 0x30fc }, /* KATAKANA-HIRAGANA PROLONGED SOUND MARK */ +{ XK_kana_A, 0x30a2 }, /* KATAKANA LETTER A */ +{ XK_kana_I, 0x30a4 }, /* KATAKANA LETTER I */ +{ XK_kana_U, 0x30a6 }, /* KATAKANA LETTER U */ +{ XK_kana_E, 0x30a8 }, /* KATAKANA LETTER E */ +{ XK_kana_O, 0x30aa }, /* KATAKANA LETTER O */ +{ XK_kana_KA, 0x30ab }, /* KATAKANA LETTER KA */ +{ XK_kana_KI, 0x30ad }, /* KATAKANA LETTER KI */ +{ XK_kana_KU, 0x30af }, /* KATAKANA LETTER KU */ +{ XK_kana_KE, 0x30b1 }, /* KATAKANA LETTER KE */ +{ XK_kana_KO, 0x30b3 }, /* KATAKANA LETTER KO */ +{ XK_kana_SA, 0x30b5 }, /* KATAKANA LETTER SA */ +{ XK_kana_SHI, 0x30b7 }, /* KATAKANA LETTER SI */ +{ XK_kana_SU, 0x30b9 }, /* KATAKANA LETTER SU */ +{ XK_kana_SE, 0x30bb }, /* KATAKANA LETTER SE */ +{ XK_kana_SO, 0x30bd }, /* KATAKANA LETTER SO */ +{ XK_kana_TA, 0x30bf }, /* KATAKANA LETTER TA */ +{ XK_kana_CHI, 0x30c1 }, /* KATAKANA LETTER TI */ +{ XK_kana_TSU, 0x30c4 }, /* KATAKANA LETTER TU */ +{ XK_kana_TE, 0x30c6 }, /* KATAKANA LETTER TE */ +{ XK_kana_TO, 0x30c8 }, /* KATAKANA LETTER TO */ +{ XK_kana_NA, 0x30ca }, /* KATAKANA LETTER NA */ +{ XK_kana_NI, 0x30cb }, /* KATAKANA LETTER NI */ +{ XK_kana_NU, 0x30cc }, /* KATAKANA LETTER NU */ +{ XK_kana_NE, 0x30cd }, /* KATAKANA LETTER NE */ +{ XK_kana_NO, 0x30ce }, /* KATAKANA LETTER NO */ +{ XK_kana_HA, 0x30cf }, /* KATAKANA LETTER HA */ +{ XK_kana_HI, 0x30d2 }, /* KATAKANA LETTER HI */ +{ XK_kana_FU, 0x30d5 }, /* KATAKANA LETTER HU */ +{ XK_kana_HE, 0x30d8 }, /* KATAKANA LETTER HE */ +{ XK_kana_HO, 0x30db }, /* KATAKANA LETTER HO */ +{ XK_kana_MA, 0x30de }, /* KATAKANA LETTER MA */ +{ XK_kana_MI, 0x30df }, /* KATAKANA LETTER MI */ +{ XK_kana_MU, 0x30e0 }, /* KATAKANA LETTER MU */ +{ XK_kana_ME, 0x30e1 }, /* KATAKANA LETTER ME */ +{ XK_kana_MO, 0x30e2 }, /* KATAKANA LETTER MO */ +{ XK_kana_YA, 0x30e4 }, /* KATAKANA LETTER YA */ +{ XK_kana_YU, 0x30e6 }, /* KATAKANA LETTER YU */ +{ XK_kana_YO, 0x30e8 }, /* KATAKANA LETTER YO */ +{ XK_kana_RA, 0x30e9 }, /* KATAKANA LETTER RA */ +{ XK_kana_RI, 0x30ea }, /* KATAKANA LETTER RI */ +{ XK_kana_RU, 0x30eb }, /* KATAKANA LETTER RU */ +{ XK_kana_RE, 0x30ec }, /* KATAKANA LETTER RE */ +{ XK_kana_RO, 0x30ed }, /* KATAKANA LETTER RO */ +{ XK_kana_WA, 0x30ef }, /* KATAKANA LETTER WA */ +{ XK_kana_N, 0x30f3 }, /* KATAKANA LETTER N */ +{ XK_voicedsound, 0x309b }, /* KATAKANA-HIRAGANA VOICED SOUND MARK */ +{ XK_semivoicedsound, 0x309c }, /* KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ +#endif // defined(XK_overline) +#if defined(XK_Farsi_0) +{ XK_Farsi_0, 0x06f0 }, /* EXTENDED ARABIC-INDIC DIGIT 0 */ +{ XK_Farsi_1, 0x06f1 }, /* EXTENDED ARABIC-INDIC DIGIT 1 */ +{ XK_Farsi_2, 0x06f2 }, /* EXTENDED ARABIC-INDIC DIGIT 2 */ +{ XK_Farsi_3, 0x06f3 }, /* EXTENDED ARABIC-INDIC DIGIT 3 */ +{ XK_Farsi_4, 0x06f4 }, /* EXTENDED ARABIC-INDIC DIGIT 4 */ +{ XK_Farsi_5, 0x06f5 }, /* EXTENDED ARABIC-INDIC DIGIT 5 */ +{ XK_Farsi_6, 0x06f6 }, /* EXTENDED ARABIC-INDIC DIGIT 6 */ +{ XK_Farsi_7, 0x06f7 }, /* EXTENDED ARABIC-INDIC DIGIT 7 */ +{ XK_Farsi_8, 0x06f8 }, /* EXTENDED ARABIC-INDIC DIGIT 8 */ +{ XK_Farsi_9, 0x06f9 }, /* EXTENDED ARABIC-INDIC DIGIT 9 */ +{ XK_Arabic_percent, 0x066a }, /* ARABIC PERCENT */ +{ XK_Arabic_superscript_alef, 0x0670 }, /* ARABIC LETTER SUPERSCRIPT ALEF */ +{ XK_Arabic_tteh, 0x0679 }, /* ARABIC LETTER TTEH */ +{ XK_Arabic_peh, 0x067e }, /* ARABIC LETTER PEH */ +{ XK_Arabic_tcheh, 0x0686 }, /* ARABIC LETTER TCHEH */ +{ XK_Arabic_ddal, 0x0688 }, /* ARABIC LETTER DDAL */ +{ XK_Arabic_rreh, 0x0691 }, /* ARABIC LETTER RREH */ +{ XK_Arabic_comma, 0x060c }, /* ARABIC COMMA */ +{ XK_Arabic_fullstop, 0x06d4 }, /* ARABIC FULLSTOP */ +{ XK_Arabic_semicolon, 0x061b }, /* ARABIC SEMICOLON */ +{ XK_Arabic_0, 0x0660 }, /* ARABIC 0 */ +{ XK_Arabic_1, 0x0661 }, /* ARABIC 1 */ +{ XK_Arabic_2, 0x0662 }, /* ARABIC 2 */ +{ XK_Arabic_3, 0x0663 }, /* ARABIC 3 */ +{ XK_Arabic_4, 0x0664 }, /* ARABIC 4 */ +{ XK_Arabic_5, 0x0665 }, /* ARABIC 5 */ +{ XK_Arabic_6, 0x0666 }, /* ARABIC 6 */ +{ XK_Arabic_7, 0x0667 }, /* ARABIC 7 */ +{ XK_Arabic_8, 0x0668 }, /* ARABIC 8 */ +{ XK_Arabic_9, 0x0669 }, /* ARABIC 9 */ +{ XK_Arabic_question_mark, 0x061f }, /* ARABIC QUESTION MARK */ +{ XK_Arabic_hamza, 0x0621 }, /* ARABIC LETTER HAMZA */ +{ XK_Arabic_maddaonalef, 0x0622 }, /* ARABIC LETTER ALEF WITH MADDA ABOVE */ +{ XK_Arabic_hamzaonalef, 0x0623 }, /* ARABIC LETTER ALEF WITH HAMZA ABOVE */ +{ XK_Arabic_hamzaonwaw, 0x0624 }, /* ARABIC LETTER WAW WITH HAMZA ABOVE */ +{ XK_Arabic_hamzaunderalef, 0x0625 }, /* ARABIC LETTER ALEF WITH HAMZA BELOW */ +{ XK_Arabic_hamzaonyeh, 0x0626 }, /* ARABIC LETTER YEH WITH HAMZA ABOVE */ +{ XK_Arabic_alef, 0x0627 }, /* ARABIC LETTER ALEF */ +{ XK_Arabic_beh, 0x0628 }, /* ARABIC LETTER BEH */ +{ XK_Arabic_tehmarbuta, 0x0629 }, /* ARABIC LETTER TEH MARBUTA */ +{ XK_Arabic_teh, 0x062a }, /* ARABIC LETTER TEH */ +{ XK_Arabic_theh, 0x062b }, /* ARABIC LETTER THEH */ +{ XK_Arabic_jeem, 0x062c }, /* ARABIC LETTER JEEM */ +{ XK_Arabic_hah, 0x062d }, /* ARABIC LETTER HAH */ +{ XK_Arabic_khah, 0x062e }, /* ARABIC LETTER KHAH */ +{ XK_Arabic_dal, 0x062f }, /* ARABIC LETTER DAL */ +{ XK_Arabic_thal, 0x0630 }, /* ARABIC LETTER THAL */ +{ XK_Arabic_ra, 0x0631 }, /* ARABIC LETTER REH */ +{ XK_Arabic_zain, 0x0632 }, /* ARABIC LETTER ZAIN */ +{ XK_Arabic_seen, 0x0633 }, /* ARABIC LETTER SEEN */ +{ XK_Arabic_sheen, 0x0634 }, /* ARABIC LETTER SHEEN */ +{ XK_Arabic_sad, 0x0635 }, /* ARABIC LETTER SAD */ +{ XK_Arabic_dad, 0x0636 }, /* ARABIC LETTER DAD */ +{ XK_Arabic_tah, 0x0637 }, /* ARABIC LETTER TAH */ +{ XK_Arabic_zah, 0x0638 }, /* ARABIC LETTER ZAH */ +{ XK_Arabic_ain, 0x0639 }, /* ARABIC LETTER AIN */ +{ XK_Arabic_ghain, 0x063a }, /* ARABIC LETTER GHAIN */ +{ XK_Arabic_tatweel, 0x0640 }, /* ARABIC TATWEEL */ +{ XK_Arabic_feh, 0x0641 }, /* ARABIC LETTER FEH */ +{ XK_Arabic_qaf, 0x0642 }, /* ARABIC LETTER QAF */ +{ XK_Arabic_kaf, 0x0643 }, /* ARABIC LETTER KAF */ +{ XK_Arabic_lam, 0x0644 }, /* ARABIC LETTER LAM */ +{ XK_Arabic_meem, 0x0645 }, /* ARABIC LETTER MEEM */ +{ XK_Arabic_noon, 0x0646 }, /* ARABIC LETTER NOON */ +{ XK_Arabic_ha, 0x0647 }, /* ARABIC LETTER HEH */ +{ XK_Arabic_waw, 0x0648 }, /* ARABIC LETTER WAW */ +{ XK_Arabic_alefmaksura, 0x0649 }, /* ARABIC LETTER ALEF MAKSURA */ +{ XK_Arabic_yeh, 0x064a }, /* ARABIC LETTER YEH */ +{ XK_Arabic_fathatan, 0x064b }, /* ARABIC FATHATAN */ +{ XK_Arabic_dammatan, 0x064c }, /* ARABIC DAMMATAN */ +{ XK_Arabic_kasratan, 0x064d }, /* ARABIC KASRATAN */ +{ XK_Arabic_fatha, 0x064e }, /* ARABIC FATHA */ +{ XK_Arabic_damma, 0x064f }, /* ARABIC DAMMA */ +{ XK_Arabic_kasra, 0x0650 }, /* ARABIC KASRA */ +{ XK_Arabic_shadda, 0x0651 }, /* ARABIC SHADDA */ +{ XK_Arabic_sukun, 0x0652 }, /* ARABIC SUKUN */ +{ XK_Arabic_madda_above, 0x0653 }, /* ARABIC MADDA ABOVE */ +{ XK_Arabic_hamza_above, 0x0654 }, /* ARABIC HAMZA ABOVE */ +{ XK_Arabic_hamza_below, 0x0655 }, /* ARABIC HAMZA BELOW */ +{ XK_Arabic_jeh, 0x0698 }, /* ARABIC LETTER JEH */ +{ XK_Arabic_veh, 0x06a4 }, /* ARABIC LETTER VEH */ +{ XK_Arabic_keheh, 0x06a9 }, /* ARABIC LETTER KEHEH */ +{ XK_Arabic_gaf, 0x06af }, /* ARABIC LETTER GAF */ +{ XK_Arabic_noon_ghunna, 0x06ba }, /* ARABIC LETTER NOON GHUNNA */ +{ XK_Arabic_heh_doachashmee, 0x06be }, /* ARABIC LETTER HEH DOACHASHMEE */ +{ XK_Arabic_farsi_yeh, 0x06cc }, /* ARABIC LETTER FARSI YEH */ +{ XK_Arabic_yeh_baree, 0x06d2 }, /* ARABIC LETTER YEH BAREE */ +{ XK_Arabic_heh_goal, 0x06c1 }, /* ARABIC LETTER HEH GOAL */ +#endif // defined(XK_Farsi_0) +#if defined(XK_Serbian_dje) +{ XK_Serbian_dje, 0x0452 }, /* CYRILLIC SMALL LETTER DJE */ +{ XK_Macedonia_gje, 0x0453 }, /* CYRILLIC SMALL LETTER GJE */ +{ XK_Cyrillic_io, 0x0451 }, /* CYRILLIC SMALL LETTER IO */ +{ XK_Ukrainian_ie, 0x0454 }, /* CYRILLIC SMALL LETTER UKRAINIAN IE */ +{ XK_Macedonia_dse, 0x0455 }, /* CYRILLIC SMALL LETTER DZE */ +{ XK_Ukrainian_i, 0x0456 }, /* CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ +{ XK_Ukrainian_yi, 0x0457 }, /* CYRILLIC SMALL LETTER YI */ +{ XK_Cyrillic_je, 0x0458 }, /* CYRILLIC SMALL LETTER JE */ +{ XK_Cyrillic_lje, 0x0459 }, /* CYRILLIC SMALL LETTER LJE */ +{ XK_Cyrillic_nje, 0x045a }, /* CYRILLIC SMALL LETTER NJE */ +{ XK_Serbian_tshe, 0x045b }, /* CYRILLIC SMALL LETTER TSHE */ +{ XK_Macedonia_kje, 0x045c }, /* CYRILLIC SMALL LETTER KJE */ +#if defined(XK_Ukrainian_ghe_with_upturn) +{ XK_Ukrainian_ghe_with_upturn, 0x0491 }, /* CYRILLIC SMALL LETTER GHE WITH UPTURN */ +#endif +{ XK_Byelorussian_shortu, 0x045e }, /* CYRILLIC SMALL LETTER SHORT U */ +{ XK_Cyrillic_dzhe, 0x045f }, /* CYRILLIC SMALL LETTER DZHE */ +{ XK_numerosign, 0x2116 }, /* NUMERO SIGN */ +{ XK_Serbian_DJE, 0x0402 }, /* CYRILLIC CAPITAL LETTER DJE */ +{ XK_Macedonia_GJE, 0x0403 }, /* CYRILLIC CAPITAL LETTER GJE */ +{ XK_Cyrillic_IO, 0x0401 }, /* CYRILLIC CAPITAL LETTER IO */ +{ XK_Ukrainian_IE, 0x0404 }, /* CYRILLIC CAPITAL LETTER UKRAINIAN IE */ +{ XK_Macedonia_DSE, 0x0405 }, /* CYRILLIC CAPITAL LETTER DZE */ +{ XK_Ukrainian_I, 0x0406 }, /* CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ +{ XK_Ukrainian_YI, 0x0407 }, /* CYRILLIC CAPITAL LETTER YI */ +{ XK_Cyrillic_JE, 0x0408 }, /* CYRILLIC CAPITAL LETTER JE */ +{ XK_Cyrillic_LJE, 0x0409 }, /* CYRILLIC CAPITAL LETTER LJE */ +{ XK_Cyrillic_NJE, 0x040a }, /* CYRILLIC CAPITAL LETTER NJE */ +{ XK_Serbian_TSHE, 0x040b }, /* CYRILLIC CAPITAL LETTER TSHE */ +{ XK_Macedonia_KJE, 0x040c }, /* CYRILLIC CAPITAL LETTER KJE */ +#if defined(XK_Ukrainian_GHE_WITH_UPTURN) +{ XK_Ukrainian_GHE_WITH_UPTURN, 0x0490 }, /* CYRILLIC CAPITAL LETTER GHE WITH UPTURN */ +#endif +{ XK_Byelorussian_SHORTU, 0x040e }, /* CYRILLIC CAPITAL LETTER SHORT U */ +{ XK_Cyrillic_DZHE, 0x040f }, /* CYRILLIC CAPITAL LETTER DZHE */ +{ XK_Cyrillic_yu, 0x044e }, /* CYRILLIC SMALL LETTER YU */ +{ XK_Cyrillic_a, 0x0430 }, /* CYRILLIC SMALL LETTER A */ +{ XK_Cyrillic_be, 0x0431 }, /* CYRILLIC SMALL LETTER BE */ +{ XK_Cyrillic_tse, 0x0446 }, /* CYRILLIC SMALL LETTER TSE */ +{ XK_Cyrillic_de, 0x0434 }, /* CYRILLIC SMALL LETTER DE */ +{ XK_Cyrillic_ie, 0x0435 }, /* CYRILLIC SMALL LETTER IE */ +{ XK_Cyrillic_ef, 0x0444 }, /* CYRILLIC SMALL LETTER EF */ +{ XK_Cyrillic_ghe, 0x0433 }, /* CYRILLIC SMALL LETTER GHE */ +{ XK_Cyrillic_ha, 0x0445 }, /* CYRILLIC SMALL LETTER HA */ +{ XK_Cyrillic_i, 0x0438 }, /* CYRILLIC SMALL LETTER I */ +{ XK_Cyrillic_shorti, 0x0439 }, /* CYRILLIC SMALL LETTER SHORT I */ +{ XK_Cyrillic_ka, 0x043a }, /* CYRILLIC SMALL LETTER KA */ +{ XK_Cyrillic_el, 0x043b }, /* CYRILLIC SMALL LETTER EL */ +{ XK_Cyrillic_em, 0x043c }, /* CYRILLIC SMALL LETTER EM */ +{ XK_Cyrillic_en, 0x043d }, /* CYRILLIC SMALL LETTER EN */ +{ XK_Cyrillic_o, 0x043e }, /* CYRILLIC SMALL LETTER O */ +{ XK_Cyrillic_pe, 0x043f }, /* CYRILLIC SMALL LETTER PE */ +{ XK_Cyrillic_ya, 0x044f }, /* CYRILLIC SMALL LETTER YA */ +{ XK_Cyrillic_er, 0x0440 }, /* CYRILLIC SMALL LETTER ER */ +{ XK_Cyrillic_es, 0x0441 }, /* CYRILLIC SMALL LETTER ES */ +{ XK_Cyrillic_te, 0x0442 }, /* CYRILLIC SMALL LETTER TE */ +{ XK_Cyrillic_u, 0x0443 }, /* CYRILLIC SMALL LETTER U */ +{ XK_Cyrillic_zhe, 0x0436 }, /* CYRILLIC SMALL LETTER ZHE */ +{ XK_Cyrillic_ve, 0x0432 }, /* CYRILLIC SMALL LETTER VE */ +{ XK_Cyrillic_softsign, 0x044c }, /* CYRILLIC SMALL LETTER SOFT SIGN */ +{ XK_Cyrillic_yeru, 0x044b }, /* CYRILLIC SMALL LETTER YERU */ +{ XK_Cyrillic_ze, 0x0437 }, /* CYRILLIC SMALL LETTER ZE */ +{ XK_Cyrillic_sha, 0x0448 }, /* CYRILLIC SMALL LETTER SHA */ +{ XK_Cyrillic_e, 0x044d }, /* CYRILLIC SMALL LETTER E */ +{ XK_Cyrillic_shcha, 0x0449 }, /* CYRILLIC SMALL LETTER SHCHA */ +{ XK_Cyrillic_che, 0x0447 }, /* CYRILLIC SMALL LETTER CHE */ +{ XK_Cyrillic_hardsign, 0x044a }, /* CYRILLIC SMALL LETTER HARD SIGN */ +{ XK_Cyrillic_YU, 0x042e }, /* CYRILLIC CAPITAL LETTER YU */ +{ XK_Cyrillic_A, 0x0410 }, /* CYRILLIC CAPITAL LETTER A */ +{ XK_Cyrillic_BE, 0x0411 }, /* CYRILLIC CAPITAL LETTER BE */ +{ XK_Cyrillic_TSE, 0x0426 }, /* CYRILLIC CAPITAL LETTER TSE */ +{ XK_Cyrillic_DE, 0x0414 }, /* CYRILLIC CAPITAL LETTER DE */ +{ XK_Cyrillic_IE, 0x0415 }, /* CYRILLIC CAPITAL LETTER IE */ +{ XK_Cyrillic_EF, 0x0424 }, /* CYRILLIC CAPITAL LETTER EF */ +{ XK_Cyrillic_GHE, 0x0413 }, /* CYRILLIC CAPITAL LETTER GHE */ +{ XK_Cyrillic_HA, 0x0425 }, /* CYRILLIC CAPITAL LETTER HA */ +{ XK_Cyrillic_I, 0x0418 }, /* CYRILLIC CAPITAL LETTER I */ +{ XK_Cyrillic_SHORTI, 0x0419 }, /* CYRILLIC CAPITAL LETTER SHORT I */ +{ XK_Cyrillic_KA, 0x041a }, /* CYRILLIC CAPITAL LETTER KA */ +{ XK_Cyrillic_EL, 0x041b }, /* CYRILLIC CAPITAL LETTER EL */ +{ XK_Cyrillic_EM, 0x041c }, /* CYRILLIC CAPITAL LETTER EM */ +{ XK_Cyrillic_EN, 0x041d }, /* CYRILLIC CAPITAL LETTER EN */ +{ XK_Cyrillic_O, 0x041e }, /* CYRILLIC CAPITAL LETTER O */ +{ XK_Cyrillic_PE, 0x041f }, /* CYRILLIC CAPITAL LETTER PE */ +{ XK_Cyrillic_YA, 0x042f }, /* CYRILLIC CAPITAL LETTER YA */ +{ XK_Cyrillic_ER, 0x0420 }, /* CYRILLIC CAPITAL LETTER ER */ +{ XK_Cyrillic_ES, 0x0421 }, /* CYRILLIC CAPITAL LETTER ES */ +{ XK_Cyrillic_TE, 0x0422 }, /* CYRILLIC CAPITAL LETTER TE */ +{ XK_Cyrillic_U, 0x0423 }, /* CYRILLIC CAPITAL LETTER U */ +{ XK_Cyrillic_ZHE, 0x0416 }, /* CYRILLIC CAPITAL LETTER ZHE */ +{ XK_Cyrillic_VE, 0x0412 }, /* CYRILLIC CAPITAL LETTER VE */ +{ XK_Cyrillic_SOFTSIGN, 0x042c }, /* CYRILLIC CAPITAL LETTER SOFT SIGN */ +{ XK_Cyrillic_YERU, 0x042b }, /* CYRILLIC CAPITAL LETTER YERU */ +{ XK_Cyrillic_ZE, 0x0417 }, /* CYRILLIC CAPITAL LETTER ZE */ +{ XK_Cyrillic_SHA, 0x0428 }, /* CYRILLIC CAPITAL LETTER SHA */ +{ XK_Cyrillic_E, 0x042d }, /* CYRILLIC CAPITAL LETTER E */ +{ XK_Cyrillic_SHCHA, 0x0429 }, /* CYRILLIC CAPITAL LETTER SHCHA */ +{ XK_Cyrillic_CHE, 0x0427 }, /* CYRILLIC CAPITAL LETTER CHE */ +{ XK_Cyrillic_HARDSIGN, 0x042a }, /* CYRILLIC CAPITAL LETTER HARD SIGN */ +#endif // defined(XK_Serbian_dje) +#if defined(XK_Greek_ALPHAaccent) +{ XK_Greek_ALPHAaccent, 0x0386 }, /* GREEK CAPITAL LETTER ALPHA WITH TONOS */ +{ XK_Greek_EPSILONaccent, 0x0388 }, /* GREEK CAPITAL LETTER EPSILON WITH TONOS */ +{ XK_Greek_ETAaccent, 0x0389 }, /* GREEK CAPITAL LETTER ETA WITH TONOS */ +{ XK_Greek_IOTAaccent, 0x038a }, /* GREEK CAPITAL LETTER IOTA WITH TONOS */ +{ XK_Greek_IOTAdiaeresis, 0x03aa }, /* GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ +{ XK_Greek_OMICRONaccent, 0x038c }, /* GREEK CAPITAL LETTER OMICRON WITH TONOS */ +{ XK_Greek_UPSILONaccent, 0x038e }, /* GREEK CAPITAL LETTER UPSILON WITH TONOS */ +{ XK_Greek_UPSILONdieresis, 0x03ab }, /* GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ +{ XK_Greek_OMEGAaccent, 0x038f }, /* GREEK CAPITAL LETTER OMEGA WITH TONOS */ +{ XK_Greek_accentdieresis, 0x0385 }, /* GREEK DIALYTIKA TONOS */ +{ XK_Greek_horizbar, 0x2015 }, /* HORIZONTAL BAR */ +{ XK_Greek_alphaaccent, 0x03ac }, /* GREEK SMALL LETTER ALPHA WITH TONOS */ +{ XK_Greek_epsilonaccent, 0x03ad }, /* GREEK SMALL LETTER EPSILON WITH TONOS */ +{ XK_Greek_etaaccent, 0x03ae }, /* GREEK SMALL LETTER ETA WITH TONOS */ +{ XK_Greek_iotaaccent, 0x03af }, /* GREEK SMALL LETTER IOTA WITH TONOS */ +{ XK_Greek_iotadieresis, 0x03ca }, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA */ +{ XK_Greek_iotaaccentdieresis, 0x0390 }, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ +{ XK_Greek_omicronaccent, 0x03cc }, /* GREEK SMALL LETTER OMICRON WITH TONOS */ +{ XK_Greek_upsilonaccent, 0x03cd }, /* GREEK SMALL LETTER UPSILON WITH TONOS */ +{ XK_Greek_upsilondieresis, 0x03cb }, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ +{ XK_Greek_upsilonaccentdieresis, 0x03b0 }, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ +{ XK_Greek_omegaaccent, 0x03ce }, /* GREEK SMALL LETTER OMEGA WITH TONOS */ +{ XK_Greek_ALPHA, 0x0391 }, /* GREEK CAPITAL LETTER ALPHA */ +{ XK_Greek_BETA, 0x0392 }, /* GREEK CAPITAL LETTER BETA */ +{ XK_Greek_GAMMA, 0x0393 }, /* GREEK CAPITAL LETTER GAMMA */ +{ XK_Greek_DELTA, 0x0394 }, /* GREEK CAPITAL LETTER DELTA */ +{ XK_Greek_EPSILON, 0x0395 }, /* GREEK CAPITAL LETTER EPSILON */ +{ XK_Greek_ZETA, 0x0396 }, /* GREEK CAPITAL LETTER ZETA */ +{ XK_Greek_ETA, 0x0397 }, /* GREEK CAPITAL LETTER ETA */ +{ XK_Greek_THETA, 0x0398 }, /* GREEK CAPITAL LETTER THETA */ +{ XK_Greek_IOTA, 0x0399 }, /* GREEK CAPITAL LETTER IOTA */ +{ XK_Greek_KAPPA, 0x039a }, /* GREEK CAPITAL LETTER KAPPA */ +{ XK_Greek_LAMBDA, 0x039b }, /* GREEK CAPITAL LETTER LAMDA */ +{ XK_Greek_MU, 0x039c }, /* GREEK CAPITAL LETTER MU */ +{ XK_Greek_NU, 0x039d }, /* GREEK CAPITAL LETTER NU */ +{ XK_Greek_XI, 0x039e }, /* GREEK CAPITAL LETTER XI */ +{ XK_Greek_OMICRON, 0x039f }, /* GREEK CAPITAL LETTER OMICRON */ +{ XK_Greek_PI, 0x03a0 }, /* GREEK CAPITAL LETTER PI */ +{ XK_Greek_RHO, 0x03a1 }, /* GREEK CAPITAL LETTER RHO */ +{ XK_Greek_SIGMA, 0x03a3 }, /* GREEK CAPITAL LETTER SIGMA */ +{ XK_Greek_TAU, 0x03a4 }, /* GREEK CAPITAL LETTER TAU */ +{ XK_Greek_UPSILON, 0x03a5 }, /* GREEK CAPITAL LETTER UPSILON */ +{ XK_Greek_PHI, 0x03a6 }, /* GREEK CAPITAL LETTER PHI */ +{ XK_Greek_CHI, 0x03a7 }, /* GREEK CAPITAL LETTER CHI */ +{ XK_Greek_PSI, 0x03a8 }, /* GREEK CAPITAL LETTER PSI */ +{ XK_Greek_OMEGA, 0x03a9 }, /* GREEK CAPITAL LETTER OMEGA */ +{ XK_Greek_alpha, 0x03b1 }, /* GREEK SMALL LETTER ALPHA */ +{ XK_Greek_beta, 0x03b2 }, /* GREEK SMALL LETTER BETA */ +{ XK_Greek_gamma, 0x03b3 }, /* GREEK SMALL LETTER GAMMA */ +{ XK_Greek_delta, 0x03b4 }, /* GREEK SMALL LETTER DELTA */ +{ XK_Greek_epsilon, 0x03b5 }, /* GREEK SMALL LETTER EPSILON */ +{ XK_Greek_zeta, 0x03b6 }, /* GREEK SMALL LETTER ZETA */ +{ XK_Greek_eta, 0x03b7 }, /* GREEK SMALL LETTER ETA */ +{ XK_Greek_theta, 0x03b8 }, /* GREEK SMALL LETTER THETA */ +{ XK_Greek_iota, 0x03b9 }, /* GREEK SMALL LETTER IOTA */ +{ XK_Greek_kappa, 0x03ba }, /* GREEK SMALL LETTER KAPPA */ +{ XK_Greek_lambda, 0x03bb }, /* GREEK SMALL LETTER LAMDA */ +{ XK_Greek_mu, 0x03bc }, /* GREEK SMALL LETTER MU */ +{ XK_Greek_nu, 0x03bd }, /* GREEK SMALL LETTER NU */ +{ XK_Greek_xi, 0x03be }, /* GREEK SMALL LETTER XI */ +{ XK_Greek_omicron, 0x03bf }, /* GREEK SMALL LETTER OMICRON */ +{ XK_Greek_pi, 0x03c0 }, /* GREEK SMALL LETTER PI */ +{ XK_Greek_rho, 0x03c1 }, /* GREEK SMALL LETTER RHO */ +{ XK_Greek_sigma, 0x03c3 }, /* GREEK SMALL LETTER SIGMA */ +{ XK_Greek_finalsmallsigma, 0x03c2 }, /* GREEK SMALL LETTER FINAL SIGMA */ +{ XK_Greek_tau, 0x03c4 }, /* GREEK SMALL LETTER TAU */ +{ XK_Greek_upsilon, 0x03c5 }, /* GREEK SMALL LETTER UPSILON */ +{ XK_Greek_phi, 0x03c6 }, /* GREEK SMALL LETTER PHI */ +{ XK_Greek_chi, 0x03c7 }, /* GREEK SMALL LETTER CHI */ +{ XK_Greek_psi, 0x03c8 }, /* GREEK SMALL LETTER PSI */ +{ XK_Greek_omega, 0x03c9 }, /* GREEK SMALL LETTER OMEGA */ +#endif // defined(XK_Greek_ALPHAaccent) +{ XK_leftradical, 0x23b7 }, /* ??? */ +{ XK_topleftradical, 0x250c }, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */ +{ XK_horizconnector, 0x2500 }, /* BOX DRAWINGS LIGHT HORIZONTAL */ +{ XK_topintegral, 0x2320 }, /* TOP HALF INTEGRAL */ +{ XK_botintegral, 0x2321 }, /* BOTTOM HALF INTEGRAL */ +{ XK_vertconnector, 0x2502 }, /* BOX DRAWINGS LIGHT VERTICAL */ +{ XK_topleftsqbracket, 0x23a1 }, /* ??? */ +{ XK_botleftsqbracket, 0x23a3 }, /* ??? */ +{ XK_toprightsqbracket, 0x23a4 }, /* ??? */ +{ XK_botrightsqbracket, 0x23a6 }, /* ??? */ +{ XK_topleftparens, 0x239b }, /* ??? */ +{ XK_botleftparens, 0x239d }, /* ??? */ +{ XK_toprightparens, 0x239e }, /* ??? */ +{ XK_botrightparens, 0x23a0 }, /* ??? */ +{ XK_leftmiddlecurlybrace, 0x23a8 }, /* ??? */ +{ XK_rightmiddlecurlybrace, 0x23ac }, /* ??? */ +{ XK_lessthanequal, 0x2264 }, /* LESS-THAN OR EQUAL TO */ +{ XK_notequal, 0x2260 }, /* NOT EQUAL TO */ +{ XK_greaterthanequal, 0x2265 }, /* GREATER-THAN OR EQUAL TO */ +{ XK_integral, 0x222b }, /* INTEGRAL */ +{ XK_therefore, 0x2234 }, /* THEREFORE */ +{ XK_variation, 0x221d }, /* PROPORTIONAL TO */ +{ XK_infinity, 0x221e }, /* INFINITY */ +{ XK_nabla, 0x2207 }, /* NABLA */ +{ XK_approximate, 0x223c }, /* TILDE OPERATOR */ +{ XK_similarequal, 0x2243 }, /* ASYMPTOTICALLY EQUAL TO */ +{ XK_ifonlyif, 0x21d4 }, /* LEFT RIGHT DOUBLE ARROW */ +{ XK_implies, 0x21d2 }, /* RIGHTWARDS DOUBLE ARROW */ +{ XK_identical, 0x2261 }, /* IDENTICAL TO */ +{ XK_radical, 0x221a }, /* SQUARE ROOT */ +{ XK_includedin, 0x2282 }, /* SUBSET OF */ +{ XK_includes, 0x2283 }, /* SUPERSET OF */ +{ XK_intersection, 0x2229 }, /* INTERSECTION */ +{ XK_union, 0x222a }, /* UNION */ +{ XK_logicaland, 0x2227 }, /* LOGICAL AND */ +{ XK_logicalor, 0x2228 }, /* LOGICAL OR */ +{ XK_partialderivative, 0x2202 }, /* PARTIAL DIFFERENTIAL */ +{ XK_function, 0x0192 }, /* LATIN SMALL LETTER F WITH HOOK */ +{ XK_leftarrow, 0x2190 }, /* LEFTWARDS ARROW */ +{ XK_uparrow, 0x2191 }, /* UPWARDS ARROW */ +{ XK_rightarrow, 0x2192 }, /* RIGHTWARDS ARROW */ +{ XK_downarrow, 0x2193 }, /* DOWNWARDS ARROW */ +/*{ XK_blank, ??? }, */ +{ XK_soliddiamond, 0x25c6 }, /* BLACK DIAMOND */ +{ XK_checkerboard, 0x2592 }, /* MEDIUM SHADE */ +{ XK_ht, 0x2409 }, /* SYMBOL FOR HORIZONTAL TABULATION */ +{ XK_ff, 0x240c }, /* SYMBOL FOR FORM FEED */ +{ XK_cr, 0x240d }, /* SYMBOL FOR CARRIAGE RETURN */ +{ XK_lf, 0x240a }, /* SYMBOL FOR LINE FEED */ +{ XK_nl, 0x2424 }, /* SYMBOL FOR NEWLINE */ +{ XK_vt, 0x240b }, /* SYMBOL FOR VERTICAL TABULATION */ +{ XK_lowrightcorner, 0x2518 }, /* BOX DRAWINGS LIGHT UP AND LEFT */ +{ XK_uprightcorner, 0x2510 }, /* BOX DRAWINGS LIGHT DOWN AND LEFT */ +{ XK_upleftcorner, 0x250c }, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */ +{ XK_lowleftcorner, 0x2514 }, /* BOX DRAWINGS LIGHT UP AND RIGHT */ +{ XK_crossinglines, 0x253c }, /* BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ +{ XK_horizlinescan1, 0x23ba }, /* HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */ +{ XK_horizlinescan3, 0x23bb }, /* HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */ +{ XK_horizlinescan5, 0x2500 }, /* BOX DRAWINGS LIGHT HORIZONTAL */ +{ XK_horizlinescan7, 0x23bc }, /* HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */ +{ XK_horizlinescan9, 0x23bd }, /* HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */ +{ XK_leftt, 0x251c }, /* BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ +{ XK_rightt, 0x2524 }, /* BOX DRAWINGS LIGHT VERTICAL AND LEFT */ +{ XK_bott, 0x2534 }, /* BOX DRAWINGS LIGHT UP AND HORIZONTAL */ +{ XK_topt, 0x252c }, /* BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ +{ XK_vertbar, 0x2502 }, /* BOX DRAWINGS LIGHT VERTICAL */ +{ XK_emspace, 0x2003 }, /* EM SPACE */ +{ XK_enspace, 0x2002 }, /* EN SPACE */ +{ XK_em3space, 0x2004 }, /* THREE-PER-EM SPACE */ +{ XK_em4space, 0x2005 }, /* FOUR-PER-EM SPACE */ +{ XK_digitspace, 0x2007 }, /* FIGURE SPACE */ +{ XK_punctspace, 0x2008 }, /* PUNCTUATION SPACE */ +{ XK_thinspace, 0x2009 }, /* THIN SPACE */ +{ XK_hairspace, 0x200a }, /* HAIR SPACE */ +{ XK_emdash, 0x2014 }, /* EM DASH */ +{ XK_endash, 0x2013 }, /* EN DASH */ +/*{ XK_signifblank, ??? }, */ +{ XK_ellipsis, 0x2026 }, /* HORIZONTAL ELLIPSIS */ +{ XK_doubbaselinedot, 0x2025 }, /* TWO DOT LEADER */ +{ XK_onethird, 0x2153 }, /* VULGAR FRACTION ONE THIRD */ +{ XK_twothirds, 0x2154 }, /* VULGAR FRACTION TWO THIRDS */ +{ XK_onefifth, 0x2155 }, /* VULGAR FRACTION ONE FIFTH */ +{ XK_twofifths, 0x2156 }, /* VULGAR FRACTION TWO FIFTHS */ +{ XK_threefifths, 0x2157 }, /* VULGAR FRACTION THREE FIFTHS */ +{ XK_fourfifths, 0x2158 }, /* VULGAR FRACTION FOUR FIFTHS */ +{ XK_onesixth, 0x2159 }, /* VULGAR FRACTION ONE SIXTH */ +{ XK_fivesixths, 0x215a }, /* VULGAR FRACTION FIVE SIXTHS */ +{ XK_careof, 0x2105 }, /* CARE OF */ +{ XK_figdash, 0x2012 }, /* FIGURE DASH */ +{ XK_leftanglebracket, 0x2329 }, /* LEFT-POINTING ANGLE BRACKET */ +/*{ XK_decimalpoint, ??? }, */ +{ XK_rightanglebracket, 0x232a }, /* RIGHT-POINTING ANGLE BRACKET */ +/*{ XK_marker, ??? }, */ +{ XK_oneeighth, 0x215b }, /* VULGAR FRACTION ONE EIGHTH */ +{ XK_threeeighths, 0x215c }, /* VULGAR FRACTION THREE EIGHTHS */ +{ XK_fiveeighths, 0x215d }, /* VULGAR FRACTION FIVE EIGHTHS */ +{ XK_seveneighths, 0x215e }, /* VULGAR FRACTION SEVEN EIGHTHS */ +{ XK_trademark, 0x2122 }, /* TRADE MARK SIGN */ +{ XK_signaturemark, 0x2613 }, /* SALTIRE */ +/*{ XK_trademarkincircle, ??? }, */ +{ XK_leftopentriangle, 0x25c1 }, /* WHITE LEFT-POINTING TRIANGLE */ +{ XK_rightopentriangle, 0x25b7 }, /* WHITE RIGHT-POINTING TRIANGLE */ +{ XK_emopencircle, 0x25cb }, /* WHITE CIRCLE */ +{ XK_emopenrectangle, 0x25af }, /* WHITE VERTICAL RECTANGLE */ +{ XK_leftsinglequotemark, 0x2018 }, /* LEFT SINGLE QUOTATION MARK */ +{ XK_rightsinglequotemark, 0x2019 }, /* RIGHT SINGLE QUOTATION MARK */ +{ XK_leftdoublequotemark, 0x201c }, /* LEFT DOUBLE QUOTATION MARK */ +{ XK_rightdoublequotemark, 0x201d }, /* RIGHT DOUBLE QUOTATION MARK */ +{ XK_prescription, 0x211e }, /* PRESCRIPTION TAKE */ +{ XK_minutes, 0x2032 }, /* PRIME */ +{ XK_seconds, 0x2033 }, /* DOUBLE PRIME */ +{ XK_latincross, 0x271d }, /* LATIN CROSS */ +/*{ XK_hexagram, ??? }, */ +{ XK_filledrectbullet, 0x25ac }, /* BLACK RECTANGLE */ +{ XK_filledlefttribullet, 0x25c0 }, /* BLACK LEFT-POINTING TRIANGLE */ +{ XK_filledrighttribullet, 0x25b6 }, /* BLACK RIGHT-POINTING TRIANGLE */ +{ XK_emfilledcircle, 0x25cf }, /* BLACK CIRCLE */ +{ XK_emfilledrect, 0x25ae }, /* BLACK VERTICAL RECTANGLE */ +{ XK_enopencircbullet, 0x25e6 }, /* WHITE BULLET */ +{ XK_enopensquarebullet, 0x25ab }, /* WHITE SMALL SQUARE */ +{ XK_openrectbullet, 0x25ad }, /* WHITE RECTANGLE */ +{ XK_opentribulletup, 0x25b3 }, /* WHITE UP-POINTING TRIANGLE */ +{ XK_opentribulletdown, 0x25bd }, /* WHITE DOWN-POINTING TRIANGLE */ +{ XK_openstar, 0x2606 }, /* WHITE STAR */ +{ XK_enfilledcircbullet, 0x2022 }, /* BULLET */ +{ XK_enfilledsqbullet, 0x25aa }, /* BLACK SMALL SQUARE */ +{ XK_filledtribulletup, 0x25b2 }, /* BLACK UP-POINTING TRIANGLE */ +{ XK_filledtribulletdown, 0x25bc }, /* BLACK DOWN-POINTING TRIANGLE */ +{ XK_leftpointer, 0x261c }, /* WHITE LEFT POINTING INDEX */ +{ XK_rightpointer, 0x261e }, /* WHITE RIGHT POINTING INDEX */ +{ XK_club, 0x2663 }, /* BLACK CLUB SUIT */ +{ XK_diamond, 0x2666 }, /* BLACK DIAMOND SUIT */ +{ XK_heart, 0x2665 }, /* BLACK HEART SUIT */ +{ XK_maltesecross, 0x2720 }, /* MALTESE CROSS */ +{ XK_dagger, 0x2020 }, /* DAGGER */ +{ XK_doubledagger, 0x2021 }, /* DOUBLE DAGGER */ +{ XK_checkmark, 0x2713 }, /* CHECK MARK */ +{ XK_ballotcross, 0x2717 }, /* BALLOT X */ +{ XK_musicalsharp, 0x266f }, /* MUSIC SHARP SIGN */ +{ XK_musicalflat, 0x266d }, /* MUSIC FLAT SIGN */ +{ XK_malesymbol, 0x2642 }, /* MALE SIGN */ +{ XK_femalesymbol, 0x2640 }, /* FEMALE SIGN */ +{ XK_telephone, 0x260e }, /* BLACK TELEPHONE */ +{ XK_telephonerecorder, 0x2315 }, /* TELEPHONE RECORDER */ +{ XK_phonographcopyright, 0x2117 }, /* SOUND RECORDING COPYRIGHT */ +{ XK_caret, 0x2038 }, /* CARET */ +{ XK_singlelowquotemark, 0x201a }, /* SINGLE LOW-9 QUOTATION MARK */ +{ XK_doublelowquotemark, 0x201e }, /* DOUBLE LOW-9 QUOTATION MARK */ +/*{ XK_cursor, ??? }, */ +{ XK_leftcaret, 0x003c }, /* LESS-THAN SIGN */ +{ XK_rightcaret, 0x003e }, /* GREATER-THAN SIGN */ +{ XK_downcaret, 0x2228 }, /* LOGICAL OR */ +{ XK_upcaret, 0x2227 }, /* LOGICAL AND */ +{ XK_overbar, 0x00af }, /* MACRON */ +{ XK_downtack, 0x22a5 }, /* UP TACK */ +{ XK_upshoe, 0x2229 }, /* INTERSECTION */ +{ XK_downstile, 0x230a }, /* LEFT FLOOR */ +{ XK_underbar, 0x005f }, /* LOW LINE */ +{ XK_jot, 0x2218 }, /* RING OPERATOR */ +{ XK_quad, 0x2395 }, /* APL FUNCTIONAL SYMBOL QUAD */ +{ XK_uptack, 0x22a4 }, /* DOWN TACK */ +{ XK_circle, 0x25cb }, /* WHITE CIRCLE */ +{ XK_upstile, 0x2308 }, /* LEFT CEILING */ +{ XK_downshoe, 0x222a }, /* UNION */ +{ XK_rightshoe, 0x2283 }, /* SUPERSET OF */ +{ XK_leftshoe, 0x2282 }, /* SUBSET OF */ +{ XK_lefttack, 0x22a2 }, /* RIGHT TACK */ +{ XK_righttack, 0x22a3 }, /* LEFT TACK */ +#if defined(XK_hebrew_doublelowline) +{ XK_hebrew_doublelowline, 0x2017 }, /* DOUBLE LOW LINE */ +{ XK_hebrew_aleph, 0x05d0 }, /* HEBREW LETTER ALEF */ +{ XK_hebrew_bet, 0x05d1 }, /* HEBREW LETTER BET */ +{ XK_hebrew_gimel, 0x05d2 }, /* HEBREW LETTER GIMEL */ +{ XK_hebrew_dalet, 0x05d3 }, /* HEBREW LETTER DALET */ +{ XK_hebrew_he, 0x05d4 }, /* HEBREW LETTER HE */ +{ XK_hebrew_waw, 0x05d5 }, /* HEBREW LETTER VAV */ +{ XK_hebrew_zain, 0x05d6 }, /* HEBREW LETTER ZAYIN */ +{ XK_hebrew_chet, 0x05d7 }, /* HEBREW LETTER HET */ +{ XK_hebrew_tet, 0x05d8 }, /* HEBREW LETTER TET */ +{ XK_hebrew_yod, 0x05d9 }, /* HEBREW LETTER YOD */ +{ XK_hebrew_finalkaph, 0x05da }, /* HEBREW LETTER FINAL KAF */ +{ XK_hebrew_kaph, 0x05db }, /* HEBREW LETTER KAF */ +{ XK_hebrew_lamed, 0x05dc }, /* HEBREW LETTER LAMED */ +{ XK_hebrew_finalmem, 0x05dd }, /* HEBREW LETTER FINAL MEM */ +{ XK_hebrew_mem, 0x05de }, /* HEBREW LETTER MEM */ +{ XK_hebrew_finalnun, 0x05df }, /* HEBREW LETTER FINAL NUN */ +{ XK_hebrew_nun, 0x05e0 }, /* HEBREW LETTER NUN */ +{ XK_hebrew_samech, 0x05e1 }, /* HEBREW LETTER SAMEKH */ +{ XK_hebrew_ayin, 0x05e2 }, /* HEBREW LETTER AYIN */ +{ XK_hebrew_finalpe, 0x05e3 }, /* HEBREW LETTER FINAL PE */ +{ XK_hebrew_pe, 0x05e4 }, /* HEBREW LETTER PE */ +{ XK_hebrew_finalzade, 0x05e5 }, /* HEBREW LETTER FINAL TSADI */ +{ XK_hebrew_zade, 0x05e6 }, /* HEBREW LETTER TSADI */ +{ XK_hebrew_qoph, 0x05e7 }, /* HEBREW LETTER QOF */ +{ XK_hebrew_resh, 0x05e8 }, /* HEBREW LETTER RESH */ +{ XK_hebrew_shin, 0x05e9 }, /* HEBREW LETTER SHIN */ +{ XK_hebrew_taw, 0x05ea }, /* HEBREW LETTER TAV */ +#endif // defined(XK_hebrew_doublelowline) +#if defined(XK_Thai_kokai) +{ XK_Thai_kokai, 0x0e01 }, /* THAI CHARACTER KO KAI */ +{ XK_Thai_khokhai, 0x0e02 }, /* THAI CHARACTER KHO KHAI */ +{ XK_Thai_khokhuat, 0x0e03 }, /* THAI CHARACTER KHO KHUAT */ +{ XK_Thai_khokhwai, 0x0e04 }, /* THAI CHARACTER KHO KHWAI */ +{ XK_Thai_khokhon, 0x0e05 }, /* THAI CHARACTER KHO KHON */ +{ XK_Thai_khorakhang, 0x0e06 }, /* THAI CHARACTER KHO RAKHANG */ +{ XK_Thai_ngongu, 0x0e07 }, /* THAI CHARACTER NGO NGU */ +{ XK_Thai_chochan, 0x0e08 }, /* THAI CHARACTER CHO CHAN */ +{ XK_Thai_choching, 0x0e09 }, /* THAI CHARACTER CHO CHING */ +{ XK_Thai_chochang, 0x0e0a }, /* THAI CHARACTER CHO CHANG */ +{ XK_Thai_soso, 0x0e0b }, /* THAI CHARACTER SO SO */ +{ XK_Thai_chochoe, 0x0e0c }, /* THAI CHARACTER CHO CHOE */ +{ XK_Thai_yoying, 0x0e0d }, /* THAI CHARACTER YO YING */ +{ XK_Thai_dochada, 0x0e0e }, /* THAI CHARACTER DO CHADA */ +{ XK_Thai_topatak, 0x0e0f }, /* THAI CHARACTER TO PATAK */ +{ XK_Thai_thothan, 0x0e10 }, /* THAI CHARACTER THO THAN */ +{ XK_Thai_thonangmontho, 0x0e11 }, /* THAI CHARACTER THO NANGMONTHO */ +{ XK_Thai_thophuthao, 0x0e12 }, /* THAI CHARACTER THO PHUTHAO */ +{ XK_Thai_nonen, 0x0e13 }, /* THAI CHARACTER NO NEN */ +{ XK_Thai_dodek, 0x0e14 }, /* THAI CHARACTER DO DEK */ +{ XK_Thai_totao, 0x0e15 }, /* THAI CHARACTER TO TAO */ +{ XK_Thai_thothung, 0x0e16 }, /* THAI CHARACTER THO THUNG */ +{ XK_Thai_thothahan, 0x0e17 }, /* THAI CHARACTER THO THAHAN */ +{ XK_Thai_thothong, 0x0e18 }, /* THAI CHARACTER THO THONG */ +{ XK_Thai_nonu, 0x0e19 }, /* THAI CHARACTER NO NU */ +{ XK_Thai_bobaimai, 0x0e1a }, /* THAI CHARACTER BO BAIMAI */ +{ XK_Thai_popla, 0x0e1b }, /* THAI CHARACTER PO PLA */ +{ XK_Thai_phophung, 0x0e1c }, /* THAI CHARACTER PHO PHUNG */ +{ XK_Thai_fofa, 0x0e1d }, /* THAI CHARACTER FO FA */ +{ XK_Thai_phophan, 0x0e1e }, /* THAI CHARACTER PHO PHAN */ +{ XK_Thai_fofan, 0x0e1f }, /* THAI CHARACTER FO FAN */ +{ XK_Thai_phosamphao, 0x0e20 }, /* THAI CHARACTER PHO SAMPHAO */ +{ XK_Thai_moma, 0x0e21 }, /* THAI CHARACTER MO MA */ +{ XK_Thai_yoyak, 0x0e22 }, /* THAI CHARACTER YO YAK */ +{ XK_Thai_rorua, 0x0e23 }, /* THAI CHARACTER RO RUA */ +{ XK_Thai_ru, 0x0e24 }, /* THAI CHARACTER RU */ +{ XK_Thai_loling, 0x0e25 }, /* THAI CHARACTER LO LING */ +{ XK_Thai_lu, 0x0e26 }, /* THAI CHARACTER LU */ +{ XK_Thai_wowaen, 0x0e27 }, /* THAI CHARACTER WO WAEN */ +{ XK_Thai_sosala, 0x0e28 }, /* THAI CHARACTER SO SALA */ +{ XK_Thai_sorusi, 0x0e29 }, /* THAI CHARACTER SO RUSI */ +{ XK_Thai_sosua, 0x0e2a }, /* THAI CHARACTER SO SUA */ +{ XK_Thai_hohip, 0x0e2b }, /* THAI CHARACTER HO HIP */ +{ XK_Thai_lochula, 0x0e2c }, /* THAI CHARACTER LO CHULA */ +{ XK_Thai_oang, 0x0e2d }, /* THAI CHARACTER O ANG */ +{ XK_Thai_honokhuk, 0x0e2e }, /* THAI CHARACTER HO NOKHUK */ +{ XK_Thai_paiyannoi, 0x0e2f }, /* THAI CHARACTER PAIYANNOI */ +{ XK_Thai_saraa, 0x0e30 }, /* THAI CHARACTER SARA A */ +{ XK_Thai_maihanakat, 0x0e31 }, /* THAI CHARACTER MAI HAN-AKAT */ +{ XK_Thai_saraaa, 0x0e32 }, /* THAI CHARACTER SARA AA */ +{ XK_Thai_saraam, 0x0e33 }, /* THAI CHARACTER SARA AM */ +{ XK_Thai_sarai, 0x0e34 }, /* THAI CHARACTER SARA I */ +{ XK_Thai_saraii, 0x0e35 }, /* THAI CHARACTER SARA II */ +{ XK_Thai_saraue, 0x0e36 }, /* THAI CHARACTER SARA UE */ +{ XK_Thai_sarauee, 0x0e37 }, /* THAI CHARACTER SARA UEE */ +{ XK_Thai_sarau, 0x0e38 }, /* THAI CHARACTER SARA U */ +{ XK_Thai_sarauu, 0x0e39 }, /* THAI CHARACTER SARA UU */ +{ XK_Thai_phinthu, 0x0e3a }, /* THAI CHARACTER PHINTHU */ +/*{ XK_Thai_maihanakat_maitho, ??? }, */ +{ XK_Thai_baht, 0x0e3f }, /* THAI CURRENCY SYMBOL BAHT */ +{ XK_Thai_sarae, 0x0e40 }, /* THAI CHARACTER SARA E */ +{ XK_Thai_saraae, 0x0e41 }, /* THAI CHARACTER SARA AE */ +{ XK_Thai_sarao, 0x0e42 }, /* THAI CHARACTER SARA O */ +{ XK_Thai_saraaimaimuan, 0x0e43 }, /* THAI CHARACTER SARA AI MAIMUAN */ +{ XK_Thai_saraaimaimalai, 0x0e44 }, /* THAI CHARACTER SARA AI MAIMALAI */ +{ XK_Thai_lakkhangyao, 0x0e45 }, /* THAI CHARACTER LAKKHANGYAO */ +{ XK_Thai_maiyamok, 0x0e46 }, /* THAI CHARACTER MAIYAMOK */ +{ XK_Thai_maitaikhu, 0x0e47 }, /* THAI CHARACTER MAITAIKHU */ +{ XK_Thai_maiek, 0x0e48 }, /* THAI CHARACTER MAI EK */ +{ XK_Thai_maitho, 0x0e49 }, /* THAI CHARACTER MAI THO */ +{ XK_Thai_maitri, 0x0e4a }, /* THAI CHARACTER MAI TRI */ +{ XK_Thai_maichattawa, 0x0e4b }, /* THAI CHARACTER MAI CHATTAWA */ +{ XK_Thai_thanthakhat, 0x0e4c }, /* THAI CHARACTER THANTHAKHAT */ +{ XK_Thai_nikhahit, 0x0e4d }, /* THAI CHARACTER NIKHAHIT */ +{ XK_Thai_leksun, 0x0e50 }, /* THAI DIGIT ZERO */ +{ XK_Thai_leknung, 0x0e51 }, /* THAI DIGIT ONE */ +{ XK_Thai_leksong, 0x0e52 }, /* THAI DIGIT TWO */ +{ XK_Thai_leksam, 0x0e53 }, /* THAI DIGIT THREE */ +{ XK_Thai_leksi, 0x0e54 }, /* THAI DIGIT FOUR */ +{ XK_Thai_lekha, 0x0e55 }, /* THAI DIGIT FIVE */ +{ XK_Thai_lekhok, 0x0e56 }, /* THAI DIGIT SIX */ +{ XK_Thai_lekchet, 0x0e57 }, /* THAI DIGIT SEVEN */ +{ XK_Thai_lekpaet, 0x0e58 }, /* THAI DIGIT EIGHT */ +{ XK_Thai_lekkao, 0x0e59 }, /* THAI DIGIT NINE */ +#endif // defined(XK_Thai_kokai) +#if defined(XK_Hangul_Kiyeog) +{ XK_Hangul_Kiyeog, 0x3131 }, /* HANGUL LETTER KIYEOK */ +{ XK_Hangul_SsangKiyeog, 0x3132 }, /* HANGUL LETTER SSANGKIYEOK */ +{ XK_Hangul_KiyeogSios, 0x3133 }, /* HANGUL LETTER KIYEOK-SIOS */ +{ XK_Hangul_Nieun, 0x3134 }, /* HANGUL LETTER NIEUN */ +{ XK_Hangul_NieunJieuj, 0x3135 }, /* HANGUL LETTER NIEUN-CIEUC */ +{ XK_Hangul_NieunHieuh, 0x3136 }, /* HANGUL LETTER NIEUN-HIEUH */ +{ XK_Hangul_Dikeud, 0x3137 }, /* HANGUL LETTER TIKEUT */ +{ XK_Hangul_SsangDikeud, 0x3138 }, /* HANGUL LETTER SSANGTIKEUT */ +{ XK_Hangul_Rieul, 0x3139 }, /* HANGUL LETTER RIEUL */ +{ XK_Hangul_RieulKiyeog, 0x313a }, /* HANGUL LETTER RIEUL-KIYEOK */ +{ XK_Hangul_RieulMieum, 0x313b }, /* HANGUL LETTER RIEUL-MIEUM */ +{ XK_Hangul_RieulPieub, 0x313c }, /* HANGUL LETTER RIEUL-PIEUP */ +{ XK_Hangul_RieulSios, 0x313d }, /* HANGUL LETTER RIEUL-SIOS */ +{ XK_Hangul_RieulTieut, 0x313e }, /* HANGUL LETTER RIEUL-THIEUTH */ +{ XK_Hangul_RieulPhieuf, 0x313f }, /* HANGUL LETTER RIEUL-PHIEUPH */ +{ XK_Hangul_RieulHieuh, 0x3140 }, /* HANGUL LETTER RIEUL-HIEUH */ +{ XK_Hangul_Mieum, 0x3141 }, /* HANGUL LETTER MIEUM */ +{ XK_Hangul_Pieub, 0x3142 }, /* HANGUL LETTER PIEUP */ +{ XK_Hangul_SsangPieub, 0x3143 }, /* HANGUL LETTER SSANGPIEUP */ +{ XK_Hangul_PieubSios, 0x3144 }, /* HANGUL LETTER PIEUP-SIOS */ +{ XK_Hangul_Sios, 0x3145 }, /* HANGUL LETTER SIOS */ +{ XK_Hangul_SsangSios, 0x3146 }, /* HANGUL LETTER SSANGSIOS */ +{ XK_Hangul_Ieung, 0x3147 }, /* HANGUL LETTER IEUNG */ +{ XK_Hangul_Jieuj, 0x3148 }, /* HANGUL LETTER CIEUC */ +{ XK_Hangul_SsangJieuj, 0x3149 }, /* HANGUL LETTER SSANGCIEUC */ +{ XK_Hangul_Cieuc, 0x314a }, /* HANGUL LETTER CHIEUCH */ +{ XK_Hangul_Khieuq, 0x314b }, /* HANGUL LETTER KHIEUKH */ +{ XK_Hangul_Tieut, 0x314c }, /* HANGUL LETTER THIEUTH */ +{ XK_Hangul_Phieuf, 0x314d }, /* HANGUL LETTER PHIEUPH */ +{ XK_Hangul_Hieuh, 0x314e }, /* HANGUL LETTER HIEUH */ +{ XK_Hangul_A, 0x314f }, /* HANGUL LETTER A */ +{ XK_Hangul_AE, 0x3150 }, /* HANGUL LETTER AE */ +{ XK_Hangul_YA, 0x3151 }, /* HANGUL LETTER YA */ +{ XK_Hangul_YAE, 0x3152 }, /* HANGUL LETTER YAE */ +{ XK_Hangul_EO, 0x3153 }, /* HANGUL LETTER EO */ +{ XK_Hangul_E, 0x3154 }, /* HANGUL LETTER E */ +{ XK_Hangul_YEO, 0x3155 }, /* HANGUL LETTER YEO */ +{ XK_Hangul_YE, 0x3156 }, /* HANGUL LETTER YE */ +{ XK_Hangul_O, 0x3157 }, /* HANGUL LETTER O */ +{ XK_Hangul_WA, 0x3158 }, /* HANGUL LETTER WA */ +{ XK_Hangul_WAE, 0x3159 }, /* HANGUL LETTER WAE */ +{ XK_Hangul_OE, 0x315a }, /* HANGUL LETTER OE */ +{ XK_Hangul_YO, 0x315b }, /* HANGUL LETTER YO */ +{ XK_Hangul_U, 0x315c }, /* HANGUL LETTER U */ +{ XK_Hangul_WEO, 0x315d }, /* HANGUL LETTER WEO */ +{ XK_Hangul_WE, 0x315e }, /* HANGUL LETTER WE */ +{ XK_Hangul_WI, 0x315f }, /* HANGUL LETTER WI */ +{ XK_Hangul_YU, 0x3160 }, /* HANGUL LETTER YU */ +{ XK_Hangul_EU, 0x3161 }, /* HANGUL LETTER EU */ +{ XK_Hangul_YI, 0x3162 }, /* HANGUL LETTER YI */ +{ XK_Hangul_I, 0x3163 }, /* HANGUL LETTER I */ +{ XK_Hangul_J_Kiyeog, 0x11a8 }, /* HANGUL JONGSEONG KIYEOK */ +{ XK_Hangul_J_SsangKiyeog, 0x11a9 }, /* HANGUL JONGSEONG SSANGKIYEOK */ +{ XK_Hangul_J_KiyeogSios, 0x11aa }, /* HANGUL JONGSEONG KIYEOK-SIOS */ +{ XK_Hangul_J_Nieun, 0x11ab }, /* HANGUL JONGSEONG NIEUN */ +{ XK_Hangul_J_NieunJieuj, 0x11ac }, /* HANGUL JONGSEONG NIEUN-CIEUC */ +{ XK_Hangul_J_NieunHieuh, 0x11ad }, /* HANGUL JONGSEONG NIEUN-HIEUH */ +{ XK_Hangul_J_Dikeud, 0x11ae }, /* HANGUL JONGSEONG TIKEUT */ +{ XK_Hangul_J_Rieul, 0x11af }, /* HANGUL JONGSEONG RIEUL */ +{ XK_Hangul_J_RieulKiyeog, 0x11b0 }, /* HANGUL JONGSEONG RIEUL-KIYEOK */ +{ XK_Hangul_J_RieulMieum, 0x11b1 }, /* HANGUL JONGSEONG RIEUL-MIEUM */ +{ XK_Hangul_J_RieulPieub, 0x11b2 }, /* HANGUL JONGSEONG RIEUL-PIEUP */ +{ XK_Hangul_J_RieulSios, 0x11b3 }, /* HANGUL JONGSEONG RIEUL-SIOS */ +{ XK_Hangul_J_RieulTieut, 0x11b4 }, /* HANGUL JONGSEONG RIEUL-THIEUTH */ +{ XK_Hangul_J_RieulPhieuf, 0x11b5 }, /* HANGUL JONGSEONG RIEUL-PHIEUPH */ +{ XK_Hangul_J_RieulHieuh, 0x11b6 }, /* HANGUL JONGSEONG RIEUL-HIEUH */ +{ XK_Hangul_J_Mieum, 0x11b7 }, /* HANGUL JONGSEONG MIEUM */ +{ XK_Hangul_J_Pieub, 0x11b8 }, /* HANGUL JONGSEONG PIEUP */ +{ XK_Hangul_J_PieubSios, 0x11b9 }, /* HANGUL JONGSEONG PIEUP-SIOS */ +{ XK_Hangul_J_Sios, 0x11ba }, /* HANGUL JONGSEONG SIOS */ +{ XK_Hangul_J_SsangSios, 0x11bb }, /* HANGUL JONGSEONG SSANGSIOS */ +{ XK_Hangul_J_Ieung, 0x11bc }, /* HANGUL JONGSEONG IEUNG */ +{ XK_Hangul_J_Jieuj, 0x11bd }, /* HANGUL JONGSEONG CIEUC */ +{ XK_Hangul_J_Cieuc, 0x11be }, /* HANGUL JONGSEONG CHIEUCH */ +{ XK_Hangul_J_Khieuq, 0x11bf }, /* HANGUL JONGSEONG KHIEUKH */ +{ XK_Hangul_J_Tieut, 0x11c0 }, /* HANGUL JONGSEONG THIEUTH */ +{ XK_Hangul_J_Phieuf, 0x11c1 }, /* HANGUL JONGSEONG PHIEUPH */ +{ XK_Hangul_J_Hieuh, 0x11c2 }, /* HANGUL JONGSEONG HIEUH */ +{ XK_Hangul_RieulYeorinHieuh, 0x316d }, /* HANGUL LETTER RIEUL-YEORINHIEUH */ +{ XK_Hangul_SunkyeongeumMieum, 0x3171 }, /* HANGUL LETTER KAPYEOUNMIEUM */ +{ XK_Hangul_SunkyeongeumPieub, 0x3178 }, /* HANGUL LETTER KAPYEOUNPIEUP */ +{ XK_Hangul_PanSios, 0x317f }, /* HANGUL LETTER PANSIOS */ +{ XK_Hangul_KkogjiDalrinIeung, 0x3181 }, /* HANGUL LETTER YESIEUNG */ +{ XK_Hangul_SunkyeongeumPhieuf, 0x3184 }, /* HANGUL LETTER KAPYEOUNPHIEUPH */ +{ XK_Hangul_YeorinHieuh, 0x3186 }, /* HANGUL LETTER YEORINHIEUH */ +{ XK_Hangul_AraeA, 0x318d }, /* HANGUL LETTER ARAEA */ +{ XK_Hangul_AraeAE, 0x318e }, /* HANGUL LETTER ARAEAE */ +{ XK_Hangul_J_PanSios, 0x11eb }, /* HANGUL JONGSEONG PANSIOS */ +{ XK_Hangul_J_KkogjiDalrinIeung, 0x11f0 }, /* HANGUL JONGSEONG YESIEUNG */ +{ XK_Hangul_J_YeorinHieuh, 0x11f9 }, /* HANGUL JONGSEONG YEORINHIEUH */ +{ XK_Korean_Won, 0x20a9 }, /* WON SIGN */ +#endif // defined(XK_Hangul_Kiyeog) +{ XK_OE, 0x0152 }, /* LATIN CAPITAL LIGATURE OE */ +{ XK_oe, 0x0153 }, /* LATIN SMALL LIGATURE OE */ +{ XK_Ydiaeresis, 0x0178 }, /* LATIN CAPITAL LETTER Y WITH DIAERESIS */ +{ XK_EuroSign, 0x20ac }, /* EURO SIGN */ + +/* combining dead keys */ +{ XK_dead_abovedot, 0x0307 }, /* COMBINING DOT ABOVE */ +{ XK_dead_abovering, 0x030a }, /* COMBINING RING ABOVE */ +{ XK_dead_acute, 0x0301 }, /* COMBINING ACUTE ACCENT */ +{ XK_dead_breve, 0x0306 }, /* COMBINING BREVE */ +{ XK_dead_caron, 0x030c }, /* COMBINING CARON */ +{ XK_dead_cedilla, 0x0327 }, /* COMBINING CEDILLA */ +{ XK_dead_circumflex, 0x0302 }, /* COMBINING CIRCUMFLEX ACCENT */ +{ XK_dead_diaeresis, 0x0308 }, /* COMBINING DIAERESIS */ +{ XK_dead_doubleacute, 0x030b }, /* COMBINING DOUBLE ACUTE ACCENT */ +{ XK_dead_grave, 0x0300 }, /* COMBINING GRAVE ACCENT */ +{ XK_dead_macron, 0x0304 }, /* COMBINING MACRON */ +{ XK_dead_ogonek, 0x0328 }, /* COMBINING OGONEK */ +{ XK_dead_tilde, 0x0303 } /* COMBINING TILDE */ +}; +/* XXX -- map these too +XK_Cyrillic_GHE_bar +XK_Cyrillic_ZHE_descender +XK_Cyrillic_KA_descender +XK_Cyrillic_KA_vertstroke +XK_Cyrillic_EN_descender +XK_Cyrillic_U_straight +XK_Cyrillic_U_straight_bar +XK_Cyrillic_HA_descender +XK_Cyrillic_CHE_descender +XK_Cyrillic_CHE_vertstroke +XK_Cyrillic_SHHA +XK_Cyrillic_SCHWA +XK_Cyrillic_I_macron +XK_Cyrillic_O_bar +XK_Cyrillic_U_macron +XK_Cyrillic_ghe_bar +XK_Cyrillic_zhe_descender +XK_Cyrillic_ka_descender +XK_Cyrillic_ka_vertstroke +XK_Cyrillic_en_descender +XK_Cyrillic_u_straight +XK_Cyrillic_u_straight_bar +XK_Cyrillic_ha_descender +XK_Cyrillic_che_descender +XK_Cyrillic_che_vertstroke +XK_Cyrillic_shha +XK_Cyrillic_schwa +XK_Cyrillic_i_macron +XK_Cyrillic_o_bar +XK_Cyrillic_u_macron + +XK_Armenian_eternity +XK_Armenian_ligature_ew +XK_Armenian_full_stop +XK_Armenian_verjaket +XK_Armenian_parenright +XK_Armenian_parenleft +XK_Armenian_guillemotright +XK_Armenian_guillemotleft +XK_Armenian_em_dash +XK_Armenian_dot +XK_Armenian_mijaket +XK_Armenian_but +XK_Armenian_separation_mark +XK_Armenian_comma +XK_Armenian_en_dash +XK_Armenian_hyphen +XK_Armenian_yentamna +XK_Armenian_ellipsis +XK_Armenian_amanak +XK_Armenian_exclam +XK_Armenian_accent +XK_Armenian_shesht +XK_Armenian_paruyk +XK_Armenian_question +XK_Armenian_AYB +XK_Armenian_ayb +XK_Armenian_BEN +XK_Armenian_ben +XK_Armenian_GIM +XK_Armenian_gim +XK_Armenian_DA +XK_Armenian_da +XK_Armenian_YECH +XK_Armenian_yech +XK_Armenian_ZA +XK_Armenian_za +XK_Armenian_E +XK_Armenian_e +XK_Armenian_AT +XK_Armenian_at +XK_Armenian_TO +XK_Armenian_to +XK_Armenian_ZHE +XK_Armenian_zhe +XK_Armenian_INI +XK_Armenian_ini +XK_Armenian_LYUN +XK_Armenian_lyun +XK_Armenian_KHE +XK_Armenian_khe +XK_Armenian_TSA +XK_Armenian_tsa +XK_Armenian_KEN +XK_Armenian_ken +XK_Armenian_HO +XK_Armenian_ho +XK_Armenian_DZA +XK_Armenian_dza +XK_Armenian_GHAT +XK_Armenian_ghat +XK_Armenian_TCHE +XK_Armenian_tche +XK_Armenian_MEN +XK_Armenian_men +XK_Armenian_HI +XK_Armenian_hi +XK_Armenian_NU +XK_Armenian_nu +XK_Armenian_SHA +XK_Armenian_sha +XK_Armenian_VO +XK_Armenian_vo +XK_Armenian_CHA +XK_Armenian_cha +XK_Armenian_PE +XK_Armenian_pe +XK_Armenian_JE +XK_Armenian_je +XK_Armenian_RA +XK_Armenian_ra +XK_Armenian_SE +XK_Armenian_se +XK_Armenian_VEV +XK_Armenian_vev +XK_Armenian_TYUN +XK_Armenian_tyun +XK_Armenian_RE +XK_Armenian_re +XK_Armenian_TSO +XK_Armenian_tso +XK_Armenian_VYUN +XK_Armenian_vyun +XK_Armenian_PYUR +XK_Armenian_pyur +XK_Armenian_KE +XK_Armenian_ke +XK_Armenian_O +XK_Armenian_o +XK_Armenian_FE +XK_Armenian_fe +XK_Armenian_apostrophe +XK_Armenian_section_sign + +XK_Georgian_an +XK_Georgian_ban +XK_Georgian_gan +XK_Georgian_don +XK_Georgian_en +XK_Georgian_vin +XK_Georgian_zen +XK_Georgian_tan +XK_Georgian_in +XK_Georgian_kan +XK_Georgian_las +XK_Georgian_man +XK_Georgian_nar +XK_Georgian_on +XK_Georgian_par +XK_Georgian_zhar +XK_Georgian_rae +XK_Georgian_san +XK_Georgian_tar +XK_Georgian_un +XK_Georgian_phar +XK_Georgian_khar +XK_Georgian_ghan +XK_Georgian_qar +XK_Georgian_shin +XK_Georgian_chin +XK_Georgian_can +XK_Georgian_jil +XK_Georgian_cil +XK_Georgian_char +XK_Georgian_xan +XK_Georgian_jhan +XK_Georgian_hae +XK_Georgian_he +XK_Georgian_hie +XK_Georgian_we +XK_Georgian_har +XK_Georgian_hoe +XK_Georgian_fi + +XK_Ccedillaabovedot +XK_Xabovedot +XK_Qabovedot +XK_Ibreve +XK_IE +XK_UO +XK_Zstroke +XK_Gcaron +XK_Obarred +XK_ccedillaabovedot +XK_xabovedot +XK_Ocaron +XK_qabovedot +XK_ibreve +XK_ie +XK_uo +XK_zstroke +XK_gcaron +XK_ocaron +XK_obarred +XK_SCHWA +XK_Lbelowdot +XK_Lstrokebelowdot +XK_Gtilde +XK_lbelowdot +XK_lstrokebelowdot +XK_gtilde +XK_schwa + +XK_Abelowdot +XK_abelowdot +XK_Ahook +XK_ahook +XK_Acircumflexacute +XK_acircumflexacute +XK_Acircumflexgrave +XK_acircumflexgrave +XK_Acircumflexhook +XK_acircumflexhook +XK_Acircumflextilde +XK_acircumflextilde +XK_Acircumflexbelowdot +XK_acircumflexbelowdot +XK_Abreveacute +XK_abreveacute +XK_Abrevegrave +XK_abrevegrave +XK_Abrevehook +XK_abrevehook +XK_Abrevetilde +XK_abrevetilde +XK_Abrevebelowdot +XK_abrevebelowdot +XK_Ebelowdot +XK_ebelowdot +XK_Ehook +XK_ehook +XK_Etilde +XK_etilde +XK_Ecircumflexacute +XK_ecircumflexacute +XK_Ecircumflexgrave +XK_ecircumflexgrave +XK_Ecircumflexhook +XK_ecircumflexhook +XK_Ecircumflextilde +XK_ecircumflextilde +XK_Ecircumflexbelowdot +XK_ecircumflexbelowdot +XK_Ihook +XK_ihook +XK_Ibelowdot +XK_ibelowdot +XK_Obelowdot +XK_obelowdot +XK_Ohook +XK_ohook +XK_Ocircumflexacute +XK_ocircumflexacute +XK_Ocircumflexgrave +XK_ocircumflexgrave +XK_Ocircumflexhook +XK_ocircumflexhook +XK_Ocircumflextilde +XK_ocircumflextilde +XK_Ocircumflexbelowdot +XK_ocircumflexbelowdot +XK_Ohornacute +XK_ohornacute +XK_Ohorngrave +XK_ohorngrave +XK_Ohornhook +XK_ohornhook +XK_Ohorntilde +XK_ohorntilde +XK_Ohornbelowdot +XK_ohornbelowdot +XK_Ubelowdot +XK_ubelowdot +XK_Uhook +XK_uhook +XK_Uhornacute +XK_uhornacute +XK_Uhorngrave +XK_uhorngrave +XK_Uhornhook +XK_uhornhook +XK_Uhorntilde +XK_uhorntilde +XK_Uhornbelowdot +XK_uhornbelowdot +XK_Ybelowdot +XK_ybelowdot +XK_Yhook +XK_yhook +XK_Ytilde +XK_ytilde +XK_Ohorn +XK_ohorn +XK_Uhorn +XK_uhorn +*/ + +// map "Internet" keys to KeyIDs +static const KeySym s_map1008FF[] = +{ + /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x10 */ 0, kKeyAudioDown, kKeyAudioMute, kKeyAudioUp, + /* 0x14 */ kKeyAudioPlay, kKeyAudioStop, kKeyAudioPrev, kKeyAudioNext, + /* 0x18 */ kKeyWWWHome, kKeyAppMail, 0, kKeyWWWSearch, 0, 0, 0, 0, + /* 0x20 */ 0, 0, 0, 0, 0, 0, kKeyWWWBack, kKeyWWWForward, + /* 0x28 */ kKeyWWWStop, kKeyWWWRefresh, 0, 0, kKeyEject, 0, 0, 0, + /* 0x30 */ kKeyWWWFavorites, 0, kKeyAppMedia, 0, 0, 0, 0, 0, + /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x40 */ kKeyAppUser1, kKeyAppUser2, 0, 0, 0, 0, 0, 0, + /* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x50 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x60 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x68 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x78 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x88 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x98 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xa8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xb0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xb8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xc0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xc8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xd0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xe0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xe8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, 0 +}; + + +// +// CXWindowsUtil +// + +CXWindowsUtil::CKeySymMap CXWindowsUtil::s_keySymToUCS4; + +bool +CXWindowsUtil::getWindowProperty(Display* display, Window window, + Atom property, CString* data, Atom* type, + SInt32* format, bool deleteProperty) +{ + assert(display != NULL); + + Atom actualType; + int actualDatumSize; + + // ignore errors. XGetWindowProperty() will report failure. + CXWindowsUtil::CErrorLock lock(display); + + // read the property + bool okay = true; + const long length = XMaxRequestSize(display); + long offset = 0; + unsigned long bytesLeft = 1; + while (bytesLeft != 0) { + // get more data + unsigned long numItems; + unsigned char* rawData; + if (XGetWindowProperty(display, window, property, + offset, length, False, AnyPropertyType, + &actualType, &actualDatumSize, + &numItems, &bytesLeft, &rawData) != Success || + actualType == None || actualDatumSize == 0) { + // failed + okay = false; + break; + } + + // compute bytes read and advance offset + unsigned long numBytes; + switch (actualDatumSize) { + case 8: + default: + numBytes = numItems; + offset += numItems / 4; + break; + + case 16: + numBytes = 2 * numItems; + offset += numItems / 2; + break; + + case 32: + numBytes = 4 * numItems; + offset += numItems; + break; + } + + // append data + if (data != NULL) { + data->append((char*)rawData, numBytes); + } + else { + // data is not required so don't try to get any more + bytesLeft = 0; + } + + // done with returned data + XFree(rawData); + } + + // delete the property if requested + if (deleteProperty) { + XDeleteProperty(display, window, property); + } + + // save property info + if (type != NULL) { + *type = actualType; + } + if (format != NULL) { + *format = static_cast(actualDatumSize); + } + + if (okay) { + LOG((CLOG_DEBUG2 "read property %d on window 0x%08x: bytes=%d", property, window, (data == NULL) ? 0 : data->size())); + return true; + } + else { + LOG((CLOG_DEBUG2 "can't read property %d on window 0x%08x", property, window)); + return false; + } +} + +bool +CXWindowsUtil::setWindowProperty(Display* display, Window window, + Atom property, const void* vdata, UInt32 size, + Atom type, SInt32 format) +{ + const UInt32 length = 4 * XMaxRequestSize(display); + const unsigned char* data = reinterpret_cast(vdata); + const UInt32 datumSize = static_cast(format / 8); + + // save errors + bool error = false; + CXWindowsUtil::CErrorLock lock(display, &error); + + // how much data to send in first chunk? + UInt32 chunkSize = size; + if (chunkSize > length) { + chunkSize = length; + } + + // send first chunk + XChangeProperty(display, window, property, + type, format, PropModeReplace, + data, chunkSize / datumSize); + + // append remaining chunks + data += chunkSize; + size -= chunkSize; + while (!error && size > 0) { + chunkSize = size; + if (chunkSize > length) { + chunkSize = length; + } + XChangeProperty(display, window, property, + type, format, PropModeAppend, + data, chunkSize / datumSize); + data += chunkSize; + size -= chunkSize; + } + + return !error; +} + +Time +CXWindowsUtil::getCurrentTime(Display* display, Window window) +{ + // select property events on window + XWindowAttributes attr; + XGetWindowAttributes(display, window, &attr); + XSelectInput(display, window, attr.your_event_mask | PropertyChangeMask); + + // make a property name to receive dummy change + Atom atom = XInternAtom(display, "TIMESTAMP", False); + + // do a zero-length append to get the current time + unsigned char dummy; + XChangeProperty(display, window, atom, + XA_INTEGER, 8, + PropModeAppend, + &dummy, 0); + + // look for property notify events with the following + CPropertyNotifyPredicateInfo filter; + filter.m_window = window; + filter.m_property = atom; + + // wait for reply + XEvent xevent; + XIfEvent(display, &xevent, &CXWindowsUtil::propertyNotifyPredicate, + (XPointer)&filter); + assert(xevent.type == PropertyNotify); + assert(xevent.xproperty.window == window); + assert(xevent.xproperty.atom == atom); + + // restore event mask + XSelectInput(display, window, attr.your_event_mask); + + return xevent.xproperty.time; +} + +KeyID +CXWindowsUtil::mapKeySymToKeyID(KeySym k) +{ + initKeyMaps(); + + switch (k & 0xffffff00) { + case 0x0000: + // Latin-1 + return static_cast(k); + + case 0xfe00: + // ISO 9995 Function and Modifier Keys + switch (k) { + case XK_ISO_Left_Tab: + return kKeyLeftTab; + + case XK_ISO_Level3_Shift: + return kKeyAltGr; + + case XK_ISO_Next_Group: + return kKeyNextGroup; + + case XK_ISO_Prev_Group: + return kKeyPrevGroup; + + case XK_dead_grave: + return kKeyDeadGrave; + + case XK_dead_acute: + return kKeyDeadAcute; + + case XK_dead_circumflex: + return kKeyDeadCircumflex; + + case XK_dead_tilde: + return kKeyDeadTilde; + + case XK_dead_macron: + return kKeyDeadMacron; + + case XK_dead_breve: + return kKeyDeadBreve; + + case XK_dead_abovedot: + return kKeyDeadAbovedot; + + case XK_dead_diaeresis: + return kKeyDeadDiaeresis; + + case XK_dead_abovering: + return kKeyDeadAbovering; + + case XK_dead_doubleacute: + return kKeyDeadDoubleacute; + + case XK_dead_caron: + return kKeyDeadCaron; + + case XK_dead_cedilla: + return kKeyDeadCedilla; + + case XK_dead_ogonek: + return kKeyDeadOgonek; + + default: + return kKeyNone; + } + + case 0xff00: + // MISCELLANY + return static_cast(k - 0xff00 + 0xef00); + + case 0x1008ff00: + // "Internet" keys + return s_map1008FF[k & 0xff]; + + default: { + // lookup character in table + CKeySymMap::const_iterator index = s_keySymToUCS4.find(k); + if (index != s_keySymToUCS4.end()) { + return static_cast(index->second); + } + + // unknown character + return kKeyNone; + } + } +} + +UInt32 +CXWindowsUtil::getModifierBitForKeySym(KeySym keysym) +{ + switch (keysym) { + case XK_Shift_L: + case XK_Shift_R: + return kKeyModifierBitShift; + + case XK_Control_L: + case XK_Control_R: + return kKeyModifierBitControl; + + case XK_Alt_L: + case XK_Alt_R: + return kKeyModifierBitAlt; + + case XK_Meta_L: + case XK_Meta_R: + return kKeyModifierBitMeta; + + case XK_Super_L: + case XK_Super_R: + case XK_Hyper_L: + case XK_Hyper_R: + return kKeyModifierBitSuper; + + case XK_Mode_switch: + case XK_ISO_Level3_Shift: + return kKeyModifierBitAltGr; + + case XK_Caps_Lock: + return kKeyModifierBitCapsLock; + + case XK_Num_Lock: + return kKeyModifierBitNumLock; + + case XK_Scroll_Lock: + return kKeyModifierBitScrollLock; + + default: + return kKeyModifierBitNone; + } +} + +CString +CXWindowsUtil::atomToString(Display* display, Atom atom) +{ + if (atom == 0) { + return "None"; + } + + bool error = false; + CXWindowsUtil::CErrorLock lock(display, &error); + char* name = XGetAtomName(display, atom); + if (error) { + return CStringUtil::print(" (%d)", (int)atom); + } + else { + CString msg = CStringUtil::print("%s (%d)", name, (int)atom); + XFree(name); + return msg; + } +} + +CString +CXWindowsUtil::atomsToString(Display* display, const Atom* atom, UInt32 num) +{ + char** names = new char*[num]; + bool error = false; + CXWindowsUtil::CErrorLock lock(display, &error); + XGetAtomNames(display, const_cast(atom), (int)num, names); + CString msg; + if (error) { + for (UInt32 i = 0; i < num; ++i) { + msg += CStringUtil::print(" (%d), ", (int)atom[i]); + } + } + else { + for (UInt32 i = 0; i < num; ++i) { + msg += CStringUtil::print("%s (%d), ", names[i], (int)atom[i]); + XFree(names[i]); + } + } + delete[] names; + if (msg.size() > 2) { + msg.erase(msg.size() - 2); + } + return msg; +} + +void +CXWindowsUtil::convertAtomProperty(CString& data) +{ + // as best i can tell, 64-bit systems don't pack Atoms into properties + // as 32-bit numbers but rather as the 64-bit numbers they are. that + // seems wrong but we have to cope. sometimes we'll get a list of + // atoms that's 8*n+4 bytes long, missing the trailing 4 bytes which + // should all be 0. since we're going to reference the Atoms as + // 64-bit numbers we have to ensure the last number is a full 64 bits. + if (sizeof(Atom) != 4 && ((data.size() / 4) & 1) != 0) { + UInt32 zero = 0; + data.append(reinterpret_cast(&zero), sizeof(zero)); + } +} + +void +CXWindowsUtil::appendAtomData(CString& data, Atom atom) +{ + data.append(reinterpret_cast(&atom), sizeof(Atom)); +} + +void +CXWindowsUtil::replaceAtomData(CString& data, UInt32 index, Atom atom) +{ + data.replace(index * sizeof(Atom), sizeof(Atom), + reinterpret_cast(&atom), + sizeof(Atom)); +} + +void +CXWindowsUtil::appendTimeData(CString& data, Time time) +{ + data.append(reinterpret_cast(&time), sizeof(Time)); +} + +Bool +CXWindowsUtil::propertyNotifyPredicate(Display*, XEvent* xevent, XPointer arg) +{ + CPropertyNotifyPredicateInfo* filter = + reinterpret_cast(arg); + return (xevent->type == PropertyNotify && + xevent->xproperty.window == filter->m_window && + xevent->xproperty.atom == filter->m_property && + xevent->xproperty.state == PropertyNewValue) ? True : False; +} + +void +CXWindowsUtil::initKeyMaps() +{ + if (s_keySymToUCS4.empty()) { + for (size_t i =0; i < sizeof(s_keymap) / sizeof(s_keymap[0]); ++i) { + s_keySymToUCS4[s_keymap[i].keysym] = s_keymap[i].ucs4; + } + } +} + + +// +// CXWindowsUtil::CErrorLock +// + +CXWindowsUtil::CErrorLock* CXWindowsUtil::CErrorLock::s_top = NULL; + +CXWindowsUtil::CErrorLock::CErrorLock(Display* display) : + m_display(display) +{ + install(&CXWindowsUtil::CErrorLock::ignoreHandler, NULL); +} + +CXWindowsUtil::CErrorLock::CErrorLock(Display* display, bool* flag) : + m_display(display) +{ + install(&CXWindowsUtil::CErrorLock::saveHandler, flag); +} + +CXWindowsUtil::CErrorLock::CErrorLock(Display* display, + ErrorHandler handler, void* data) : + m_display(display) +{ + install(handler, data); +} + +CXWindowsUtil::CErrorLock::~CErrorLock() +{ + // make sure everything finishes before uninstalling handler + if (m_display != NULL) { + XSync(m_display, False); + } + + // restore old handler + XSetErrorHandler(m_oldXHandler); + s_top = m_next; +} + +void +CXWindowsUtil::CErrorLock::install(ErrorHandler handler, void* data) +{ + // make sure everything finishes before installing handler + if (m_display != NULL) { + XSync(m_display, False); + } + + // install handler + m_handler = handler; + m_userData = data; + m_oldXHandler = XSetErrorHandler( + &CXWindowsUtil::CErrorLock::internalHandler); + m_next = s_top; + s_top = this; +} + +int +CXWindowsUtil::CErrorLock::internalHandler(Display* display, XErrorEvent* event) +{ + if (s_top != NULL && s_top->m_handler != NULL) { + s_top->m_handler(display, event, s_top->m_userData); + } + return 0; +} + +void +CXWindowsUtil::CErrorLock::ignoreHandler(Display*, XErrorEvent* e, void*) +{ + LOG((CLOG_DEBUG1 "ignoring X error: %d", e->error_code)); +} + +void +CXWindowsUtil::CErrorLock::saveHandler(Display*, XErrorEvent* e, void* flag) +{ + LOG((CLOG_DEBUG1 "flagging X error: %d", e->error_code)); + *reinterpret_cast(flag) = true; +} diff --git a/lib/platform/CXWindowsUtil.h b/lib/platform/CXWindowsUtil.h new file mode 100644 index 00000000..a9049ef6 --- /dev/null +++ b/lib/platform/CXWindowsUtil.h @@ -0,0 +1,185 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSUTIL_H +#define CXWINDOWSUTIL_H + +#include "CString.h" +#include "BasicTypes.h" +#include "stdmap.h" +#include "stdvector.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +#endif + +//! X11 utility functions +class CXWindowsUtil { +public: + typedef std::vector KeySyms; + + //! Get property + /*! + Gets property \c property on \c window. \b Appends the data to + \c *data if \c data is not NULL, saves the property type in \c *type + if \c type is not NULL, and saves the property format in \c *format + if \c format is not NULL. If \c deleteProperty is true then the + property is deleted after being read. + */ + static bool getWindowProperty(Display*, + Window window, Atom property, + CString* data, Atom* type, + SInt32* format, bool deleteProperty); + + //! Set property + /*! + Sets property \c property on \c window to \c size bytes of data from + \c data. + */ + static bool setWindowProperty(Display*, + Window window, Atom property, + const void* data, UInt32 size, + Atom type, SInt32 format); + + //! Get X server time + /*! + Returns the current X server time. + */ + static Time getCurrentTime(Display*, Window); + + //! Convert KeySym to KeyID + /*! + Converts a KeySym to the equivalent KeyID. Returns kKeyNone if the + KeySym cannot be mapped. + */ + static UInt32 mapKeySymToKeyID(KeySym); + + //! Convert KeySym to corresponding KeyModifierMask + /*! + Converts a KeySym to the corresponding KeyModifierMask, or 0 if the + KeySym is not a modifier. + */ + static UInt32 getModifierBitForKeySym(KeySym keysym); + + //! Convert Atom to its string + /*! + Converts \p atom to its string representation. + */ + static CString atomToString(Display*, Atom atom); + + //! Convert several Atoms to a string + /*! + Converts each atom in \p atoms to its string representation and + concatenates the results. + */ + static CString atomsToString(Display* display, + const Atom* atom, UInt32 num); + + //! Prepare a property of atoms for use + /*! + 64-bit systems may need to modify a property's data if it's a + list of Atoms before using it. + */ + static void convertAtomProperty(CString& data); + + //! Append an Atom to property data + /*! + Converts \p atom to a 32-bit on-the-wire format and appends it to + \p data. + */ + static void appendAtomData(CString& data, Atom atom); + + //! Replace an Atom in property data + /*! + Converts \p atom to a 32-bit on-the-wire format and replaces the atom + at index \p index in \p data. + */ + static void replaceAtomData(CString& data, + UInt32 index, Atom atom); + + //! Append an Time to property data + /*! + Converts \p time to a 32-bit on-the-wire format and appends it to + \p data. + */ + static void appendTimeData(CString& data, Time time); + + //! X11 error handler + /*! + This class sets an X error handler in the c'tor and restores the + previous error handler in the d'tor. A lock should only be + installed while the display is locked by the thread. + + CErrorLock() ignores errors + CErrorLock(bool* flag) sets *flag to true if any error occurs + */ + class CErrorLock { + public: + //! Error handler type + typedef void (*ErrorHandler)(Display*, XErrorEvent*, void* userData); + + /*! + Ignore X11 errors. + */ + CErrorLock(Display*); + + /*! + Set \c *errorFlag if any error occurs. + */ + CErrorLock(Display*, bool* errorFlag); + + /*! + Call \c handler on each error. + */ + CErrorLock(Display*, ErrorHandler handler, void* userData); + + ~CErrorLock(); + + private: + void install(ErrorHandler, void*); + static int internalHandler(Display*, XErrorEvent*); + static void ignoreHandler(Display*, XErrorEvent*, void*); + static void saveHandler(Display*, XErrorEvent*, void*); + + private: + typedef int (*XErrorHandler)(Display*, XErrorEvent*); + + Display* m_display; + ErrorHandler m_handler; + void* m_userData; + XErrorHandler m_oldXHandler; + CErrorLock* m_next; + static CErrorLock* s_top; + }; + +private: + class CPropertyNotifyPredicateInfo { + public: + Window m_window; + Atom m_property; + }; + + static Bool propertyNotifyPredicate(Display*, + XEvent* xevent, XPointer arg); + + static void initKeyMaps(); + +private: + typedef std::map CKeySymMap; + + static CKeySymMap s_keySymToUCS4; +}; + +#endif diff --git a/lib/platform/Makefile.am b/lib/platform/Makefile.am new file mode 100644 index 00000000..6ff0523c --- /dev/null +++ b/lib/platform/Makefile.am @@ -0,0 +1,123 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +XWINDOWS_SOURCE_FILES = \ + CXWindowsClipboard.cpp \ + CXWindowsClipboardAnyBitmapConverter.cpp\ + CXWindowsClipboardBMPConverter.cpp \ + CXWindowsClipboardHTMLConverter.cpp \ + CXWindowsClipboardTextConverter.cpp \ + CXWindowsClipboardUCS2Converter.cpp \ + CXWindowsClipboardUTF8Converter.cpp \ + CXWindowsEventQueueBuffer.cpp \ + CXWindowsKeyState.cpp \ + CXWindowsScreen.cpp \ + CXWindowsScreenSaver.cpp \ + CXWindowsUtil.cpp \ + CXWindowsClipboard.h \ + CXWindowsClipboardAnyBitmapConverter.h \ + CXWindowsClipboardBMPConverter.h \ + CXWindowsClipboardHTMLConverter.h \ + CXWindowsClipboardTextConverter.h \ + CXWindowsClipboardUCS2Converter.h \ + CXWindowsClipboardUTF8Converter.h \ + CXWindowsEventQueueBuffer.h \ + CXWindowsKeyState.h \ + CXWindowsScreen.h \ + CXWindowsScreenSaver.h \ + CXWindowsUtil.h \ + $(NULL) +MSWINDOWS_SOURCE_FILES = \ + CMSWindowsClipboard.cpp \ + CMSWindowsClipboardAnyTextConverter.cpp \ + CMSWindowsClipboardBitmapConverter.cpp \ + CMSWindowsClipboardHTMLConverter.cpp \ + CMSWindowsClipboardTextConverter.cpp \ + CMSWindowsClipboardUTF16Converter.cpp \ + CMSWindowsDesks.cpp \ + CMSWindowsEventQueueBuffer.cpp \ + CMSWindowsKeyState.cpp \ + CMSWindowsScreen.cpp \ + CMSWindowsScreenSaver.cpp \ + CMSWindowsUtil.cpp \ + CMSWindowsClipboard.h \ + CMSWindowsClipboardAnyTextConverter.h \ + CMSWindowsClipboardBitmapConverter.h \ + CMSWindowsClipboardHTMLConverter.h \ + CMSWindowsClipboardTextConverter.h \ + CMSWindowsClipboardUTF16Converter.h \ + CMSWindowsDesks.h \ + CMSWindowsEventQueueBuffer.h \ + CMSWindowsKeyState.h \ + CMSWindowsScreen.h \ + CMSWindowsScreenSaver.h \ + CMSWindowsUtil.h \ + $(NULL) +MSWINDOWS_HOOK_SOURCE_FILES = \ + CSynergyHook.cpp \ + CSynergyHook.h \ + $(NULL) +CARBON_SOURCE_FILES = \ + COSXClipboard.cpp \ + COSXClipboardAnyTextConverter.cpp \ + COSXClipboardTextConverter.cpp \ + COSXClipboardUTF16Converter.cpp \ + COSXEventQueueBuffer.cpp \ + COSXKeyState.cpp \ + COSXScreen.cpp \ + COSXScreenSaver.cpp \ + COSXScreenSaverUtil.m \ + COSXClipboard.h \ + COSXClipboardAnyTextConverter.h \ + COSXClipboardTextConverter.h \ + COSXClipboardUTF16Converter.h \ + COSXEventQueueBuffer.h \ + COSXKeyState.h \ + COSXScreen.h \ + COSXScreenSaver.h \ + COSXScreenSaverUtil.h \ + OSXScreenSaverControl.h \ + $(NULL) + +EXTRA_DIST = \ + Makefile.win \ + $(XWINDOWS_SOURCE_FILES) \ + $(MSWINDOWS_SOURCE_FILES) \ + $(MSWINDOWS_HOOK_SOURCE_FILES) \ + $(CARBON_SOURCE_FILES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libplatform.a +if XWINDOWS +libplatform_a_SOURCES = $(XWINDOWS_SOURCE_FILES) +endif +if MSWINDOWS +libplatform_a_SOURCES = $(MSWINDOWS_SOURCE_FILES) +endif +if CARBON +libplatform_a_SOURCES = $(CARBON_SOURCE_FILES) +endif +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/synergy \ + $(NULL) diff --git a/lib/platform/Makefile.win b/lib/platform/Makefile.win new file mode 100644 index 00000000..001200d1 --- /dev/null +++ b/lib/platform/Makefile.win @@ -0,0 +1,119 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +LIB_PLATFORM_SRC = lib\platform +LIB_PLATFORM_DST = $(BUILD_DST)\$(LIB_PLATFORM_SRC) +LIB_PLATFORM_LIB = "$(LIB_PLATFORM_DST)\platform.lib" +LIB_PLATFORM_CPP = \ + "CMSWindowsClipboard.cpp" \ + "CMSWindowsClipboardAnyTextConverter.cpp" \ + "CMSWindowsClipboardBitmapConverter.cpp" \ + "CMSWindowsClipboardHTMLConverter.cpp" \ + "CMSWindowsClipboardTextConverter.cpp" \ + "CMSWindowsClipboardUTF16Converter.cpp" \ + "CMSWindowsDesks.cpp" \ + "CMSWindowsEventQueueBuffer.cpp" \ + "CMSWindowsKeyState.cpp" \ + "CMSWindowsScreen.cpp" \ + "CMSWindowsScreenSaver.cpp" \ + "CMSWindowsUtil.cpp" \ + $(NULL) +LIB_PLATFORM_OBJ = \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboard.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboardAnyTextConverter.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboardBitmapConverter.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboardHTMLConverter.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboardTextConverter.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboardUTF16Converter.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsDesks.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsEventQueueBuffer.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsKeyState.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsScreen.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsScreenSaver.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsUtil.obj" \ + $(NULL) +LIB_PLATFORM_HOOK_CPP = \ + "$(LIB_PLATFORM_SRC)\CSynergyHook.cpp" \ + $(NULL) +LIB_PLATFORM_HOOK_OBJ = \ + "$(LIB_PLATFORM_DST)\CSynergyHook.obj" \ + $(NULL) +LIB_PLATFORM_HOOK_DLL = "$(BUILD_DST)\synrgyhk.dll" +LIB_PLATFORM_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_PLATFORM_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_PLATFORM_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_PLATFORM_LIB) $(LIB_PLATFORM_HOOK_DLL) + +# Hook should be as small as possible. +cpphookdebug = $(cppdebug:-Ox=-O1) + +# Don't do security checks or run time error checking on hook. +cpphookflags = $(cppflags:-GS=) +cpphookdebug = $(cpphookdebug:/GZ=) +cpphookdebug = $(cpphookdebug:/RTC1=) + +# Dependency rules +$(LIB_PLATFORM_OBJ): $(AUTODEP) +!if EXIST($(LIB_PLATFORM_DST)\deps.mak) +!include $(LIB_PLATFORM_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_PLATFORM_SRC)\}.cpp{$(LIB_PLATFORM_DST)\}.obj:: +!else +{$(LIB_PLATFORM_SRC)\}.cpp{$(LIB_PLATFORM_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_PLATFORM_SRC) + -@$(MKDIR) $(LIB_PLATFORM_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_PLATFORM_INC) \ + /Fo$(LIB_PLATFORM_DST)\ \ + /Fd$(LIB_PLATFORM_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_PLATFORM_SRC) $(LIB_PLATFORM_DST) +$(LIB_PLATFORM_LIB): $(LIB_PLATFORM_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_PLATFORM_SRC) $(LIB_PLATFORM_DST) \ + $(LIB_PLATFORM_OBJ:.obj=.d) $(LIB_PLATFORM_HOOK_OBJ:.obj=.d) + +# Hook build rules +$(LIB_PLATFORM_HOOK_OBJ): \ + $(LIB_PLATFORM_HOOK_CPP) $(LIB_PLATFORM_HOOK_CPP:.cpp=.h) + @$(ECHO) Compile $(LIB_PLATFORM_HOOK_CPP) + -@$(MKDIR) $(LIB_PLATFORM_DST) 2>NUL: + $(cpp) $(cpphookdebug) $(cpphookflags) $(cppvarsmt) /showIncludes \ + -D_DLL -D_USRDLL -DSYNRGYHK_EXPORTS \ + $(LIB_PLATFORM_INC) \ + /Fo$(LIB_PLATFORM_DST)\ \ + /Fd$(@:.obj=.pdb) \ + $(LIB_PLATFORM_HOOK_CPP) | \ + $(AUTODEP) $(LIB_PLATFORM_SRC) $(LIB_PLATFORM_DST) +$(LIB_PLATFORM_HOOK_DLL): $(LIB_PLATFORM_HOOK_OBJ) + @$(ECHO) Link $(@F) + $(link) $(ldebug) $(lflags) $(guilibsmt) \ + /entry:"DllMain$(DLLENTRY)" /dll \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_PLATFORM_SRC) $(LIB_PLATFORM_DST) \ + $(LIB_PLATFORM_OBJ:.obj=.d) $(LIB_PLATFORM_HOOK_OBJ:.obj=.d) diff --git a/lib/platform/OSXScreenSaverControl.h b/lib/platform/OSXScreenSaverControl.h new file mode 100644 index 00000000..75aecb17 --- /dev/null +++ b/lib/platform/OSXScreenSaverControl.h @@ -0,0 +1,36 @@ +// ScreenSaver.framework private API +// Class dumping by Alex Harper http://www.ragingmenace.com/ + +#import + +@protocol ScreenSaverControl +- (double)screenSaverTimeRemaining; +- (void)restartForUser:fp12; +- (void)screenSaverStopNow; +- (void)screenSaverStartNow; +- (void)setScreenSaverCanRun:(char)fp12; +- (BOOL)screenSaverCanRun; +- (BOOL)screenSaverIsRunning; +@end + + +@interface ScreenSaverController:NSObject + ++ controller; ++ monitor; ++ daemonConnectionName; ++ daemonPath; ++ enginePath; +- init; +- (void)dealloc; +- (void)_connectionClosed:fp12; +- (BOOL)screenSaverIsRunning; +- (BOOL)screenSaverCanRun; +- (void)setScreenSaverCanRun:(char)fp12; +- (void)screenSaverStartNow; +- (void)screenSaverStopNow; +- (void)restartForUser:fp12; +- (double)screenSaverTimeRemaining; + +@end + diff --git a/lib/server/CBaseClientProxy.cpp b/lib/server/CBaseClientProxy.cpp new file mode 100644 index 00000000..0f049e35 --- /dev/null +++ b/lib/server/CBaseClientProxy.cpp @@ -0,0 +1,52 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 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 "CBaseClientProxy.h" + +// +// CBaseClientProxy +// + +CBaseClientProxy::CBaseClientProxy(const CString& name) : + m_name(name), + m_x(0), + m_y(0) +{ + // do nothing +} + +CBaseClientProxy::~CBaseClientProxy() +{ + // do nothing +} + +void +CBaseClientProxy::setJumpCursorPos(SInt32 x, SInt32 y) +{ + m_x = x; + m_y = y; +} + +void +CBaseClientProxy::getJumpCursorPos(SInt32& x, SInt32& y) const +{ + x = m_x; + y = m_y; +} + +CString +CBaseClientProxy::getName() const +{ + return m_name; +} diff --git a/lib/server/CBaseClientProxy.h b/lib/server/CBaseClientProxy.h new file mode 100644 index 00000000..e9cceca4 --- /dev/null +++ b/lib/server/CBaseClientProxy.h @@ -0,0 +1,85 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CBASECLIENTPROXY_H +#define CBASECLIENTPROXY_H + +#include "IClient.h" +#include "CString.h" + +//! Generic proxy for client or primary +class CBaseClientProxy : public IClient { +public: + /*! + \c name is the name of the client. + */ + CBaseClientProxy(const CString& name); + ~CBaseClientProxy(); + + //! @name manipulators + //@{ + + //! Save cursor position + /*! + Save the position of the cursor when jumping from client. + */ + void setJumpCursorPos(SInt32 x, SInt32 y); + + //@} + //! @name accessors + //@{ + + //! Get cursor position + /*! + Get the position of the cursor when last jumping from client. + */ + void getJumpCursorPos(SInt32& x, SInt32& y) const; + + //@} + + // IScreen + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver) = 0; + virtual bool leave() = 0; + virtual void setClipboard(ClipboardID, const IClipboard*) = 0; + virtual void grabClipboard(ClipboardID) = 0; + virtual void setClipboardDirty(ClipboardID, bool) = 0; + virtual void keyDown(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton) = 0; + virtual void keyUp(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void mouseDown(ButtonID) = 0; + virtual void mouseUp(ButtonID) = 0; + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0; + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel) = 0; + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta) = 0; + virtual void screensaver(bool activate) = 0; + virtual void resetOptions() = 0; + virtual void setOptions(const COptionsList& options) = 0; + virtual CString getName() const; + +private: + CString m_name; + SInt32 m_x, m_y; +}; + +#endif diff --git a/lib/server/CClientListener.cpp b/lib/server/CClientListener.cpp new file mode 100644 index 00000000..803f44a0 --- /dev/null +++ b/lib/server/CClientListener.cpp @@ -0,0 +1,194 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CClientListener.h" +#include "CClientProxy.h" +#include "CClientProxyUnknown.h" +#include "CPacketStreamFilter.h" +#include "IStreamFilterFactory.h" +#include "IDataSocket.h" +#include "IListenSocket.h" +#include "ISocketFactory.h" +#include "XSocket.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CClientListener +// + +CEvent::Type CClientListener::s_connectedEvent = CEvent::kUnknown; + +CClientListener::CClientListener(const CNetworkAddress& address, + ISocketFactory* socketFactory, + IStreamFilterFactory* streamFilterFactory) : + m_socketFactory(socketFactory), + m_streamFilterFactory(streamFilterFactory) +{ + assert(m_socketFactory != NULL); + + try { + // create listen socket + m_listen = m_socketFactory->createListen(); + + // bind listen address + LOG((CLOG_DEBUG1 "binding listen socket")); + m_listen->bind(address); + } + catch (XSocketAddressInUse&) { + delete m_listen; + delete m_socketFactory; + delete m_streamFilterFactory; + throw; + } + catch (XBase&) { + delete m_listen; + delete m_socketFactory; + delete m_streamFilterFactory; + throw; + } + LOG((CLOG_DEBUG1 "listening for clients")); + + // setup event handler + EVENTQUEUE->adoptHandler(IListenSocket::getConnectingEvent(), m_listen, + new TMethodEventJob(this, + &CClientListener::handleClientConnecting)); +} + +CClientListener::~CClientListener() +{ + LOG((CLOG_DEBUG1 "stop listening for clients")); + + // discard already connected clients + for (CNewClients::iterator index = m_newClients.begin(); + index != m_newClients.end(); ++index) { + CClientProxyUnknown* client = *index; + EVENTQUEUE->removeHandler( + CClientProxyUnknown::getSuccessEvent(), client); + EVENTQUEUE->removeHandler( + CClientProxyUnknown::getFailureEvent(), client); + EVENTQUEUE->removeHandler( + CClientProxy::getDisconnectedEvent(), client); + delete client; + } + + // discard waiting clients + CClientProxy* client = getNextClient(); + while (client != NULL) { + delete client; + client = getNextClient(); + } + + EVENTQUEUE->removeHandler(IListenSocket::getConnectingEvent(), m_listen); + delete m_listen; + delete m_socketFactory; + delete m_streamFilterFactory; +} + +CClientProxy* +CClientListener::getNextClient() +{ + CClientProxy* client = NULL; + if (!m_waitingClients.empty()) { + client = m_waitingClients.front(); + m_waitingClients.pop_front(); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + } + return client; +} + +CEvent::Type +CClientListener::getConnectedEvent() +{ + return CEvent::registerTypeOnce(s_connectedEvent, + "CClientListener::connected"); +} + +void +CClientListener::handleClientConnecting(const CEvent&, void*) +{ + // accept client connection + IStream* stream = m_listen->accept(); + if (stream == NULL) { + return; + } + LOG((CLOG_NOTE "accepted client connection")); + + // filter socket messages, including a packetizing filter + if (m_streamFilterFactory != NULL) { + stream = m_streamFilterFactory->create(stream, true); + } + stream = new CPacketStreamFilter(stream, true); + + // create proxy for unknown client + CClientProxyUnknown* client = new CClientProxyUnknown(stream, 30.0); + m_newClients.insert(client); + + // watch for events from unknown client + EVENTQUEUE->adoptHandler(CClientProxyUnknown::getSuccessEvent(), client, + new TMethodEventJob(this, + &CClientListener::handleUnknownClient, client)); + EVENTQUEUE->adoptHandler(CClientProxyUnknown::getFailureEvent(), client, + new TMethodEventJob(this, + &CClientListener::handleUnknownClient, client)); +} + +void +CClientListener::handleUnknownClient(const CEvent&, void* vclient) +{ + CClientProxyUnknown* unknownClient = + reinterpret_cast(vclient); + + // we should have the client in our new client list + assert(m_newClients.count(unknownClient) == 1); + + // get the real client proxy and install it + CClientProxy* client = unknownClient->orphanClientProxy(); + if (client != NULL) { + // handshake was successful + m_waitingClients.push_back(client); + EVENTQUEUE->addEvent(CEvent(getConnectedEvent(), this)); + + // watch for client to disconnect while it's in our queue + EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), client, + new TMethodEventJob(this, + &CClientListener::handleClientDisconnected, + client)); + } + + // now finished with unknown client + EVENTQUEUE->removeHandler(CClientProxyUnknown::getSuccessEvent(), client); + EVENTQUEUE->removeHandler(CClientProxyUnknown::getFailureEvent(), client); + m_newClients.erase(unknownClient); + delete unknownClient; +} + +void +CClientListener::handleClientDisconnected(const CEvent&, void* vclient) +{ + CClientProxy* client = reinterpret_cast(vclient); + + // find client in waiting clients queue + for (CWaitingClients::iterator i = m_waitingClients.begin(), + n = m_waitingClients.end(); i != n; ++i) { + if (*i == client) { + m_waitingClients.erase(i); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), + client); + delete client; + break; + } + } +} diff --git a/lib/server/CClientListener.h b/lib/server/CClientListener.h new file mode 100644 index 00000000..e5302e69 --- /dev/null +++ b/lib/server/CClientListener.h @@ -0,0 +1,76 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTLISTENER_H +#define CCLIENTLISTENER_H + +#include "CConfig.h" +#include "CEvent.h" +#include "stddeque.h" +#include "stdset.h" + +class CClientProxy; +class CClientProxyUnknown; +class CNetworkAddress; +class IListenSocket; +class ISocketFactory; +class IStreamFilterFactory; + +class CClientListener { +public: + // The factories are adopted. + CClientListener(const CNetworkAddress&, + ISocketFactory*, IStreamFilterFactory*); + ~CClientListener(); + + //! @name accessors + //@{ + + //! Get next connected client + /*! + Returns the next connected client and removes it from the internal + list. The client is responsible for deleting the returned client. + Returns NULL if no clients are available. + */ + CClientProxy* getNextClient(); + + //! Get connected event type + /*! + Returns the connected event type. This is sent whenever a + a client connects. + */ + static CEvent::Type getConnectedEvent(); + + //@} + +private: + // client connection event handlers + void handleClientConnecting(const CEvent&, void*); + void handleUnknownClient(const CEvent&, void*); + void handleClientDisconnected(const CEvent&, void*); + +private: + typedef std::set CNewClients; + typedef std::deque CWaitingClients; + + IListenSocket* m_listen; + ISocketFactory* m_socketFactory; + IStreamFilterFactory* m_streamFilterFactory; + CNewClients m_newClients; + CWaitingClients m_waitingClients; + + static CEvent::Type s_connectedEvent; +}; + +#endif diff --git a/lib/server/CClientProxy.cpp b/lib/server/CClientProxy.cpp new file mode 100644 index 00000000..715126d5 --- /dev/null +++ b/lib/server/CClientProxy.cpp @@ -0,0 +1,81 @@ +/* + * 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 "CClientProxy.h" +#include "CProtocolUtil.h" +#include "IStream.h" +#include "CLog.h" + +// +// CClientProxy +// + +CEvent::Type CClientProxy::s_readyEvent = CEvent::kUnknown; +CEvent::Type CClientProxy::s_disconnectedEvent = CEvent::kUnknown; +CEvent::Type CClientProxy::s_clipboardChangedEvent= CEvent::kUnknown; + +CClientProxy::CClientProxy(const CString& name, IStream* stream) : + CBaseClientProxy(name), + m_stream(stream) +{ + // do nothing +} + +CClientProxy::~CClientProxy() +{ + delete m_stream; +} + +void +CClientProxy::close(const char* msg) +{ + LOG((CLOG_DEBUG1 "send close \"%s\" to \"%s\"", msg, getName().c_str())); + CProtocolUtil::writef(getStream(), msg); + + // force the close to be sent before we return + getStream()->flush(); +} + +IStream* +CClientProxy::getStream() const +{ + return m_stream; +} + +CEvent::Type +CClientProxy::getReadyEvent() +{ + return CEvent::registerTypeOnce(s_readyEvent, + "CClientProxy::ready"); +} + +CEvent::Type +CClientProxy::getDisconnectedEvent() +{ + return CEvent::registerTypeOnce(s_disconnectedEvent, + "CClientProxy::disconnected"); +} + +CEvent::Type +CClientProxy::getClipboardChangedEvent() +{ + return CEvent::registerTypeOnce(s_clipboardChangedEvent, + "CClientProxy::clipboardChanged"); +} + +void* +CClientProxy::getEventTarget() const +{ + return static_cast(const_cast(this)); +} diff --git a/lib/server/CClientProxy.h b/lib/server/CClientProxy.h new file mode 100644 index 00000000..51ad014b --- /dev/null +++ b/lib/server/CClientProxy.h @@ -0,0 +1,113 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTPROXY_H +#define CCLIENTPROXY_H + +#include "CBaseClientProxy.h" +#include "CEvent.h" +#include "CString.h" + +class IStream; + +//! Generic proxy for client +class CClientProxy : public CBaseClientProxy { +public: + /*! + \c name is the name of the client. + */ + CClientProxy(const CString& name, IStream* adoptedStream); + ~CClientProxy(); + + //! @name manipulators + //@{ + + //! Disconnect + /*! + Ask the client to disconnect, using \p msg as the reason. + */ + void close(const char* msg); + + //@} + //! @name accessors + //@{ + + //! Get stream + /*! + Returns the stream passed to the c'tor. + */ + IStream* getStream() const; + + //! Get ready event type + /*! + Returns the ready event type. This is sent when the client has + completed the initial handshake. Until it is sent, the client is + not fully connected. + */ + static CEvent::Type getReadyEvent(); + + //! Get disconnect event type + /*! + Returns the disconnect event type. This is sent when the client + disconnects or is disconnected. The target is getEventTarget(). + */ + static CEvent::Type getDisconnectedEvent(); + + //! Get clipboard changed event type + /*! + Returns the clipboard changed event type. This is sent whenever the + contents of the clipboard has changed. The data is a pointer to a + IScreen::CClipboardInfo. + */ + static CEvent::Type getClipboardChangedEvent(); + + //@} + + // IScreen + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver) = 0; + virtual bool leave() = 0; + virtual void setClipboard(ClipboardID, const IClipboard*) = 0; + virtual void grabClipboard(ClipboardID) = 0; + virtual void setClipboardDirty(ClipboardID, bool) = 0; + virtual void keyDown(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton) = 0; + virtual void keyUp(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void mouseDown(ButtonID) = 0; + virtual void mouseUp(ButtonID) = 0; + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0; + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel) = 0; + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta) = 0; + virtual void screensaver(bool activate) = 0; + virtual void resetOptions() = 0; + virtual void setOptions(const COptionsList& options) = 0; + +private: + IStream* m_stream; + + static CEvent::Type s_readyEvent; + static CEvent::Type s_disconnectedEvent; + static CEvent::Type s_clipboardChangedEvent; +}; + +#endif diff --git a/lib/server/CClientProxy1_0.cpp b/lib/server/CClientProxy1_0.cpp new file mode 100644 index 00000000..b40372e5 --- /dev/null +++ b/lib/server/CClientProxy1_0.cpp @@ -0,0 +1,498 @@ +/* + * 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 "CClientProxy1_0.h" +#include "CProtocolUtil.h" +#include "XSynergy.h" +#include "IStream.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include + +// +// CClientProxy1_0 +// + +CClientProxy1_0::CClientProxy1_0(const CString& name, IStream* stream) : + CClientProxy(name, stream), + m_heartbeatTimer(NULL), + m_parser(&CClientProxy1_0::parseHandshakeMessage) +{ + // install event handlers + EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleData, NULL)); + EVENTQUEUE->adoptHandler(IStream::getOutputErrorEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleWriteError, NULL)); + EVENTQUEUE->adoptHandler(IStream::getInputShutdownEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleDisconnect, NULL)); + EVENTQUEUE->adoptHandler(IStream::getOutputShutdownEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleWriteError, NULL)); + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CClientProxy1_0::handleFlatline, NULL)); + + setHeartbeatRate(kHeartRate, kHeartRate * kHeartBeatsUntilDeath); + + LOG((CLOG_DEBUG1 "querying client \"%s\" info", getName().c_str())); + CProtocolUtil::writef(getStream(), kMsgQInfo); +} + +CClientProxy1_0::~CClientProxy1_0() +{ + removeHandlers(); +} + +void +CClientProxy1_0::disconnect() +{ + removeHandlers(); + getStream()->close(); + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), getEventTarget())); +} + +void +CClientProxy1_0::removeHandlers() +{ + // uninstall event handlers + EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputErrorEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getInputShutdownEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputShutdownEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + + // remove timer + removeHeartbeatTimer(); +} + +void +CClientProxy1_0::addHeartbeatTimer() +{ + if (m_heartbeatAlarm > 0.0) { + m_heartbeatTimer = EVENTQUEUE->newOneShotTimer(m_heartbeatAlarm, this); + } +} + +void +CClientProxy1_0::removeHeartbeatTimer() +{ + if (m_heartbeatTimer != NULL) { + EVENTQUEUE->deleteTimer(m_heartbeatTimer); + m_heartbeatTimer = NULL; + } +} + +void +CClientProxy1_0::resetHeartbeatTimer() +{ + // reset the alarm + removeHeartbeatTimer(); + addHeartbeatTimer(); +} + +void +CClientProxy1_0::resetHeartbeatRate() +{ + setHeartbeatRate(kHeartRate, kHeartRate * kHeartBeatsUntilDeath); +} + +void +CClientProxy1_0::setHeartbeatRate(double, double alarm) +{ + m_heartbeatAlarm = alarm; +} + +void +CClientProxy1_0::handleData(const CEvent&, void*) +{ + // handle messages until there are no more. first read message code. + UInt8 code[4]; + UInt32 n = getStream()->read(code, 4); + while (n != 0) { + // verify we got an entire code + if (n != 4) { + LOG((CLOG_ERR "incomplete message from \"%s\": %d bytes", getName().c_str(), n)); + disconnect(); + return; + } + + // parse message + LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3])); + if (!(this->*m_parser)(code)) { + LOG((CLOG_ERR "invalid message from client \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3])); + disconnect(); + return; + } + + // next message + n = getStream()->read(code, 4); + } + + // restart heartbeat timer + resetHeartbeatTimer(); +} + +bool +CClientProxy1_0::parseHandshakeMessage(const UInt8* code) +{ + if (memcmp(code, kMsgCNoop, 4) == 0) { + // discard no-ops + LOG((CLOG_DEBUG2 "no-op from", getName().c_str())); + return true; + } + else if (memcmp(code, kMsgDInfo, 4) == 0) { + // future messages get parsed by parseMessage + m_parser = &CClientProxy1_0::parseMessage; + if (recvInfo()) { + EVENTQUEUE->addEvent(CEvent(getReadyEvent(), getEventTarget())); + addHeartbeatTimer(); + return true; + } + } + return false; +} + +bool +CClientProxy1_0::parseMessage(const UInt8* code) +{ + if (memcmp(code, kMsgDInfo, 4) == 0) { + if (recvInfo()) { + EVENTQUEUE->addEvent( + CEvent(getShapeChangedEvent(), getEventTarget())); + return true; + } + return false; + } + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // discard no-ops + LOG((CLOG_DEBUG2 "no-op from", getName().c_str())); + return true; + } + else if (memcmp(code, kMsgCClipboard, 4) == 0) { + return recvGrabClipboard(); + } + else if (memcmp(code, kMsgDClipboard, 4) == 0) { + return recvClipboard(); + } + return false; +} + +void +CClientProxy1_0::handleDisconnect(const CEvent&, void*) +{ + LOG((CLOG_NOTE "client \"%s\" has disconnected", getName().c_str())); + disconnect(); +} + +void +CClientProxy1_0::handleWriteError(const CEvent&, void*) +{ + LOG((CLOG_WARN "error writing to client \"%s\"", getName().c_str())); + disconnect(); +} + +void +CClientProxy1_0::handleFlatline(const CEvent&, void*) +{ + // didn't get a heartbeat fast enough. assume client is dead. + LOG((CLOG_NOTE "client \"%s\" is dead", getName().c_str())); + disconnect(); +} + +bool +CClientProxy1_0::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + CClipboard::copy(clipboard, &m_clipboard[id].m_clipboard); + return true; +} + +void +CClientProxy1_0::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + x = m_info.m_x; + y = m_info.m_y; + w = m_info.m_w; + h = m_info.m_h; +} + +void +CClientProxy1_0::getCursorPos(SInt32& x, SInt32& y) const +{ + // note -- this returns the cursor pos from when we last got client info + x = m_info.m_mx; + y = m_info.m_my; +} + +void +CClientProxy1_0::enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, bool) +{ + LOG((CLOG_DEBUG1 "send enter to \"%s\", %d,%d %d %04x", getName().c_str(), xAbs, yAbs, seqNum, mask)); + CProtocolUtil::writef(getStream(), kMsgCEnter, + xAbs, yAbs, seqNum, mask); +} + +bool +CClientProxy1_0::leave() +{ + LOG((CLOG_DEBUG1 "send leave to \"%s\"", getName().c_str())); + CProtocolUtil::writef(getStream(), kMsgCLeave); + + // we can never prevent the user from leaving + return true; +} + +void +CClientProxy1_0::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + // ignore if this clipboard is already clean + if (m_clipboard[id].m_dirty) { + // this clipboard is now clean + m_clipboard[id].m_dirty = false; + CClipboard::copy(&m_clipboard[id].m_clipboard, clipboard); + + CString data = m_clipboard[id].m_clipboard.marshall(); + LOG((CLOG_DEBUG "send clipboard %d to \"%s\" size=%d", id, getName().c_str(), data.size())); + CProtocolUtil::writef(getStream(), kMsgDClipboard, id, 0, &data); + } +} + +void +CClientProxy1_0::grabClipboard(ClipboardID id) +{ + LOG((CLOG_DEBUG "send grab clipboard %d to \"%s\"", id, getName().c_str())); + CProtocolUtil::writef(getStream(), kMsgCClipboard, id, 0); + + // this clipboard is now dirty + m_clipboard[id].m_dirty = true; +} + +void +CClientProxy1_0::setClipboardDirty(ClipboardID id, bool dirty) +{ + m_clipboard[id].m_dirty = dirty; +} + +void +CClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask, KeyButton) +{ + LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask)); + CProtocolUtil::writef(getStream(), kMsgDKeyDown1_0, key, mask); +} + +void +CClientProxy1_0::keyRepeat(KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton) +{ + LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d", getName().c_str(), key, mask, count)); + CProtocolUtil::writef(getStream(), kMsgDKeyRepeat1_0, key, mask, count); +} + +void +CClientProxy1_0::keyUp(KeyID key, KeyModifierMask mask, KeyButton) +{ + LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask)); + CProtocolUtil::writef(getStream(), kMsgDKeyUp1_0, key, mask); +} + +void +CClientProxy1_0::mouseDown(ButtonID button) +{ + LOG((CLOG_DEBUG1 "send mouse down to \"%s\" id=%d", getName().c_str(), button)); + CProtocolUtil::writef(getStream(), kMsgDMouseDown, button); +} + +void +CClientProxy1_0::mouseUp(ButtonID button) +{ + LOG((CLOG_DEBUG1 "send mouse up to \"%s\" id=%d", getName().c_str(), button)); + CProtocolUtil::writef(getStream(), kMsgDMouseUp, button); +} + +void +CClientProxy1_0::mouseMove(SInt32 xAbs, SInt32 yAbs) +{ + LOG((CLOG_DEBUG2 "send mouse move to \"%s\" %d,%d", getName().c_str(), xAbs, yAbs)); + CProtocolUtil::writef(getStream(), kMsgDMouseMove, xAbs, yAbs); +} + +void +CClientProxy1_0::mouseRelativeMove(SInt32, SInt32) +{ + // ignore -- not supported in protocol 1.0 +} + +void +CClientProxy1_0::mouseWheel(SInt32, SInt32 yDelta) +{ + // clients prior to 1.3 only support the y axis + LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d", getName().c_str(), yDelta)); + CProtocolUtil::writef(getStream(), kMsgDMouseWheel1_0, yDelta); +} + +void +CClientProxy1_0::screensaver(bool on) +{ + LOG((CLOG_DEBUG1 "send screen saver to \"%s\" on=%d", getName().c_str(), on ? 1 : 0)); + CProtocolUtil::writef(getStream(), kMsgCScreenSaver, on ? 1 : 0); +} + +void +CClientProxy1_0::resetOptions() +{ + LOG((CLOG_DEBUG1 "send reset options to \"%s\"", getName().c_str())); + CProtocolUtil::writef(getStream(), kMsgCResetOptions); + + // reset heart rate and death + resetHeartbeatRate(); + removeHeartbeatTimer(); + addHeartbeatTimer(); +} + +void +CClientProxy1_0::setOptions(const COptionsList& options) +{ + LOG((CLOG_DEBUG1 "send set options to \"%s\" size=%d", getName().c_str(), options.size())); + CProtocolUtil::writef(getStream(), kMsgDSetOptions, &options); + + // check options + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + if (options[i] == kOptionHeartbeat) { + double rate = 1.0e-3 * static_cast(options[i + 1]); + if (rate <= 0.0) { + rate = -1.0; + } + setHeartbeatRate(rate, rate * kHeartBeatsUntilDeath); + removeHeartbeatTimer(); + addHeartbeatTimer(); + } + } +} + +bool +CClientProxy1_0::recvInfo() +{ + // parse the message + SInt16 x, y, w, h, dummy1, mx, my; + if (!CProtocolUtil::readf(getStream(), kMsgDInfo + 4, + &x, &y, &w, &h, &dummy1, &mx, &my)) { + return false; + } + LOG((CLOG_DEBUG "received client \"%s\" info shape=%d,%d %dx%d at %d,%d", getName().c_str(), x, y, w, h, mx, my)); + + // validate + if (w <= 0 || h <= 0) { + return false; + } + if (mx < x || mx >= x + w || my < y || my >= y + h) { + mx = x + w / 2; + my = y + h / 2; + } + + // save + m_info.m_x = x; + m_info.m_y = y; + m_info.m_w = w; + m_info.m_h = h; + m_info.m_mx = mx; + m_info.m_my = my; + + // acknowledge receipt + LOG((CLOG_DEBUG1 "send info ack to \"%s\"", getName().c_str())); + CProtocolUtil::writef(getStream(), kMsgCInfoAck); + return true; +} + +bool +CClientProxy1_0::recvClipboard() +{ + // parse message + ClipboardID id; + UInt32 seqNum; + CString data; + if (!CProtocolUtil::readf(getStream(), + kMsgDClipboard + 4, &id, &seqNum, &data)) { + return false; + } + LOG((CLOG_DEBUG "received client \"%s\" clipboard %d seqnum=%d, size=%d", getName().c_str(), id, seqNum, data.size())); + + // validate + if (id >= kClipboardEnd) { + return false; + } + + // save clipboard + m_clipboard[id].m_clipboard.unmarshall(data, 0); + m_clipboard[id].m_sequenceNumber = seqNum; + + // notify + CClipboardInfo* info = new CClipboardInfo; + info->m_id = id; + info->m_sequenceNumber = seqNum; + EVENTQUEUE->addEvent(CEvent(getClipboardChangedEvent(), + getEventTarget(), info)); + + return true; +} + +bool +CClientProxy1_0::recvGrabClipboard() +{ + // parse message + ClipboardID id; + UInt32 seqNum; + if (!CProtocolUtil::readf(getStream(), kMsgCClipboard + 4, &id, &seqNum)) { + return false; + } + LOG((CLOG_DEBUG "received client \"%s\" grabbed clipboard %d seqnum=%d", getName().c_str(), id, seqNum)); + + // validate + if (id >= kClipboardEnd) { + return false; + } + + // notify + CClipboardInfo* info = new CClipboardInfo; + info->m_id = id; + info->m_sequenceNumber = seqNum; + EVENTQUEUE->addEvent(CEvent(getClipboardGrabbedEvent(), + getEventTarget(), info)); + + return true; +} + + +// +// CClientProxy1_0::CClientClipboard +// + +CClientProxy1_0::CClientClipboard::CClientClipboard() : + m_clipboard(), + m_sequenceNumber(0), + m_dirty(true) +{ + // do nothing +} diff --git a/lib/server/CClientProxy1_0.h b/lib/server/CClientProxy1_0.h new file mode 100644 index 00000000..9ebfc18d --- /dev/null +++ b/lib/server/CClientProxy1_0.h @@ -0,0 +1,100 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTPROXY1_0_H +#define CCLIENTPROXY1_0_H + +#include "CClientProxy.h" +#include "CClipboard.h" +#include "ProtocolTypes.h" + +class CEvent; +class CEventQueueTimer; + +//! Proxy for client implementing protocol version 1.0 +class CClientProxy1_0 : public CClientProxy { +public: + CClientProxy1_0(const CString& name, IStream* adoptedStream); + ~CClientProxy1_0(); + + // IScreen + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver); + virtual bool leave(); + virtual void setClipboard(ClipboardID, const IClipboard*); + virtual void grabClipboard(ClipboardID); + virtual void setClipboardDirty(ClipboardID, bool); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); + virtual void mouseDown(ButtonID); + virtual void mouseUp(ButtonID); + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs); + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + +protected: + virtual bool parseHandshakeMessage(const UInt8* code); + virtual bool parseMessage(const UInt8* code); + + virtual void resetHeartbeatRate(); + virtual void setHeartbeatRate(double rate, double alarm); + virtual void resetHeartbeatTimer(); + virtual void addHeartbeatTimer(); + virtual void removeHeartbeatTimer(); + +private: + void disconnect(); + void removeHandlers(); + + void handleData(const CEvent&, void*); + void handleDisconnect(const CEvent&, void*); + void handleWriteError(const CEvent&, void*); + void handleFlatline(const CEvent&, void*); + + bool recvInfo(); + bool recvClipboard(); + bool recvGrabClipboard(); + +private: + typedef bool (CClientProxy1_0::*MessageParser)(const UInt8*); + struct CClientClipboard { + public: + CClientClipboard(); + + public: + CClipboard m_clipboard; + UInt32 m_sequenceNumber; + bool m_dirty; + }; + + CClientInfo m_info; + CClientClipboard m_clipboard[kClipboardEnd]; + double m_heartbeatAlarm; + CEventQueueTimer* m_heartbeatTimer; + MessageParser m_parser; +}; + +#endif diff --git a/lib/server/CClientProxy1_1.cpp b/lib/server/CClientProxy1_1.cpp new file mode 100644 index 00000000..ccce9305 --- /dev/null +++ b/lib/server/CClientProxy1_1.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 "CClientProxy1_1.h" +#include "CProtocolUtil.h" +#include "CLog.h" +#include + +// +// CClientProxy1_1 +// + +CClientProxy1_1::CClientProxy1_1(const CString& name, IStream* stream) : + CClientProxy1_0(name, stream) +{ + // do nothing +} + +CClientProxy1_1::~CClientProxy1_1() +{ + // do nothing +} + +void +CClientProxy1_1::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button)); + CProtocolUtil::writef(getStream(), kMsgDKeyDown, key, mask, button); +} + +void +CClientProxy1_1::keyRepeat(KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d, button=0x%04x", getName().c_str(), key, mask, count, button)); + CProtocolUtil::writef(getStream(), kMsgDKeyRepeat, key, mask, count, button); +} + +void +CClientProxy1_1::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button)); + CProtocolUtil::writef(getStream(), kMsgDKeyUp, key, mask, button); +} diff --git a/lib/server/CClientProxy1_1.h b/lib/server/CClientProxy1_1.h new file mode 100644 index 00000000..08764829 --- /dev/null +++ b/lib/server/CClientProxy1_1.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. + */ + +#ifndef CCLIENTPROXY1_1_H +#define CCLIENTPROXY1_1_H + +#include "CClientProxy1_0.h" + +//! Proxy for client implementing protocol version 1.1 +class CClientProxy1_1 : public CClientProxy1_0 { +public: + CClientProxy1_1(const CString& name, IStream* adoptedStream); + ~CClientProxy1_1(); + + // IClient overrides + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); +}; + +#endif diff --git a/lib/server/CClientProxy1_2.cpp b/lib/server/CClientProxy1_2.cpp new file mode 100644 index 00000000..29f0a56b --- /dev/null +++ b/lib/server/CClientProxy1_2.cpp @@ -0,0 +1,39 @@ +/* + * 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 "CClientProxy1_2.h" +#include "CProtocolUtil.h" +#include "CLog.h" + +// +// CClientProxy1_1 +// + +CClientProxy1_2::CClientProxy1_2(const CString& name, IStream* stream) : + CClientProxy1_1(name, stream) +{ + // do nothing +} + +CClientProxy1_2::~CClientProxy1_2() +{ + // do nothing +} + +void +CClientProxy1_2::mouseRelativeMove(SInt32 xRel, SInt32 yRel) +{ + LOG((CLOG_DEBUG2 "send mouse relative move to \"%s\" %d,%d", getName().c_str(), xRel, yRel)); + CProtocolUtil::writef(getStream(), kMsgDMouseRelMove, xRel, yRel); +} diff --git a/lib/server/CClientProxy1_2.h b/lib/server/CClientProxy1_2.h new file mode 100644 index 00000000..3f8bb0e3 --- /dev/null +++ b/lib/server/CClientProxy1_2.h @@ -0,0 +1,30 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTPROXY1_2_H +#define CCLIENTPROXY1_2_H + +#include "CClientProxy1_1.h" + +//! Proxy for client implementing protocol version 1.2 +class CClientProxy1_2 : public CClientProxy1_1 { +public: + CClientProxy1_2(const CString& name, IStream* adoptedStream); + ~CClientProxy1_2(); + + // IClient overrides + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); +}; + +#endif diff --git a/lib/server/CClientProxy1_3.cpp b/lib/server/CClientProxy1_3.cpp new file mode 100644 index 00000000..f68b550e --- /dev/null +++ b/lib/server/CClientProxy1_3.cpp @@ -0,0 +1,114 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 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 "CClientProxy1_3.h" +#include "CProtocolUtil.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CClientProxy1_3 +// + +CClientProxy1_3::CClientProxy1_3(const CString& name, IStream* stream) : + CClientProxy1_2(name, stream), + m_keepAliveRate(kKeepAliveRate), + m_keepAliveTimer(NULL) +{ + setHeartbeatRate(kKeepAliveRate, kKeepAliveRate * kKeepAlivesUntilDeath); +} + +CClientProxy1_3::~CClientProxy1_3() +{ + // cannot do this in superclass or our override wouldn't get called + removeHeartbeatTimer(); +} + +void +CClientProxy1_3::mouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d,%+d", getName().c_str(), xDelta, yDelta)); + CProtocolUtil::writef(getStream(), kMsgDMouseWheel, xDelta, yDelta); +} + +bool +CClientProxy1_3::parseMessage(const UInt8* code) +{ + // process message + if (memcmp(code, kMsgCKeepAlive, 4) == 0) { + // reset alarm + resetHeartbeatTimer(); + return true; + } + else { + return CClientProxy1_2::parseMessage(code); + } +} + +void +CClientProxy1_3::resetHeartbeatRate() +{ + setHeartbeatRate(kKeepAliveRate, kKeepAliveRate * kKeepAlivesUntilDeath); +} + +void +CClientProxy1_3::setHeartbeatRate(double rate, double) +{ + m_keepAliveRate = rate; + CClientProxy1_2::setHeartbeatRate(rate, rate * kKeepAlivesUntilDeath); +} + +void +CClientProxy1_3::resetHeartbeatTimer() +{ + // reset the alarm but not the keep alive timer + CClientProxy1_2::removeHeartbeatTimer(); + CClientProxy1_2::addHeartbeatTimer(); +} + +void +CClientProxy1_3::addHeartbeatTimer() +{ + // create and install a timer to periodically send keep alives + if (m_keepAliveRate > 0.0) { + m_keepAliveTimer = EVENTQUEUE->newTimer(m_keepAliveRate, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_keepAliveTimer, + new TMethodEventJob(this, + &CClientProxy1_3::handleKeepAlive, NULL)); + } + + // superclass does the alarm + CClientProxy1_2::addHeartbeatTimer(); +} + +void +CClientProxy1_3::removeHeartbeatTimer() +{ + // remove the timer that sends keep alives periodically + if (m_keepAliveTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_keepAliveTimer); + EVENTQUEUE->deleteTimer(m_keepAliveTimer); + m_keepAliveTimer = NULL; + } + + // superclass does the alarm + CClientProxy1_2::removeHeartbeatTimer(); +} + +void +CClientProxy1_3::handleKeepAlive(const CEvent&, void*) +{ + CProtocolUtil::writef(getStream(), kMsgCKeepAlive); +} diff --git a/lib/server/CClientProxy1_3.h b/lib/server/CClientProxy1_3.h new file mode 100644 index 00000000..681094b6 --- /dev/null +++ b/lib/server/CClientProxy1_3.h @@ -0,0 +1,47 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTPROXY1_3_H +#define CCLIENTPROXY1_3_H + +#include "CClientProxy1_2.h" + +//! Proxy for client implementing protocol version 1.3 +class CClientProxy1_3 : public CClientProxy1_2 { +public: + CClientProxy1_3(const CString& name, IStream* adoptedStream); + ~CClientProxy1_3(); + + // IClient overrides + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + +protected: + // CClientProxy overrides + virtual bool parseMessage(const UInt8* code); + virtual void resetHeartbeatRate(); + virtual void setHeartbeatRate(double rate, double alarm); + virtual void resetHeartbeatTimer(); + virtual void addHeartbeatTimer(); + virtual void removeHeartbeatTimer(); + +private: + void handleKeepAlive(const CEvent&, void*); + + +private: + double m_keepAliveRate; + CEventQueueTimer* m_keepAliveTimer; +}; + +#endif diff --git a/lib/server/CClientProxyUnknown.cpp b/lib/server/CClientProxyUnknown.cpp new file mode 100644 index 00000000..51a25efa --- /dev/null +++ b/lib/server/CClientProxyUnknown.cpp @@ -0,0 +1,287 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CClientProxyUnknown.h" +#include "CClientProxy1_0.h" +#include "CClientProxy1_1.h" +#include "CClientProxy1_2.h" +#include "CClientProxy1_3.h" +#include "ProtocolTypes.h" +#include "CProtocolUtil.h" +#include "XSynergy.h" +#include "IStream.h" +#include "XIO.h" +#include "CLog.h" +#include "CString.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CClientProxyUnknown +// + +CEvent::Type CClientProxyUnknown::s_successEvent = CEvent::kUnknown; +CEvent::Type CClientProxyUnknown::s_failureEvent = CEvent::kUnknown; + +CClientProxyUnknown::CClientProxyUnknown(IStream* stream, double timeout) : + m_stream(stream), + m_proxy(NULL), + m_ready(false) +{ + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CClientProxyUnknown::handleTimeout, NULL)); + m_timer = EVENTQUEUE->newOneShotTimer(timeout, this); + addStreamHandlers(); + + LOG((CLOG_DEBUG1 "saying hello")); + CProtocolUtil::writef(m_stream, kMsgHello, + kProtocolMajorVersion, + kProtocolMinorVersion); +} + +CClientProxyUnknown::~CClientProxyUnknown() +{ + removeHandlers(); + removeTimer(); + delete m_stream; + delete m_proxy; +} + +CClientProxy* +CClientProxyUnknown::orphanClientProxy() +{ + if (m_ready) { + removeHandlers(); + CClientProxy* proxy = m_proxy; + m_proxy = NULL; + return proxy; + } + else { + return NULL; + } +} + +CEvent::Type +CClientProxyUnknown::getSuccessEvent() +{ + return CEvent::registerTypeOnce(s_successEvent, + "CClientProxy::success"); +} + +CEvent::Type +CClientProxyUnknown::getFailureEvent() +{ + return CEvent::registerTypeOnce(s_failureEvent, + "CClientProxy::failure"); +} + +void +CClientProxyUnknown::sendSuccess() +{ + m_ready = true; + removeTimer(); + EVENTQUEUE->addEvent(CEvent(getSuccessEvent(), this)); +} + +void +CClientProxyUnknown::sendFailure() +{ + delete m_proxy; + m_proxy = NULL; + m_ready = false; + removeHandlers(); + removeTimer(); + EVENTQUEUE->addEvent(CEvent(getFailureEvent(), this)); +} + +void +CClientProxyUnknown::addStreamHandlers() +{ + assert(m_stream != NULL); + + EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleData)); + EVENTQUEUE->adoptHandler(IStream::getOutputErrorEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleWriteError)); + EVENTQUEUE->adoptHandler(IStream::getInputShutdownEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleDisconnect)); + EVENTQUEUE->adoptHandler(IStream::getOutputShutdownEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleWriteError)); +} + +void +CClientProxyUnknown::addProxyHandlers() +{ + assert(m_proxy != NULL); + + EVENTQUEUE->adoptHandler(CClientProxy::getReadyEvent(), + m_proxy, + new TMethodEventJob(this, + &CClientProxyUnknown::handleReady)); + EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), + m_proxy, + new TMethodEventJob(this, + &CClientProxyUnknown::handleDisconnect)); +} + +void +CClientProxyUnknown::removeHandlers() +{ + if (m_stream != NULL) { + EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputErrorEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getInputShutdownEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputShutdownEvent(), + m_stream->getEventTarget()); + } + if (m_proxy != NULL) { + EVENTQUEUE->removeHandler(CClientProxy::getReadyEvent(), + m_proxy); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), + m_proxy); + } +} + +void +CClientProxyUnknown::removeTimer() +{ + if (m_timer != NULL) { + EVENTQUEUE->deleteTimer(m_timer); + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + m_timer = NULL; + } +} + +void +CClientProxyUnknown::handleData(const CEvent&, void*) +{ + LOG((CLOG_DEBUG1 "parsing hello reply")); + + CString name(""); + try { + // limit the maximum length of the hello + UInt32 n = m_stream->getSize(); + if (n > kMaxHelloLength) { + LOG((CLOG_DEBUG1 "hello reply too long")); + throw XBadClient(); + } + + // parse the reply to hello + SInt16 major, minor; + if (!CProtocolUtil::readf(m_stream, kMsgHelloBack, + &major, &minor, &name)) { + throw XBadClient(); + } + + // disallow invalid version numbers + if (major <= 0 || minor < 0) { + throw XIncompatibleClient(major, minor); + } + + // remove stream event handlers. the proxy we're about to create + // may install its own handlers and we don't want to accidentally + // remove those later. + removeHandlers(); + + // create client proxy for highest version supported by the client + if (major == 1) { + switch (minor) { + case 0: + m_proxy = new CClientProxy1_0(name, m_stream); + break; + + case 1: + m_proxy = new CClientProxy1_1(name, m_stream); + break; + + case 2: + m_proxy = new CClientProxy1_2(name, m_stream); + break; + + case 3: + m_proxy = new CClientProxy1_3(name, m_stream); + break; + } + } + + // hangup (with error) if version isn't supported + if (m_proxy == NULL) { + throw XIncompatibleClient(major, minor); + } + + // the proxy is created and now proxy now owns the stream + LOG((CLOG_DEBUG1 "created proxy for client \"%s\" version %d.%d", name.c_str(), major, minor)); + m_stream = NULL; + + // wait until the proxy signals that it's ready or has disconnected + addProxyHandlers(); + return; + } + catch (XIncompatibleClient& e) { + // client is incompatible + LOG((CLOG_WARN "client \"%s\" has incompatible version %d.%d)", name.c_str(), e.getMajor(), e.getMinor())); + CProtocolUtil::writef(m_stream, + kMsgEIncompatible, + kProtocolMajorVersion, kProtocolMinorVersion); + } + catch (XBadClient&) { + // client not behaving + LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str())); + CProtocolUtil::writef(m_stream, kMsgEBad); + } + catch (XBase& e) { + // misc error + LOG((CLOG_WARN "error communicating with client \"%s\": %s", name.c_str(), e.what())); + } + sendFailure(); +} + +void +CClientProxyUnknown::handleWriteError(const CEvent&, void*) +{ + LOG((CLOG_NOTE "error communicating with new client")); + sendFailure(); +} + +void +CClientProxyUnknown::handleTimeout(const CEvent&, void*) +{ + LOG((CLOG_NOTE "new client is unresponsive")); + sendFailure(); +} + +void +CClientProxyUnknown::handleDisconnect(const CEvent&, void*) +{ + LOG((CLOG_NOTE "new client disconnected")); + sendFailure(); +} + +void +CClientProxyUnknown::handleReady(const CEvent&, void*) +{ + sendSuccess(); +} diff --git a/lib/server/CClientProxyUnknown.h b/lib/server/CClientProxyUnknown.h new file mode 100644 index 00000000..140a1aee --- /dev/null +++ b/lib/server/CClientProxyUnknown.h @@ -0,0 +1,83 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTPROXYUNKNOWN_H +#define CCLIENTPROXYUNKNOWN_H + +#include "CEvent.h" + +class CClientProxy; +class CEventQueueTimer; +class IStream; + +class CClientProxyUnknown { +public: + CClientProxyUnknown(IStream* stream, double timeout); + ~CClientProxyUnknown(); + + //! @name manipulators + //@{ + + //! Get the client proxy + /*! + Returns the client proxy created after a successful handshake + (i.e. when this object sends a success event). Returns NULL + if the handshake is unsuccessful or incomplete. + */ + CClientProxy* orphanClientProxy(); + + //@} + //! @name accessors + //@{ + + //! Get success event type + /*! + Returns the success event type. This is sent when the client has + correctly responded to the hello message. The target is this. + */ + static CEvent::Type getSuccessEvent(); + + //! Get failure event type + /*! + Returns the failure event type. This is sent when a client fails + to correctly respond to the hello message. The target is this. + */ + static CEvent::Type getFailureEvent(); + + //@} + +private: + void sendSuccess(); + void sendFailure(); + void addStreamHandlers(); + void addProxyHandlers(); + void removeHandlers(); + void removeTimer(); + void handleData(const CEvent&, void*); + void handleWriteError(const CEvent&, void*); + void handleTimeout(const CEvent&, void*); + void handleDisconnect(const CEvent&, void*); + void handleReady(const CEvent&, void*); + +private: + IStream* m_stream; + CEventQueueTimer* m_timer; + CClientProxy* m_proxy; + bool m_ready; + + static CEvent::Type s_successEvent; + static CEvent::Type s_failureEvent; +}; + +#endif diff --git a/lib/server/CConfig.cpp b/lib/server/CConfig.cpp new file mode 100644 index 00000000..92054f42 --- /dev/null +++ b/lib/server/CConfig.cpp @@ -0,0 +1,2277 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "CServer.h" +#include "CKeyMap.h" +#include "KeyTypes.h" +#include "XSocket.h" +#include "stdistream.h" +#include "stdostream.h" +#include + +// +// CConfig +// + +CConfig::CConfig() : m_hasLockToScreenAction(false) +{ + // do nothing +} + +CConfig::~CConfig() +{ + // do nothing +} + +bool +CConfig::addScreen(const CString& name) +{ + // alias name must not exist + if (m_nameToCanonicalName.find(name) != m_nameToCanonicalName.end()) { + return false; + } + + // add cell + m_map.insert(std::make_pair(name, CCell())); + + // add name + m_nameToCanonicalName.insert(std::make_pair(name, name)); + + return true; +} + +bool +CConfig::renameScreen(const CString& oldName, + const CString& newName) +{ + // get canonical name and find cell + CString oldCanonical = getCanonicalName(oldName); + CCellMap::iterator index = m_map.find(oldCanonical); + if (index == m_map.end()) { + return false; + } + + // accept if names are equal but replace with new name to maintain + // case. otherwise, the new name must not exist. + if (!CStringUtil::CaselessCmp::equal(oldName, newName) && + m_nameToCanonicalName.find(newName) != m_nameToCanonicalName.end()) { + return false; + } + + // update cell + CCell tmpCell = index->second; + m_map.erase(index); + m_map.insert(std::make_pair(newName, tmpCell)); + + // update name + m_nameToCanonicalName.erase(oldCanonical); + m_nameToCanonicalName.insert(std::make_pair(newName, newName)); + + // update connections + CName oldNameObj(this, oldName); + for (index = m_map.begin(); index != m_map.end(); ++index) { + index->second.rename(oldNameObj, newName); + } + + // update alias targets + if (CStringUtil::CaselessCmp::equal(oldName, oldCanonical)) { + for (CNameMap::iterator index = m_nameToCanonicalName.begin(); + index != m_nameToCanonicalName.end(); ++index) { + if (CStringUtil::CaselessCmp::equal( + index->second, oldCanonical)) { + index->second = newName; + } + } + } + + return true; +} + +void +CConfig::removeScreen(const CString& name) +{ + // get canonical name and find cell + CString canonical = getCanonicalName(name); + CCellMap::iterator index = m_map.find(canonical); + if (index == m_map.end()) { + return; + } + + // remove from map + m_map.erase(index); + + // disconnect + CName nameObj(this, name); + for (index = m_map.begin(); index != m_map.end(); ++index) { + index->second.remove(nameObj); + } + + // remove aliases (and canonical name) + for (CNameMap::iterator index = m_nameToCanonicalName.begin(); + index != m_nameToCanonicalName.end(); ) { + if (index->second == canonical) { + m_nameToCanonicalName.erase(index++); + } + else { + ++index; + } + } +} + +void +CConfig::removeAllScreens() +{ + m_map.clear(); + m_nameToCanonicalName.clear(); +} + +bool +CConfig::addAlias(const CString& canonical, const CString& alias) +{ + // alias name must not exist + if (m_nameToCanonicalName.find(alias) != m_nameToCanonicalName.end()) { + return false; + } + + // canonical name must be known + if (m_map.find(canonical) == m_map.end()) { + return false; + } + + // insert alias + m_nameToCanonicalName.insert(std::make_pair(alias, canonical)); + + return true; +} + +bool +CConfig::removeAlias(const CString& alias) +{ + // must not be a canonical name + if (m_map.find(alias) != m_map.end()) { + return false; + } + + // find alias + CNameMap::iterator index = m_nameToCanonicalName.find(alias); + if (index == m_nameToCanonicalName.end()) { + return false; + } + + // remove alias + m_nameToCanonicalName.erase(index); + + return true; +} + +bool +CConfig::removeAliases(const CString& canonical) +{ + // must be a canonical name + if (m_map.find(canonical) == m_map.end()) { + return false; + } + + // find and removing matching aliases + for (CNameMap::iterator index = m_nameToCanonicalName.begin(); + index != m_nameToCanonicalName.end(); ) { + if (index->second == canonical && index->first != canonical) { + m_nameToCanonicalName.erase(index++); + } + else { + ++index; + } + } + + return true; +} + +void +CConfig::removeAllAliases() +{ + // remove all names + m_nameToCanonicalName.clear(); + + // put the canonical names back in + for (CCellMap::iterator index = m_map.begin(); + index != m_map.end(); ++index) { + m_nameToCanonicalName.insert( + std::make_pair(index->first, index->first)); + } +} + +bool +CConfig::connect(const CString& srcName, + EDirection srcSide, + float srcStart, float srcEnd, + const CString& dstName, + float dstStart, float dstEnd) +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CCellMap::iterator index = m_map.find(getCanonicalName(srcName)); + if (index == m_map.end()) { + return false; + } + + // add link + CCellEdge srcEdge(srcSide, CInterval(srcStart, srcEnd)); + CCellEdge dstEdge(dstName, srcSide, CInterval(dstStart, dstEnd)); + return index->second.add(srcEdge, dstEdge); +} + +bool +CConfig::disconnect(const CString& srcName, EDirection srcSide) +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CCellMap::iterator index = m_map.find(srcName); + if (index == m_map.end()) { + return false; + } + + // disconnect side + index->second.remove(srcSide); + + return true; +} + +bool +CConfig::disconnect(const CString& srcName, EDirection srcSide, float position) +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CCellMap::iterator index = m_map.find(srcName); + if (index == m_map.end()) { + return false; + } + + // disconnect side + index->second.remove(srcSide, position); + + return true; +} + +void +CConfig::setSynergyAddress(const CNetworkAddress& addr) +{ + m_synergyAddress = addr; +} + +bool +CConfig::addOption(const CString& name, OptionID option, OptionValue value) +{ + // find options + CScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CCellMap::iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + if (options == NULL) { + return false; + } + + // add option + options->insert(std::make_pair(option, value)); + return true; +} + +bool +CConfig::removeOption(const CString& name, OptionID option) +{ + // find options + CScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CCellMap::iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + if (options == NULL) { + return false; + } + + // remove option + options->erase(option); + return true; +} + +bool +CConfig::removeOptions(const CString& name) +{ + // find options + CScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CCellMap::iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + if (options == NULL) { + return false; + } + + // remove options + options->clear(); + return true; +} + +bool +CConfig::isValidScreenName(const CString& name) const +{ + // name is valid if matches validname + // name ::= [_A-Za-z0-9] | [_A-Za-z0-9][-_A-Za-z0-9]*[_A-Za-z0-9] + // domain ::= . name + // validname ::= name domain* + // we also accept names ending in . because many OS X users have + // so misconfigured their systems. + + // empty name is invalid + if (name.empty()) { + return false; + } + + // check each dot separated part + CString::size_type b = 0; + for (;;) { + // accept trailing . + if (b == name.size()) { + break; + } + + // find end of part + CString::size_type e = name.find('.', b); + if (e == CString::npos) { + e = name.size(); + } + + // part may not be empty + if (e - b < 1) { + return false; + } + + // check first and last characters + if (!(isalnum(name[b]) || name[b] == '_') || + !(isalnum(name[e - 1]) || name[e - 1] == '_')) { + return false; + } + + // check interior characters + for (CString::size_type i = b; i < e; ++i) { + if (!isalnum(name[i]) && name[i] != '_' && name[i] != '-') { + return false; + } + } + + // next part + if (e == name.size()) { + // no more parts + break; + } + b = e + 1; + } + + return true; +} + +CConfig::const_iterator +CConfig::begin() const +{ + return const_iterator(m_map.begin()); +} + +CConfig::const_iterator +CConfig::end() const +{ + return const_iterator(m_map.end()); +} + +CConfig::all_const_iterator +CConfig::beginAll() const +{ + return m_nameToCanonicalName.begin(); +} + +CConfig::all_const_iterator +CConfig::endAll() const +{ + return m_nameToCanonicalName.end(); +} + +bool +CConfig::isScreen(const CString& name) const +{ + return (m_nameToCanonicalName.count(name) > 0); +} + +bool +CConfig::isCanonicalName(const CString& name) const +{ + return (!name.empty() && + CStringUtil::CaselessCmp::equal(getCanonicalName(name), name)); +} + +CString +CConfig::getCanonicalName(const CString& name) const +{ + CNameMap::const_iterator index = m_nameToCanonicalName.find(name); + if (index == m_nameToCanonicalName.end()) { + return CString(); + } + else { + return index->second; + } +} + +CString +CConfig::getNeighbor(const CString& srcName, EDirection srcSide, + float position, float* positionOut) const +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + if (index == m_map.end()) { + return CString(); + } + + // find edge + const CCellEdge* srcEdge, *dstEdge; + if (!index->second.getLink(srcSide, position, srcEdge, dstEdge)) { + // no neighbor + return ""; + } + else { + // compute position on neighbor + if (positionOut != NULL) { + *positionOut = + dstEdge->inverseTransform(srcEdge->transform(position)); + } + + // return neighbor's name + return getCanonicalName(dstEdge->getName()); + } +} + +bool +CConfig::hasNeighbor(const CString& srcName, EDirection srcSide) const +{ + return hasNeighbor(srcName, srcSide, 0.0f, 1.0f); +} + +bool +CConfig::hasNeighbor(const CString& srcName, EDirection srcSide, + float start, float end) const +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + if (index == m_map.end()) { + return false; + } + + return index->second.overlaps(CCellEdge(srcSide, CInterval(start, end))); +} + +CConfig::link_const_iterator +CConfig::beginNeighbor(const CString& srcName) const +{ + CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + assert(index != m_map.end()); + return index->second.begin(); +} + +CConfig::link_const_iterator +CConfig::endNeighbor(const CString& srcName) const +{ + CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + assert(index != m_map.end()); + return index->second.end(); +} + +const CNetworkAddress& +CConfig::getSynergyAddress() const +{ + return m_synergyAddress; +} + +const CConfig::CScreenOptions* +CConfig::getOptions(const CString& name) const +{ + // find options + const CScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CCellMap::const_iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + + // return options + return options; +} + +bool +CConfig::hasLockToScreenAction() const +{ + return m_hasLockToScreenAction; +} + +bool +CConfig::operator==(const CConfig& x) const +{ + if (m_synergyAddress != x.m_synergyAddress) { + return false; + } + if (m_map.size() != x.m_map.size()) { + return false; + } + if (m_nameToCanonicalName.size() != x.m_nameToCanonicalName.size()) { + return false; + } + + // compare global options + if (m_globalOptions != x.m_globalOptions) { + return false; + } + + for (CCellMap::const_iterator index1 = m_map.begin(), + index2 = x.m_map.begin(); + index1 != m_map.end(); ++index1, ++index2) { + // compare names + if (!CStringUtil::CaselessCmp::equal(index1->first, index2->first)) { + return false; + } + + // compare cells + if (index1->second != index2->second) { + return false; + } + } + + for (CNameMap::const_iterator index1 = m_nameToCanonicalName.begin(), + index2 = x.m_nameToCanonicalName.begin(); + index1 != m_nameToCanonicalName.end(); + ++index1, ++index2) { + if (!CStringUtil::CaselessCmp::equal(index1->first, index2->first) || + !CStringUtil::CaselessCmp::equal(index1->second, index2->second)) { + return false; + } + } + + // compare input filters + if (m_inputFilter != x.m_inputFilter) { + return false; + } + + return true; +} + +bool +CConfig::operator!=(const CConfig& x) const +{ + return !operator==(x); +} + +void +CConfig::read(CConfigReadContext& context) +{ + CConfig tmp; + while (context) { + tmp.readSection(context); + } + *this = tmp; +} + +const char* +CConfig::dirName(EDirection dir) +{ + static const char* s_name[] = { "left", "right", "up", "down" }; + + assert(dir >= kFirstDirection && dir <= kLastDirection); + + return s_name[dir - kFirstDirection]; +} + +CInputFilter* +CConfig::getInputFilter() +{ + return &m_inputFilter; +} + +CString +CConfig::formatInterval(const CInterval& x) +{ + if (x.first == 0.0f && x.second == 1.0f) { + return ""; + } + return CStringUtil::print("(%d,%d)", (int)(x.first * 100.0f + 0.5f), + (int)(x.second * 100.0f + 0.5f)); +} + +void +CConfig::readSection(CConfigReadContext& s) +{ + static const char s_section[] = "section:"; + static const char s_options[] = "options"; + static const char s_screens[] = "screens"; + static const char s_links[] = "links"; + static const char s_aliases[] = "aliases"; + + CString line; + if (!s.readLine(line)) { + // no more sections + return; + } + + // should be a section header + if (line.find(s_section) != 0) { + throw XConfigRead(s, "found data outside section"); + } + + // get section name + CString::size_type i = line.find_first_not_of(" \t", sizeof(s_section) - 1); + if (i == CString::npos) { + throw XConfigRead(s, "section name is missing"); + } + CString name = line.substr(i); + i = name.find_first_of(" \t"); + if (i != CString::npos) { + throw XConfigRead(s, "unexpected data after section name"); + } + + // read section + if (name == s_options) { + readSectionOptions(s); + } + else if (name == s_screens) { + readSectionScreens(s); + } + else if (name == s_links) { + readSectionLinks(s); + } + else if (name == s_aliases) { + readSectionAliases(s); + } + else { + throw XConfigRead(s, "unknown section name \"%{1}\"", name); + } +} + +void +CConfig::readSectionOptions(CConfigReadContext& s) +{ + CString line; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // parse argument: `nameAndArgs = [values][;[values]]' + // nameAndArgs := [(arg[,...])] + // values := valueAndArgs[,valueAndArgs]... + // valueAndArgs := [(arg[,...])] + CString::size_type i = 0; + CString name, value; + CConfigReadContext::ArgList nameArgs, valueArgs; + s.parseNameWithArgs("name", line, "=", i, name, nameArgs); + ++i; + s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs); + + bool handled = true; + if (name == "address") { + try { + m_synergyAddress = CNetworkAddress(value, kDefaultPort); + m_synergyAddress.resolve(); + } + catch (XSocketAddress& e) { + throw XConfigRead(s, + CString("invalid address argument ") + e.what()); + } + } + else if (name == "heartbeat") { + addOption("", kOptionHeartbeat, s.parseInt(value)); + } + else if (name == "switchCorners") { + addOption("", kOptionScreenSwitchCorners, s.parseCorners(value)); + } + else if (name == "switchCornerSize") { + addOption("", kOptionScreenSwitchCornerSize, s.parseInt(value)); + } + else if (name == "switchDelay") { + addOption("", kOptionScreenSwitchDelay, s.parseInt(value)); + } + else if (name == "switchDoubleTap") { + addOption("", kOptionScreenSwitchTwoTap, s.parseInt(value)); + } + else if (name == "screenSaverSync") { + addOption("", kOptionScreenSaverSync, s.parseBoolean(value)); + } + else if (name == "relativeMouseMoves") { + addOption("", kOptionRelativeMouseMoves, s.parseBoolean(value)); + } + else if (name == "win32KeepForeground") { + addOption("", kOptionWin32KeepForeground, s.parseBoolean(value)); + } + else { + handled = false; + } + + if (handled) { + // make sure handled options aren't followed by more values + if (i < line.size() && (line[i] == ',' || line[i] == ';')) { + throw XConfigRead(s, "to many arguments to %s", name.c_str()); + } + } + else { + // make filter rule + CInputFilter::CRule rule(parseCondition(s, name, nameArgs)); + + // save first action (if any) + if (!value.empty() || line[i] != ';') { + parseAction(s, value, valueArgs, rule, true); + } + + // get remaining activate actions + while (i < line.length() && line[i] != ';') { + ++i; + s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs); + parseAction(s, value, valueArgs, rule, true); + } + + // get deactivate actions + if (i < line.length() && line[i] == ';') { + // allow trailing ';' + i = line.find_first_not_of(" \t", i + 1); + if (i == CString::npos) { + i = line.length(); + } + else { + --i; + } + + // get actions + while (i < line.length()) { + ++i; + s.parseNameWithArgs("value", line, ",\n", + i, value, valueArgs); + parseAction(s, value, valueArgs, rule, false); + } + } + + // add rule + m_inputFilter.addFilterRule(rule); + } + } + throw XConfigRead(s, "unexpected end of options section"); +} + +void +CConfig::readSectionScreens(CConfigReadContext& s) +{ + CString line; + CString screen; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // see if it's the next screen + if (line[line.size() - 1] == ':') { + // strip : + screen = line.substr(0, line.size() - 1); + + // verify validity of screen name + if (!isValidScreenName(screen)) { + throw XConfigRead(s, "invalid screen name \"%{1}\"", screen); + } + + // add the screen to the configuration + if (!addScreen(screen)) { + throw XConfigRead(s, "duplicate screen name \"%{1}\"", screen); + } + } + else if (screen.empty()) { + throw XConfigRead(s, "argument before first screen"); + } + else { + // parse argument: `=' + CString::size_type i = line.find_first_of(" \t="); + if (i == 0) { + throw XConfigRead(s, "missing argument name"); + } + if (i == CString::npos) { + throw XConfigRead(s, "missing ="); + } + CString name = line.substr(0, i); + i = line.find_first_not_of(" \t", i); + if (i == CString::npos || line[i] != '=') { + throw XConfigRead(s, "missing ="); + } + i = line.find_first_not_of(" \t", i + 1); + CString value; + if (i != CString::npos) { + value = line.substr(i); + } + + // handle argument + if (name == "halfDuplexCapsLock") { + addOption(screen, kOptionHalfDuplexCapsLock, + s.parseBoolean(value)); + } + else if (name == "halfDuplexNumLock") { + addOption(screen, kOptionHalfDuplexNumLock, + s.parseBoolean(value)); + } + else if (name == "halfDuplexScrollLock") { + addOption(screen, kOptionHalfDuplexScrollLock, + s.parseBoolean(value)); + } + else if (name == "shift") { + addOption(screen, kOptionModifierMapForShift, + s.parseModifierKey(value)); + } + else if (name == "ctrl") { + addOption(screen, kOptionModifierMapForControl, + s.parseModifierKey(value)); + } + else if (name == "alt") { + addOption(screen, kOptionModifierMapForAlt, + s.parseModifierKey(value)); + } + else if (name == "meta") { + addOption(screen, kOptionModifierMapForMeta, + s.parseModifierKey(value)); + } + else if (name == "super") { + addOption(screen, kOptionModifierMapForSuper, + s.parseModifierKey(value)); + } + else if (name == "xtestIsXineramaUnaware") { + addOption(screen, kOptionXTestXineramaUnaware, + s.parseBoolean(value)); + } + else if (name == "switchCorners") { + addOption(screen, kOptionScreenSwitchCorners, + s.parseCorners(value)); + } + else if (name == "switchCornerSize") { + addOption(screen, kOptionScreenSwitchCornerSize, + s.parseInt(value)); + } + else { + // unknown argument + throw XConfigRead(s, "unknown argument \"%{1}\"", name); + } + } + } + throw XConfigRead(s, "unexpected end of screens section"); +} + +void +CConfig::readSectionLinks(CConfigReadContext& s) +{ + CString line; + CString screen; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // see if it's the next screen + if (line[line.size() - 1] == ':') { + // strip : + screen = line.substr(0, line.size() - 1); + + // verify we know about the screen + if (!isScreen(screen)) { + throw XConfigRead(s, "unknown screen name \"%{1}\"", screen); + } + if (!isCanonicalName(screen)) { + throw XConfigRead(s, "cannot use screen name alias here"); + } + } + else if (screen.empty()) { + throw XConfigRead(s, "argument before first screen"); + } + else { + // parse argument: `[(,)]=[(,)]' + // the stuff in brackets is optional. interval values must be + // in the range [0,100] and start < end. if not given the + // interval is taken to be (0,100). + CString::size_type i = 0; + CString side, dstScreen, srcArgString, dstArgString; + CConfigReadContext::ArgList srcArgs, dstArgs; + s.parseNameWithArgs("link", line, "=", i, side, srcArgs); + ++i; + s.parseNameWithArgs("screen", line, "", i, dstScreen, dstArgs); + CInterval srcInterval(s.parseInterval(srcArgs)); + CInterval dstInterval(s.parseInterval(dstArgs)); + + // handle argument + EDirection dir; + if (side == "left") { + dir = kLeft; + } + else if (side == "right") { + dir = kRight; + } + else if (side == "up") { + dir = kTop; + } + else if (side == "down") { + dir = kBottom; + } + else { + // unknown argument + throw XConfigRead(s, "unknown side \"%{1}\" in link", side); + } + if (!isScreen(dstScreen)) { + throw XConfigRead(s, "unknown screen name \"%{1}\"", dstScreen); + } + if (!connect(screen, dir, + srcInterval.first, srcInterval.second, + dstScreen, + dstInterval.first, dstInterval.second)) { + throw XConfigRead(s, "overlapping range"); + } + } + } + throw XConfigRead(s, "unexpected end of links section"); +} + +void +CConfig::readSectionAliases(CConfigReadContext& s) +{ + CString line; + CString screen; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // see if it's the next screen + if (line[line.size() - 1] == ':') { + // strip : + screen = line.substr(0, line.size() - 1); + + // verify we know about the screen + if (!isScreen(screen)) { + throw XConfigRead(s, "unknown screen name \"%{1}\"", screen); + } + if (!isCanonicalName(screen)) { + throw XConfigRead(s, "cannot use screen name alias here"); + } + } + else if (screen.empty()) { + throw XConfigRead(s, "argument before first screen"); + } + else { + // verify validity of screen name + if (!isValidScreenName(line)) { + throw XConfigRead(s, "invalid screen alias \"%{1}\"", line); + } + + // add alias + if (!addAlias(screen, line)) { + throw XConfigRead(s, "alias \"%{1}\" is already used", line); + } + } + } + throw XConfigRead(s, "unexpected end of aliases section"); +} + + +CInputFilter::CCondition* +CConfig::parseCondition(CConfigReadContext& s, + const CString& name, const std::vector& args) +{ + if (name == "keystroke") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for condition: keystroke(modifiers+key)"); + } + + IPlatformScreen::CKeyInfo* keyInfo = s.parseKeystroke(args[0]); + + return new CInputFilter::CKeystrokeCondition(keyInfo); + } + + if (name == "mousebutton") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for condition: mousebutton(modifiers+button)"); + } + + IPlatformScreen::CButtonInfo* mouseInfo = s.parseMouse(args[0]); + + return new CInputFilter::CMouseButtonCondition(mouseInfo); + } + + if (name == "connect") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for condition: connect([screen])"); + } + + CString screen = args[0]; + if (isScreen(screen)) { + screen = getCanonicalName(screen); + } + else if (!screen.empty()) { + throw XConfigRead(s, "unknown screen name \"%{1}\" in connect", screen); + } + + return new CInputFilter::CScreenConnectedCondition(screen); + } + + throw XConfigRead(s, "unknown argument \"%{1}\"", name); +} + +void +CConfig::parseAction(CConfigReadContext& s, + const CString& name, const std::vector& args, + CInputFilter::CRule& rule, bool activate) +{ + CInputFilter::CAction* action; + + if (name == "keystroke" || name == "keyDown" || name == "keyUp") { + if (args.size() < 1 || args.size() > 2) { + throw XConfigRead(s, "syntax for action: keystroke(modifiers+key[,screens])"); + } + + IPlatformScreen::CKeyInfo* keyInfo; + if (args.size() == 1) { + keyInfo = s.parseKeystroke(args[0]); + } + else { + std::set screens; + parseScreens(s, args[1], screens); + keyInfo = s.parseKeystroke(args[0], screens); + } + + if (name == "keystroke") { + IPlatformScreen::CKeyInfo* keyInfo2 = + IKeyState::CKeyInfo::alloc(*keyInfo); + action = new CInputFilter::CKeystrokeAction(keyInfo2, true); + rule.adoptAction(action, true); + action = new CInputFilter::CKeystrokeAction(keyInfo, false); + activate = false; + } + else if (name == "keyDown") { + action = new CInputFilter::CKeystrokeAction(keyInfo, true); + } + else { + action = new CInputFilter::CKeystrokeAction(keyInfo, false); + } + } + + else if (name == "mousebutton" || + name == "mouseDown" || name == "mouseUp") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: mousebutton(modifiers+button)"); + } + + IPlatformScreen::CButtonInfo* mouseInfo = s.parseMouse(args[0]); + + if (name == "mousebutton") { + IPlatformScreen::CButtonInfo* mouseInfo2 = + IPlatformScreen::CButtonInfo::alloc(*mouseInfo); + action = new CInputFilter::CMouseButtonAction(mouseInfo2, true); + rule.adoptAction(action, true); + action = new CInputFilter::CMouseButtonAction(mouseInfo, false); + activate = false; + } + else if (name == "mouseDown") { + action = new CInputFilter::CMouseButtonAction(mouseInfo, true); + } + else { + action = new CInputFilter::CMouseButtonAction(mouseInfo, false); + } + } + +/* XXX -- not supported + else if (name == "modifier") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: modifier(modifiers)"); + } + + KeyModifierMask mask = s.parseModifier(args[0]); + + action = new CInputFilter::CModifierAction(mask, ~mask); + } +*/ + + else if (name == "switchToScreen") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: switchToScreen(name)"); + } + + CString screen = args[0]; + if (isScreen(screen)) { + screen = getCanonicalName(screen); + } + else if (!screen.empty()) { + throw XConfigRead(s, "unknown screen name in switchToScreen"); + } + + action = new CInputFilter::CSwitchToScreenAction(screen); + } + + else if (name == "switchInDirection") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: switchInDirection()"); + } + + EDirection direction; + if (args[0] == "left") { + direction = kLeft; + } + else if (args[0] == "right") { + direction = kRight; + } + else if (args[0] == "up") { + direction = kTop; + } + else if (args[0] == "down") { + direction = kBottom; + } + else { + throw XConfigRead(s, "unknown direction \"%{1}\" in switchToScreen", args[0]); + } + + action = new CInputFilter::CSwitchInDirectionAction(direction); + } + + else if (name == "lockCursorToScreen") { + if (args.size() > 1) { + throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])"); + } + + CInputFilter::CLockCursorToScreenAction::Mode mode = + CInputFilter::CLockCursorToScreenAction::kToggle; + if (args.size() == 1) { + if (args[0] == "off") { + mode = CInputFilter::CLockCursorToScreenAction::kOff; + } + else if (args[0] == "on") { + mode = CInputFilter::CLockCursorToScreenAction::kOn; + } + else if (args[0] == "toggle") { + mode = CInputFilter::CLockCursorToScreenAction::kToggle; + } + else { + throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])"); + } + } + + if (mode != CInputFilter::CLockCursorToScreenAction::kOff) { + m_hasLockToScreenAction = true; + } + + action = new CInputFilter::CLockCursorToScreenAction(mode); + } + + else if (name == "keyboardBroadcast") { + if (args.size() > 2) { + throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])"); + } + + CInputFilter::CKeyboardBroadcastAction::Mode mode = + CInputFilter::CKeyboardBroadcastAction::kToggle; + if (args.size() >= 1) { + if (args[0] == "off") { + mode = CInputFilter::CKeyboardBroadcastAction::kOff; + } + else if (args[0] == "on") { + mode = CInputFilter::CKeyboardBroadcastAction::kOn; + } + else if (args[0] == "toggle") { + mode = CInputFilter::CKeyboardBroadcastAction::kToggle; + } + else { + throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])"); + } + } + + std::set screens; + if (args.size() >= 2) { + parseScreens(s, args[1], screens); + } + + action = new CInputFilter::CKeyboardBroadcastAction(mode, screens); + } + + else { + throw XConfigRead(s, "unknown action argument \"%{1}\"", name); + } + + rule.adoptAction(action, activate); +} + +void +CConfig::parseScreens(CConfigReadContext& c, + const CString& s, std::set& screens) const +{ + screens.clear(); + + CString::size_type i = 0; + while (i < s.size()) { + // find end of next screen name + CString::size_type j = s.find(':', i); + if (j == CString::npos) { + j = s.size(); + } + + // extract name + CString rawName; + i = s.find_first_not_of(" \t", i); + if (i < j) { + rawName = s.substr(i, s.find_last_not_of(" \t", j - 1) - i + 1); + } + + // add name + if (rawName == "*") { + screens.insert("*"); + } + else if (!rawName.empty()) { + CString name = getCanonicalName(rawName); + if (name.empty()) { + throw XConfigRead(c, "unknown screen name \"%{1}\"", rawName); + } + screens.insert(name); + } + + // next + i = j + 1; + } +} + +const char* +CConfig::getOptionName(OptionID id) +{ + if (id == kOptionHalfDuplexCapsLock) { + return "halfDuplexCapsLock"; + } + if (id == kOptionHalfDuplexNumLock) { + return "halfDuplexNumLock"; + } + if (id == kOptionHalfDuplexScrollLock) { + return "halfDuplexScrollLock"; + } + if (id == kOptionModifierMapForShift) { + return "shift"; + } + if (id == kOptionModifierMapForControl) { + return "ctrl"; + } + if (id == kOptionModifierMapForAlt) { + return "alt"; + } + if (id == kOptionModifierMapForMeta) { + return "meta"; + } + if (id == kOptionModifierMapForSuper) { + return "super"; + } + if (id == kOptionHeartbeat) { + return "heartbeat"; + } + if (id == kOptionScreenSwitchCorners) { + return "switchCorners"; + } + if (id == kOptionScreenSwitchCornerSize) { + return "switchCornerSize"; + } + if (id == kOptionScreenSwitchDelay) { + return "switchDelay"; + } + if (id == kOptionScreenSwitchTwoTap) { + return "switchDoubleTap"; + } + if (id == kOptionScreenSaverSync) { + return "screenSaverSync"; + } + if (id == kOptionXTestXineramaUnaware) { + return "xtestIsXineramaUnaware"; + } + if (id == kOptionRelativeMouseMoves) { + return "relativeMouseMoves"; + } + if (id == kOptionWin32KeepForeground) { + return "win32KeepForeground"; + } + return NULL; +} + +CString +CConfig::getOptionValue(OptionID id, OptionValue value) +{ + if (id == kOptionHalfDuplexCapsLock || + id == kOptionHalfDuplexNumLock || + id == kOptionHalfDuplexScrollLock || + id == kOptionScreenSaverSync || + id == kOptionXTestXineramaUnaware || + id == kOptionRelativeMouseMoves || + id == kOptionWin32KeepForeground) { + return (value != 0) ? "true" : "false"; + } + if (id == kOptionModifierMapForShift || + id == kOptionModifierMapForControl || + id == kOptionModifierMapForAlt || + id == kOptionModifierMapForMeta || + id == kOptionModifierMapForSuper) { + switch (value) { + case kKeyModifierIDShift: + return "shift"; + + case kKeyModifierIDControl: + return "ctrl"; + + case kKeyModifierIDAlt: + return "alt"; + + case kKeyModifierIDMeta: + return "meta"; + + case kKeyModifierIDSuper: + return "super"; + + default: + return "none"; + } + } + if (id == kOptionHeartbeat || + id == kOptionScreenSwitchCornerSize || + id == kOptionScreenSwitchDelay || + id == kOptionScreenSwitchTwoTap) { + return CStringUtil::print("%d", value); + } + if (id == kOptionScreenSwitchCorners) { + std::string result("none"); + if ((value & kTopLeftMask) != 0) { + result += " +top-left"; + } + if ((value & kTopRightMask) != 0) { + result += " +top-right"; + } + if ((value & kBottomLeftMask) != 0) { + result += " +bottom-left"; + } + if ((value & kBottomRightMask) != 0) { + result += " +bottom-right"; + } + return result; + } + + return ""; +} + + +// +// CConfig::CName +// + +CConfig::CName::CName(CConfig* config, const CString& name) : + m_config(config), + m_name(config->getCanonicalName(name)) +{ + // do nothing +} + +bool +CConfig::CName::operator==(const CString& name) const +{ + CString canonical = m_config->getCanonicalName(name); + return CStringUtil::CaselessCmp::equal(canonical, m_name); +} + + +// +// CConfig::CCellEdge +// + +CConfig::CCellEdge::CCellEdge(EDirection side, float position) +{ + init("", side, CInterval(position, position)); +} + +CConfig::CCellEdge::CCellEdge(EDirection side, const CInterval& interval) +{ + assert(interval.first >= 0.0f); + assert(interval.second <= 1.0f); + assert(interval.first < interval.second); + + init("", side, interval); +} + +CConfig::CCellEdge::CCellEdge(const CString& name, + EDirection side, const CInterval& interval) +{ + assert(interval.first >= 0.0f); + assert(interval.second <= 1.0f); + assert(interval.first < interval.second); + + init(name, side, interval); +} + +CConfig::CCellEdge::~CCellEdge() +{ + // do nothing +} + +void +CConfig::CCellEdge::init(const CString& name, EDirection side, + const CInterval& interval) +{ + assert(side != kNoDirection); + + m_name = name; + m_side = side; + m_interval = interval; +} + +CConfig::CInterval +CConfig::CCellEdge::getInterval() const +{ + return m_interval; +} + +void +CConfig::CCellEdge::setName(const CString& newName) +{ + m_name = newName; +} + +CString +CConfig::CCellEdge::getName() const +{ + return m_name; +} + +EDirection +CConfig::CCellEdge::getSide() const +{ + return m_side; +} + +bool +CConfig::CCellEdge::overlaps(const CCellEdge& edge) const +{ + const CInterval& x = m_interval; + const CInterval& y = edge.m_interval; + if (m_side != edge.m_side) { + return false; + } + return (x.first >= y.first && x.first < y.second) || + (x.second > y.first && x.second <= y.second) || + (y.first >= x.first && y.first < x.second) || + (y.second > x.first && y.second <= x.second); +} + +bool +CConfig::CCellEdge::isInside(float x) const +{ + return (x >= m_interval.first && x < m_interval.second); +} + +float +CConfig::CCellEdge::transform(float x) const +{ + return (x - m_interval.first) / (m_interval.second - m_interval.first); +} + + +float +CConfig::CCellEdge::inverseTransform(float x) const +{ + return x * (m_interval.second - m_interval.first) + m_interval.first; +} + +bool +CConfig::CCellEdge::operator<(const CCellEdge& o) const +{ + if (static_cast(m_side) < static_cast(o.m_side)) { + return true; + } + else if (static_cast(m_side) > static_cast(o.m_side)) { + return false; + } + + return (m_interval.first < o.m_interval.first); +} + +bool +CConfig::CCellEdge::operator==(const CCellEdge& x) const +{ + return (m_side == x.m_side && m_interval == x.m_interval); +} + +bool +CConfig::CCellEdge::operator!=(const CCellEdge& x) const +{ + return !operator==(x); +} + + +// +// CConfig::CCell +// + +bool +CConfig::CCell::add(const CCellEdge& src, const CCellEdge& dst) +{ + // cannot add an edge that overlaps other existing edges but we + // can exactly replace an edge. + if (!hasEdge(src) && overlaps(src)) { + return false; + } + + m_neighbors.erase(src); + m_neighbors.insert(std::make_pair(src, dst)); + return true; +} + +void +CConfig::CCell::remove(EDirection side) +{ + for (CEdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ) { + if (j->first.getSide() == side) { + m_neighbors.erase(j++); + } + else { + ++j; + } + } +} + +void +CConfig::CCell::remove(EDirection side, float position) +{ + for (CEdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ++j) { + if (j->first.getSide() == side && j->first.isInside(position)) { + m_neighbors.erase(j); + break; + } + } +} +void +CConfig::CCell::remove(const CName& name) +{ + for (CEdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ) { + if (name == j->second.getName()) { + m_neighbors.erase(j++); + } + else { + ++j; + } + } +} + +void +CConfig::CCell::rename(const CName& oldName, const CString& newName) +{ + for (CEdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ++j) { + if (oldName == j->second.getName()) { + j->second.setName(newName); + } + } +} + +bool +CConfig::CCell::hasEdge(const CCellEdge& edge) const +{ + CEdgeLinks::const_iterator i = m_neighbors.find(edge); + return (i != m_neighbors.end() && i->first == edge); +} + +bool +CConfig::CCell::overlaps(const CCellEdge& edge) const +{ + CEdgeLinks::const_iterator i = m_neighbors.upper_bound(edge); + if (i != m_neighbors.end() && i->first.overlaps(edge)) { + return true; + } + if (i != m_neighbors.begin() && (--i)->first.overlaps(edge)) { + return true; + } + return false; +} + +bool +CConfig::CCell::getLink(EDirection side, float position, + const CCellEdge*& src, const CCellEdge*& dst) const +{ + CCellEdge edge(side, position); + CEdgeLinks::const_iterator i = m_neighbors.upper_bound(edge); + if (i == m_neighbors.begin()) { + return false; + } + --i; + if (i->first.getSide() == side && i->first.isInside(position)) { + src = &i->first; + dst = &i->second; + return true; + } + return false; +} + +bool +CConfig::CCell::operator==(const CCell& x) const +{ + // compare options + if (m_options != x.m_options) { + return false; + } + + // compare links + if (m_neighbors.size() != x.m_neighbors.size()) { + return false; + } + for (CEdgeLinks::const_iterator index1 = m_neighbors.begin(), + index2 = x.m_neighbors.begin(); + index1 != m_neighbors.end(); + ++index1, ++index2) { + if (index1->first != index2->first) { + return false; + } + if (index1->second != index2->second) { + return false; + } + + // operator== doesn't compare names. only compare destination + // names. + if (!CStringUtil::CaselessCmp::equal(index1->second.getName(), + index2->second.getName())) { + return false; + } + } + return true; +} + +bool +CConfig::CCell::operator!=(const CCell& x) const +{ + return !operator==(x); +} + +CConfig::CCell::const_iterator +CConfig::CCell::begin() const +{ + return m_neighbors.begin(); +} + +CConfig::CCell::const_iterator +CConfig::CCell::end() const +{ + return m_neighbors.end(); +} + + +// +// CConfig I/O +// + +std::istream& +operator>>(std::istream& s, CConfig& config) +{ + CConfigReadContext context(s); + config.read(context); + return s; +} + +std::ostream& +operator<<(std::ostream& s, const CConfig& config) +{ + // screens section + s << "section: screens" << std::endl; + for (CConfig::const_iterator screen = config.begin(); + screen != config.end(); ++screen) { + s << "\t" << screen->c_str() << ":" << std::endl; + const CConfig::CScreenOptions* options = config.getOptions(*screen); + if (options != NULL && options->size() > 0) { + for (CConfig::CScreenOptions::const_iterator + option = options->begin(); + option != options->end(); ++option) { + const char* name = CConfig::getOptionName(option->first); + CString value = CConfig::getOptionValue(option->first, + option->second); + if (name != NULL && !value.empty()) { + s << "\t\t" << name << " = " << value << std::endl; + } + } + } + } + s << "end" << std::endl; + + // links section + CString neighbor; + s << "section: links" << std::endl; + for (CConfig::const_iterator screen = config.begin(); + screen != config.end(); ++screen) { + s << "\t" << screen->c_str() << ":" << std::endl; + + for (CConfig::link_const_iterator + link = config.beginNeighbor(*screen), + nend = config.endNeighbor(*screen); link != nend; ++link) { + s << "\t\t" << CConfig::dirName(link->first.getSide()) << + CConfig::formatInterval(link->first.getInterval()) << + " = " << link->second.getName().c_str() << + CConfig::formatInterval(link->second.getInterval()) << + std::endl; + } + } + s << "end" << std::endl; + + // aliases section (if there are any) + if (config.m_map.size() != config.m_nameToCanonicalName.size()) { + // map canonical to alias + typedef std::multimap CMNameMap; + CMNameMap aliases; + for (CConfig::CNameMap::const_iterator + index = config.m_nameToCanonicalName.begin(); + index != config.m_nameToCanonicalName.end(); + ++index) { + if (index->first != index->second) { + aliases.insert(std::make_pair(index->second, index->first)); + } + } + + // dump it + CString screen; + s << "section: aliases" << std::endl; + for (CMNameMap::const_iterator index = aliases.begin(); + index != aliases.end(); ++index) { + if (index->first != screen) { + screen = index->first; + s << "\t" << screen.c_str() << ":" << std::endl; + } + s << "\t\t" << index->second.c_str() << std::endl; + } + s << "end" << std::endl; + } + + // options section + s << "section: options" << std::endl; + const CConfig::CScreenOptions* options = config.getOptions(""); + if (options != NULL && options->size() > 0) { + for (CConfig::CScreenOptions::const_iterator + option = options->begin(); + option != options->end(); ++option) { + const char* name = CConfig::getOptionName(option->first); + CString value = CConfig::getOptionValue(option->first, + option->second); + if (name != NULL && !value.empty()) { + s << "\t" << name << " = " << value << std::endl; + } + } + } + if (config.m_synergyAddress.isValid()) { + s << "\taddress = " << + config.m_synergyAddress.getHostname().c_str() << std::endl; + } + s << config.m_inputFilter.format("\t"); + s << "end" << std::endl; + + return s; +} + + +// +// CConfigReadContext +// + +CConfigReadContext::CConfigReadContext(std::istream& s, SInt32 firstLine) : + m_stream(s), + m_line(firstLine - 1) +{ + // do nothing +} + +CConfigReadContext::~CConfigReadContext() +{ + // do nothing +} + +bool +CConfigReadContext::readLine(CString& line) +{ + ++m_line; + while (std::getline(m_stream, line)) { + // strip leading whitespace + CString::size_type i = line.find_first_not_of(" \t"); + if (i != CString::npos) { + line.erase(0, i); + } + + // strip comments and then trailing whitespace + i = line.find('#'); + if (i != CString::npos) { + line.erase(i); + } + i = line.find_last_not_of(" \r\t"); + if (i != CString::npos) { + line.erase(i + 1); + } + + // return non empty line + if (!line.empty()) { + // make sure there are no invalid characters + for (i = 0; i < line.length(); ++i) { + if (!isgraph(line[i]) && line[i] != ' ' && line[i] != '\t') { + throw XConfigRead(*this, + "invalid character %{1}", + CStringUtil::print("%#2x", line[i])); + } + } + + return true; + } + + // next line + ++m_line; + } + return false; +} + +UInt32 +CConfigReadContext::getLineNumber() const +{ + return m_line; +} + +CConfigReadContext::operator void*() const +{ + return m_stream; +} + +bool +CConfigReadContext::operator!() const +{ + return !m_stream; +} + +OptionValue +CConfigReadContext::parseBoolean(const CString& arg) const +{ + if (CStringUtil::CaselessCmp::equal(arg, "true")) { + return static_cast(true); + } + if (CStringUtil::CaselessCmp::equal(arg, "false")) { + return static_cast(false); + } + throw XConfigRead(*this, "invalid boolean argument \"%{1}\"", arg); +} + +OptionValue +CConfigReadContext::parseInt(const CString& arg) const +{ + const char* s = arg.c_str(); + char* end; + long tmp = strtol(s, &end, 10); + if (*end != '\0') { + // invalid characters + throw XConfigRead(*this, "invalid integer argument \"%{1}\"", arg); + } + OptionValue value = static_cast(tmp); + if (value != tmp) { + // out of range + throw XConfigRead(*this, "integer argument \"%{1}\" out of range", arg); + } + return value; +} + +OptionValue +CConfigReadContext::parseModifierKey(const CString& arg) const +{ + if (CStringUtil::CaselessCmp::equal(arg, "shift")) { + return static_cast(kKeyModifierIDShift); + } + if (CStringUtil::CaselessCmp::equal(arg, "ctrl")) { + return static_cast(kKeyModifierIDControl); + } + if (CStringUtil::CaselessCmp::equal(arg, "alt")) { + return static_cast(kKeyModifierIDAlt); + } + if (CStringUtil::CaselessCmp::equal(arg, "meta")) { + return static_cast(kKeyModifierIDMeta); + } + if (CStringUtil::CaselessCmp::equal(arg, "super")) { + return static_cast(kKeyModifierIDSuper); + } + if (CStringUtil::CaselessCmp::equal(arg, "none")) { + return static_cast(kKeyModifierIDNull); + } + throw XConfigRead(*this, "invalid argument \"%{1}\"", arg); +} + +OptionValue +CConfigReadContext::parseCorner(const CString& arg) const +{ + if (CStringUtil::CaselessCmp::equal(arg, "left")) { + return kTopLeftMask | kBottomLeftMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "right")) { + return kTopRightMask | kBottomRightMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "top")) { + return kTopLeftMask | kTopRightMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "bottom")) { + return kBottomLeftMask | kBottomRightMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "top-left")) { + return kTopLeftMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "top-right")) { + return kTopRightMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "bottom-left")) { + return kBottomLeftMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "bottom-right")) { + return kBottomRightMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "none")) { + return kNoCornerMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "all")) { + return kAllCornersMask; + } + throw XConfigRead(*this, "invalid argument \"%{1}\"", arg); +} + +OptionValue +CConfigReadContext::parseCorners(const CString& args) const +{ + // find first token + CString::size_type i = args.find_first_not_of(" \t", 0); + if (i == CString::npos) { + throw XConfigRead(*this, "missing corner argument"); + } + CString::size_type j = args.find_first_of(" \t", i); + + // parse first corner token + OptionValue corners = parseCorner(args.substr(i, j - i)); + + // get +/- + i = args.find_first_not_of(" \t", j); + while (i != CString::npos) { + // parse +/- + bool add; + if (args[i] == '-') { + add = false; + } + else if (args[i] == '+') { + add = true; + } + else { + throw XConfigRead(*this, + "invalid corner operator \"%{1}\"", + CString(args.c_str() + i, 1)); + } + + // get next corner token + i = args.find_first_not_of(" \t", i + 1); + j = args.find_first_of(" \t", i); + if (i == CString::npos) { + throw XConfigRead(*this, "missing corner argument"); + } + + // parse next corner token + if (add) { + corners |= parseCorner(args.substr(i, j - i)); + } + else { + corners &= ~parseCorner(args.substr(i, j - i)); + } + i = args.find_first_not_of(" \t", j); + } + + return corners; +} + +CConfig::CInterval +CConfigReadContext::parseInterval(const ArgList& args) const +{ + if (args.size() == 0) { + return CConfig::CInterval(0.0f, 1.0f); + } + if (args.size() != 2 || args[0].empty() || args[1].empty()) { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + + char* end; + long startValue = strtol(args[0].c_str(), &end, 10); + if (end[0] != '\0') { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + long endValue = strtol(args[1].c_str(), &end, 10); + if (end[0] != '\0') { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + + if (startValue < 0 || startValue > 100 || + endValue < 0 || endValue > 100 || + startValue >= endValue) { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + + return CConfig::CInterval(startValue / 100.0f, endValue / 100.0f); +} + +void +CConfigReadContext::parseNameWithArgs( + const CString& type, const CString& line, + const CString& delim, CString::size_type& index, + CString& name, ArgList& args) const +{ + // skip leading whitespace + CString::size_type i = line.find_first_not_of(" \t", index); + if (i == CString::npos) { + throw XConfigRead(*this, CString("missing ") + type); + } + + // find end of name + CString::size_type j = line.find_first_of(" \t(" + delim, i); + if (j == CString::npos) { + j = line.length(); + } + + // save name + name = line.substr(i, j - i); + args.clear(); + + // is it okay to not find a delimiter? + bool needDelim = (!delim.empty() && delim.find('\n') == CString::npos); + + // skip whitespace + i = line.find_first_not_of(" \t", j); + if (i == CString::npos && needDelim) { + // expected delimiter but didn't find it + throw XConfigRead(*this, CString("missing ") + delim[0]); + } + if (i == CString::npos) { + // no arguments + index = line.length(); + return; + } + if (line[i] != '(') { + // no arguments + index = i; + return; + } + + // eat '(' + ++i; + + // parse arguments + j = line.find_first_of(",)", i); + while (j != CString::npos) { + // extract arg + CString arg(line.substr(i, j - i)); + i = j; + + // trim whitespace + j = arg.find_first_not_of(" \t"); + if (j != CString::npos) { + arg.erase(0, j); + } + j = arg.find_last_not_of(" \t"); + if (j != CString::npos) { + arg.erase(j + 1); + } + + // save arg + args.push_back(arg); + + // exit loop at end of arguments + if (line[i] == ')') { + break; + } + + // eat ',' + ++i; + + // next + j = line.find_first_of(",)", i); + } + + // verify ')' + if (j == CString::npos) { + // expected ) + throw XConfigRead(*this, "missing )"); + } + + // eat ')' + ++i; + + // skip whitespace + j = line.find_first_not_of(" \t", i); + if (j == CString::npos && needDelim) { + // expected delimiter but didn't find it + throw XConfigRead(*this, CString("missing ") + delim[0]); + } + + // verify delimiter + if (needDelim && delim.find(line[j]) == CString::npos) { + throw XConfigRead(*this, CString("expected ") + delim[0]); + } + + if (j == CString::npos) { + j = line.length(); + } + + index = j; + return; +} + +IPlatformScreen::CKeyInfo* +CConfigReadContext::parseKeystroke(const CString& keystroke) const +{ + return parseKeystroke(keystroke, std::set()); +} + +IPlatformScreen::CKeyInfo* +CConfigReadContext::parseKeystroke(const CString& keystroke, + const std::set& screens) const +{ + CString s = keystroke; + + KeyModifierMask mask; + if (!CKeyMap::parseModifiers(s, mask)) { + throw XConfigRead(*this, "unable to parse key modifiers"); + } + + KeyID key; + if (!CKeyMap::parseKey(s, key)) { + throw XConfigRead(*this, "unable to parse key"); + } + + if (key == kKeyNone && mask == 0) { + throw XConfigRead(*this, "missing key and/or modifiers in keystroke"); + } + + return IPlatformScreen::CKeyInfo::alloc(key, mask, 0, 0, screens); +} + +IPlatformScreen::CButtonInfo* +CConfigReadContext::parseMouse(const CString& mouse) const +{ + CString s = mouse; + + KeyModifierMask mask; + if (!CKeyMap::parseModifiers(s, mask)) { + throw XConfigRead(*this, "unable to parse button modifiers"); + } + + char* end; + ButtonID button = (ButtonID)strtol(s.c_str(), &end, 10); + if (*end != '\0') { + throw XConfigRead(*this, "unable to parse button"); + } + if (s.empty() || button <= 0) { + throw XConfigRead(*this, "invalid button"); + } + + return IPlatformScreen::CButtonInfo::alloc(button, mask); +} + +KeyModifierMask +CConfigReadContext::parseModifier(const CString& modifiers) const +{ + CString s = modifiers; + + KeyModifierMask mask; + if (!CKeyMap::parseModifiers(s, mask)) { + throw XConfigRead(*this, "unable to parse modifiers"); + } + + if (mask == 0) { + throw XConfigRead(*this, "no modifiers specified"); + } + + return mask; +} + +CString +CConfigReadContext::concatArgs(const ArgList& args) +{ + CString s("("); + for (size_t i = 0; i < args.size(); ++i) { + if (i != 0) { + s += ","; + } + s += args[i]; + } + s += ")"; + return s; +} + + +// +// CConfig I/O exceptions +// + +XConfigRead::XConfigRead(const CConfigReadContext& context, + const CString& error) : + m_error(CStringUtil::print("line %d: %s", + context.getLineNumber(), error.c_str())) +{ + // do nothing +} + +XConfigRead::XConfigRead(const CConfigReadContext& context, + const char* errorFmt, const CString& arg) : + m_error(CStringUtil::print("line %d: ", context.getLineNumber()) + + CStringUtil::format(errorFmt, arg.c_str())) +{ + // do nothing +} + +XConfigRead::~XConfigRead() +{ + // do nothing +} + +CString +XConfigRead::getWhat() const throw() +{ + return format("XConfigRead", "read error: %{1}", m_error.c_str()); +} diff --git a/lib/server/CConfig.h b/lib/server/CConfig.h new file mode 100644 index 00000000..c0d2faa8 --- /dev/null +++ b/lib/server/CConfig.h @@ -0,0 +1,536 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCONFIG_H +#define CCONFIG_H + +#include "OptionTypes.h" +#include "ProtocolTypes.h" +#include "CNetworkAddress.h" +#include "CStringUtil.h" +#include "CInputFilter.h" +#include "XBase.h" +#include "stdmap.h" +#include "stdset.h" +#include "IPlatformScreen.h" +#include + +class CConfig; +class CConfigReadContext; + +namespace std { +template <> +struct iterator_traits { + typedef CString value_type; + typedef ptrdiff_t difference_type; + typedef bidirectional_iterator_tag iterator_category; + typedef CString* pointer; + typedef CString& reference; +}; +}; + +//! Server configuration +/*! +This class holds server configuration information. That includes +the names of screens and their aliases, the links between them, +and network addresses. + +Note that case is preserved in screen names but is ignored when +comparing names. Screen names and their aliases share a +namespace and must be unique. +*/ +class CConfig { +public: + typedef std::map CScreenOptions; + typedef std::pair CInterval; + + class CCellEdge { + public: + CCellEdge(EDirection side, float position); + CCellEdge(EDirection side, const CInterval&); + CCellEdge(const CString& name, EDirection side, const CInterval&); + ~CCellEdge(); + + CInterval getInterval() const; + void setName(const CString& newName); + CString getName() const; + EDirection getSide() const; + bool overlaps(const CCellEdge&) const; + bool isInside(float x) const; + + // transform position to [0,1] + float transform(float x) const; + + // transform [0,1] to position + float inverseTransform(float x) const; + + // compares side and start of interval + bool operator<(const CCellEdge&) const; + + // compares side and interval + bool operator==(const CCellEdge&) const; + bool operator!=(const CCellEdge&) const; + + private: + void init(const CString& name, EDirection side, + const CInterval&); + + private: + CString m_name; + EDirection m_side; + CInterval m_interval; + }; + +private: + class CName { + public: + CName(CConfig*, const CString& name); + + bool operator==(const CString& name) const; + + private: + CConfig* m_config; + CString m_name; + }; + + class CCell { + private: + typedef std::map CEdgeLinks; + + public: + typedef CEdgeLinks::const_iterator const_iterator; + + bool add(const CCellEdge& src, const CCellEdge& dst); + void remove(EDirection side); + void remove(EDirection side, float position); + void remove(const CName& destinationName); + void rename(const CName& oldName, const CString& newName); + + bool hasEdge(const CCellEdge&) const; + bool overlaps(const CCellEdge&) const; + + bool getLink(EDirection side, float position, + const CCellEdge*& src, const CCellEdge*& dst) const; + + bool operator==(const CCell&) const; + bool operator!=(const CCell&) const; + + const_iterator begin() const; + const_iterator end() const; + + private: + CEdgeLinks m_neighbors; + + public: + CScreenOptions m_options; + }; + typedef std::map CCellMap; + typedef std::map CNameMap; + +public: + typedef CCell::const_iterator link_const_iterator; + typedef CCellMap::const_iterator internal_const_iterator; + typedef CNameMap::const_iterator all_const_iterator; + class const_iterator : std::iterator_traits { + public: + explicit const_iterator() : m_i() { } + explicit const_iterator(const internal_const_iterator& i) : m_i(i) { } + + const_iterator& operator=(const const_iterator& i) { + m_i = i.m_i; + return *this; + } + CString operator*() { return m_i->first; } + const CString* operator->() { return &(m_i->first); } + const_iterator& operator++() { ++m_i; return *this; } + const_iterator operator++(int) { return const_iterator(m_i++); } + const_iterator& operator--() { --m_i; return *this; } + const_iterator operator--(int) { return const_iterator(m_i--); } + bool operator==(const const_iterator& i) const { + return (m_i == i.m_i); + } + bool operator!=(const const_iterator& i) const { + return (m_i != i.m_i); + } + + private: + internal_const_iterator m_i; + }; + + CConfig(); + virtual ~CConfig(); + + //! @name manipulators + //@{ + + //! Add screen + /*! + Adds a screen, returning true iff successful. If a screen or + alias with the given name exists then it fails. + */ + bool addScreen(const CString& name); + + //! Rename screen + /*! + Renames a screen. All references to the name are updated. + Returns true iff successful. + */ + bool renameScreen(const CString& oldName, + const CString& newName); + + //! Remove screen + /*! + Removes a screen. This also removes aliases for the screen and + disconnects any connections to the screen. \c name may be an + alias. + */ + void removeScreen(const CString& name); + + //! Remove all screens + /*! + Removes all screens, aliases, and connections. + */ + void removeAllScreens(); + + //! Add alias + /*! + Adds an alias for a screen name. An alias can be used + any place the canonical screen name can (except addScreen()). + Returns false if the alias name already exists or the canonical + name is unknown, otherwise returns true. + */ + bool addAlias(const CString& canonical, + const CString& alias); + + //! Remove alias + /*! + Removes an alias for a screen name. It returns false if the + alias is unknown or a canonical name, otherwise returns true. + */ + bool removeAlias(const CString& alias); + + //! Remove aliases + /*! + Removes all aliases for a canonical screen name. It returns false + if the canonical name is unknown, otherwise returns true. + */ + bool removeAliases(const CString& canonical); + + //! Remove all aliases + /*! + This removes all aliases but not the screens. + */ + void removeAllAliases(); + + //! Connect screens + /*! + Establishes a one-way connection between portions of opposite edges + of two screens. Each portion is described by an interval defined + by two numbers, the start and end of the interval half-open on the + end. The numbers range from 0 to 1, inclusive, for the left/top + to the right/bottom. The user will be able to jump from the + \c srcStart to \c srcSend interval of \c srcSide of screen + \c srcName to the opposite side of screen \c dstName in the interval + \c dstStart and \c dstEnd when both screens are connected to the + server and the user isn't locked to a screen. Returns false if + \c srcName is unknown. \c srcStart must be less than or equal to + \c srcEnd and \c dstStart must be less then or equal to \c dstEnd + and all of \c srcStart, \c srcEnd, \c dstStart, or \c dstEnd must + be inside the range [0,1]. + */ + bool connect(const CString& srcName, + EDirection srcSide, + float srcStart, float srcEnd, + const CString& dstName, + float dstStart, float dstEnd); + + //! Disconnect screens + /*! + Removes all connections created by connect() on side \c srcSide. + Returns false if \c srcName is unknown. + */ + bool disconnect(const CString& srcName, + EDirection srcSide); + + //! Disconnect screens + /*! + Removes the connections created by connect() on side \c srcSide + covering position \c position. Returns false if \c srcName is + unknown. + */ + bool disconnect(const CString& srcName, + EDirection srcSide, float position); + + //! Set server address + /*! + Set the synergy listen addresses. There is no default address so + this must be called to run a server using this configuration. + */ + void setSynergyAddress(const CNetworkAddress&); + + //! Add a screen option + /*! + Adds an option and its value to the named screen. Replaces the + existing option's value if there is one. Returns true iff \c name + is a known screen. + */ + bool addOption(const CString& name, + OptionID option, OptionValue value); + + //! Remove a screen option + /*! + Removes an option and its value from the named screen. Does + nothing if the option doesn't exist on the screen. Returns true + iff \c name is a known screen. + */ + bool removeOption(const CString& name, OptionID option); + + //! Remove a screen options + /*! + Removes all options and values from the named screen. Returns true + iff \c name is a known screen. + */ + bool removeOptions(const CString& name); + + //! Get the hot key input filter + /*! + Returns the hot key input filter. Clients can modify hotkeys using + that object. + */ + CInputFilter* getInputFilter(); + + //@} + //! @name accessors + //@{ + + //! Test screen name validity + /*! + Returns true iff \c name is a valid screen name. + */ + bool isValidScreenName(const CString& name) const; + + //! Get beginning (canonical) screen name iterator + const_iterator begin() const; + //! Get ending (canonical) screen name iterator + const_iterator end() const; + + //! Get beginning screen name iterator + all_const_iterator beginAll() const; + //! Get ending screen name iterator + all_const_iterator endAll() const; + + //! Test for screen name + /*! + Returns true iff \c name names a screen. + */ + bool isScreen(const CString& name) const; + + //! Test for canonical screen name + /*! + Returns true iff \c name is the canonical name of a screen. + */ + bool isCanonicalName(const CString& name) const; + + //! Get canonical name + /*! + Returns the canonical name of a screen or the empty string if + the name is unknown. Returns the canonical name if one is given. + */ + CString getCanonicalName(const CString& name) const; + + //! Get neighbor + /*! + Returns the canonical screen name of the neighbor in the given + direction (set through connect()) at position \c position. Returns + the empty string if there is no neighbor in that direction, otherwise + saves the position on the neighbor in \c positionOut if it's not + \c NULL. + */ + CString getNeighbor(const CString&, EDirection, + float position, float* positionOut) const; + + //! Check for neighbor + /*! + Returns \c true if the screen has a neighbor anywhere along the edge + given by the direction. + */ + bool hasNeighbor(const CString&, EDirection) const; + + //! Check for neighbor + /*! + Returns \c true if the screen has a neighbor in the given range along + the edge given by the direction. + */ + bool hasNeighbor(const CString&, EDirection, + float start, float end) const; + + //! Get beginning neighbor iterator + link_const_iterator beginNeighbor(const CString&) const; + //! Get ending neighbor iterator + link_const_iterator endNeighbor(const CString&) const; + + //! Get the server address + const CNetworkAddress& getSynergyAddress() const; + + //! Get the screen options + /*! + Returns all the added options for the named screen. Returns NULL + if the screen is unknown and an empty collection if there are no + options. + */ + const CScreenOptions* getOptions(const CString& name) const; + + //! Check for lock to screen action + /*! + Returns \c true if this configuration has a lock to screen action. + This is for backwards compatible support of ScrollLock locking. + */ + bool hasLockToScreenAction() const; + + //! Compare configurations + bool operator==(const CConfig&) const; + //! Compare configurations + bool operator!=(const CConfig&) const; + + //! Read configuration + /*! + Reads a configuration from a context. Throws XConfigRead on error + and context is unchanged. + */ + void read(CConfigReadContext& context); + + //! Read configuration + /*! + Reads a configuration from a stream. Throws XConfigRead on error. + */ + friend std::istream& operator>>(std::istream&, CConfig&); + + //! Write configuration + /*! + Writes a configuration to a stream. + */ + friend std::ostream& operator<<(std::ostream&, const CConfig&); + + //! Get direction name + /*! + Returns the name of a direction (for debugging). + */ + static const char* dirName(EDirection); + + //! Get interval as string + /*! + Returns an interval as a parseable string. + */ + static CString formatInterval(const CInterval&); + + //@} + +private: + void readSection(CConfigReadContext&); + void readSectionOptions(CConfigReadContext&); + void readSectionScreens(CConfigReadContext&); + void readSectionLinks(CConfigReadContext&); + void readSectionAliases(CConfigReadContext&); + + CInputFilter::CCondition* + parseCondition(CConfigReadContext&, + const CString& condition, + const std::vector& args); + void parseAction(CConfigReadContext&, + const CString& action, + const std::vector& args, + CInputFilter::CRule&, bool activate); + + void parseScreens(CConfigReadContext&, const CString&, + std::set& screens) const; + static const char* getOptionName(OptionID); + static CString getOptionValue(OptionID, OptionValue); + +private: + CCellMap m_map; + CNameMap m_nameToCanonicalName; + CNetworkAddress m_synergyAddress; + CScreenOptions m_globalOptions; + CInputFilter m_inputFilter; + bool m_hasLockToScreenAction; +}; + +//! Configuration read context +/*! +Maintains a context when reading a configuration from a stream. +*/ +class CConfigReadContext { +public: + typedef std::vector ArgList; + + CConfigReadContext(std::istream&, SInt32 firstLine = 1); + ~CConfigReadContext(); + + bool readLine(CString&); + UInt32 getLineNumber() const; + + operator void*() const; + bool operator!() const; + + OptionValue parseBoolean(const CString&) const; + OptionValue parseInt(const CString&) const; + OptionValue parseModifierKey(const CString&) const; + OptionValue parseCorner(const CString&) const; + OptionValue parseCorners(const CString&) const; + CConfig::CInterval + parseInterval(const ArgList& args) const; + void parseNameWithArgs( + const CString& type, const CString& line, + const CString& delim, CString::size_type& index, + CString& name, ArgList& args) const; + IPlatformScreen::CKeyInfo* + parseKeystroke(const CString& keystroke) const; + IPlatformScreen::CKeyInfo* + parseKeystroke(const CString& keystroke, + const std::set& screens) const; + IPlatformScreen::CButtonInfo* + parseMouse(const CString& mouse) const; + KeyModifierMask parseModifier(const CString& modifiers) const; + +private: + // not implemented + CConfigReadContext& operator=(const CConfigReadContext&); + + static CString concatArgs(const ArgList& args); + +private: + std::istream& m_stream; + SInt32 m_line; +}; + +//! Configuration stream read exception +/*! +Thrown when a configuration stream cannot be parsed. +*/ +class XConfigRead : public XBase { +public: + XConfigRead(const CConfigReadContext& context, const CString&); + XConfigRead(const CConfigReadContext& context, + const char* errorFmt, const CString& arg); + ~XConfigRead(); + +protected: + // XBase overrides + virtual CString getWhat() const throw(); + +private: + CString m_error; +}; + +#endif diff --git a/lib/server/CInputFilter.cpp b/lib/server/CInputFilter.cpp new file mode 100644 index 00000000..d5d7fc20 --- /dev/null +++ b/lib/server/CInputFilter.cpp @@ -0,0 +1,1066 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2005 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 "CInputFilter.h" +#include "CServer.h" +#include "CPrimaryClient.h" +#include "CKeyMap.h" +#include "CEventQueue.h" +#include "CLog.h" +#include "TMethodEventJob.h" +#include +#include + +// ----------------------------------------------------------------------------- +// Input Filter Condition Classes +// ----------------------------------------------------------------------------- +CInputFilter::CCondition::CCondition() +{ + // do nothing +} + +CInputFilter::CCondition::~CCondition() +{ + // do nothing +} + +void +CInputFilter::CCondition::enablePrimary(CPrimaryClient*) +{ + // do nothing +} + +void +CInputFilter::CCondition::disablePrimary(CPrimaryClient*) +{ + // do nothing +} + +CInputFilter::CKeystrokeCondition::CKeystrokeCondition( + IPlatformScreen::CKeyInfo* info) : + m_id(0), + m_key(info->m_key), + m_mask(info->m_mask) +{ + free(info); +} + +CInputFilter::CKeystrokeCondition::CKeystrokeCondition( + KeyID key, KeyModifierMask mask) : + m_id(0), + m_key(key), + m_mask(mask) +{ + // do nothing +} + +CInputFilter::CKeystrokeCondition::~CKeystrokeCondition() +{ + // do nothing +} + +KeyID +CInputFilter::CKeystrokeCondition::getKey() const +{ + return m_key; +} + +KeyModifierMask +CInputFilter::CKeystrokeCondition::getMask() const +{ + return m_mask; +} + +CInputFilter::CCondition* +CInputFilter::CKeystrokeCondition::clone() const +{ + return new CKeystrokeCondition(m_key, m_mask); +} + +CString +CInputFilter::CKeystrokeCondition::format() const +{ + return CStringUtil::print("keystroke(%s)", + CKeyMap::formatKey(m_key, m_mask).c_str()); +} + +CInputFilter::EFilterStatus +CInputFilter::CKeystrokeCondition::match(const CEvent& event) +{ + EFilterStatus status; + + // check for hotkey events + CEvent::Type type = event.getType(); + if (type == IPrimaryScreen::getHotKeyDownEvent()) { + status = kActivate; + } + else if (type == IPrimaryScreen::getHotKeyUpEvent()) { + status = kDeactivate; + } + else { + return kNoMatch; + } + + // check if it's our hotkey + IPrimaryScreen::CHotKeyInfo* kinfo = + reinterpret_cast(event.getData()); + if (kinfo->m_id != m_id) { + return kNoMatch; + } + + return status; +} + +void +CInputFilter::CKeystrokeCondition::enablePrimary(CPrimaryClient* primary) +{ + m_id = primary->registerHotKey(m_key, m_mask); +} + +void +CInputFilter::CKeystrokeCondition::disablePrimary(CPrimaryClient* primary) +{ + primary->unregisterHotKey(m_id); + m_id = 0; +} + +CInputFilter::CMouseButtonCondition::CMouseButtonCondition( + IPlatformScreen::CButtonInfo* info) : + m_button(info->m_button), + m_mask(info->m_mask) +{ + free(info); +} + +CInputFilter::CMouseButtonCondition::CMouseButtonCondition( + ButtonID button, KeyModifierMask mask) : + m_button(button), + m_mask(mask) +{ + // do nothing +} + +CInputFilter::CMouseButtonCondition::~CMouseButtonCondition() +{ + // do nothing +} + +ButtonID +CInputFilter::CMouseButtonCondition::getButton() const +{ + return m_button; +} + +KeyModifierMask +CInputFilter::CMouseButtonCondition::getMask() const +{ + return m_mask; +} + +CInputFilter::CCondition* +CInputFilter::CMouseButtonCondition::clone() const +{ + return new CMouseButtonCondition(m_button, m_mask); +} + +CString +CInputFilter::CMouseButtonCondition::format() const +{ + CString key = CKeyMap::formatKey(kKeyNone, m_mask); + if (!key.empty()) { + key += "+"; + } + return CStringUtil::print("mousebutton(%s%d)", key.c_str(), m_button); +} + +CInputFilter::EFilterStatus +CInputFilter::CMouseButtonCondition::match(const CEvent& event) +{ + static const KeyModifierMask s_ignoreMask = + KeyModifierAltGr | KeyModifierCapsLock | + KeyModifierNumLock | KeyModifierScrollLock; + + EFilterStatus status; + + // check for hotkey events + CEvent::Type type = event.getType(); + if (type == IPrimaryScreen::getButtonDownEvent()) { + status = kActivate; + } + else if (type == IPrimaryScreen::getButtonUpEvent()) { + status = kDeactivate; + } + else { + return kNoMatch; + } + + // check if it's the right button and modifiers. ignore modifiers + // that cannot be combined with a mouse button. + IPlatformScreen::CButtonInfo* minfo = + reinterpret_cast(event.getData()); + if (minfo->m_button != m_button || + (minfo->m_mask & ~s_ignoreMask) != m_mask) { + return kNoMatch; + } + + return status; +} + +CInputFilter::CScreenConnectedCondition::CScreenConnectedCondition( + const CString& screen) : + m_screen(screen) +{ + // do nothing +} + +CInputFilter::CScreenConnectedCondition::~CScreenConnectedCondition() +{ + // do nothing +} + +CInputFilter::CCondition* +CInputFilter::CScreenConnectedCondition::clone() const +{ + return new CScreenConnectedCondition(m_screen); +} + +CString +CInputFilter::CScreenConnectedCondition::format() const +{ + return CStringUtil::print("connect(%s)", m_screen.c_str()); +} + +CInputFilter::EFilterStatus +CInputFilter::CScreenConnectedCondition::match(const CEvent& event) +{ + if (event.getType() == CServer::getConnectedEvent()) { + CServer::CScreenConnectedInfo* info = + reinterpret_cast(event.getData()); + if (m_screen == info->m_screen || m_screen.empty()) { + return kActivate; + } + } + + return kNoMatch; +} + +// ----------------------------------------------------------------------------- +// Input Filter Action Classes +// ----------------------------------------------------------------------------- +CInputFilter::CAction::CAction() +{ + // do nothing +} + +CInputFilter::CAction::~CAction() +{ + // do nothing +} + +CInputFilter::CLockCursorToScreenAction::CLockCursorToScreenAction(Mode mode) : + m_mode(mode) +{ + // do nothing +} + +CInputFilter::CLockCursorToScreenAction::Mode +CInputFilter::CLockCursorToScreenAction::getMode() const +{ + return m_mode; +} + +CInputFilter::CAction* +CInputFilter::CLockCursorToScreenAction::clone() const +{ + return new CLockCursorToScreenAction(*this); +} + +CString +CInputFilter::CLockCursorToScreenAction::format() const +{ + static const char* s_mode[] = { "off", "on", "toggle" }; + + return CStringUtil::print("lockCursorToScreen(%s)", s_mode[m_mode]); +} + +void +CInputFilter::CLockCursorToScreenAction::perform(const CEvent& event) +{ + static const CServer::CLockCursorToScreenInfo::State s_state[] = { + CServer::CLockCursorToScreenInfo::kOff, + CServer::CLockCursorToScreenInfo::kOn, + CServer::CLockCursorToScreenInfo::kToggle + }; + + // send event + CServer::CLockCursorToScreenInfo* info = + CServer::CLockCursorToScreenInfo::alloc(s_state[m_mode]); + EVENTQUEUE->addEvent(CEvent(CServer::getLockCursorToScreenEvent(), + event.getTarget(), info, + CEvent::kDeliverImmediately)); +} + +CInputFilter::CSwitchToScreenAction::CSwitchToScreenAction( + const CString& screen) : + m_screen(screen) +{ + // do nothing +} + +CString +CInputFilter::CSwitchToScreenAction::getScreen() const +{ + return m_screen; +} + +CInputFilter::CAction* +CInputFilter::CSwitchToScreenAction::clone() const +{ + return new CSwitchToScreenAction(*this); +} + +CString +CInputFilter::CSwitchToScreenAction::format() const +{ + return CStringUtil::print("switchToScreen(%s)", m_screen.c_str()); +} + +void +CInputFilter::CSwitchToScreenAction::perform(const CEvent& event) +{ + // pick screen name. if m_screen is empty then use the screen from + // event if it has one. + CString screen = m_screen; + if (screen.empty() && event.getType() == CServer::getConnectedEvent()) { + CServer::CScreenConnectedInfo* info = + reinterpret_cast(event.getData()); + screen = info->m_screen; + } + + // send event + CServer::CSwitchToScreenInfo* info = + CServer::CSwitchToScreenInfo::alloc(screen); + EVENTQUEUE->addEvent(CEvent(CServer::getSwitchToScreenEvent(), + event.getTarget(), info, + CEvent::kDeliverImmediately)); +} + +CInputFilter::CSwitchInDirectionAction::CSwitchInDirectionAction( + EDirection direction) : + m_direction(direction) +{ + // do nothing +} + +EDirection +CInputFilter::CSwitchInDirectionAction::getDirection() const +{ + return m_direction; +} + +CInputFilter::CAction* +CInputFilter::CSwitchInDirectionAction::clone() const +{ + return new CSwitchInDirectionAction(*this); +} + +CString +CInputFilter::CSwitchInDirectionAction::format() const +{ + static const char* s_names[] = { + "", + "left", + "right", + "up", + "down" + }; + + return CStringUtil::print("switchInDirection(%s)", s_names[m_direction]); +} + +void +CInputFilter::CSwitchInDirectionAction::perform(const CEvent& event) +{ + CServer::CSwitchInDirectionInfo* info = + CServer::CSwitchInDirectionInfo::alloc(m_direction); + EVENTQUEUE->addEvent(CEvent(CServer::getSwitchInDirectionEvent(), + event.getTarget(), info, + CEvent::kDeliverImmediately)); +} + +CInputFilter::CKeyboardBroadcastAction::CKeyboardBroadcastAction(Mode mode) : + m_mode(mode) +{ + // do nothing +} + +CInputFilter::CKeyboardBroadcastAction::CKeyboardBroadcastAction( + Mode mode, + const std::set& screens) : + m_mode(mode), + m_screens(IKeyState::CKeyInfo::join(screens)) +{ + // do nothing +} + +CInputFilter::CKeyboardBroadcastAction::Mode +CInputFilter::CKeyboardBroadcastAction::getMode() const +{ + return m_mode; +} + +std::set +CInputFilter::CKeyboardBroadcastAction::getScreens() const +{ + std::set screens; + IKeyState::CKeyInfo::split(m_screens.c_str(), screens); + return screens; +} + +CInputFilter::CAction* +CInputFilter::CKeyboardBroadcastAction::clone() const +{ + return new CKeyboardBroadcastAction(*this); +} + +CString +CInputFilter::CKeyboardBroadcastAction::format() const +{ + static const char* s_mode[] = { "off", "on", "toggle" }; + static const char* s_name = "keyboardBroadcast"; + + if (m_screens.empty() || m_screens[0] == '*') { + return CStringUtil::print("%s(%s)", s_name, s_mode[m_mode]); + } + else { + return CStringUtil::print("%s(%s,%.*s)", s_name, s_mode[m_mode], + m_screens.size() - 2, + m_screens.c_str() + 1); + } +} + +void +CInputFilter::CKeyboardBroadcastAction::perform(const CEvent& event) +{ + static const CServer::CKeyboardBroadcastInfo::State s_state[] = { + CServer::CKeyboardBroadcastInfo::kOff, + CServer::CKeyboardBroadcastInfo::kOn, + CServer::CKeyboardBroadcastInfo::kToggle + }; + + // send event + CServer::CKeyboardBroadcastInfo* info = + CServer::CKeyboardBroadcastInfo::alloc(s_state[m_mode], m_screens); + EVENTQUEUE->addEvent(CEvent(CServer::getKeyboardBroadcastEvent(), + event.getTarget(), info, + CEvent::kDeliverImmediately)); +} + +CInputFilter::CKeystrokeAction::CKeystrokeAction( + IPlatformScreen::CKeyInfo* info, bool press) : + m_keyInfo(info), + m_press(press) +{ + // do nothing +} + +CInputFilter::CKeystrokeAction::~CKeystrokeAction() +{ + free(m_keyInfo); +} + +void +CInputFilter::CKeystrokeAction::adoptInfo(IPlatformScreen::CKeyInfo* info) +{ + free(m_keyInfo); + m_keyInfo = info; +} + +const IPlatformScreen::CKeyInfo* +CInputFilter::CKeystrokeAction::getInfo() const +{ + return m_keyInfo; +} + +bool +CInputFilter::CKeystrokeAction::isOnPress() const +{ + return m_press; +} + +CInputFilter::CAction* +CInputFilter::CKeystrokeAction::clone() const +{ + IKeyState::CKeyInfo* info = IKeyState::CKeyInfo::alloc(*m_keyInfo); + return new CKeystrokeAction(info, m_press); +} + +CString +CInputFilter::CKeystrokeAction::format() const +{ + const char* type = formatName(); + + if (m_keyInfo->m_screens[0] == '\0') { + return CStringUtil::print("%s(%s)", type, + CKeyMap::formatKey(m_keyInfo->m_key, + m_keyInfo->m_mask).c_str()); + } + else if (m_keyInfo->m_screens[0] == '*') { + return CStringUtil::print("%s(%s,*)", type, + CKeyMap::formatKey(m_keyInfo->m_key, + m_keyInfo->m_mask).c_str()); + } + else { + return CStringUtil::print("%s(%s,%.*s)", type, + CKeyMap::formatKey(m_keyInfo->m_key, + m_keyInfo->m_mask).c_str(), + strlen(m_keyInfo->m_screens + 1) - 1, + m_keyInfo->m_screens + 1); + } +} + +void +CInputFilter::CKeystrokeAction::perform(const CEvent& event) +{ + CEvent::Type type = m_press ? IPlatformScreen::getKeyDownEvent() : + IPlatformScreen::getKeyUpEvent(); + EVENTQUEUE->addEvent(CEvent(IPlatformScreen::getFakeInputBeginEvent(), + event.getTarget(), NULL, + CEvent::kDeliverImmediately)); + EVENTQUEUE->addEvent(CEvent(type, event.getTarget(), m_keyInfo, + CEvent::kDeliverImmediately | + CEvent::kDontFreeData)); + EVENTQUEUE->addEvent(CEvent(IPlatformScreen::getFakeInputEndEvent(), + event.getTarget(), NULL, + CEvent::kDeliverImmediately)); +} + +const char* +CInputFilter::CKeystrokeAction::formatName() const +{ + return (m_press ? "keyDown" : "keyUp"); +} + +CInputFilter::CMouseButtonAction::CMouseButtonAction( + IPlatformScreen::CButtonInfo* info, bool press) : + m_buttonInfo(info), + m_press(press) +{ + // do nothing +} + +CInputFilter::CMouseButtonAction::~CMouseButtonAction() +{ + free(m_buttonInfo); +} + +const IPlatformScreen::CButtonInfo* +CInputFilter::CMouseButtonAction::getInfo() const +{ + return m_buttonInfo; +} + +bool +CInputFilter::CMouseButtonAction::isOnPress() const +{ + return m_press; +} + +CInputFilter::CAction* +CInputFilter::CMouseButtonAction::clone() const +{ + IPlatformScreen::CButtonInfo* info = + IPrimaryScreen::CButtonInfo::alloc(*m_buttonInfo); + return new CMouseButtonAction(info, m_press); +} + +CString +CInputFilter::CMouseButtonAction::format() const +{ + const char* type = formatName(); + + CString key = CKeyMap::formatKey(kKeyNone, m_buttonInfo->m_mask); + return CStringUtil::print("%s(%s%s%d)", type, + key.c_str(), key.empty() ? "" : "+", + m_buttonInfo->m_button); +} + +void +CInputFilter::CMouseButtonAction::perform(const CEvent& event) + +{ + // send modifiers + IPlatformScreen::CKeyInfo* modifierInfo = NULL; + if (m_buttonInfo->m_mask != 0) { + KeyID key = m_press ? kKeySetModifiers : kKeyClearModifiers; + modifierInfo = + IKeyState::CKeyInfo::alloc(key, m_buttonInfo->m_mask, 0, 1); + EVENTQUEUE->addEvent(CEvent(IPlatformScreen::getKeyDownEvent(), + event.getTarget(), modifierInfo, + CEvent::kDeliverImmediately)); + } + + // send button + CEvent::Type type = m_press ? IPlatformScreen::getButtonDownEvent() : + IPlatformScreen::getButtonUpEvent(); + EVENTQUEUE->addEvent(CEvent(type, event.getTarget(), m_buttonInfo, + CEvent::kDeliverImmediately | + CEvent::kDontFreeData)); +} + +const char* +CInputFilter::CMouseButtonAction::formatName() const +{ + return (m_press ? "mouseDown" : "mouseUp"); +} + +// +// CInputFilter::CRule +// + +CInputFilter::CRule::CRule() : + m_condition(NULL) +{ + // do nothing +} + +CInputFilter::CRule::CRule(CCondition* adoptedCondition) : + m_condition(adoptedCondition) +{ + // do nothing +} + +CInputFilter::CRule::CRule(const CRule& rule) : + m_condition(NULL) +{ + copy(rule); +} + +CInputFilter::CRule::~CRule() +{ + clear(); +} + +CInputFilter::CRule& +CInputFilter::CRule::operator=(const CRule& rule) +{ + if (&rule != this) { + copy(rule); + } + return *this; +} + +void +CInputFilter::CRule::clear() +{ + delete m_condition; + for (CActionList::iterator i = m_activateActions.begin(); + i != m_activateActions.end(); ++i) { + delete *i; + } + for (CActionList::iterator i = m_deactivateActions.begin(); + i != m_deactivateActions.end(); ++i) { + delete *i; + } + + m_condition = NULL; + m_activateActions.clear(); + m_deactivateActions.clear(); +} + +void +CInputFilter::CRule::copy(const CRule& rule) +{ + clear(); + if (rule.m_condition != NULL) { + m_condition = rule.m_condition->clone(); + } + for (CActionList::const_iterator i = rule.m_activateActions.begin(); + i != rule.m_activateActions.end(); ++i) { + m_activateActions.push_back((*i)->clone()); + } + for (CActionList::const_iterator i = rule.m_deactivateActions.begin(); + i != rule.m_deactivateActions.end(); ++i) { + m_deactivateActions.push_back((*i)->clone()); + } +} + +void +CInputFilter::CRule::setCondition(CCondition* adopted) +{ + delete m_condition; + m_condition = adopted; +} + +void +CInputFilter::CRule::adoptAction(CAction* action, bool onActivation) +{ + if (action != NULL) { + if (onActivation) { + m_activateActions.push_back(action); + } + else { + m_deactivateActions.push_back(action); + } + } +} + +void +CInputFilter::CRule::removeAction(bool onActivation, UInt32 index) +{ + if (onActivation) { + delete m_activateActions[index]; + m_activateActions.erase(m_activateActions.begin() + index); + } + else { + delete m_deactivateActions[index]; + m_deactivateActions.erase(m_deactivateActions.begin() + index); + } +} + +void +CInputFilter::CRule::replaceAction(CAction* adopted, + bool onActivation, UInt32 index) +{ + if (adopted == NULL) { + removeAction(onActivation, index); + } + else if (onActivation) { + delete m_activateActions[index]; + m_activateActions[index] = adopted; + } + else { + delete m_deactivateActions[index]; + m_deactivateActions[index] = adopted; + } +} + +void +CInputFilter::CRule::enable(CPrimaryClient* primaryClient) +{ + if (m_condition != NULL) { + m_condition->enablePrimary(primaryClient); + } +} + +void +CInputFilter::CRule::disable(CPrimaryClient* primaryClient) +{ + if (m_condition != NULL) { + m_condition->disablePrimary(primaryClient); + } +} + +bool +CInputFilter::CRule::handleEvent(const CEvent& event) +{ + // NULL condition never matches + if (m_condition == NULL) { + return false; + } + + // match + const CActionList* actions; + switch (m_condition->match(event)) { + default: + // not handled + return false; + + case kActivate: + actions = &m_activateActions; + LOG((CLOG_DEBUG1 "activate actions")); + break; + + case kDeactivate: + actions = &m_deactivateActions; + LOG((CLOG_DEBUG1 "deactivate actions")); + break; + } + + // perform actions + for (CActionList::const_iterator i = actions->begin(); + i != actions->end(); ++i) { + LOG((CLOG_DEBUG1 "hotkey: %s", (*i)->format().c_str())); + (*i)->perform(event); + } + + return true; +} + +CString +CInputFilter::CRule::format() const +{ + CString s; + if (m_condition != NULL) { + // condition + s += m_condition->format(); + s += " = "; + + // activate actions + CActionList::const_iterator i = m_activateActions.begin(); + if (i != m_activateActions.end()) { + s += (*i)->format(); + while (++i != m_activateActions.end()) { + s += ", "; + s += (*i)->format(); + } + } + + // deactivate actions + if (!m_deactivateActions.empty()) { + s += "; "; + i = m_deactivateActions.begin(); + if (i != m_deactivateActions.end()) { + s += (*i)->format(); + while (++i != m_deactivateActions.end()) { + s += ", "; + s += (*i)->format(); + } + } + } + } + return s; +} + +const CInputFilter::CCondition* +CInputFilter::CRule::getCondition() const +{ + return m_condition; +} + +UInt32 +CInputFilter::CRule::getNumActions(bool onActivation) const +{ + if (onActivation) { + return static_cast(m_activateActions.size()); + } + else { + return static_cast(m_deactivateActions.size()); + } +} + +const CInputFilter::CAction& +CInputFilter::CRule::getAction(bool onActivation, UInt32 index) const +{ + if (onActivation) { + return *m_activateActions[index]; + } + else { + return *m_deactivateActions[index]; + } +} + + +// ----------------------------------------------------------------------------- +// Input Filter Class +// ----------------------------------------------------------------------------- +CInputFilter::CInputFilter() : + m_primaryClient(NULL) +{ + // do nothing +} + +CInputFilter::CInputFilter(const CInputFilter& x) : + m_ruleList(x.m_ruleList), + m_primaryClient(NULL) +{ + setPrimaryClient(x.m_primaryClient); +} + +CInputFilter::~CInputFilter() +{ + setPrimaryClient(NULL); +} + +CInputFilter& +CInputFilter::operator=(const CInputFilter& x) +{ + if (&x != this) { + CPrimaryClient* oldClient = m_primaryClient; + setPrimaryClient(NULL); + + m_ruleList = x.m_ruleList; + + setPrimaryClient(oldClient); + } + return *this; +} + +void +CInputFilter::addFilterRule(const CRule& rule) +{ + m_ruleList.push_back(rule); + if (m_primaryClient != NULL) { + m_ruleList.back().enable(m_primaryClient); + } +} + +void +CInputFilter::removeFilterRule(UInt32 index) +{ + if (m_primaryClient != NULL) { + m_ruleList[index].disable(m_primaryClient); + } + m_ruleList.erase(m_ruleList.begin() + index); +} + +CInputFilter::CRule& +CInputFilter::getRule(UInt32 index) +{ + return m_ruleList[index]; +} + +void +CInputFilter::setPrimaryClient(CPrimaryClient* client) +{ + if (m_primaryClient == client) { + return; + } + + if (m_primaryClient != NULL) { + for (CRuleList::iterator rule = m_ruleList.begin(); + rule != m_ruleList.end(); ++rule) { + rule->disable(m_primaryClient); + } + + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyDownEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyUpEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyRepeatEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getButtonDownEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getButtonUpEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getHotKeyDownEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getHotKeyUpEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(CServer::getConnectedEvent(), + m_primaryClient->getEventTarget()); + } + + m_primaryClient = client; + + if (m_primaryClient != NULL) { + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyDownEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyUpEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyRepeatEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonDownEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonUpEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getHotKeyDownEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getHotKeyUpEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(CServer::getConnectedEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + + for (CRuleList::iterator rule = m_ruleList.begin(); + rule != m_ruleList.end(); ++rule) { + rule->enable(m_primaryClient); + } + } +} + +CString +CInputFilter::format(const CString& linePrefix) const +{ + CString s; + for (CRuleList::const_iterator i = m_ruleList.begin(); + i != m_ruleList.end(); ++i) { + s += linePrefix; + s += i->format(); + s += "\n"; + } + return s; +} + +UInt32 +CInputFilter::getNumRules() const +{ + return static_cast(m_ruleList.size()); +} + +bool +CInputFilter::operator==(const CInputFilter& x) const +{ + // if there are different numbers of rules then we can't be equal + if (m_ruleList.size() != x.m_ruleList.size()) { + return false; + } + + // compare rule lists. the easiest way to do that is to format each + // rule into a string, sort the strings, then compare the results. + std::vector aList, bList; + for (CRuleList::const_iterator i = m_ruleList.begin(); + i != m_ruleList.end(); ++i) { + aList.push_back(i->format()); + } + for (CRuleList::const_iterator i = x.m_ruleList.begin(); + i != x.m_ruleList.end(); ++i) { + bList.push_back(i->format()); + } + std::partial_sort(aList.begin(), aList.end(), aList.end()); + std::partial_sort(bList.begin(), bList.end(), bList.end()); + return (aList == bList); +} + +bool +CInputFilter::operator!=(const CInputFilter& x) const +{ + return !operator==(x); +} + +void +CInputFilter::handleEvent(const CEvent& event, void*) +{ + // copy event and adjust target + CEvent myEvent(event.getType(), this, event.getData(), + event.getFlags() | CEvent::kDontFreeData | + CEvent::kDeliverImmediately); + + // let each rule try to match the event until one does + for (CRuleList::iterator rule = m_ruleList.begin(); + rule != m_ruleList.end(); ++rule) { + if (rule->handleEvent(myEvent)) { + // handled + return; + } + } + + // not handled so pass through + EVENTQUEUE->addEvent(myEvent); +} diff --git a/lib/server/CInputFilter.h b/lib/server/CInputFilter.h new file mode 100644 index 00000000..571ec82b --- /dev/null +++ b/lib/server/CInputFilter.h @@ -0,0 +1,344 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2005 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CINPUTFILTER_H +#define CINPUTFILTER_H + +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "ProtocolTypes.h" +#include "IPlatformScreen.h" +#include "CString.h" +#include "stdmap.h" +#include "stdset.h" + +class CPrimaryClient; +class CEvent; + +class CInputFilter { +public: + // ------------------------------------------------------------------------- + // Input Filter Condition Classes + // ------------------------------------------------------------------------- + enum EFilterStatus { + kNoMatch, + kActivate, + kDeactivate + }; + + class CCondition { + public: + CCondition(); + virtual ~CCondition(); + + virtual CCondition* clone() const = 0; + virtual CString format() const = 0; + + virtual EFilterStatus match(const CEvent&) = 0; + + virtual void enablePrimary(CPrimaryClient*); + virtual void disablePrimary(CPrimaryClient*); + }; + + // CKeystrokeCondition + class CKeystrokeCondition : public CCondition { + public: + CKeystrokeCondition(IPlatformScreen::CKeyInfo*); + CKeystrokeCondition(KeyID key, KeyModifierMask mask); + virtual ~CKeystrokeCondition(); + + KeyID getKey() const; + KeyModifierMask getMask() const; + + // CCondition overrides + virtual CCondition* clone() const; + virtual CString format() const; + virtual EFilterStatus match(const CEvent&); + virtual void enablePrimary(CPrimaryClient*); + virtual void disablePrimary(CPrimaryClient*); + + private: + UInt32 m_id; + KeyID m_key; + KeyModifierMask m_mask; + }; + + // CMouseButtonCondition + class CMouseButtonCondition : public CCondition { + public: + CMouseButtonCondition(IPlatformScreen::CButtonInfo*); + CMouseButtonCondition(ButtonID, KeyModifierMask mask); + virtual ~CMouseButtonCondition(); + + ButtonID getButton() const; + KeyModifierMask getMask() const; + + // CCondition overrides + virtual CCondition* clone() const; + virtual CString format() const; + virtual EFilterStatus match(const CEvent&); + + private: + ButtonID m_button; + KeyModifierMask m_mask; + }; + + // CScreenConnectedCondition + class CScreenConnectedCondition : public CCondition { + public: + CScreenConnectedCondition(const CString& screen); + virtual ~CScreenConnectedCondition(); + + // CCondition overrides + virtual CCondition* clone() const; + virtual CString format() const; + virtual EFilterStatus match(const CEvent&); + + private: + CString m_screen; + }; + + // ------------------------------------------------------------------------- + // Input Filter Action Classes + // ------------------------------------------------------------------------- + + class CAction { + public: + CAction(); + virtual ~CAction(); + + virtual CAction* clone() const = 0; + virtual CString format() const = 0; + + virtual void perform(const CEvent&) = 0; + }; + + // CLockCursorToScreenAction + class CLockCursorToScreenAction : public CAction { + public: + enum Mode { kOff, kOn, kToggle }; + + CLockCursorToScreenAction(Mode = kToggle); + + Mode getMode() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + private: + Mode m_mode; + }; + + // CSwitchToScreenAction + class CSwitchToScreenAction : public CAction { + public: + CSwitchToScreenAction(const CString& screen); + + CString getScreen() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + private: + CString m_screen; + }; + + // CSwitchInDirectionAction + class CSwitchInDirectionAction : public CAction { + public: + CSwitchInDirectionAction(EDirection); + + EDirection getDirection() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + private: + EDirection m_direction; + }; + + // CKeyboardBroadcastAction + class CKeyboardBroadcastAction : public CAction { + public: + enum Mode { kOff, kOn, kToggle }; + + CKeyboardBroadcastAction(Mode = kToggle); + CKeyboardBroadcastAction(Mode, const std::set& screens); + + Mode getMode() const; + std::set getScreens() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + private: + Mode m_mode; + CString m_screens; + }; + + // CKeystrokeAction + class CKeystrokeAction : public CAction { + public: + CKeystrokeAction(IPlatformScreen::CKeyInfo* adoptedInfo, bool press); + ~CKeystrokeAction(); + + void adoptInfo(IPlatformScreen::CKeyInfo*); + const IPlatformScreen::CKeyInfo* + getInfo() const; + bool isOnPress() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + protected: + virtual const char* formatName() const; + + private: + IPlatformScreen::CKeyInfo* m_keyInfo; + bool m_press; + }; + + // CMouseButtonAction -- modifier combinations not implemented yet + class CMouseButtonAction : public CAction { + public: + CMouseButtonAction(IPlatformScreen::CButtonInfo* adoptedInfo, + bool press); + ~CMouseButtonAction(); + + const IPlatformScreen::CButtonInfo* + getInfo() const; + bool isOnPress() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + protected: + virtual const char* formatName() const; + + private: + IPlatformScreen::CButtonInfo* m_buttonInfo; + bool m_press; + }; + + class CRule { + public: + CRule(); + CRule(CCondition* adopted); + CRule(const CRule&); + ~CRule(); + + CRule& operator=(const CRule&); + + // replace the condition + void setCondition(CCondition* adopted); + + // add an action to the rule + void adoptAction(CAction*, bool onActivation); + + // remove an action from the rule + void removeAction(bool onActivation, UInt32 index); + + // replace an action in the rule + void replaceAction(CAction* adopted, + bool onActivation, UInt32 index); + + // enable/disable + void enable(CPrimaryClient*); + void disable(CPrimaryClient*); + + // event handling + bool handleEvent(const CEvent&); + + // convert rule to a string + CString format() const; + + // get the rule's condition + const CCondition* + getCondition() const; + + // get number of actions + UInt32 getNumActions(bool onActivation) const; + + // get action by index + const CAction& getAction(bool onActivation, UInt32 index) const; + + private: + void clear(); + void copy(const CRule&); + + private: + typedef std::vector CActionList; + + CCondition* m_condition; + CActionList m_activateActions; + CActionList m_deactivateActions; + }; + + // ------------------------------------------------------------------------- + // Input Filter Class + // ------------------------------------------------------------------------- + typedef std::vector CRuleList; + + CInputFilter(); + CInputFilter(const CInputFilter&); + virtual ~CInputFilter(); + + CInputFilter& operator=(const CInputFilter&); + + // add rule, adopting the condition and the actions + void addFilterRule(const CRule& rule); + + // remove a rule + void removeFilterRule(UInt32 index); + + // get rule by index + CRule& getRule(UInt32 index); + + // enable event filtering using the given primary client. disable + // if client is NULL. + void setPrimaryClient(CPrimaryClient* client); + + // convert rules to a string + CString format(const CString& linePrefix) const; + + // get number of rules + UInt32 getNumRules() const; + + //! Compare filters + bool operator==(const CInputFilter&) const; + //! Compare filters + bool operator!=(const CInputFilter&) const; + +private: + // event handling + void handleEvent(const CEvent&, void*); + +private: + CRuleList m_ruleList; + CPrimaryClient* m_primaryClient; +}; + +#endif diff --git a/lib/server/CPrimaryClient.cpp b/lib/server/CPrimaryClient.cpp new file mode 100644 index 00000000..03146655 --- /dev/null +++ b/lib/server/CPrimaryClient.cpp @@ -0,0 +1,257 @@ +/* + * 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 "CPrimaryClient.h" +#include "CScreen.h" +#include "CClipboard.h" +#include "CLog.h" + +// +// CPrimaryClient +// + +CPrimaryClient::CPrimaryClient(const CString& name, CScreen* screen) : + CBaseClientProxy(name), + m_screen(screen), + m_fakeInputCount(0) +{ + // all clipboards are clean + for (UInt32 i = 0; i < kClipboardEnd; ++i) { + m_clipboardDirty[i] = false; + } +} + +CPrimaryClient::~CPrimaryClient() +{ + // do nothing +} + +void +CPrimaryClient::reconfigure(UInt32 activeSides) +{ + m_screen->reconfigure(activeSides); +} + +UInt32 +CPrimaryClient::registerHotKey(KeyID key, KeyModifierMask mask) +{ + return m_screen->registerHotKey(key, mask); +} + +void +CPrimaryClient::unregisterHotKey(UInt32 id) +{ + m_screen->unregisterHotKey(id); +} + +void +CPrimaryClient::fakeInputBegin() +{ + if (++m_fakeInputCount == 1) { + m_screen->fakeInputBegin(); + } +} + +void +CPrimaryClient::fakeInputEnd() +{ + if (--m_fakeInputCount == 0) { + m_screen->fakeInputEnd(); + } +} + +SInt32 +CPrimaryClient::getJumpZoneSize() const +{ + return m_screen->getJumpZoneSize(); +} + +void +CPrimaryClient::getCursorCenter(SInt32& x, SInt32& y) const +{ + m_screen->getCursorCenter(x, y); +} + +KeyModifierMask +CPrimaryClient::getToggleMask() const +{ + return m_screen->pollActiveModifiers(); +} + +bool +CPrimaryClient::isLockedToScreen() const +{ + return m_screen->isLockedToScreen(); +} + +void* +CPrimaryClient::getEventTarget() const +{ + return m_screen->getEventTarget(); +} + +bool +CPrimaryClient::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + return m_screen->getClipboard(id, clipboard); +} + +void +CPrimaryClient::getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const +{ + m_screen->getShape(x, y, width, height); +} + +void +CPrimaryClient::getCursorPos(SInt32& x, SInt32& y) const +{ + m_screen->getCursorPos(x, y); +} + +void +CPrimaryClient::enable() +{ + m_screen->enable(); +} + +void +CPrimaryClient::disable() +{ + m_screen->disable(); +} + +void +CPrimaryClient::enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, bool screensaver) +{ + m_screen->setSequenceNumber(seqNum); + if (!screensaver) { + m_screen->warpCursor(xAbs, yAbs); + } + m_screen->enter(mask); +} + +bool +CPrimaryClient::leave() +{ + return m_screen->leave(); +} + +void +CPrimaryClient::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + // ignore if this clipboard is already clean + if (m_clipboardDirty[id]) { + // this clipboard is now clean + m_clipboardDirty[id] = false; + + // set clipboard + m_screen->setClipboard(id, clipboard); + } +} + +void +CPrimaryClient::grabClipboard(ClipboardID id) +{ + // grab clipboard + m_screen->grabClipboard(id); + + // clipboard is dirty (because someone else owns it now) + m_clipboardDirty[id] = true; +} + +void +CPrimaryClient::setClipboardDirty(ClipboardID id, bool dirty) +{ + m_clipboardDirty[id] = dirty; +} + +void +CPrimaryClient::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) +{ + if (m_fakeInputCount > 0) { +// XXX -- don't forward keystrokes to primary screen for now + (void)key; + (void)mask; + (void)button; +// m_screen->keyDown(key, mask, button); + } +} + +void +CPrimaryClient::keyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton) +{ + // ignore +} + +void +CPrimaryClient::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) +{ + if (m_fakeInputCount > 0) { +// XXX -- don't forward keystrokes to primary screen for now + (void)key; + (void)mask; + (void)button; +// m_screen->keyUp(key, mask, button); + } +} + +void +CPrimaryClient::mouseDown(ButtonID) +{ + // ignore +} + +void +CPrimaryClient::mouseUp(ButtonID) +{ + // ignore +} + +void +CPrimaryClient::mouseMove(SInt32 x, SInt32 y) +{ + m_screen->warpCursor(x, y); +} + +void +CPrimaryClient::mouseRelativeMove(SInt32, SInt32) +{ + // ignore +} + +void +CPrimaryClient::mouseWheel(SInt32, SInt32) +{ + // ignore +} + +void +CPrimaryClient::screensaver(bool) +{ + // ignore +} + +void +CPrimaryClient::resetOptions() +{ + m_screen->resetOptions(); +} + +void +CPrimaryClient::setOptions(const COptionsList& options) +{ + m_screen->setOptions(options); +} diff --git a/lib/server/CPrimaryClient.h b/lib/server/CPrimaryClient.h new file mode 100644 index 00000000..e768a21d --- /dev/null +++ b/lib/server/CPrimaryClient.h @@ -0,0 +1,145 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CPRIMARYCLIENT_H +#define CPRIMARYCLIENT_H + +#include "CBaseClientProxy.h" +#include "ProtocolTypes.h" + +class CScreen; + +//! Primary screen as pseudo-client +/*! +The primary screen does not have a client associated with it. This +class provides a pseudo-client to allow the primary screen to be +treated as if it was a client. +*/ +class CPrimaryClient : public CBaseClientProxy { +public: + /*! + \c name is the name of the server and \p screen is primary screen. + */ + CPrimaryClient(const CString& name, CScreen* screen); + ~CPrimaryClient(); + + //! @name manipulators + //@{ + + //! Update configuration + /*! + Handles reconfiguration of jump zones. + */ + void reconfigure(UInt32 activeSides); + + //! Register a system hotkey + /*! + Registers a system-wide hotkey for key \p key with modifiers \p mask. + Returns an id used to unregister the hotkey. + */ + UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + + //! Unregister a system hotkey + /*! + Unregisters a previously registered hot key. + */ + void unregisterHotKey(UInt32 id); + + //! Prepare to synthesize input on primary screen + /*! + Prepares the primary screen to receive synthesized input. We do not + want to receive this synthesized input as user input so this method + ensures that we ignore it. Calls to \c fakeInputBegin() and + \c fakeInputEnd() may be nested; only the outermost have an effect. + */ + void fakeInputBegin(); + + //! Done synthesizing input on primary screen + /*! + Undoes whatever \c fakeInputBegin() did. + */ + void fakeInputEnd(); + + //@} + //! @name accessors + //@{ + + //! Get jump zone size + /*! + Return the jump zone size, the size of the regions on the edges of + the screen that cause the cursor to jump to another screen. + */ + SInt32 getJumpZoneSize() const; + + //! Get cursor center position + /*! + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. + */ + void getCursorCenter(SInt32& x, SInt32& y) const; + + //! Get toggle key state + /*! + Returns the primary screen's current toggle modifier key state. + */ + KeyModifierMask getToggleMask() const; + + //! Get screen lock state + /*! + Returns true if the user is locked to the screen. + */ + bool isLockedToScreen() const; + + //@} + + // FIXME -- these probably belong on IScreen + virtual void enable(); + virtual void disable(); + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver); + virtual bool leave(); + virtual void setClipboard(ClipboardID, const IClipboard*); + virtual void grabClipboard(ClipboardID); + virtual void setClipboardDirty(ClipboardID, bool); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); + virtual void mouseDown(ButtonID); + virtual void mouseUp(ButtonID); + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs); + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + +private: + CScreen* m_screen; + bool m_clipboardDirty[kClipboardEnd]; + SInt32 m_fakeInputCount; +}; + +#endif diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp new file mode 100644 index 00000000..3d4d32c2 --- /dev/null +++ b/lib/server/CServer.cpp @@ -0,0 +1,2176 @@ +/* + * 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 "CServer.h" +#include "CClientProxy.h" +#include "CClientProxyUnknown.h" +#include "CPrimaryClient.h" +#include "IPlatformScreen.h" +#include "OptionTypes.h" +#include "ProtocolTypes.h" +#include "XScreen.h" +#include "XSynergy.h" +#include "IDataSocket.h" +#include "IListenSocket.h" +#include "XSocket.h" +#include "IEventQueue.h" +#include "CLog.h" +#include "TMethodEventJob.h" +#include "CArch.h" +#include + +// +// CServer +// + +CEvent::Type CServer::s_errorEvent = CEvent::kUnknown; +CEvent::Type CServer::s_connectedEvent = CEvent::kUnknown; +CEvent::Type CServer::s_disconnectedEvent = CEvent::kUnknown; +CEvent::Type CServer::s_switchToScreen = CEvent::kUnknown; +CEvent::Type CServer::s_switchInDirection = CEvent::kUnknown; +CEvent::Type CServer::s_keyboardBroadcast = CEvent::kUnknown; +CEvent::Type CServer::s_lockCursorToScreen = CEvent::kUnknown; + +CServer::CServer(const CConfig& config, CPrimaryClient* primaryClient) : + m_primaryClient(primaryClient), + m_active(primaryClient), + m_seqNum(0), + m_xDelta(0), + m_yDelta(0), + m_xDelta2(0), + m_yDelta2(0), + m_config(), + m_inputFilter(m_config.getInputFilter()), + m_activeSaver(NULL), + m_switchDir(kNoDirection), + m_switchScreen(NULL), + m_switchWaitDelay(0.0), + m_switchWaitTimer(NULL), + m_switchTwoTapDelay(0.0), + m_switchTwoTapEngaged(false), + m_switchTwoTapArmed(false), + m_switchTwoTapZone(3), + m_relativeMoves(false), + m_keyboardBroadcasting(false), + m_lockedToScreen(false) +{ + // must have a primary client and it must have a canonical name + assert(m_primaryClient != NULL); + assert(config.isScreen(primaryClient->getName())); + + CString primaryName = getName(primaryClient); + + // clear clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + CClipboardInfo& clipboard = m_clipboards[id]; + clipboard.m_clipboardOwner = primaryName; + clipboard.m_clipboardSeqNum = m_seqNum; + if (clipboard.m_clipboard.open(0)) { + clipboard.m_clipboard.empty(); + clipboard.m_clipboard.close(); + } + clipboard.m_clipboardData = clipboard.m_clipboard.marshall(); + } + + // install event handlers + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CServer::handleSwitchWaitTimeout)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyDownEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleKeyDownEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyUpEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleKeyUpEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyRepeatEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleKeyRepeatEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonDownEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleButtonDownEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonUpEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleButtonUpEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnPrimaryEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleMotionPrimaryEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnSecondaryEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleMotionSecondaryEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getWheelEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleWheelEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverActivatedEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleScreensaverActivatedEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverDeactivatedEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleScreensaverDeactivatedEvent)); + EVENTQUEUE->adoptHandler(getSwitchToScreenEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleSwitchToScreenEvent)); + EVENTQUEUE->adoptHandler(getSwitchInDirectionEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleSwitchInDirectionEvent)); + EVENTQUEUE->adoptHandler(getKeyboardBroadcastEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleKeyboardBroadcastEvent)); + EVENTQUEUE->adoptHandler(getLockCursorToScreenEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleLockCursorToScreenEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getFakeInputBeginEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleFakeInputBeginEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getFakeInputEndEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleFakeInputEndEvent)); + + // add connection + addClient(m_primaryClient); + + // set initial configuration + setConfig(config); + + // enable primary client + m_primaryClient->enable(); + m_inputFilter->setPrimaryClient(m_primaryClient); +} + +CServer::~CServer() +{ + // remove event handlers and timers + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyDownEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyUpEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyRepeatEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getButtonDownEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getButtonUpEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnPrimaryEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnSecondaryEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getWheelEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverActivatedEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverDeactivatedEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getFakeInputBeginEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getFakeInputEndEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + stopSwitch(); + + // force immediate disconnection of secondary clients + disconnect(); + for (COldClients::iterator index = m_oldClients.begin(); + index != m_oldClients.begin(); ++index) { + CBaseClientProxy* client = index->first; + EVENTQUEUE->deleteTimer(index->second); + EVENTQUEUE->removeHandler(CEvent::kTimer, client); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + delete client; + } + + // remove input filter + m_inputFilter->setPrimaryClient(NULL); + + // disable and disconnect primary client + m_primaryClient->disable(); + removeClient(m_primaryClient); +} + +bool +CServer::setConfig(const CConfig& config) +{ + // refuse configuration if it doesn't include the primary screen + if (!config.isScreen(m_primaryClient->getName())) { + return false; + } + + // close clients that are connected but being dropped from the + // configuration. + closeClients(config); + + // cut over + m_config = config; + processOptions(); + + // add ScrollLock as a hotkey to lock to the screen. this was a + // built-in feature in earlier releases and is now supported via + // the user configurable hotkey mechanism. if the user has already + // registered ScrollLock for something else then that will win but + // we will unfortunately generate a warning. if the user has + // configured a CLockCursorToScreenAction then we don't add + // ScrollLock as a hotkey. + if (!m_config.hasLockToScreenAction()) { + IPlatformScreen::CKeyInfo* key = + IPlatformScreen::CKeyInfo::alloc(kKeyScrollLock, 0, 0, 0); + CInputFilter::CRule rule(new CInputFilter::CKeystrokeCondition(key)); + rule.adoptAction(new CInputFilter::CLockCursorToScreenAction, true); + m_inputFilter->addFilterRule(rule); + } + + // tell primary screen about reconfiguration + m_primaryClient->reconfigure(getActivePrimarySides()); + + // tell all (connected) clients about current options + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + CBaseClientProxy* client = index->second; + sendOptions(client); + } + + return true; +} + +void +CServer::adoptClient(CBaseClientProxy* client) +{ + assert(client != NULL); + + // watch for client disconnection + EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), client, + new TMethodEventJob(this, + &CServer::handleClientDisconnected, client)); + + // name must be in our configuration + if (!m_config.isScreen(client->getName())) { + LOG((CLOG_WARN "a client with name \"%s\" is not in the map", client->getName().c_str())); + closeClient(client, kMsgEUnknown); + return; + } + + // add client to client list + if (!addClient(client)) { + // can only have one screen with a given name at any given time + LOG((CLOG_WARN "a client with name \"%s\" is already connected", getName(client).c_str())); + closeClient(client, kMsgEBusy); + return; + } + LOG((CLOG_NOTE "client \"%s\" has connected", getName(client).c_str())); + + // send configuration options to client + sendOptions(client); + + // activate screen saver on new client if active on the primary screen + if (m_activeSaver != NULL) { + client->screensaver(true); + } + + // send notification + CServer::CScreenConnectedInfo* info = + CServer::CScreenConnectedInfo::alloc(getName(client)); + EVENTQUEUE->addEvent(CEvent(CServer::getConnectedEvent(), + m_primaryClient->getEventTarget(), info)); +} + +void +CServer::disconnect() +{ + // close all secondary clients + if (m_clients.size() > 1 || !m_oldClients.empty()) { + CConfig emptyConfig; + closeClients(emptyConfig); + } + else { + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this)); + } +} + +UInt32 +CServer::getNumClients() const +{ + return m_clients.size(); +} + +void +CServer::getClients(std::vector& list) const +{ + list.clear(); + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + list.push_back(index->first); + } +} + +CEvent::Type +CServer::getErrorEvent() +{ + return CEvent::registerTypeOnce(s_errorEvent, + "CServer::error"); +} + +CEvent::Type +CServer::getConnectedEvent() +{ + return CEvent::registerTypeOnce(s_connectedEvent, + "CServer::connected"); +} + +CEvent::Type +CServer::getDisconnectedEvent() +{ + return CEvent::registerTypeOnce(s_disconnectedEvent, + "CServer::disconnected"); +} + +CEvent::Type +CServer::getSwitchToScreenEvent() +{ + return CEvent::registerTypeOnce(s_switchToScreen, + "CServer::switchToScreen"); +} + +CEvent::Type +CServer::getSwitchInDirectionEvent() +{ + return CEvent::registerTypeOnce(s_switchInDirection, + "CServer::switchInDirection"); +} + +CEvent::Type +CServer::getKeyboardBroadcastEvent() +{ + return CEvent::registerTypeOnce(s_keyboardBroadcast, + "CServer:keyboardBroadcast"); +} + +CEvent::Type +CServer::getLockCursorToScreenEvent() +{ + return CEvent::registerTypeOnce(s_lockCursorToScreen, + "CServer::lockCursorToScreen"); +} + +CString +CServer::getName(const CBaseClientProxy* client) const +{ + CString name = m_config.getCanonicalName(client->getName()); + if (name.empty()) { + name = client->getName(); + } + return name; +} + +UInt32 +CServer::getActivePrimarySides() const +{ + UInt32 sides = 0; + if (!isLockedToScreenServer()) { + if (hasAnyNeighbor(m_primaryClient, kLeft)) { + sides |= kLeftMask; + } + if (hasAnyNeighbor(m_primaryClient, kRight)) { + sides |= kRightMask; + } + if (hasAnyNeighbor(m_primaryClient, kTop)) { + sides |= kTopMask; + } + if (hasAnyNeighbor(m_primaryClient, kBottom)) { + sides |= kBottomMask; + } + } + return sides; +} + +bool +CServer::isLockedToScreenServer() const +{ + // locked if scroll-lock is toggled on + return m_lockedToScreen; +} + +bool +CServer::isLockedToScreen() const +{ + // locked if we say we're locked + if (isLockedToScreenServer()) { + LOG((CLOG_DEBUG "locked to screen")); + return true; + } + + // locked if primary says we're locked + if (m_primaryClient->isLockedToScreen()) { + return true; + } + + // not locked + return false; +} + +SInt32 +CServer::getJumpZoneSize(CBaseClientProxy* client) const +{ + if (client == m_primaryClient) { + return m_primaryClient->getJumpZoneSize(); + } + else { + return 0; + } +} + +void +CServer::switchScreen(CBaseClientProxy* dst, + SInt32 x, SInt32 y, bool forScreensaver) +{ + assert(dst != NULL); +#ifndef NDEBUG + { + SInt32 dx, dy, dw, dh; + dst->getShape(dx, dy, dw, dh); + assert(x >= dx && y >= dy && x < dx + dw && y < dy + dh); + } +#endif + assert(m_active != NULL); + + LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", getName(m_active).c_str(), getName(dst).c_str(), x, y)); + + // stop waiting to switch + stopSwitch(); + + // record new position + m_x = x; + m_y = y; + m_xDelta = 0; + m_yDelta = 0; + m_xDelta2 = 0; + m_yDelta2 = 0; + + // wrapping means leaving the active screen and entering it again. + // since that's a waste of time we skip that and just warp the + // mouse. + if (m_active != dst) { + // leave active screen + if (!m_active->leave()) { + // cannot leave screen + LOG((CLOG_WARN "can't leave screen")); + return; + } + + // update the primary client's clipboards if we're leaving the + // primary screen. + if (m_active == m_primaryClient) { + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + CClipboardInfo& clipboard = m_clipboards[id]; + if (clipboard.m_clipboardOwner == getName(m_primaryClient)) { + onClipboardChanged(m_primaryClient, + id, clipboard.m_clipboardSeqNum); + } + } + } + + // cut over + m_active = dst; + + // increment enter sequence number + ++m_seqNum; + + // enter new screen + m_active->enter(x, y, m_seqNum, + m_primaryClient->getToggleMask(), + forScreensaver); + + // send the clipboard data to new active screen + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_active->setClipboard(id, &m_clipboards[id].m_clipboard); + } + } + else { + m_active->mouseMove(x, y); + } +} + +void +CServer::jumpToScreen(CBaseClientProxy* newScreen) +{ + assert(newScreen != NULL); + + // record the current cursor position on the active screen + m_active->setJumpCursorPos(m_x, m_y); + + // get the last cursor position on the target screen + SInt32 x, y; + newScreen->getJumpCursorPos(x, y); + + switchScreen(newScreen, x, y, false); +} + +float +CServer::mapToFraction(CBaseClientProxy* client, + EDirection dir, SInt32 x, SInt32 y) const +{ + SInt32 sx, sy, sw, sh; + client->getShape(sx, sy, sw, sh); + switch (dir) { + case kLeft: + case kRight: + return static_cast(y - sy + 0.5f) / static_cast(sh); + + case kTop: + case kBottom: + return static_cast(x - sx + 0.5f) / static_cast(sw); + + case kNoDirection: + assert(0 && "bad direction"); + break; + } + return 0.0f; +} + +void +CServer::mapToPixel(CBaseClientProxy* client, + EDirection dir, float f, SInt32& x, SInt32& y) const +{ + SInt32 sx, sy, sw, sh; + client->getShape(sx, sy, sw, sh); + switch (dir) { + case kLeft: + case kRight: + y = static_cast(f * sh) + sy; + break; + + case kTop: + case kBottom: + x = static_cast(f * sw) + sx; + break; + + case kNoDirection: + assert(0 && "bad direction"); + break; + } +} + +bool +CServer::hasAnyNeighbor(CBaseClientProxy* client, EDirection dir) const +{ + assert(client != NULL); + + return m_config.hasNeighbor(getName(client), dir); +} + +CBaseClientProxy* +CServer::getNeighbor(CBaseClientProxy* src, + EDirection dir, SInt32& x, SInt32& y) const +{ + // note -- must be locked on entry + + assert(src != NULL); + + // get source screen name + CString srcName = getName(src); + assert(!srcName.empty()); + LOG((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str())); + + // convert position to fraction + float t = mapToFraction(src, dir, x, y); + + // search for the closest neighbor that exists in direction dir + float tTmp; + for (;;) { + CString dstName(m_config.getNeighbor(srcName, dir, t, &tTmp)); + + // if nothing in that direction then return NULL. if the + // destination is the source then we can make no more + // progress in this direction. since we haven't found a + // connected neighbor we return NULL. + if (dstName.empty()) { + LOG((CLOG_DEBUG2 "no neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str())); + return NULL; + } + + // look up neighbor cell. if the screen is connected and + // ready then we can stop. + CClientList::const_iterator index = m_clients.find(dstName); + if (index != m_clients.end()) { + LOG((CLOG_DEBUG2 "\"%s\" is on %s of \"%s\" at %f", dstName.c_str(), CConfig::dirName(dir), srcName.c_str(), t)); + mapToPixel(index->second, dir, tTmp, x, y); + return index->second; + } + + // skip over unconnected screen + LOG((CLOG_DEBUG2 "ignored \"%s\" on %s of \"%s\"", dstName.c_str(), CConfig::dirName(dir), srcName.c_str())); + srcName = dstName; + + // use position on skipped screen + t = tTmp; + } +} + +CBaseClientProxy* +CServer::mapToNeighbor(CBaseClientProxy* src, + EDirection srcSide, SInt32& x, SInt32& y) const +{ + // note -- must be locked on entry + + assert(src != NULL); + + // get the first neighbor + CBaseClientProxy* dst = getNeighbor(src, srcSide, x, y); + if (dst == NULL) { + return NULL; + } + + // get the source screen's size + SInt32 dx, dy, dw, dh; + CBaseClientProxy* lastGoodScreen = src; + lastGoodScreen->getShape(dx, dy, dw, dh); + + // find destination screen, adjusting x or y (but not both). the + // searches are done in a sort of canonical screen space where + // the upper-left corner is 0,0 for each screen. we adjust from + // actual to canonical position on entry to and from canonical to + // actual on exit from the search. + switch (srcSide) { + case kLeft: + x -= dx; + while (dst != NULL) { + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + x += dw; + if (x >= 0) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + x += dx; + break; + + case kRight: + x -= dx; + while (dst != NULL) { + x -= dw; + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + if (x < dw) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + x += dx; + break; + + case kTop: + y -= dy; + while (dst != NULL) { + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + y += dh; + if (y >= 0) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + y += dy; + break; + + case kBottom: + y -= dy; + while (dst != NULL) { + y -= dh; + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + if (y < dh) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + y += dy; + break; + + case kNoDirection: + assert(0 && "bad direction"); + return NULL; + } + + // save destination screen + assert(lastGoodScreen != NULL); + dst = lastGoodScreen; + + // if entering primary screen then be sure to move in far enough + // to avoid the jump zone. if entering a side that doesn't have + // a neighbor (i.e. an asymmetrical side) then we don't need to + // move inwards because that side can't provoke a jump. + avoidJumpZone(dst, srcSide, x, y); + + return dst; +} + +void +CServer::avoidJumpZone(CBaseClientProxy* dst, + EDirection dir, SInt32& x, SInt32& y) const +{ + // we only need to avoid jump zones on the primary screen + if (dst != m_primaryClient) { + return; + } + + const CString dstName(getName(dst)); + SInt32 dx, dy, dw, dh; + dst->getShape(dx, dy, dw, dh); + float t = mapToFraction(dst, dir, x, y); + SInt32 z = getJumpZoneSize(dst); + + // move in far enough to avoid the jump zone. if entering a side + // that doesn't have a neighbor (i.e. an asymmetrical side) then we + // don't need to move inwards because that side can't provoke a jump. + switch (dir) { + case kLeft: + if (!m_config.getNeighbor(dstName, kRight, t, NULL).empty() && + x > dx + dw - 1 - z) + x = dx + dw - 1 - z; + break; + + case kRight: + if (!m_config.getNeighbor(dstName, kLeft, t, NULL).empty() && + x < dx + z) + x = dx + z; + break; + + case kTop: + if (!m_config.getNeighbor(dstName, kBottom, t, NULL).empty() && + y > dy + dh - 1 - z) + y = dy + dh - 1 - z; + break; + + case kBottom: + if (!m_config.getNeighbor(dstName, kTop, t, NULL).empty() && + y < dy + z) + y = dy + z; + break; + + case kNoDirection: + assert(0 && "bad direction"); + } +} + +bool +CServer::isSwitchOkay(CBaseClientProxy* newScreen, + EDirection dir, SInt32 x, SInt32 y, + SInt32 xActive, SInt32 yActive) +{ + LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", getName(m_active).c_str(), CConfig::dirName(dir))); + + // is there a neighbor? + if (newScreen == NULL) { + // there's no neighbor. we don't want to switch and we don't + // want to try to switch later. + LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(dir))); + stopSwitch(); + return false; + } + + // should we switch or not? + bool preventSwitch = false; + bool allowSwitch = false; + + // note if the switch direction has changed. save the new + // direction and screen if so. + bool isNewDirection = (dir != m_switchDir); + if (isNewDirection || m_switchScreen == NULL) { + m_switchDir = dir; + m_switchScreen = newScreen; + } + + // is this a double tap and do we care? + if (!allowSwitch && m_switchTwoTapDelay > 0.0) { + if (isNewDirection || + !isSwitchTwoTapStarted() || !shouldSwitchTwoTap()) { + // tapping a different or new edge or second tap not + // fast enough. prepare for second tap. + preventSwitch = true; + startSwitchTwoTap(); + } + else { + // got second tap + allowSwitch = true; + } + } + + // if waiting before a switch then prepare to switch later + if (!allowSwitch && m_switchWaitDelay > 0.0) { + if (isNewDirection || !isSwitchWaitStarted()) { + startSwitchWait(x, y); + } + preventSwitch = true; + } + + // are we in a locked corner? first check if screen has the option set + // and, if not, check the global options. + const CConfig::CScreenOptions* options = + m_config.getOptions(getName(m_active)); + if (options == NULL || options->count(kOptionScreenSwitchCorners) == 0) { + options = m_config.getOptions(""); + } + if (options != NULL && options->count(kOptionScreenSwitchCorners) > 0) { + // get corner mask and size + CConfig::CScreenOptions::const_iterator i = + options->find(kOptionScreenSwitchCorners); + UInt32 corners = static_cast(i->second); + i = options->find(kOptionScreenSwitchCornerSize); + SInt32 size = 0; + if (i != options->end()) { + size = i->second; + } + + // see if we're in a locked corner + if ((getCorner(m_active, xActive, yActive, size) & corners) != 0) { + // yep, no switching + LOG((CLOG_DEBUG1 "locked in corner")); + preventSwitch = true; + stopSwitch(); + } + } + + // ignore if mouse is locked to screen and don't try to switch later + if (!preventSwitch && isLockedToScreen()) { + LOG((CLOG_DEBUG1 "locked to screen")); + preventSwitch = true; + stopSwitch(); + } + + return !preventSwitch; +} + +void +CServer::noSwitch(SInt32 x, SInt32 y) +{ + armSwitchTwoTap(x, y); + stopSwitchWait(); +} + +void +CServer::stopSwitch() +{ + if (m_switchScreen != NULL) { + m_switchScreen = NULL; + m_switchDir = kNoDirection; + stopSwitchTwoTap(); + stopSwitchWait(); + } +} + +void +CServer::startSwitchTwoTap() +{ + m_switchTwoTapEngaged = true; + m_switchTwoTapArmed = false; + m_switchTwoTapTimer.reset(); + LOG((CLOG_DEBUG1 "waiting for second tap")); +} + +void +CServer::armSwitchTwoTap(SInt32 x, SInt32 y) +{ + if (m_switchTwoTapEngaged) { + if (m_switchTwoTapTimer.getTime() > m_switchTwoTapDelay) { + // second tap took too long. disengage. + stopSwitchTwoTap(); + } + else if (!m_switchTwoTapArmed) { + // still time for a double tap. see if we left the tap + // zone and, if so, arm the two tap. + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + SInt32 tapZone = m_primaryClient->getJumpZoneSize(); + if (tapZone < m_switchTwoTapZone) { + tapZone = m_switchTwoTapZone; + } + if (x >= ax + tapZone && x < ax + aw - tapZone && + y >= ay + tapZone && y < ay + ah - tapZone) { + // win32 can generate bogus mouse events that appear to + // move in the opposite direction that the mouse actually + // moved. try to ignore that crap here. + switch (m_switchDir) { + case kLeft: + m_switchTwoTapArmed = (m_xDelta > 0 && m_xDelta2 > 0); + break; + + case kRight: + m_switchTwoTapArmed = (m_xDelta < 0 && m_xDelta2 < 0); + break; + + case kTop: + m_switchTwoTapArmed = (m_yDelta > 0 && m_yDelta2 > 0); + break; + + case kBottom: + m_switchTwoTapArmed = (m_yDelta < 0 && m_yDelta2 < 0); + break; + + default: + break; + } + } + } + } +} + +void +CServer::stopSwitchTwoTap() +{ + m_switchTwoTapEngaged = false; + m_switchTwoTapArmed = false; +} + +bool +CServer::isSwitchTwoTapStarted() const +{ + return m_switchTwoTapEngaged; +} + +bool +CServer::shouldSwitchTwoTap() const +{ + // this is the second tap if two-tap is armed and this tap + // came fast enough + return (m_switchTwoTapArmed && + m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay); +} + +void +CServer::startSwitchWait(SInt32 x, SInt32 y) +{ + stopSwitchWait(); + m_switchWaitX = x; + m_switchWaitY = y; + m_switchWaitTimer = EVENTQUEUE->newOneShotTimer(m_switchWaitDelay, this); + LOG((CLOG_DEBUG1 "waiting to switch")); +} + +void +CServer::stopSwitchWait() +{ + if (m_switchWaitTimer != NULL) { + EVENTQUEUE->deleteTimer(m_switchWaitTimer); + m_switchWaitTimer = NULL; + } +} + +bool +CServer::isSwitchWaitStarted() const +{ + return (m_switchWaitTimer != NULL); +} + +UInt32 +CServer::getCorner(CBaseClientProxy* client, + SInt32 x, SInt32 y, SInt32 size) const +{ + assert(client != NULL); + + // get client screen shape + SInt32 ax, ay, aw, ah; + client->getShape(ax, ay, aw, ah); + + // check for x,y on the left or right + SInt32 xSide; + if (x <= ax) { + xSide = -1; + } + else if (x >= ax + aw - 1) { + xSide = 1; + } + else { + xSide = 0; + } + + // check for x,y on the top or bottom + SInt32 ySide; + if (y <= ay) { + ySide = -1; + } + else if (y >= ay + ah - 1) { + ySide = 1; + } + else { + ySide = 0; + } + + // if against the left or right then check if y is within size + if (xSide != 0) { + if (y < ay + size) { + return (xSide < 0) ? kTopLeftMask : kTopRightMask; + } + else if (y >= ay + ah - size) { + return (xSide < 0) ? kBottomLeftMask : kBottomRightMask; + } + } + + // if against the left or right then check if y is within size + if (ySide != 0) { + if (x < ax + size) { + return (ySide < 0) ? kTopLeftMask : kBottomLeftMask; + } + else if (x >= ax + aw - size) { + return (ySide < 0) ? kTopRightMask : kBottomRightMask; + } + } + + return kNoCornerMask; +} + +void +CServer::stopRelativeMoves() +{ + if (m_relativeMoves && m_active != m_primaryClient) { + // warp to the center of the active client so we know where we are + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + m_x = ax + (aw >> 1); + m_y = ay + (ah >> 1); + m_xDelta = 0; + m_yDelta = 0; + m_xDelta2 = 0; + m_yDelta2 = 0; + LOG((CLOG_DEBUG2 "synchronize move on %s by %d,%d", getName(m_active).c_str(), m_x, m_y)); + m_active->mouseMove(m_x, m_y); + } +} + +void +CServer::sendOptions(CBaseClientProxy* client) const +{ + COptionsList optionsList; + + // look up options for client + const CConfig::CScreenOptions* options = + m_config.getOptions(getName(client)); + if (options != NULL) { + // convert options to a more convenient form for sending + optionsList.reserve(2 * options->size()); + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + optionsList.push_back(index->first); + optionsList.push_back(static_cast(index->second)); + } + } + + // look up global options + options = m_config.getOptions(""); + if (options != NULL) { + // convert options to a more convenient form for sending + optionsList.reserve(optionsList.size() + 2 * options->size()); + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + optionsList.push_back(index->first); + optionsList.push_back(static_cast(index->second)); + } + } + + // send the options + client->resetOptions(); + client->setOptions(optionsList); +} + +void +CServer::processOptions() +{ + const CConfig::CScreenOptions* options = m_config.getOptions(""); + if (options == NULL) { + return; + } + + bool newRelativeMoves = m_relativeMoves; + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + const OptionID id = index->first; + const OptionValue value = index->second; + if (id == kOptionScreenSwitchDelay) { + m_switchWaitDelay = 1.0e-3 * static_cast(value); + if (m_switchWaitDelay < 0.0) { + m_switchWaitDelay = 0.0; + } + stopSwitchWait(); + } + else if (id == kOptionScreenSwitchTwoTap) { + m_switchTwoTapDelay = 1.0e-3 * static_cast(value); + if (m_switchTwoTapDelay < 0.0) { + m_switchTwoTapDelay = 0.0; + } + stopSwitchTwoTap(); + } + else if (id == kOptionRelativeMouseMoves) { + newRelativeMoves = (value != 0); + } + } + + if (m_relativeMoves && !newRelativeMoves) { + stopRelativeMoves(); + } + m_relativeMoves = newRelativeMoves; +} + +void +CServer::handleShapeChanged(const CEvent&, void* vclient) +{ + // ignore events from unknown clients + CBaseClientProxy* client = reinterpret_cast(vclient); + if (m_clientSet.count(client) == 0) { + return; + } + + LOG((CLOG_INFO "screen \"%s\" shape changed", getName(client).c_str())); + + // update jump coordinate + SInt32 x, y; + client->getCursorPos(x, y); + client->setJumpCursorPos(x, y); + + // update the mouse coordinates + if (client == m_active) { + m_x = x; + m_y = y; + } + + // handle resolution change to primary screen + if (client == m_primaryClient) { + if (client == m_active) { + onMouseMovePrimary(m_x, m_y); + } + else { + onMouseMoveSecondary(0, 0); + } + } +} + +void +CServer::handleClipboardGrabbed(const CEvent& event, void* vclient) +{ + // ignore events from unknown clients + CBaseClientProxy* grabber = reinterpret_cast(vclient); + if (m_clientSet.count(grabber) == 0) { + return; + } + const IScreen::CClipboardInfo* info = + reinterpret_cast(event.getData()); + + // ignore grab if sequence number is old. always allow primary + // screen to grab. + CClipboardInfo& clipboard = m_clipboards[info->m_id]; + if (grabber != m_primaryClient && + info->m_sequenceNumber < clipboard.m_clipboardSeqNum) { + LOG((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", getName(grabber).c_str(), info->m_id)); + return; + } + + // mark screen as owning clipboard + LOG((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", getName(grabber).c_str(), info->m_id, clipboard.m_clipboardOwner.c_str())); + clipboard.m_clipboardOwner = getName(grabber); + clipboard.m_clipboardSeqNum = info->m_sequenceNumber; + + // clear the clipboard data (since it's not known at this point) + if (clipboard.m_clipboard.open(0)) { + clipboard.m_clipboard.empty(); + clipboard.m_clipboard.close(); + } + clipboard.m_clipboardData = clipboard.m_clipboard.marshall(); + + // tell all other screens to take ownership of clipboard. tell the + // grabber that it's clipboard isn't dirty. + for (CClientList::iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + CBaseClientProxy* client = index->second; + if (client == grabber) { + client->setClipboardDirty(info->m_id, false); + } + else { + client->grabClipboard(info->m_id); + } + } +} + +void +CServer::handleClipboardChanged(const CEvent& event, void* vclient) +{ + // ignore events from unknown clients + CBaseClientProxy* sender = reinterpret_cast(vclient); + if (m_clientSet.count(sender) == 0) { + return; + } + const IScreen::CClipboardInfo* info = + reinterpret_cast(event.getData()); + onClipboardChanged(sender, info->m_id, info->m_sequenceNumber); +} + +void +CServer::handleKeyDownEvent(const CEvent& event, void*) +{ + IPlatformScreen::CKeyInfo* info = + reinterpret_cast(event.getData()); + onKeyDown(info->m_key, info->m_mask, info->m_button, info->m_screens); +} + +void +CServer::handleKeyUpEvent(const CEvent& event, void*) +{ + IPlatformScreen::CKeyInfo* info = + reinterpret_cast(event.getData()); + onKeyUp(info->m_key, info->m_mask, info->m_button, info->m_screens); +} + +void +CServer::handleKeyRepeatEvent(const CEvent& event, void*) +{ + IPlatformScreen::CKeyInfo* info = + reinterpret_cast(event.getData()); + onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button); +} + +void +CServer::handleButtonDownEvent(const CEvent& event, void*) +{ + IPlatformScreen::CButtonInfo* info = + reinterpret_cast(event.getData()); + onMouseDown(info->m_button); +} + +void +CServer::handleButtonUpEvent(const CEvent& event, void*) +{ + IPlatformScreen::CButtonInfo* info = + reinterpret_cast(event.getData()); + onMouseUp(info->m_button); +} + +void +CServer::handleMotionPrimaryEvent(const CEvent& event, void*) +{ + IPlatformScreen::CMotionInfo* info = + reinterpret_cast(event.getData()); + onMouseMovePrimary(info->m_x, info->m_y); +} + +void +CServer::handleMotionSecondaryEvent(const CEvent& event, void*) +{ + IPlatformScreen::CMotionInfo* info = + reinterpret_cast(event.getData()); + onMouseMoveSecondary(info->m_x, info->m_y); +} + +void +CServer::handleWheelEvent(const CEvent& event, void*) +{ + IPlatformScreen::CWheelInfo* info = + reinterpret_cast(event.getData()); + onMouseWheel(info->m_xDelta, info->m_yDelta); +} + +void +CServer::handleScreensaverActivatedEvent(const CEvent&, void*) +{ + onScreensaver(true); +} + +void +CServer::handleScreensaverDeactivatedEvent(const CEvent&, void*) +{ + onScreensaver(false); +} + +void +CServer::handleSwitchWaitTimeout(const CEvent&, void*) +{ + // ignore if mouse is locked to screen + if (isLockedToScreen()) { + LOG((CLOG_DEBUG1 "locked to screen")); + stopSwitch(); + return; + } + + // switch screen + switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false); +} + +void +CServer::handleClientDisconnected(const CEvent&, void* vclient) +{ + // client has disconnected. it might be an old client or an + // active client. we don't care so just handle it both ways. + CBaseClientProxy* client = reinterpret_cast(vclient); + removeActiveClient(client); + removeOldClient(client); + delete client; +} + +void +CServer::handleClientCloseTimeout(const CEvent&, void* vclient) +{ + // client took too long to disconnect. just dump it. + CBaseClientProxy* client = reinterpret_cast(vclient); + LOG((CLOG_NOTE "forced disconnection of client \"%s\"", getName(client).c_str())); + removeOldClient(client); + delete client; +} + +void +CServer::handleSwitchToScreenEvent(const CEvent& event, void*) +{ + CSwitchToScreenInfo* info = + reinterpret_cast(event.getData()); + + CClientList::const_iterator index = m_clients.find(info->m_screen); + if (index == m_clients.end()) { + LOG((CLOG_DEBUG1 "screen \"%s\" not active", info->m_screen)); + } + else { + jumpToScreen(index->second); + } +} + +void +CServer::handleSwitchInDirectionEvent(const CEvent& event, void*) +{ + CSwitchInDirectionInfo* info = + reinterpret_cast(event.getData()); + + // jump to screen in chosen direction from center of this screen + SInt32 x = m_x, y = m_y; + CBaseClientProxy* newScreen = + getNeighbor(m_active, info->m_direction, x, y); + if (newScreen == NULL) { + LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(info->m_direction))); + } + else { + jumpToScreen(newScreen); + } +} + +void +CServer::handleKeyboardBroadcastEvent(const CEvent& event, void*) +{ + CKeyboardBroadcastInfo* info = (CKeyboardBroadcastInfo*)event.getData(); + + // choose new state + bool newState; + switch (info->m_state) { + case CKeyboardBroadcastInfo::kOff: + newState = false; + break; + + default: + case CKeyboardBroadcastInfo::kOn: + newState = true; + break; + + case CKeyboardBroadcastInfo::kToggle: + newState = !m_keyboardBroadcasting; + break; + } + + // enter new state + if (newState != m_keyboardBroadcasting || + info->m_screens != m_keyboardBroadcastingScreens) { + m_keyboardBroadcasting = newState; + m_keyboardBroadcastingScreens = info->m_screens; + LOG((CLOG_DEBUG "keyboard broadcasting %s: %s", m_keyboardBroadcasting ? "on" : "off", m_keyboardBroadcastingScreens.c_str())); + } +} + +void +CServer::handleLockCursorToScreenEvent(const CEvent& event, void*) +{ + CLockCursorToScreenInfo* info = (CLockCursorToScreenInfo*)event.getData(); + + // choose new state + bool newState; + switch (info->m_state) { + case CLockCursorToScreenInfo::kOff: + newState = false; + break; + + default: + case CLockCursorToScreenInfo::kOn: + newState = true; + break; + + case CLockCursorToScreenInfo::kToggle: + newState = !m_lockedToScreen; + break; + } + + // enter new state + if (newState != m_lockedToScreen) { + m_lockedToScreen = newState; + LOG((CLOG_DEBUG "cursor %s current screen", m_lockedToScreen ? "locked to" : "unlocked from")); + + m_primaryClient->reconfigure(getActivePrimarySides()); + if (!isLockedToScreenServer()) { + stopRelativeMoves(); + } + } +} + +void +CServer::handleFakeInputBeginEvent(const CEvent&, void*) +{ + m_primaryClient->fakeInputBegin(); +} + +void +CServer::handleFakeInputEndEvent(const CEvent&, void*) +{ + m_primaryClient->fakeInputEnd(); +} + +void +CServer::onClipboardChanged(CBaseClientProxy* sender, + ClipboardID id, UInt32 seqNum) +{ + CClipboardInfo& clipboard = m_clipboards[id]; + + // ignore update if sequence number is old + if (seqNum < clipboard.m_clipboardSeqNum) { + LOG((CLOG_INFO "ignored screen \"%s\" update of clipboard %d (missequenced)", getName(sender).c_str(), id)); + return; + } + + // should be the expected client + assert(sender == m_clients.find(clipboard.m_clipboardOwner)->second); + + // get data + sender->getClipboard(id, &clipboard.m_clipboard); + + // ignore if data hasn't changed + CString data = clipboard.m_clipboard.marshall(); + if (data == clipboard.m_clipboardData) { + LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", clipboard.m_clipboardOwner.c_str(), id)); + return; + } + + // got new data + LOG((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id)); + clipboard.m_clipboardData = data; + + // tell all clients except the sender that the clipboard is dirty + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + CBaseClientProxy* client = index->second; + client->setClipboardDirty(id, client != sender); + } + + // send the new clipboard to the active screen + m_active->setClipboard(id, &clipboard.m_clipboard); +} + +void +CServer::onScreensaver(bool activated) +{ + LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated")); + + if (activated) { + // save current screen and position + m_activeSaver = m_active; + m_xSaver = m_x; + m_ySaver = m_y; + + // jump to primary screen + if (m_active != m_primaryClient) { + switchScreen(m_primaryClient, 0, 0, true); + } + } + else { + // jump back to previous screen and position. we must check + // that the position is still valid since the screen may have + // changed resolutions while the screen saver was running. + if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) { + // check position + CBaseClientProxy* screen = m_activeSaver; + SInt32 x, y, w, h; + screen->getShape(x, y, w, h); + SInt32 zoneSize = getJumpZoneSize(screen); + if (m_xSaver < x + zoneSize) { + m_xSaver = x + zoneSize; + } + else if (m_xSaver >= x + w - zoneSize) { + m_xSaver = x + w - zoneSize - 1; + } + if (m_ySaver < y + zoneSize) { + m_ySaver = y + zoneSize; + } + else if (m_ySaver >= y + h - zoneSize) { + m_ySaver = y + h - zoneSize - 1; + } + + // jump + switchScreen(screen, m_xSaver, m_ySaver, false); + } + + // reset state + m_activeSaver = NULL; + } + + // send message to all clients + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + CBaseClientProxy* client = index->second; + client->screensaver(activated); + } +} + +void +CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button, + const char* screens) +{ + LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button)); + assert(m_active != NULL); + + // relay + if (!m_keyboardBroadcasting || + (screens && IKeyState::CKeyInfo::isDefault(screens))) { + m_active->keyDown(id, mask, button); + } + else { + if (!screens && m_keyboardBroadcasting) { + screens = m_keyboardBroadcastingScreens.c_str(); + if (IKeyState::CKeyInfo::isDefault(screens)) { + screens = "*"; + } + } + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + if (IKeyState::CKeyInfo::contains(screens, index->first)) { + index->second->keyDown(id, mask, button); + } + } + } +} + +void +CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button, + const char* screens) +{ + LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button)); + assert(m_active != NULL); + + // relay + if (!m_keyboardBroadcasting || + (screens && IKeyState::CKeyInfo::isDefault(screens))) { + m_active->keyUp(id, mask, button); + } + else { + if (!screens && m_keyboardBroadcasting) { + screens = m_keyboardBroadcastingScreens.c_str(); + if (IKeyState::CKeyInfo::isDefault(screens)) { + screens = "*"; + } + } + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + if (IKeyState::CKeyInfo::contains(screens, index->first)) { + index->second->keyUp(id, mask, button); + } + } + } +} + +void +CServer::onKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button)); + assert(m_active != NULL); + + // relay + m_active->keyRepeat(id, mask, count, button); +} + +void +CServer::onMouseDown(ButtonID id) +{ + LOG((CLOG_DEBUG1 "onMouseDown id=%d", id)); + assert(m_active != NULL); + + // relay + m_active->mouseDown(id); +} + +void +CServer::onMouseUp(ButtonID id) +{ + LOG((CLOG_DEBUG1 "onMouseUp id=%d", id)); + assert(m_active != NULL); + + // relay + m_active->mouseUp(id); +} + +bool +CServer::onMouseMovePrimary(SInt32 x, SInt32 y) +{ + LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y)); + + // mouse move on primary (server's) screen + if (m_active != m_primaryClient) { + // stale event -- we're actually on a secondary screen + return false; + } + + // save last delta + m_xDelta2 = m_xDelta; + m_yDelta2 = m_yDelta; + + // save current delta + m_xDelta = x - m_x; + m_yDelta = y - m_y; + + // save position + m_x = x; + m_y = y; + + // get screen shape + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + SInt32 zoneSize = getJumpZoneSize(m_active); + + // clamp position to screen + SInt32 xc = x, yc = y; + if (xc < ax + zoneSize) { + xc = ax; + } + else if (xc >= ax + aw - zoneSize) { + xc = ax + aw - 1; + } + if (yc < ay + zoneSize) { + yc = ay; + } + else if (yc >= ay + ah - zoneSize) { + yc = ay + ah - 1; + } + + // see if we should change screens + EDirection dir; + if (x < ax + zoneSize) { + x -= zoneSize; + dir = kLeft; + } + else if (x >= ax + aw - zoneSize) { + x += zoneSize; + dir = kRight; + } + else if (y < ay + zoneSize) { + y -= zoneSize; + dir = kTop; + } + else if (y >= ay + ah - zoneSize) { + y += zoneSize; + dir = kBottom; + } + else { + // still on local screen + noSwitch(x, y); + return false; + } + + // get jump destination + CBaseClientProxy* newScreen = mapToNeighbor(m_active, dir, x, y); + + // should we switch or not? + if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) { + // switch screen + switchScreen(newScreen, x, y, false); + return true; + } + else { + return false; + } +} + +void +CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy) +{ + LOG((CLOG_DEBUG2 "onMouseMoveSecondary %+d,%+d", dx, dy)); + + // mouse move on secondary (client's) screen + assert(m_active != NULL); + if (m_active == m_primaryClient) { + // stale event -- we're actually on the primary screen + return; + } + + // if doing relative motion on secondary screens and we're locked + // to the screen (which activates relative moves) then send a + // relative mouse motion. when we're doing this we pretend as if + // the mouse isn't actually moving because we're expecting some + // program on the secondary screen to warp the mouse on us, so we + // have no idea where it really is. + if (m_relativeMoves && isLockedToScreenServer()) { + LOG((CLOG_DEBUG2 "relative move on %s by %d,%d", getName(m_active).c_str(), dx, dy)); + m_active->mouseRelativeMove(dx, dy); + return; + } + + // save old position + const SInt32 xOld = m_x; + const SInt32 yOld = m_y; + + // save last delta + m_xDelta2 = m_xDelta; + m_yDelta2 = m_yDelta; + + // save current delta + m_xDelta = dx; + m_yDelta = dy; + + // accumulate motion + m_x += dx; + m_y += dy; + + // get screen shape + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + + // find direction of neighbor and get the neighbor + bool jump = true; + CBaseClientProxy* newScreen; + do { + // clamp position to screen + SInt32 xc = m_x, yc = m_y; + if (xc < ax) { + xc = ax; + } + else if (xc >= ax + aw) { + xc = ax + aw - 1; + } + if (yc < ay) { + yc = ay; + } + else if (yc >= ay + ah) { + yc = ay + ah - 1; + } + + EDirection dir; + if (m_x < ax) { + dir = kLeft; + } + else if (m_x > ax + aw - 1) { + dir = kRight; + } + else if (m_y < ay) { + dir = kTop; + } + else if (m_y > ay + ah - 1) { + dir = kBottom; + } + else { + // we haven't left the screen + newScreen = m_active; + jump = false; + + // if waiting and mouse is not on the border we're waiting + // on then stop waiting. also if it's not on the border + // then arm the double tap. + if (m_switchScreen != NULL) { + bool clearWait; + SInt32 zoneSize = m_primaryClient->getJumpZoneSize(); + switch (m_switchDir) { + case kLeft: + clearWait = (m_x >= ax + zoneSize); + break; + + case kRight: + clearWait = (m_x <= ax + aw - 1 - zoneSize); + break; + + case kTop: + clearWait = (m_y >= ay + zoneSize); + break; + + case kBottom: + clearWait = (m_y <= ay + ah - 1 + zoneSize); + break; + + default: + clearWait = false; + break; + } + if (clearWait) { + // still on local screen + noSwitch(m_x, m_y); + } + } + + // skip rest of block + break; + } + + // try to switch screen. get the neighbor. + newScreen = mapToNeighbor(m_active, dir, m_x, m_y); + + // see if we should switch + if (!isSwitchOkay(newScreen, dir, m_x, m_y, xc, yc)) { + newScreen = m_active; + jump = false; + } + } while (false); + + if (jump) { + // switch screens + switchScreen(newScreen, m_x, m_y, false); + } + else { + // same screen. clamp mouse to edge. + m_x = xOld + dx; + m_y = yOld + dy; + if (m_x < ax) { + m_x = ax; + LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", getName(m_active).c_str())); + } + else if (m_x > ax + aw - 1) { + m_x = ax + aw - 1; + LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", getName(m_active).c_str())); + } + if (m_y < ay) { + m_y = ay; + LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", getName(m_active).c_str())); + } + else if (m_y > ay + ah - 1) { + m_y = ay + ah - 1; + LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", getName(m_active).c_str())); + } + + // warp cursor if it moved. + if (m_x != xOld || m_y != yOld) { + LOG((CLOG_DEBUG2 "move on %s to %d,%d", getName(m_active).c_str(), m_x, m_y)); + m_active->mouseMove(m_x, m_y); + } + } +} + +void +CServer::onMouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + LOG((CLOG_DEBUG1 "onMouseWheel %+d,%+d", xDelta, yDelta)); + assert(m_active != NULL); + + // relay + m_active->mouseWheel(xDelta, yDelta); +} + +bool +CServer::addClient(CBaseClientProxy* client) +{ + CString name = getName(client); + if (m_clients.count(name) != 0) { + return false; + } + + // add event handlers + EVENTQUEUE->adoptHandler(IScreen::getShapeChangedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleShapeChanged, client)); + EVENTQUEUE->adoptHandler(IScreen::getClipboardGrabbedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleClipboardGrabbed, client)); + EVENTQUEUE->adoptHandler(CClientProxy::getClipboardChangedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleClipboardChanged, client)); + + // add to list + m_clientSet.insert(client); + m_clients.insert(std::make_pair(name, client)); + + // initialize client data + SInt32 x, y; + client->getCursorPos(x, y); + client->setJumpCursorPos(x, y); + + // tell primary client about the active sides + m_primaryClient->reconfigure(getActivePrimarySides()); + + return true; +} + +bool +CServer::removeClient(CBaseClientProxy* client) +{ + // return false if not in list + CClientSet::iterator i = m_clientSet.find(client); + if (i == m_clientSet.end()) { + return false; + } + + // remove event handlers + EVENTQUEUE->removeHandler(IScreen::getShapeChangedEvent(), + client->getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getClipboardGrabbedEvent(), + client->getEventTarget()); + EVENTQUEUE->removeHandler(CClientProxy::getClipboardChangedEvent(), + client->getEventTarget()); + + // remove from list + m_clients.erase(getName(client)); + m_clientSet.erase(i); + + return true; +} + +void +CServer::closeClient(CBaseClientProxy* client, const char* msg) +{ + assert(client != m_primaryClient); + assert(msg != NULL); + + // send message to client. this message should cause the client + // to disconnect. we add this client to the closed client list + // and install a timer to remove the client if it doesn't respond + // quickly enough. we also remove the client from the active + // client list since we're not going to listen to it anymore. + // note that this method also works on clients that are not in + // the m_clients list. adoptClient() may call us with such a + // client. + LOG((CLOG_NOTE "disconnecting client \"%s\"", getName(client).c_str())); + + // send message + // FIXME -- avoid type cast (kinda hard, though) + ((CClientProxy*)client)->close(msg); + + // install timer. wait timeout seconds for client to close. + double timeout = 5.0; + CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(timeout, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, timer, + new TMethodEventJob(this, + &CServer::handleClientCloseTimeout, client)); + + // move client to closing list + removeClient(client); + m_oldClients.insert(std::make_pair(client, timer)); + + // if this client is the active screen then we have to + // jump off of it + forceLeaveClient(client); +} + +void +CServer::closeClients(const CConfig& config) +{ + // collect the clients that are connected but are being dropped + // from the configuration (or who's canonical name is changing). + typedef std::set CRemovedClients; + CRemovedClients removed; + for (CClientList::iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + if (!config.isCanonicalName(index->first)) { + removed.insert(index->second); + } + } + + // don't close the primary client + removed.erase(m_primaryClient); + + // now close them. we collect the list then close in two steps + // because closeClient() modifies the collection we iterate over. + for (CRemovedClients::iterator index = removed.begin(); + index != removed.end(); ++index) { + closeClient(*index, kMsgCClose); + } +} + +void +CServer::removeActiveClient(CBaseClientProxy* client) +{ + if (removeClient(client)) { + forceLeaveClient(client); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + if (m_clients.size() == 1 && m_oldClients.empty()) { + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this)); + } + } +} + +void +CServer::removeOldClient(CBaseClientProxy* client) +{ + COldClients::iterator i = m_oldClients.find(client); + if (i != m_oldClients.end()) { + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + EVENTQUEUE->removeHandler(CEvent::kTimer, i->second); + EVENTQUEUE->deleteTimer(i->second); + m_oldClients.erase(i); + if (m_clients.size() == 1 && m_oldClients.empty()) { + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this)); + } + } +} + +void +CServer::forceLeaveClient(CBaseClientProxy* client) +{ + CBaseClientProxy* active = + (m_activeSaver != NULL) ? m_activeSaver : m_active; + if (active == client) { + // record new position (center of primary screen) + m_primaryClient->getCursorCenter(m_x, m_y); + + // stop waiting to switch to this client + if (active == m_switchScreen) { + stopSwitch(); + } + + // don't notify active screen since it has probably already + // disconnected. + LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", getName(active).c_str(), getName(m_primaryClient).c_str(), m_x, m_y)); + + // cut over + m_active = m_primaryClient; + + // enter new screen (unless we already have because of the + // screen saver) + if (m_activeSaver == NULL) { + m_primaryClient->enter(m_x, m_y, m_seqNum, + m_primaryClient->getToggleMask(), false); + } + } + + // if this screen had the cursor when the screen saver activated + // then we can't switch back to it when the screen saver + // deactivates. + if (m_activeSaver == client) { + m_activeSaver = NULL; + } + + // tell primary client about the active sides + m_primaryClient->reconfigure(getActivePrimarySides()); +} + + +// +// CServer::CClipboardInfo +// + +CServer::CClipboardInfo::CClipboardInfo() : + m_clipboard(), + m_clipboardData(), + m_clipboardOwner(), + m_clipboardSeqNum(0) +{ + // do nothing +} + + +// +// CServer::CLockCursorToScreenInfo +// + +CServer::CLockCursorToScreenInfo* +CServer::CLockCursorToScreenInfo::alloc(State state) +{ + CLockCursorToScreenInfo* info = + (CLockCursorToScreenInfo*)malloc(sizeof(CLockCursorToScreenInfo)); + info->m_state = state; + return info; +} + + +// +// CServer::CSwitchToScreenInfo +// + +CServer::CSwitchToScreenInfo* +CServer::CSwitchToScreenInfo::alloc(const CString& screen) +{ + CSwitchToScreenInfo* info = + (CSwitchToScreenInfo*)malloc(sizeof(CSwitchToScreenInfo) + + screen.size()); + strcpy(info->m_screen, screen.c_str()); + return info; +} + + +// +// CServer::CSwitchInDirectionInfo +// + +CServer::CSwitchInDirectionInfo* +CServer::CSwitchInDirectionInfo::alloc(EDirection direction) +{ + CSwitchInDirectionInfo* info = + (CSwitchInDirectionInfo*)malloc(sizeof(CSwitchInDirectionInfo)); + info->m_direction = 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 +// + +CServer::CKeyboardBroadcastInfo* +CServer::CKeyboardBroadcastInfo::alloc(State state) +{ + CKeyboardBroadcastInfo* info = + (CKeyboardBroadcastInfo*)malloc(sizeof(CKeyboardBroadcastInfo)); + info->m_state = state; + info->m_screens[0] = '\0'; + return info; +} + +CServer::CKeyboardBroadcastInfo* +CServer::CKeyboardBroadcastInfo::alloc(State state, const CString& screens) +{ + CKeyboardBroadcastInfo* info = + (CKeyboardBroadcastInfo*)malloc(sizeof(CKeyboardBroadcastInfo) + + screens.size()); + info->m_state = state; + strcpy(info->m_screens, screens.c_str()); + return info; +} diff --git a/lib/server/CServer.h b/lib/server/CServer.h new file mode 100644 index 00000000..36cf5e83 --- /dev/null +++ b/lib/server/CServer.h @@ -0,0 +1,464 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSERVER_H +#define CSERVER_H + +#include "CConfig.h" +#include "CClipboard.h" +#include "ClipboardTypes.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "CEvent.h" +#include "CStopwatch.h" +#include "stdmap.h" +#include "stdset.h" +#include "stdvector.h" + +class CBaseClientProxy; +class CEventQueueTimer; +class CPrimaryClient; +class CInputFilter; + +//! Synergy server +/*! +This class implements the top-level server algorithms for synergy. +*/ +class CServer { +public: + //! Lock cursor to screen data + class CLockCursorToScreenInfo { + public: + enum State { kOff, kOn, kToggle }; + + static CLockCursorToScreenInfo* alloc(State state = kToggle); + + public: + State m_state; + }; + + //! Switch to screen data + class CSwitchToScreenInfo { + public: + static CSwitchToScreenInfo* alloc(const CString& screen); + + public: + // this is a C-string; this type is a variable size structure + char m_screen[1]; + }; + + //! Switch in direction data + class CSwitchInDirectionInfo { + public: + static CSwitchInDirectionInfo* alloc(EDirection direction); + + public: + EDirection m_direction; + }; + + //! Screen connected data + class CScreenConnectedInfo { + public: + static CScreenConnectedInfo* alloc(const CString& screen); + + public: + // this is a C-string; this type is a variable size structure + char m_screen[1]; + }; + + //! Keyboard broadcast data + class CKeyboardBroadcastInfo { + public: + enum State { kOff, kOn, kToggle }; + + static CKeyboardBroadcastInfo* alloc(State state = kToggle); + static CKeyboardBroadcastInfo* alloc(State state, + const CString& screens); + + public: + State m_state; + char m_screens[1]; + }; + + /*! + Start the server with the configuration \p config and the primary + client (local screen) \p primaryClient. The client retains + ownership of \p primaryClient. + */ + CServer(const CConfig& config, CPrimaryClient* primaryClient); + ~CServer(); + + //! @name manipulators + //@{ + + //! Set configuration + /*! + Change the server's configuration. Returns true iff the new + configuration was accepted (it must include the server's name). + This will disconnect any clients no longer in the configuration. + */ + bool setConfig(const CConfig&); + + //! Add a client + /*! + Adds \p client to the server. The client is adopted and will be + destroyed when the client disconnects or is disconnected. + */ + void adoptClient(CBaseClientProxy* client); + + //! Disconnect clients + /*! + Disconnect clients. This tells them to disconnect but does not wait + for them to actually do so. The server sends the disconnected event + when they're all disconnected (or immediately if none are connected). + The caller can also just destroy this object to force the disconnection. + */ + void disconnect(); + + //@} + //! @name accessors + //@{ + + //! Get number of connected clients + /*! + Returns the number of connected clients, including the server itself. + */ + UInt32 getNumClients() const; + + //! Get the list of connected clients + /*! + Set the \c list to the names of the currently connected clients. + */ + void getClients(std::vector& list) const; + + //! Get error event type + /*! + Returns the error event type. This is sent when the server fails + for some reason. + */ + static CEvent::Type getErrorEvent(); + + //! Get connected event type + /*! + Returns the connected event type. This is sent when a client screen + has connected. The event data is a \c CScreenConnectedInfo* that + indicates the connected screen. + */ + static CEvent::Type getConnectedEvent(); + + //! Get disconnected event type + /*! + Returns the disconnected event type. This is sent when all the + clients have disconnected. + */ + static CEvent::Type getDisconnectedEvent(); + + //! Get switch to screen event type + /*! + Returns the switch to screen event type. The server responds to this + by switching screens. The event data is a \c CSwitchToScreenInfo* + that indicates the target screen. + */ + static CEvent::Type getSwitchToScreenEvent(); + + //! Get switch in direction event type + /*! + Returns the switch in direction event type. The server responds to this + by switching screens. The event data is a \c CSwitchInDirectionInfo* + that indicates the target direction. + */ + static CEvent::Type getSwitchInDirectionEvent(); + + //! Get keyboard broadcast event type + /*! + Returns the keyboard broadcast event type. The server responds + to this by turning on keyboard broadcasting or turning it off. The + event data is a \c CKeyboardBroadcastInfo*. + */ + static CEvent::Type getKeyboardBroadcastEvent(); + + //! Get lock cursor event type + /*! + Returns the lock cursor event type. The server responds to this + by locking the cursor to the active screen or unlocking it. The + event data is a \c CLockCursorToScreenInfo*. + */ + static CEvent::Type getLockCursorToScreenEvent(); + + //@} + +private: + // get canonical name of client + CString getName(const CBaseClientProxy*) const; + + // get the sides of the primary screen that have neighbors + UInt32 getActivePrimarySides() const; + + // returns true iff mouse should be locked to the current screen + // according to this object only, ignoring what the primary client + // says. + bool isLockedToScreenServer() const; + + // returns true iff mouse should be locked to the current screen + // according to this object or the primary client. + bool isLockedToScreen() const; + + // returns the jump zone of the client + SInt32 getJumpZoneSize(CBaseClientProxy*) const; + + // change the active screen + void switchScreen(CBaseClientProxy*, + SInt32 x, SInt32 y, bool forScreenSaver); + + // jump to screen + void jumpToScreen(CBaseClientProxy*); + + // convert pixel position to fraction, using x or y depending on the + // direction. + float mapToFraction(CBaseClientProxy*, EDirection, + SInt32 x, SInt32 y) const; + + // convert fraction to pixel position, writing only x or y depending + // on the direction. + void mapToPixel(CBaseClientProxy*, EDirection, float f, + SInt32& x, SInt32& y) const; + + // returns true if the client has a neighbor anywhere along the edge + // indicated by the direction. + bool hasAnyNeighbor(CBaseClientProxy*, EDirection) const; + + // lookup neighboring screen, mapping the coordinate independent of + // the direction to the neighbor's coordinate space. + CBaseClientProxy* getNeighbor(CBaseClientProxy*, EDirection, + SInt32& x, SInt32& y) const; + + // lookup neighboring screen. given a position relative to the + // source screen, find the screen we should move onto and where. + // if the position is sufficiently far from the source then we + // cross multiple screens. if there is no suitable screen then + // return NULL and x,y are not modified. + CBaseClientProxy* mapToNeighbor(CBaseClientProxy*, EDirection, + SInt32& x, SInt32& y) const; + + // adjusts x and y or neither to avoid ending up in a jump zone + // after entering the client in the given direction. + void avoidJumpZone(CBaseClientProxy*, EDirection, + SInt32& x, SInt32& y) const; + + // test if a switch is permitted. this includes testing user + // options like switch delay and tracking any state required to + // implement them. returns true iff a switch is permitted. + bool isSwitchOkay(CBaseClientProxy* dst, EDirection, + SInt32 x, SInt32 y, SInt32 xActive, SInt32 yActive); + + // update switch state due to a mouse move at \p x, \p y that + // doesn't switch screens. + void noSwitch(SInt32 x, SInt32 y); + + // stop switch timers + void stopSwitch(); + + // start two tap switch timer + void startSwitchTwoTap(); + + // arm the two tap switch timer if \p x, \p y is outside the tap zone + void armSwitchTwoTap(SInt32 x, SInt32 y); + + // stop the two tap switch timer + void stopSwitchTwoTap(); + + // returns true iff the two tap switch timer is started + bool isSwitchTwoTapStarted() const; + + // returns true iff should switch because of two tap + bool shouldSwitchTwoTap() const; + + // start delay switch timer + void startSwitchWait(SInt32 x, SInt32 y); + + // stop delay switch timer + void stopSwitchWait(); + + // returns true iff the delay switch timer is started + bool isSwitchWaitStarted() const; + + // returns the corner (EScreenSwitchCornerMasks) where x,y is on the + // given client. corners have the given size. + UInt32 getCorner(CBaseClientProxy*, + SInt32 x, SInt32 y, SInt32 size) const; + + // stop relative mouse moves + void stopRelativeMoves(); + + // send screen options to \c client + void sendOptions(CBaseClientProxy* client) const; + + // process options from configuration + void processOptions(); + + // event handlers + void handleShapeChanged(const CEvent&, void*); + void handleClipboardGrabbed(const CEvent&, void*); + void handleClipboardChanged(const CEvent&, void*); + void handleKeyDownEvent(const CEvent&, void*); + void handleKeyUpEvent(const CEvent&, void*); + void handleKeyRepeatEvent(const CEvent&, void*); + void handleButtonDownEvent(const CEvent&, void*); + void handleButtonUpEvent(const CEvent&, void*); + void handleMotionPrimaryEvent(const CEvent&, void*); + void handleMotionSecondaryEvent(const CEvent&, void*); + void handleWheelEvent(const CEvent&, void*); + void handleScreensaverActivatedEvent(const CEvent&, void*); + void handleScreensaverDeactivatedEvent(const CEvent&, void*); + void handleSwitchWaitTimeout(const CEvent&, void*); + void handleClientDisconnected(const CEvent&, void*); + void handleClientCloseTimeout(const CEvent&, void*); + void handleSwitchToScreenEvent(const CEvent&, void*); + void handleSwitchInDirectionEvent(const CEvent&, void*); + void handleKeyboardBroadcastEvent(const CEvent&,void*); + void handleLockCursorToScreenEvent(const CEvent&, void*); + void handleFakeInputBeginEvent(const CEvent&, void*); + void handleFakeInputEndEvent(const CEvent&, void*); + + // event processing + void onClipboardChanged(CBaseClientProxy* sender, + ClipboardID id, UInt32 seqNum); + void onScreensaver(bool activated); + void onKeyDown(KeyID, KeyModifierMask, KeyButton, + const char* screens); + void onKeyUp(KeyID, KeyModifierMask, KeyButton, + const char* screens); + void onKeyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton); + void onMouseDown(ButtonID); + void onMouseUp(ButtonID); + bool onMouseMovePrimary(SInt32 x, SInt32 y); + void onMouseMoveSecondary(SInt32 dx, SInt32 dy); + void onMouseWheel(SInt32 xDelta, SInt32 yDelta); + + // add client to list and attach event handlers for client + bool addClient(CBaseClientProxy*); + + // remove client from list and detach event handlers for client + bool removeClient(CBaseClientProxy*); + + // close a client + void closeClient(CBaseClientProxy*, const char* msg); + + // close clients not in \p config + void closeClients(const CConfig& config); + + // close all clients whether they've completed the handshake or not, + // except the primary client + void closeAllClients(); + + // remove clients from internal state + void removeActiveClient(CBaseClientProxy*); + void removeOldClient(CBaseClientProxy*); + + // force the cursor off of \p client + void forceLeaveClient(CBaseClientProxy* client); + +private: + class CClipboardInfo { + public: + CClipboardInfo(); + + public: + CClipboard m_clipboard; + CString m_clipboardData; + CString m_clipboardOwner; + UInt32 m_clipboardSeqNum; + }; + + // the primary screen client + CPrimaryClient* m_primaryClient; + + // all clients (including the primary client) indexed by name + typedef std::map CClientList; + typedef std::set CClientSet; + CClientList m_clients; + CClientSet m_clientSet; + + // all old connections that we're waiting to hangup + typedef std::map COldClients; + COldClients m_oldClients; + + // the client with focus + CBaseClientProxy* m_active; + + // the sequence number of enter messages + UInt32 m_seqNum; + + // current mouse position (in absolute screen coordinates) on + // whichever screen is active + SInt32 m_x, m_y; + + // last mouse deltas. this is needed to smooth out double tap + // on win32 which reports bogus mouse motion at the edge of + // the screen when using low level hooks, synthesizing motion + // in the opposite direction the mouse actually moved. + SInt32 m_xDelta, m_yDelta; + SInt32 m_xDelta2, m_yDelta2; + + // current configuration + CConfig m_config; + + // input filter (from m_config); + CInputFilter* m_inputFilter; + + // clipboard cache + CClipboardInfo m_clipboards[kClipboardEnd]; + + // state saved when screen saver activates + CBaseClientProxy* m_activeSaver; + SInt32 m_xSaver, m_ySaver; + + // common state for screen switch tests. all tests are always + // trying to reach the same screen in the same direction. + EDirection m_switchDir; + CBaseClientProxy* m_switchScreen; + + // state for delayed screen switching + double m_switchWaitDelay; + CEventQueueTimer* m_switchWaitTimer; + SInt32 m_switchWaitX, m_switchWaitY; + + // state for double-tap screen switching + double m_switchTwoTapDelay; + CStopwatch m_switchTwoTapTimer; + bool m_switchTwoTapEngaged; + bool m_switchTwoTapArmed; + SInt32 m_switchTwoTapZone; + + // relative mouse move option + bool m_relativeMoves; + + // flag whether or not we have broadcasting enabled and the screens to + // which we should send broadcasted keys. + bool m_keyboardBroadcasting; + CString m_keyboardBroadcastingScreens; + + // screen locking (former scroll lock) + bool m_lockedToScreen; + + static CEvent::Type s_errorEvent; + static CEvent::Type s_connectedEvent; + static CEvent::Type s_disconnectedEvent; + static CEvent::Type s_switchToScreen; + static CEvent::Type s_switchInDirection; + static CEvent::Type s_keyboardBroadcast; + static CEvent::Type s_lockCursorToScreen; +}; + +#endif diff --git a/lib/server/Makefile.am b/lib/server/Makefile.am new file mode 100644 index 00000000..e03d9b7c --- /dev/null +++ b/lib/server/Makefile.am @@ -0,0 +1,60 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libserver.a +libserver_a_SOURCES = \ + CBaseClientProxy.cpp \ + CClientListener.cpp \ + CClientProxy.cpp \ + CClientProxy1_0.cpp \ + CClientProxy1_1.cpp \ + CClientProxy1_2.cpp \ + CClientProxy1_3.cpp \ + CClientProxyUnknown.cpp \ + CConfig.cpp \ + CInputFilter.cpp \ + CPrimaryClient.cpp \ + CServer.cpp \ + CBaseClientProxy.h \ + CClientListener.h \ + CClientProxy.h \ + CClientProxy1_0.h \ + CClientProxy1_1.h \ + CClientProxy1_2.h \ + CClientProxy1_3.h \ + CClientProxyUnknown.h \ + CConfig.h \ + CInputFilter.h \ + CPrimaryClient.h \ + CServer.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + -I$(top_srcdir)/lib/synergy \ + -I$(top_srcdir)/lib/platform \ + $(NULL) diff --git a/lib/server/Makefile.win b/lib/server/Makefile.win new file mode 100644 index 00000000..ed059e51 --- /dev/null +++ b/lib/server/Makefile.win @@ -0,0 +1,83 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +LIB_SERVER_SRC = lib\server +LIB_SERVER_DST = $(BUILD_DST)\$(LIB_SERVER_SRC) +LIB_SERVER_LIB = "$(LIB_SERVER_DST)\server.lib" +LIB_SERVER_CPP = \ + "CBaseClientProxy.cpp" \ + "CClientListener.cpp" \ + "CClientProxy.cpp" \ + "CClientProxy1_0.cpp" \ + "CClientProxy1_1.cpp" \ + "CClientProxy1_2.cpp" \ + "CClientProxy1_3.cpp" \ + "CClientProxyUnknown.cpp" \ + "CConfig.cpp" \ + "CInputFilter.cpp" \ + "CPrimaryClient.cpp" \ + "CServer.cpp" \ + $(NULL) +LIB_SERVER_OBJ = \ + "$(LIB_SERVER_DST)\CBaseClientProxy.obj" \ + "$(LIB_SERVER_DST)\CClientListener.obj" \ + "$(LIB_SERVER_DST)\CClientProxy.obj" \ + "$(LIB_SERVER_DST)\CClientProxy1_0.obj" \ + "$(LIB_SERVER_DST)\CClientProxy1_1.obj" \ + "$(LIB_SERVER_DST)\CClientProxy1_2.obj" \ + "$(LIB_SERVER_DST)\CClientProxy1_3.obj" \ + "$(LIB_SERVER_DST)\CClientProxyUnknown.obj" \ + "$(LIB_SERVER_DST)\CConfig.obj" \ + "$(LIB_SERVER_DST)\CInputFilter.obj" \ + "$(LIB_SERVER_DST)\CPrimaryClient.obj" \ + "$(LIB_SERVER_DST)\CServer.obj" \ + $(NULL) +LIB_SERVER_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + /I"lib\platform" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_SERVER_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_SERVER_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_SERVER_LIB) + +# Dependency rules +$(LIB_SERVER_OBJ): $(AUTODEP) +!if EXIST($(LIB_SERVER_DST)\deps.mak) +!include $(LIB_SERVER_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_SERVER_SRC)\}.cpp{$(LIB_SERVER_DST)\}.obj:: +!else +{$(LIB_SERVER_SRC)\}.cpp{$(LIB_SERVER_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_SERVER_SRC) + -@$(MKDIR) $(LIB_SERVER_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_SERVER_INC) \ + /Fo$(LIB_SERVER_DST)\ \ + /Fd$(LIB_SERVER_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_SERVER_SRC) $(LIB_SERVER_DST) +$(LIB_SERVER_LIB): $(LIB_SERVER_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_SERVER_SRC) $(LIB_SERVER_DST) $(**:.obj=.d) diff --git a/lib/synergy/CClipboard.cpp b/lib/synergy/CClipboard.cpp new file mode 100644 index 00000000..d0388a01 --- /dev/null +++ b/lib/synergy/CClipboard.cpp @@ -0,0 +1,114 @@ +/* + * 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 "CClipboard.h" + +// +// CClipboard +// + +CClipboard::CClipboard() : + m_open(false), + m_owner(false) +{ + open(0); + empty(); + close(); +} + +CClipboard::~CClipboard() +{ + // do nothing +} + +bool +CClipboard::empty() +{ + assert(m_open); + + // clear all data + for (SInt32 index = 0; index < kNumFormats; ++index) { + m_data[index] = ""; + m_added[index] = false; + } + + // save time + m_timeOwned = m_time; + + // we're the owner now + m_owner = true; + + return true; +} + +void +CClipboard::add(EFormat format, const CString& data) +{ + assert(m_open); + assert(m_owner); + + m_data[format] = data; + m_added[format] = true; +} + +bool +CClipboard::open(Time time) const +{ + assert(!m_open); + + m_open = true; + m_time = time; + + return true; +} + +void +CClipboard::close() const +{ + assert(m_open); + + m_open = false; +} + +CClipboard::Time +CClipboard::getTime() const +{ + return m_timeOwned; +} + +bool +CClipboard::has(EFormat format) const +{ + assert(m_open); + return m_added[format]; +} + +CString +CClipboard::get(EFormat format) const +{ + assert(m_open); + return m_data[format]; +} + +void +CClipboard::unmarshall(const CString& data, Time time) +{ + IClipboard::unmarshall(this, data, time); +} + +CString +CClipboard::marshall() const +{ + return IClipboard::marshall(this); +} diff --git a/lib/synergy/CClipboard.h b/lib/synergy/CClipboard.h new file mode 100644 index 00000000..f8d10aff --- /dev/null +++ b/lib/synergy/CClipboard.h @@ -0,0 +1,70 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIPBOARD_H +#define CCLIPBOARD_H + +#include "IClipboard.h" + +//! Memory buffer clipboard +/*! +This class implements a clipboard that stores data in memory. +*/ +class CClipboard : public IClipboard { +public: + CClipboard(); + virtual ~CClipboard(); + + //! @name manipulators + //@{ + + //! Unmarshall clipboard data + /*! + Extract marshalled clipboard data and store it in this clipboard. + Sets the clipboard time to \c time. + */ + void unmarshall(const CString& data, Time time); + + //@} + //! @name accessors + //@{ + + //! Marshall clipboard data + /*! + Merge this clipboard's data into a single buffer that can be later + unmarshalled to restore the clipboard and return the buffer. + */ + CString marshall() const; + + //@} + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const CString& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual CString get(EFormat) const; + +private: + mutable bool m_open; + mutable Time m_time; + bool m_owner; + Time m_timeOwned; + bool m_added[kNumFormats]; + CString m_data[kNumFormats]; +}; + +#endif diff --git a/lib/synergy/CKeyMap.cpp b/lib/synergy/CKeyMap.cpp new file mode 100644 index 00000000..4141c8b1 --- /dev/null +++ b/lib/synergy/CKeyMap.cpp @@ -0,0 +1,1330 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2005 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 "CKeyMap.h" +#include "KeyTypes.h" +#include "CLog.h" +#include +#include +#include + +CKeyMap::CNameToKeyMap* CKeyMap::s_nameToKeyMap = NULL; +CKeyMap::CNameToModifierMap* CKeyMap::s_nameToModifierMap = NULL; +CKeyMap::CKeyToNameMap* CKeyMap::s_keyToNameMap = NULL; +CKeyMap::CModifierToNameMap* CKeyMap::s_modifierToNameMap = NULL; + +CKeyMap::CKeyMap() : + m_numGroups(0), + m_composeAcrossGroups(false) +{ + m_modifierKeyItem.m_id = kKeyNone; + m_modifierKeyItem.m_group = 0; + m_modifierKeyItem.m_button = 0; + m_modifierKeyItem.m_required = 0; + m_modifierKeyItem.m_sensitive = 0; + m_modifierKeyItem.m_generates = 0; + m_modifierKeyItem.m_dead = false; + m_modifierKeyItem.m_lock = false; + m_modifierKeyItem.m_client = 0; +} + +CKeyMap::~CKeyMap() +{ + // do nothing +} + +void +CKeyMap::swap(CKeyMap& x) +{ + m_keyIDMap.swap(x.m_keyIDMap); + m_modifierKeys.swap(x.m_modifierKeys); + m_halfDuplex.swap(x.m_halfDuplex); + m_halfDuplexMods.swap(x.m_halfDuplexMods); + SInt32 tmp1 = m_numGroups; + m_numGroups = x.m_numGroups; + x.m_numGroups = tmp1; + bool tmp2 = m_composeAcrossGroups; + m_composeAcrossGroups = x.m_composeAcrossGroups; + x.m_composeAcrossGroups = tmp2; +} + +void +CKeyMap::addKeyEntry(const KeyItem& item) +{ + // ignore kKeyNone + if (item.m_id == kKeyNone) { + return; + } + + // resize number of groups for key + SInt32 numGroups = item.m_group + 1; + if (getNumGroups() > numGroups) { + numGroups = getNumGroups(); + } + KeyGroupTable& groupTable = m_keyIDMap[item.m_id]; + if (groupTable.size() < static_cast(numGroups)) { + groupTable.resize(numGroups); + } + + // make a list from the item + KeyItemList items; + items.push_back(item); + + // set group and dead key flag on the item + KeyItem& newItem = items.back(); + newItem.m_dead = isDeadKey(item.m_id); + + // mask the required bits with the sensitive bits + newItem.m_required &= newItem.m_sensitive; + + // see if we already have this item; just return if so + KeyEntryList& entries = groupTable[item.m_group]; + for (size_t i = 0, n = entries.size(); i < n; ++i) { + if (entries[i].size() == 1 && newItem == entries[i][0]) { + return; + } + } + + // 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" : "")); +} + +void +CKeyMap::addKeyAliasEntry(KeyID targetID, SInt32 group, + KeyModifierMask targetRequired, + KeyModifierMask targetSensitive, + KeyID sourceID, + KeyModifierMask sourceRequired, + KeyModifierMask sourceSensitive) +{ + // if we can already generate the target as desired then we're done. + if (findCompatibleKey(targetID, group, targetRequired, + targetSensitive) != NULL) { + return; + } + + // find a compatible source, preferably in the same group + for (SInt32 gd = 0, n = getNumGroups(); gd < n; ++gd) { + SInt32 eg = getEffectiveGroup(group, gd); + const KeyItemList* sourceEntry = + findCompatibleKey(sourceID, eg, + sourceRequired, sourceSensitive); + if (sourceEntry != NULL && sourceEntry->size() == 1) { + CKeyMap::KeyItem targetItem = sourceEntry->back(); + targetItem.m_id = targetID; + targetItem.m_group = eg; + addKeyEntry(targetItem); + break; + } + } +} + +bool +CKeyMap::addKeyCombinationEntry(KeyID id, SInt32 group, + const KeyID* keys, UInt32 numKeys) +{ + // disallow kKeyNone + if (id == kKeyNone) { + return false; + } + + SInt32 numGroups = group + 1; + if (getNumGroups() > numGroups) { + numGroups = getNumGroups(); + } + KeyGroupTable& groupTable = m_keyIDMap[id]; + if (groupTable.size() < static_cast(numGroups)) { + groupTable.resize(numGroups); + } + if (!groupTable[group].empty()) { + // key is already in the table + return false; + } + + // convert to buttons + KeyItemList items; + for (UInt32 i = 0; i < numKeys; ++i) { + KeyIDMap::const_iterator gtIndex = m_keyIDMap.find(keys[i]); + if (gtIndex == m_keyIDMap.end()) { + return false; + } + const KeyGroupTable& groupTable = gtIndex->second; + + // if we allow group switching during composition then search all + // groups for keys, otherwise search just the given group. + SInt32 n = 1; + if (m_composeAcrossGroups) { + n = (SInt32)groupTable.size(); + } + + bool found = false; + for (SInt32 gd = 0; gd < n && !found; ++gd) { + SInt32 eg = (group + gd) % getNumGroups(); + const KeyEntryList& entries = groupTable[eg]; + for (size_t j = 0; j < entries.size(); ++j) { + if (entries[j].size() == 1) { + found = true; + items.push_back(entries[j][0]); + break; + } + } + } + if (!found) { + // required key is not in keyboard group + return false; + } + } + + // add key + groupTable[group].push_back(items); + return true; +} + +void +CKeyMap::allowGroupSwitchDuringCompose() +{ + m_composeAcrossGroups = true; +} + +void +CKeyMap::addHalfDuplexButton(KeyButton button) +{ + m_halfDuplex.insert(button); +} + +void +CKeyMap::clearHalfDuplexModifiers() +{ + m_halfDuplexMods.clear(); +} + +void +CKeyMap::addHalfDuplexModifier(KeyID key) +{ + m_halfDuplexMods.insert(key); +} + +void +CKeyMap::finish() +{ + m_numGroups = findNumGroups(); + + // make sure every key has the same number of groups + for (KeyIDMap::iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + i->second.resize(m_numGroups); + } + + // compute keys that generate each modifier + setModifierKeys(); +} + +void +CKeyMap::foreachKey(ForeachKeyCallback cb, void* userData) +{ + for (KeyIDMap::iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + KeyGroupTable& groupTable = i->second; + for (size_t group = 0; group < groupTable.size(); ++group) { + KeyEntryList& entryList = groupTable[group]; + for (size_t j = 0; j < entryList.size(); ++j) { + KeyItemList& itemList = entryList[j]; + for (size_t k = 0; k < itemList.size(); ++k) { + (*cb)(i->first, static_cast(group), + itemList[k], userData); + } + } + } + } +} + +const CKeyMap::KeyItem* +CKeyMap::mapKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + LOG((CLOG_DEBUG1 "mapKey %04x (%d) with mask %04x, start state: %04x", id, id, desiredMask, currentState)); + + // handle group change + if (id == kKeyNextGroup) { + keys.push_back(Keystroke(1, false, false)); + return NULL; + } + else if (id == kKeyPrevGroup) { + keys.push_back(Keystroke(-1, false, false)); + return NULL; + } + + const KeyItem* item; + switch (id) { + case kKeyShift_L: + case kKeyShift_R: + case kKeyControl_L: + case kKeyControl_R: + case kKeyAlt_L: + case kKeyAlt_R: + case kKeyMeta_L: + case kKeyMeta_R: + case kKeySuper_L: + case kKeySuper_R: + case kKeyAltGr: + case kKeyCapsLock: + case kKeyNumLock: + case kKeyScrollLock: + item = mapModifierKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); + break; + + case kKeySetModifiers: + if (!keysForModifierState(0, group, activeModifiers, currentState, + desiredMask, desiredMask, 0, keys)) { + LOG((CLOG_DEBUG1 "unable to set modifiers %04x", desiredMask)); + return NULL; + } + return &m_modifierKeyItem; + + case kKeyClearModifiers: + if (!keysForModifierState(0, group, activeModifiers, currentState, + currentState & ~desiredMask, + desiredMask, 0, keys)) { + LOG((CLOG_DEBUG1 "unable to clear modifiers %04x", desiredMask)); + return NULL; + } + return &m_modifierKeyItem; + + default: + if (isCommand(desiredMask)) { + item = mapCommandKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); + } + else { + item = mapCharacterKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); + } + break; + } + + if (item != NULL) { + LOG((CLOG_DEBUG1 "mapped to %03x, new state %04x", item->m_button, currentState)); + } + return item; +} + +SInt32 +CKeyMap::getNumGroups() const +{ + return m_numGroups; +} + +SInt32 +CKeyMap::getEffectiveGroup(SInt32 group, SInt32 offset) const +{ + return (group + offset + getNumGroups()) % getNumGroups(); +} + +const CKeyMap::KeyItemList* +CKeyMap::findCompatibleKey(KeyID id, SInt32 group, + KeyModifierMask required, KeyModifierMask sensitive) const +{ + assert(group >= 0 && group < getNumGroups()); + + KeyIDMap::const_iterator i = m_keyIDMap.find(id); + if (i == m_keyIDMap.end()) { + return NULL; + } + + const KeyEntryList& entries = i->second[group]; + for (size_t j = 0; j < entries.size(); ++j) { + if ((entries[j].back().m_sensitive & sensitive) == 0 || + (entries[j].back().m_required & sensitive) == + (required & sensitive)) { + return &entries[j]; + } + } + + return NULL; +} + +bool +CKeyMap::isHalfDuplex(KeyID key, KeyButton button) const +{ + return (m_halfDuplex.count(button) + m_halfDuplexMods.count(key) > 0); +} + +bool +CKeyMap::isCommand(KeyModifierMask mask) const +{ + return ((mask & getCommandModifiers()) != 0); +} + +KeyModifierMask +CKeyMap::getCommandModifiers() const +{ + // we currently treat ctrl, alt, meta and super as command modifiers. + // some platforms may have a more limited set (OS X only needs Alt) + // but this works anyway. + return KeyModifierControl | + KeyModifierAlt | + KeyModifierMeta | + KeyModifierSuper; +} + +void +CKeyMap::collectButtons(const ModifierToKeys& mods, ButtonToKeyMap& keys) +{ + keys.clear(); + for (ModifierToKeys::const_iterator i = mods.begin(); + i != mods.end(); ++i) { + keys.insert(std::make_pair(i->second.m_button, &i->second)); + } +} + +void +CKeyMap::initModifierKey(KeyItem& item) +{ + item.m_generates = 0; + item.m_lock = false; + switch (item.m_id) { + case kKeyShift_L: + case kKeyShift_R: + item.m_generates = KeyModifierShift; + break; + + case kKeyControl_L: + case kKeyControl_R: + item.m_generates = KeyModifierControl; + break; + + case kKeyAlt_L: + case kKeyAlt_R: + item.m_generates = KeyModifierAlt; + break; + + case kKeyMeta_L: + case kKeyMeta_R: + item.m_generates = KeyModifierMeta; + break; + + case kKeySuper_L: + case kKeySuper_R: + item.m_generates = KeyModifierSuper; + break; + + case kKeyAltGr: + item.m_generates = KeyModifierAltGr; + break; + + case kKeyCapsLock: + item.m_generates = KeyModifierCapsLock; + item.m_lock = true; + break; + + case kKeyNumLock: + item.m_generates = KeyModifierNumLock; + item.m_lock = true; + break; + + case kKeyScrollLock: + item.m_generates = KeyModifierScrollLock; + item.m_lock = true; + break; + + default: + // not a modifier + break; + } +} + +SInt32 +CKeyMap::findNumGroups() const +{ + size_t max = 0; + for (KeyIDMap::const_iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + if (i->second.size() > max) { + max = i->second.size(); + } + } + return static_cast(max); +} + +void +CKeyMap::setModifierKeys() +{ + m_modifierKeys.clear(); + m_modifierKeys.resize(kKeyModifierNumBits * getNumGroups()); + for (KeyIDMap::const_iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + const KeyGroupTable& groupTable = i->second; + for (size_t g = 0; g < groupTable.size(); ++g) { + const KeyEntryList& entries = groupTable[g]; + for (size_t j = 0; j < entries.size(); ++j) { + // skip multi-key sequences + if (entries[j].size() != 1) { + continue; + } + + // skip keys that don't generate a modifier + const KeyItem& item = entries[j].back(); + if (item.m_generates == 0) { + continue; + } + + // add key to each indicated modifier in this group + for (SInt32 b = 0; b < kKeyModifierNumBits; ++b) { + // skip if item doesn't generate bit b + if (((1u << b) & item.m_generates) != 0) { + SInt32 mIndex = g * kKeyModifierNumBits + b; + m_modifierKeys[mIndex].push_back(&item); + } + } + } + } + } +} + +const CKeyMap::KeyItem* +CKeyMap::mapCommandKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + static const KeyModifierMask s_overrideModifiers = 0xffffu; + + // find KeySym in table + KeyIDMap::const_iterator i = m_keyIDMap.find(id); + if (i == m_keyIDMap.end()) { + // unknown key + LOG((CLOG_DEBUG1 "key %04x is not on keyboard", id)); + return NULL; + } + const KeyGroupTable& keyGroupTable = i->second; + + // find the first key that generates this KeyID + const KeyItem* keyItem = NULL; + SInt32 numGroups = getNumGroups(); + for (SInt32 groupOffset = 0; groupOffset < numGroups; ++groupOffset) { + SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset); + const KeyEntryList& entryList = keyGroupTable[effectiveGroup]; + for (size_t i = 0; i < entryList.size(); ++i) { + if (entryList[i].size() != 1) { + // ignore multikey entries + continue; + } + + // only match based on shift; we're after the right button + // not the right character. we'll use desiredMask as-is, + // overriding the key's required modifiers, when synthesizing + // this button. + const KeyItem& item = entryList[i].back(); + if ((item.m_required & KeyModifierShift & desiredMask) == + (item.m_sensitive & KeyModifierShift & desiredMask)) { + LOG((CLOG_DEBUG1 "found key in group %d", effectiveGroup)); + keyItem = &item; + break; + } + } + if (keyItem != NULL) { + break; + } + } + if (keyItem == NULL) { + // no mapping for this keysym + LOG((CLOG_DEBUG1 "no mapping for key %04x", id)); + return NULL; + } + + // make working copy of modifiers + ModifierToKeys newModifiers = activeModifiers; + KeyModifierMask newState = currentState; + SInt32 newGroup = group; + + // don't try to change CapsLock + desiredMask = (desiredMask & ~KeyModifierCapsLock) | + (currentState & KeyModifierCapsLock); + + // add the key + if (!keysForKeyItem(*keyItem, newGroup, newModifiers, + newState, desiredMask, + s_overrideModifiers, isAutoRepeat, keys)) { + LOG((CLOG_DEBUG1 "can't map key")); + keys.clear(); + return NULL; + } + + // add keystrokes to restore modifier keys + if (!keysToRestoreModifiers(*keyItem, group, newModifiers, newState, + activeModifiers, keys)) { + LOG((CLOG_DEBUG1 "failed to restore modifiers")); + keys.clear(); + return NULL; + } + + // add keystrokes to restore group + if (newGroup != group) { + keys.push_back(Keystroke(group, true, true)); + } + + // save new modifiers + activeModifiers = newModifiers; + currentState = newState; + + return keyItem; +} + +const CKeyMap::KeyItem* +CKeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + // find KeySym in table + KeyIDMap::const_iterator i = m_keyIDMap.find(id); + if (i == m_keyIDMap.end()) { + // unknown key + LOG((CLOG_DEBUG1 "key %04x is not on keyboard", id)); + return NULL; + } + const KeyGroupTable& keyGroupTable = i->second; + + // find best key in any group, starting with the active group + SInt32 keyIndex = -1; + SInt32 numGroups = getNumGroups(); + SInt32 groupOffset; + LOG((CLOG_DEBUG1 "find best: %04x %04x", currentState, desiredMask)); + for (groupOffset = 0; groupOffset < numGroups; ++groupOffset) { + SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset); + keyIndex = findBestKey(keyGroupTable[effectiveGroup], + currentState, desiredMask); + if (keyIndex != -1) { + LOG((CLOG_DEBUG1 "found key in group %d", effectiveGroup)); + break; + } + } + if (keyIndex == -1) { + // no mapping for this keysym + LOG((CLOG_DEBUG1 "no mapping for key %04x", id)); + return NULL; + } + + // get keys to press for key + SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset); + const KeyItemList& itemList = keyGroupTable[effectiveGroup][keyIndex]; + if (itemList.empty()) { + return NULL; + } + const KeyItem& keyItem = itemList.back(); + + // make working copy of modifiers + ModifierToKeys newModifiers = activeModifiers; + KeyModifierMask newState = currentState; + SInt32 newGroup = group; + + // add each key + for (size_t j = 0; j < itemList.size(); ++j) { + if (!keysForKeyItem(itemList[j], newGroup, newModifiers, + newState, desiredMask, + 0, isAutoRepeat, keys)) { + LOG((CLOG_DEBUG1 "can't map key")); + keys.clear(); + return NULL; + } + } + + // add keystrokes to restore modifier keys + if (!keysToRestoreModifiers(keyItem, group, newModifiers, newState, + activeModifiers, keys)) { + LOG((CLOG_DEBUG1 "failed to restore modifiers")); + keys.clear(); + return NULL; + } + + // add keystrokes to restore group + if (newGroup != group) { + keys.push_back(Keystroke(group, true, true)); + } + + // save new modifiers + activeModifiers = newModifiers; + currentState = newState; + + return &keyItem; +} + +const CKeyMap::KeyItem* +CKeyMap::mapModifierKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + return mapCharacterKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); +} + +SInt32 +CKeyMap::findBestKey(const KeyEntryList& entryList, + KeyModifierMask /*currentState*/, + KeyModifierMask desiredState) const +{ + // check for an item that can accommodate the desiredState exactly + for (size_t i = 0; i < entryList.size(); ++i) { + const KeyItem& item = entryList[i].back(); + if ((item.m_required & desiredState) == + (item.m_sensitive & desiredState)) { + LOG((CLOG_DEBUG1 "best key index %d of %d (exact)", i, entryList.size())); + return i; + } + } + + // choose the item that requires the fewest modifier changes + SInt32 bestCount = 32; + SInt32 bestIndex = -1; + for (size_t i = 0; i < entryList.size(); ++i) { + const KeyItem& item = entryList[i].back(); + KeyModifierMask change = + ((item.m_required ^ desiredState) & item.m_sensitive); + SInt32 n = getNumModifiers(change); + if (n < bestCount) { + bestCount = n; + bestIndex = i; + } + } + if (bestIndex != -1) { + LOG((CLOG_DEBUG1 "best key index %d of %d (%d modifiers)", bestIndex, entryList.size(), bestCount)); + } + + return bestIndex; +} + + +const CKeyMap::KeyItem* +CKeyMap::keyForModifier(KeyButton button, SInt32 group, + SInt32 modifierBit) const +{ + assert(modifierBit >= 0 && modifierBit < kKeyModifierNumBits); + assert(group >= 0 && group < getNumGroups()); + + // find a key that generates the given modifier in the given group + // but doesn't use the given button, presumably because we're trying + // to generate a KeyID that's only bound the the given button. + // this is important when a shift button is modified by shift; we + // must use the other shift button to do the shifting. + const ModifierKeyItemList& items = + m_modifierKeys[group * kKeyModifierNumBits + modifierBit]; + for (ModifierKeyItemList::const_iterator i = items.begin(); + i != items.end(); ++i) { + if ((*i)->m_button != button) { + return (*i); + } + } + return NULL; +} + +bool +CKeyMap::keysForKeyItem(const KeyItem& keyItem, SInt32& group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, KeyModifierMask desiredState, + KeyModifierMask overrideModifiers, + bool isAutoRepeat, + Keystrokes& keystrokes) const +{ + static const KeyModifierMask s_notRequiredMask = + KeyModifierAltGr | KeyModifierNumLock | KeyModifierScrollLock; + + // add keystrokes to adjust the group + if (group != keyItem.m_group) { + group = keyItem.m_group; + keystrokes.push_back(Keystroke(group, true, false)); + } + + EKeystroke type; + if (keyItem.m_dead) { + // adjust modifiers for dead key + if (!keysForModifierState(keyItem.m_button, group, + activeModifiers, currentState, + keyItem.m_required, keyItem.m_sensitive, + 0, keystrokes)) { + LOG((CLOG_DEBUG1 "unable to match modifier state for dead key %d", keyItem.m_button)); + return false; + } + + // press and release the dead key + type = kKeystrokeClick; + } + else { + // if this a command key then we don't have to match some of the + // key's required modifiers. + KeyModifierMask sensitive = keyItem.m_sensitive & ~overrideModifiers; + + // XXX -- must handle pressing a modifier. in particular, if we want + // to synthesize a KeyID on level 1 of a KeyButton that has Shift_L + // mapped to level 0 then we must release that button if it's down + // (in order to satisfy a shift modifier) then press a different + // button (any other button) mapped to the shift modifier and then + // the Shift_L button. + // match key's required state + LOG((CLOG_DEBUG1 "state: %04x,%04x,%04x", currentState, keyItem.m_required, sensitive)); + if (!keysForModifierState(keyItem.m_button, group, + activeModifiers, currentState, + keyItem.m_required, sensitive, + 0, keystrokes)) { + LOG((CLOG_DEBUG1 "unable to match modifier state (%04x,%04x) for key %d", keyItem.m_required, keyItem.m_sensitive, keyItem.m_button)); + return false; + } + + // match desiredState as closely as possible. we must not + // change any modifiers in keyItem.m_sensitive. and if the key + // is a modifier, we don't want to change that modifier. + LOG((CLOG_DEBUG1 "desired state: %04x %04x,%04x,%04x", desiredState, currentState, keyItem.m_required, keyItem.m_sensitive)); + if (!keysForModifierState(keyItem.m_button, group, + activeModifiers, currentState, + desiredState, + ~(sensitive | keyItem.m_generates), + s_notRequiredMask, keystrokes)) { + LOG((CLOG_DEBUG1 "unable to match desired modifier state (%04x,%04x) for key %d", desiredState, ~keyItem.m_sensitive & 0xffffu, keyItem.m_button)); + return false; + } + + // repeat or press of key + type = isAutoRepeat ? kKeystrokeRepeat : kKeystrokePress; + } + addKeystrokes(type, keyItem, activeModifiers, currentState, keystrokes); + + return true; +} + +bool +CKeyMap::keysToRestoreModifiers(const KeyItem& keyItem, SInt32, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + const ModifierToKeys& desiredModifiers, + Keystrokes& keystrokes) const +{ + // XXX -- we're not considering modified modifiers here + + ModifierToKeys oldModifiers = activeModifiers; + + // get the pressed modifier buttons before and after + ButtonToKeyMap oldKeys, newKeys; + collectButtons(oldModifiers, oldKeys); + collectButtons(desiredModifiers, newKeys); + + // release unwanted keys + for (ModifierToKeys::const_iterator i = oldModifiers.begin(); + i != oldModifiers.end(); ++i) { + KeyButton button = i->second.m_button; + if (button != keyItem.m_button && newKeys.count(button) == 0) { + EKeystroke type = kKeystrokeRelease; + if (i->second.m_lock) { + type = kKeystrokeUnmodify; + } + addKeystrokes(type, i->second, + activeModifiers, currentState, keystrokes); + } + } + + // press wanted keys + for (ModifierToKeys::const_iterator i = desiredModifiers.begin(); + i != desiredModifiers.end(); ++i) { + KeyButton button = i->second.m_button; + if (button != keyItem.m_button && oldKeys.count(button) == 0) { + EKeystroke type = kKeystrokePress; + if (i->second.m_lock) { + type = kKeystrokeModify; + } + addKeystrokes(type, i->second, + activeModifiers, currentState, keystrokes); + } + } + + return true; +} + +bool +CKeyMap::keysForModifierState(KeyButton button, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask requiredState, KeyModifierMask sensitiveMask, + KeyModifierMask notRequiredMask, + Keystrokes& keystrokes) const +{ + // compute which modifiers need changing + KeyModifierMask flipMask = ((currentState ^ requiredState) & sensitiveMask); + // if a modifier is not required then don't even try to match it. if + // we don't mask out notRequiredMask then we'll try to match those + // modifiers but succeed if we can't. however, this is known not + // to work if the key itself is a modifier (the numlock toggle can + // interfere) so we don't try to match at all. + flipMask &= ~notRequiredMask; + LOG((CLOG_DEBUG1 "flip: %04x (%04x vs %04x in %04x - %04x)", flipMask, currentState, requiredState, sensitiveMask & 0xffffu, notRequiredMask & 0xffffu)); + if (flipMask == 0) { + return true; + } + + // fix modifiers. this is complicated by the fact that a modifier may + // be sensitive to other modifiers! (who thought that up?) + // + // we'll assume that modifiers with higher bits are affected by modifiers + // with lower bits. there's not much basis for that assumption except + // that we're pretty sure shift isn't changed by other modifiers. + for (SInt32 bit = kKeyModifierNumBits; bit-- > 0; ) { + KeyModifierMask mask = (1u << bit); + if ((flipMask & mask) == 0) { + // modifier is already correct + continue; + } + + // do we want the modifier active or inactive? + bool active = ((requiredState & mask) != 0); + + // get the KeyItem for the modifier in the group + const KeyItem* keyItem = keyForModifier(button, group, bit); + if (keyItem == NULL) { + if ((mask & notRequiredMask) == 0) { + LOG((CLOG_DEBUG1 "no key for modifier %04x", mask)); + return false; + } + else { + continue; + } + } + + // if this modifier is sensitive to modifiers then adjust those + // modifiers. also check if our assumption was correct. note + // that we only need to adjust the modifiers on key down. + KeyModifierMask sensitive = keyItem->m_sensitive; + if ((sensitive & mask) != 0) { + // modifier is sensitive to itself. that makes no sense + // so ignore it. + LOG((CLOG_DEBUG1 "modifier %04x modified by itself", mask)); + sensitive &= ~mask; + } + if (sensitive != 0) { + if (sensitive > mask) { + // our assumption is incorrect + LOG((CLOG_DEBUG1 "modifier %04x modified by %04x", mask, sensitive)); + return false; + } + if (active && !keysForModifierState(button, group, + activeModifiers, currentState, + keyItem->m_required, sensitive, + notRequiredMask, keystrokes)) { + return false; + } + else if (!active) { + // release the modifier + // XXX -- this doesn't work! if Alt and Meta are mapped + // to one key and we want to release Meta we can't do + // that without also releasing Alt. + // need to think about support for modified modifiers. + } + } + + // current state should match required state + if ((currentState & sensitive) != (keyItem->m_required & sensitive)) { + LOG((CLOG_DEBUG1 "unable to match modifier state for modifier %04x (%04x vs %04x in %04x)", mask, currentState, keyItem->m_required, sensitive)); + return false; + } + + // add keystrokes + EKeystroke type = active ? kKeystrokeModify : kKeystrokeUnmodify; + addKeystrokes(type, *keyItem, activeModifiers, currentState, + keystrokes); + } + + return true; +} + +void +CKeyMap::addKeystrokes(EKeystroke type, const KeyItem& keyItem, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + Keystrokes& keystrokes) const +{ + KeyButton button = keyItem.m_button; + UInt32 data = keyItem.m_client; + switch (type) { + case kKeystrokePress: + keystrokes.push_back(Keystroke(button, true, false, data)); + if (keyItem.m_generates != 0) { + if (!keyItem.m_lock || (currentState & keyItem.m_generates) == 0) { + // add modifier key and activate modifier + activeModifiers.insert(std::make_pair( + keyItem.m_generates, keyItem)); + currentState |= keyItem.m_generates; + } + else { + // deactivate locking modifier + activeModifiers.erase(keyItem.m_generates); + currentState &= ~keyItem.m_generates; + } + } + break; + + case kKeystrokeRelease: + keystrokes.push_back(Keystroke(button, false, false, data)); + if (keyItem.m_generates != 0 && !keyItem.m_lock) { + // remove key from active modifiers + std::pair range = + activeModifiers.equal_range(keyItem.m_generates); + for (ModifierToKeys::iterator i = range.first; + i != range.second; ++i) { + if (i->second.m_button == button) { + activeModifiers.erase(i); + break; + } + } + + // if no more keys for this modifier then deactivate modifier + if (activeModifiers.count(keyItem.m_generates) == 0) { + currentState &= ~keyItem.m_generates; + } + } + break; + + case kKeystrokeRepeat: + keystrokes.push_back(Keystroke(button, false, true, data)); + keystrokes.push_back(Keystroke(button, true, true, data)); + // no modifier changes on key repeat + break; + + case kKeystrokeClick: + keystrokes.push_back(Keystroke(button, true, false, data)); + keystrokes.push_back(Keystroke(button, false, false, data)); + // no modifier changes on key click + break; + + case kKeystrokeModify: + case kKeystrokeUnmodify: + if (keyItem.m_lock) { + // we assume there's just one button for this modifier + if (m_halfDuplex.count(button) > 0) { + if (type == kKeystrokeModify) { + // turn half-duplex toggle on (press) + keystrokes.push_back(Keystroke(button, true, false, data)); + } + else { + // turn half-duplex toggle off (release) + keystrokes.push_back(Keystroke(button, false, false, data)); + } + } + else { + // toggle (click) + keystrokes.push_back(Keystroke(button, true, false, data)); + keystrokes.push_back(Keystroke(button, false, false, data)); + } + } + else if (type == kKeystrokeModify) { + // press modifier + keystrokes.push_back(Keystroke(button, true, false, data)); + } + else { + // release all the keys that generate the modifier that are + // currently down + std::pair range = + activeModifiers.equal_range(keyItem.m_generates); + for (ModifierToKeys::const_iterator i = range.first; + i != range.second; ++i) { + keystrokes.push_back(Keystroke(i->second.m_button, + false, false, i->second.m_client)); + } + } + + if (type == kKeystrokeModify) { + activeModifiers.insert(std::make_pair( + keyItem.m_generates, keyItem)); + currentState |= keyItem.m_generates; + } + else { + activeModifiers.erase(keyItem.m_generates); + currentState &= ~keyItem.m_generates; + } + break; + } +} + +SInt32 +CKeyMap::getNumModifiers(KeyModifierMask state) +{ + SInt32 n = 0; + for (; state != 0; state >>= 1) { + if ((state & 1) != 0) { + ++n; + } + } + return n; +} + +bool +CKeyMap::isDeadKey(KeyID key) +{ + return (key == kKeyCompose || (key >= 0x0300 && key <= 0x036f)); +} + +KeyID +CKeyMap::getDeadKey(KeyID key) +{ + if (isDeadKey(key)) { + // already dead + return key; + } + + switch (key) { + case '`': + return kKeyDeadGrave; + + case 0xb4u: + return kKeyDeadAcute; + + case '^': + case 0x2c6: + return kKeyDeadCircumflex; + + case '~': + case 0x2dcu: + return kKeyDeadTilde; + + case 0xafu: + return kKeyDeadMacron; + + case 0x2d8u: + return kKeyDeadBreve; + + case 0x2d9u: + return kKeyDeadAbovedot; + + case 0xa8u: + return kKeyDeadDiaeresis; + + case 0xb0u: + case 0x2dau: + return kKeyDeadAbovering; + + case '\"': + case 0x2ddu: + return kKeyDeadDoubleacute; + + case 0x2c7u: + return kKeyDeadCaron; + + case 0xb8u: + return kKeyDeadCedilla; + + case 0x2dbu: + return kKeyDeadOgonek; + + default: + // unknown + return kKeyNone; + } +} + +CString +CKeyMap::formatKey(KeyID key, KeyModifierMask mask) +{ + // initialize tables + initKeyNameMaps(); + + CString x; + for (SInt32 i = 0; i < kKeyModifierNumBits; ++i) { + KeyModifierMask mod = (1u << i); + if ((mask & mod) != 0 && s_modifierToNameMap->count(mod) > 0) { + x += s_modifierToNameMap->find(mod)->second; + x += "+"; + } + } + if (key != kKeyNone) { + if (s_keyToNameMap->count(key) > 0) { + x += s_keyToNameMap->find(key)->second; + } + // XXX -- we're assuming ASCII here + else if (key >= 33 && key < 127) { + x += (char)key; + } + else { + x += CStringUtil::print("\\u%04x", key); + } + } + else if (!x.empty()) { + // remove trailing '+' + x.erase(x.size() - 1); + } + return x; +} + +bool +CKeyMap::parseKey(const CString& x, KeyID& key) +{ + // initialize tables + initKeyNameMaps(); + + // parse the key + key = kKeyNone; + if (s_nameToKeyMap->count(x) > 0) { + key = s_nameToKeyMap->find(x)->second; + } + // XXX -- we're assuming ASCII encoding here + else if (x.size() == 1) { + if (!isgraph(x[0])) { + // unknown key + return false; + } + key = (KeyID)x[0]; + } + else if (x.size() == 6 && x[0] == '\\' && x[1] == 'u') { + // escaped unicode (\uXXXX where XXXX is a hex number) + char* end; + key = (KeyID)strtol(x.c_str() + 2, &end, 16); + if (*end != '\0') { + return false; + } + } + else if (!x.empty()) { + // unknown key + return false; + } + + return true; +} + +bool +CKeyMap::parseModifiers(CString& x, KeyModifierMask& mask) +{ + // initialize tables + initKeyNameMaps(); + + mask = 0; + CString::size_type tb = x.find_first_not_of(" \t", 0); + while (tb != CString::npos) { + // get next component + CString::size_type te = x.find_first_of(" \t+)", tb); + if (te == CString::npos) { + te = x.size(); + } + CString c = x.substr(tb, te - tb); + if (c.empty()) { + // missing component + return false; + } + + if (s_nameToModifierMap->count(c) > 0) { + KeyModifierMask mod = s_nameToModifierMap->find(c)->second; + if ((mask & mod) != 0) { + // modifier appears twice + return false; + } + mask |= mod; + } + else { + // unknown string + x.erase(0, tb); + CString::size_type tb = x.find_first_not_of(" \t"); + CString::size_type te = x.find_last_not_of(" \t"); + if (tb == CString::npos) { + x = ""; + } + else { + x = x.substr(tb, te - tb + 1); + } + return true; + } + + // check for '+' or end of string + tb = x.find_first_not_of(" \t", te); + if (tb != CString::npos) { + if (x[tb] != '+') { + // expected '+' + return false; + } + tb = x.find_first_not_of(" \t", tb + 1); + } + } + + // parsed the whole thing + x = ""; + return true; +} + +void +CKeyMap::initKeyNameMaps() +{ + // initialize tables + if (s_nameToKeyMap == NULL) { + s_nameToKeyMap = new CNameToKeyMap; + s_keyToNameMap = new CKeyToNameMap; + for (const KeyNameMapEntry* i = kKeyNameMap; i->m_name != NULL; ++i) { + (*s_nameToKeyMap)[i->m_name] = i->m_id; + (*s_keyToNameMap)[i->m_id] = i->m_name; + } + } + if (s_nameToModifierMap == NULL) { + s_nameToModifierMap = new CNameToModifierMap; + s_modifierToNameMap = new CModifierToNameMap; + for (const KeyModifierNameMapEntry* i = kModifierNameMap; + i->m_name != NULL; ++i) { + (*s_nameToModifierMap)[i->m_name] = i->m_mask; + (*s_modifierToNameMap)[i->m_mask] = i->m_name; + } + } +} + + +// +// CKeyMap::KeyItem +// + +bool +CKeyMap::KeyItem::operator==(const KeyItem& x) const +{ + return (m_id == x.m_id && + m_group == x.m_group && + m_button == x.m_button && + m_required == x.m_required && + m_sensitive == x.m_sensitive && + m_generates == x.m_generates && + m_dead == x.m_dead && + m_lock == x.m_lock && + m_client == x.m_client); +} + + +// +// CKeyMap::Keystroke +// + +CKeyMap::Keystroke::Keystroke(KeyButton button, + bool press, bool repeat, UInt32 data) : + m_type(kButton) +{ + m_data.m_button.m_button = button; + m_data.m_button.m_press = press; + m_data.m_button.m_repeat = repeat; + m_data.m_button.m_client = data; +} + +CKeyMap::Keystroke::Keystroke(SInt32 group, bool absolute, bool restore) : + m_type(kGroup) +{ + m_data.m_group.m_group = group; + m_data.m_group.m_absolute = absolute; + m_data.m_group.m_restore = restore; +} diff --git a/lib/synergy/CKeyMap.h b/lib/synergy/CKeyMap.h new file mode 100644 index 00000000..7f34113b --- /dev/null +++ b/lib/synergy/CKeyMap.h @@ -0,0 +1,491 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2005 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CKEYMAP_H +#define CKEYMAP_H + +#include "KeyTypes.h" +#include "CString.h" +#include "CStringUtil.h" +#include "stdmap.h" +#include "stdset.h" +#include "stdvector.h" + +//! Key map +/*! +This class provides a keyboard mapping. +*/ +class CKeyMap { +public: + CKeyMap(); + ~CKeyMap(); + + //! KeyID synthesis info + /*! + This structure contains the information necessary to synthesize a + keystroke that generates a KeyID (stored elsewhere). \c m_sensitive + lists the modifiers that the key is affected by and must therefore + be in the correct state, which is listed in \c m_required. If the + key is mapped to a modifier, that modifier is in \c m_generates and + is not in \c m_sensitive. + */ + struct KeyItem { + public: + KeyID m_id; //!< KeyID + SInt32 m_group; //!< Group for key + KeyButton m_button; //!< Button to generate KeyID + KeyModifierMask m_required; //!< Modifiers required for KeyID + KeyModifierMask m_sensitive; //!< Modifiers key is sensitive to + KeyModifierMask m_generates; //!< Modifiers key is mapped to + bool m_dead; //!< \c true if this is a dead KeyID + bool m_lock; //!< \c true if this locks a modifier + UInt32 m_client; //!< Client data + + public: + bool operator==(const KeyItem&) const; + }; + + //! The KeyButtons needed to synthesize a KeyID + /*! + An ordered list of \c KeyItems produces a particular KeyID. If + the KeyID can be synthesized directly then there is one entry in + the list. If dead keys are required then they're listed first. + A list is the minimal set of keystrokes necessary to synthesize + the KeyID, so it doesn't include no-ops. A list does not include + any modifier keys unless the KeyID is a modifier, in which case + it has exactly one KeyItem for the modifier itself. + */ + typedef std::vector KeyItemList; + + //! A keystroke + class Keystroke { + public: + enum EType { + kButton, //!< Synthesize button + kGroup //!< Set new group + }; + + Keystroke(KeyButton, bool press, bool repeat, UInt32 clientData); + Keystroke(SInt32 group, bool absolute, bool restore); + + public: + struct CButton { + public: + KeyButton m_button; //!< Button to synthesize + bool m_press; //!< \c true iff press + bool m_repeat; //!< \c true iff for an autorepeat + UInt32 m_client; //!< Client data + }; + struct CGroup { + public: + SInt32 m_group; //!< Group/offset to change to/by + bool m_absolute; //!< \c true iff change to, else by + bool m_restore; //!< \c true iff for restoring state + }; + union CData { + public: + CButton m_button; + CGroup m_group; + }; + + EType m_type; + CData m_data; + }; + + //! A sequence of keystrokes + typedef std::vector Keystrokes; + + //! A mapping of a modifier to keys for that modifier + typedef std::multimap ModifierToKeys; + + //! A set of buttons + typedef std::map ButtonToKeyMap; + + //! Callback type for \c foreachKey + typedef void (*ForeachKeyCallback)(KeyID, SInt32 group, + KeyItem&, void* userData); + + //! @name manipulators + //@{ + + //! Swap with another \c CKeyMap + void swap(CKeyMap&); + + //! Add a key entry + /*! + Adds \p item to the entries for the item's id and group. The + \c m_dead member is set automatically. + */ + void addKeyEntry(const KeyItem& item); + + //! Add an alias key entry + /*! + If \p targetID with the modifiers given by \p targetRequired and + \p targetSensitive is not available in group \p group then find an + entry for \p sourceID with modifiers given by \p sourceRequired and + \p sourceSensitive in any group with exactly one item and, if found, + add a new item just like it except using id \p targetID. This + effectively makes the \p sourceID an alias for \p targetID (i.e. we + can generate \p targetID using \p sourceID). + */ + void addKeyAliasEntry(KeyID targetID, SInt32 group, + KeyModifierMask targetRequired, + KeyModifierMask targetSensitive, + KeyID sourceID, + KeyModifierMask sourceRequired, + KeyModifierMask sourceSensitive); + + //! Add a key sequence entry + /*! + Adds the sequence of keys \p keys (\p numKeys elements long) to + synthesize key \p id in group \p group. This looks up in the + map each key in \p keys. If all are found then each key is + converted to the button for that key and the buttons are added + as the entry for \p id. If \p id is already in the map or at + least one key in \p keys is not in the map then nothing is added + and this returns \c false, otherwise it returns \c true. + */ + bool addKeyCombinationEntry(KeyID id, SInt32 group, + const KeyID* keys, UInt32 numKeys); + + //! Enable composition across groups + /*! + If called then the keyboard map will allow switching between groups + during key composition. Not all systems allow that. + */ + void allowGroupSwitchDuringCompose(); + + //! Add a half-duplex button + /*! + Records that button \p button is a half-duplex key. This is called + when translating the system's keyboard map. It's independent of the + half-duplex modifier calls. + */ + void addHalfDuplexButton(KeyButton button); + + //! Remove all half-duplex modifiers + /*! + Removes all half-duplex modifiers. This is called to set user + configurable half-duplex settings. + */ + void clearHalfDuplexModifiers(); + + //! Add a half-duplex modifier + /*! + Records that modifier key \p key is half-duplex. This is called to + set user configurable half-duplex settings. + */ + void addHalfDuplexModifier(KeyID key); + + //! Finish adding entries + /*! + Called after adding entries, this does some internal housekeeping. + */ + void finish(); + + //! Iterate over all added keys items + /*! + Calls \p cb for every key item. + */ + void foreachKey(ForeachKeyCallback cb, void* userData); + + //@} + //! @name accessors + //@{ + + //! Map key press/repeat to keystrokes. + /*! + Converts press/repeat of key \p id in group \p group with current + modifiers as given in \p currentState and the desired modifiers in + \p desiredMask into the keystrokes necessary to synthesize that key + event in \p keys. It returns the \c KeyItem of the key being + pressed/repeated, or NULL if the key cannot be mapped. + */ + const KeyItem* mapKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + //! Get number of groups + /*! + Returns the number of keyboard groups (independent layouts) in the map. + */ + SInt32 getNumGroups() const; + + //! Compute a group number + /*! + Returns the number of the group \p offset groups after group \p group. + */ + SInt32 getEffectiveGroup(SInt32 group, SInt32 offset) const; + + //! Find key entry compatible with modifiers + /*! + Returns the \c KeyItemList for the first entry for \p id in group + \p group that is compatible with the given modifiers, or NULL + if there isn't one. A button list is compatible with a modifiers + if it is either insensitive to all modifiers in \p sensitive or + it requires the modifiers to be in the state indicated by \p required + for every modifier indicated by \p sensitive. + */ + const KeyItemList* findCompatibleKey(KeyID id, SInt32 group, + KeyModifierMask required, + KeyModifierMask sensitive) const; + + //! Test if modifier is half-duplex + /*! + Returns \c true iff modifier key \p key or button \p button is + half-duplex. + */ + bool isHalfDuplex(KeyID key, KeyButton button) const; + + //! Test if modifiers indicate a command + /*! + Returns \c true iff the modifiers in \p mask contain any command + modifiers. A command modifier is used for keyboard shortcuts and + hotkeys, Rather than trying to synthesize a character, a command + is trying to synthesize a particular set of buttons. So it's not + important to match the shift or AltGr state to achieve a character + but it is important to match the modifier state exactly. + */ + bool isCommand(KeyModifierMask mask) const; + + // Get the modifiers that indicate a command + /*! + Returns the modifiers that when combined with other keys indicate + a command (e.g. shortcut or hotkey). + */ + KeyModifierMask getCommandModifiers() const; + + //! Get buttons from modifier map + /*! + Put all the keys in \p modifiers into \p keys. + */ + static void collectButtons(const ModifierToKeys& modifiers, + ButtonToKeyMap& keys); + + //! Set modifier key state + /*! + Sets the modifier key state (\c m_generates and \c m_lock) in \p item + based on the \c m_id in \p item. + */ + static void initModifierKey(KeyItem& item); + + //! Test for a dead key + /*! + Returns \c true if \p key is a dead key. + */ + static bool isDeadKey(KeyID key); + + //! Get corresponding dead key + /*! + Returns the dead key corresponding to \p key if one exists, otherwise + return \c kKeyNone. This returns \p key if it's already a dead key. + */ + static KeyID getDeadKey(KeyID key); + + //! Get string for a key and modifier mask + /*! + Converts a key and modifier mask into a string representing the + combination. + */ + static CString formatKey(KeyID key, KeyModifierMask); + + //! Parse a string into a key + /*! + Converts a string into a key. Returns \c true on success and \c false + if the string cannot be parsed. + */ + static bool parseKey(const CString&, KeyID&); + + //! Parse a string into a modifier mask + /*! + Converts a string into a modifier mask. Returns \c true on success + and \c false if the string cannot be parsed. The modifiers plus any + remaining leading and trailing whitespace is stripped from the input + string. + */ + static bool parseModifiers(CString&, KeyModifierMask&); + + //@} + +private: + //! Ways to synthesize a key + enum EKeystroke { + kKeystrokePress, //!< Synthesize a press + kKeystrokeRelease, //!< Synthesize a release + kKeystrokeRepeat, //!< Synthesize an autorepeat + kKeystrokeClick, //!< Synthesize a press and release + kKeystrokeModify, //!< Synthesize pressing a modifier + kKeystrokeUnmodify //!< Synthesize releasing a modifier + }; + + // A list of ways to synthesize a KeyID + typedef std::vector KeyEntryList; + + // computes the number of groups + SInt32 findNumGroups() const; + + // computes the map of modifiers to the keys that generate the modifiers + void setModifierKeys(); + + // maps a command key. a command key is a keyboard shortcut and we're + // trying to synthesize a button press with an exact sets of modifiers, + // not trying to synthesize a character. so we just need to find the + // right button and synthesize the requested modifiers without regard + // to what character they would synthesize. we disallow multikey + // entries since they don't make sense as hotkeys. + const KeyItem* mapCommandKey(Keystrokes& keys, + KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + // maps a character key. a character key is trying to synthesize a + // particular KeyID and isn't entirely concerned with the modifiers + // used to do it. + const KeyItem* mapCharacterKey(Keystrokes& keys, + KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + // maps a modifier key + const KeyItem* mapModifierKey(Keystrokes& keys, + KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + // returns the index into \p entryList of the KeyItemList requiring + // the fewest modifier changes between \p currentState and + // \p desiredState. + SInt32 findBestKey(const KeyEntryList& entryList, + KeyModifierMask currentState, + KeyModifierMask desiredState) const; + + // gets the \c KeyItem used to synthesize the modifier who's bit is + // given by \p modifierBit in group \p group and does not synthesize + // the key \p button. + const KeyItem* keyForModifier(KeyButton button, SInt32 group, + SInt32 modifierBit) const; + + // fills \p keystrokes with the keys to synthesize the key in + // \p keyItem taking the modifiers into account. returns \c true + // iff successful and sets \p currentState to the + // resulting modifier state. + bool keysForKeyItem(const KeyItem& keyItem, + SInt32& group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredState, + KeyModifierMask overrideModifiers, + bool isAutoRepeat, + Keystrokes& keystrokes) const; + + // fills \p keystrokes with the keys to synthesize the modifiers + // in \p desiredModifiers from the active modifiers listed in + // \p activeModifiers not including the key in \p keyItem. + // returns \c true iff successful. + bool keysToRestoreModifiers(const KeyItem& keyItem, + SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + const ModifierToKeys& desiredModifiers, + Keystrokes& keystrokes) const; + + // fills \p keystrokes and \p undo with the keys to change the + // current modifier state in \p currentState to match the state in + // \p requiredState for each modifier indicated in \p sensitiveMask. + // returns \c true iff successful and sets \p currentState to the + // resulting modifier state. + bool keysForModifierState(KeyButton button, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask requiredState, + KeyModifierMask sensitiveMask, + KeyModifierMask notRequiredMask, + Keystrokes& keystrokes) const; + + // Adds keystrokes to synthesize key \p keyItem in mode \p type to + // \p keystrokes and to undo the synthesis to \p undo. + void addKeystrokes(EKeystroke type, + const KeyItem& keyItem, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + Keystrokes& keystrokes) const; + + // Returns the number of modifiers indicated in \p state. + static SInt32 getNumModifiers(KeyModifierMask state); + + // Initialize key name/id maps + static void initKeyNameMaps(); + + // not implemented + CKeyMap(const CKeyMap&); + CKeyMap& operator=(const CKeyMap&); + +private: + // Ways to synthesize a KeyID over multiple keyboard groups + typedef std::vector KeyGroupTable; + + // Table of KeyID to ways to synthesize that KeyID + typedef std::map KeyIDMap; + + // List of KeyItems that generate a particular modifier + typedef std::vector ModifierKeyItemList; + + // Map a modifier to the KeyItems that synthesize that modifier + typedef std::vector ModifierToKeyTable; + + // A set of keys + typedef std::set KeySet; + + // A set of buttons + typedef std::set KeyButtonSet; + + // Key maps for parsing/formatting + typedef std::map CNameToKeyMap; + typedef std::map CNameToModifierMap; + typedef std::map CKeyToNameMap; + typedef std::map CModifierToNameMap; + + // KeyID info + KeyIDMap m_keyIDMap; + SInt32 m_numGroups; + ModifierToKeyTable m_modifierKeys; + + // composition info + bool m_composeAcrossGroups; + + // half-duplex info + KeyButtonSet m_halfDuplex; // half-duplex set by synergy + KeySet m_halfDuplexMods; // half-duplex set by user + + // dummy KeyItem for changing modifiers + KeyItem m_modifierKeyItem; + + // parsing/formatting tables + static CNameToKeyMap* s_nameToKeyMap; + static CNameToModifierMap* s_nameToModifierMap; + static CKeyToNameMap* s_keyToNameMap; + static CModifierToNameMap* s_modifierToNameMap; +}; + +#endif diff --git a/lib/synergy/CKeyState.cpp b/lib/synergy/CKeyState.cpp new file mode 100644 index 00000000..ff374828 --- /dev/null +++ b/lib/synergy/CKeyState.cpp @@ -0,0 +1,886 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CKeyState.h" +#include "IEventQueue.h" +#include "CLog.h" +#include +#include + +static const KeyButton kButtonMask = (KeyButton)(IKeyState::kNumButtons - 1); + +static const KeyID s_decomposeTable[] = { + // spacing version of dead keys + 0x0060, 0x0300, 0x0020, 0, // grave, dead_grave, space + 0x00b4, 0x0301, 0x0020, 0, // acute, dead_acute, space + 0x005e, 0x0302, 0x0020, 0, // asciicircum, dead_circumflex, space + 0x007e, 0x0303, 0x0020, 0, // asciitilde, dead_tilde, space + 0x00a8, 0x0308, 0x0020, 0, // diaeresis, dead_diaeresis, space + 0x00b0, 0x030a, 0x0020, 0, // degree, dead_abovering, space + 0x00b8, 0x0327, 0x0020, 0, // cedilla, dead_cedilla, space + 0x02db, 0x0328, 0x0020, 0, // ogonek, dead_ogonek, space + 0x02c7, 0x030c, 0x0020, 0, // caron, dead_caron, space + 0x02d9, 0x0307, 0x0020, 0, // abovedot, dead_abovedot, space + 0x02dd, 0x030b, 0x0020, 0, // doubleacute, dead_doubleacute, space + 0x02d8, 0x0306, 0x0020, 0, // breve, dead_breve, space + 0x00af, 0x0304, 0x0020, 0, // macron, dead_macron, space + + // Latin-1 (ISO 8859-1) + 0x00c0, 0x0300, 0x0041, 0, // Agrave, dead_grave, A + 0x00c1, 0x0301, 0x0041, 0, // Aacute, dead_acute, A + 0x00c2, 0x0302, 0x0041, 0, // Acircumflex, dead_circumflex, A + 0x00c3, 0x0303, 0x0041, 0, // Atilde, dead_tilde, A + 0x00c4, 0x0308, 0x0041, 0, // Adiaeresis, dead_diaeresis, A + 0x00c5, 0x030a, 0x0041, 0, // Aring, dead_abovering, A + 0x00c7, 0x0327, 0x0043, 0, // Ccedilla, dead_cedilla, C + 0x00c8, 0x0300, 0x0045, 0, // Egrave, dead_grave, E + 0x00c9, 0x0301, 0x0045, 0, // Eacute, dead_acute, E + 0x00ca, 0x0302, 0x0045, 0, // Ecircumflex, dead_circumflex, E + 0x00cb, 0x0308, 0x0045, 0, // Ediaeresis, dead_diaeresis, E + 0x00cc, 0x0300, 0x0049, 0, // Igrave, dead_grave, I + 0x00cd, 0x0301, 0x0049, 0, // Iacute, dead_acute, I + 0x00ce, 0x0302, 0x0049, 0, // Icircumflex, dead_circumflex, I + 0x00cf, 0x0308, 0x0049, 0, // Idiaeresis, dead_diaeresis, I + 0x00d1, 0x0303, 0x004e, 0, // Ntilde, dead_tilde, N + 0x00d2, 0x0300, 0x004f, 0, // Ograve, dead_grave, O + 0x00d3, 0x0301, 0x004f, 0, // Oacute, dead_acute, O + 0x00d4, 0x0302, 0x004f, 0, // Ocircumflex, dead_circumflex, O + 0x00d5, 0x0303, 0x004f, 0, // Otilde, dead_tilde, O + 0x00d6, 0x0308, 0x004f, 0, // Odiaeresis, dead_diaeresis, O + 0x00d9, 0x0300, 0x0055, 0, // Ugrave, dead_grave, U + 0x00da, 0x0301, 0x0055, 0, // Uacute, dead_acute, U + 0x00db, 0x0302, 0x0055, 0, // Ucircumflex, dead_circumflex, U + 0x00dc, 0x0308, 0x0055, 0, // Udiaeresis, dead_diaeresis, U + 0x00dd, 0x0301, 0x0059, 0, // Yacute, dead_acute, Y + 0x00e0, 0x0300, 0x0061, 0, // agrave, dead_grave, a + 0x00e1, 0x0301, 0x0061, 0, // aacute, dead_acute, a + 0x00e2, 0x0302, 0x0061, 0, // acircumflex, dead_circumflex, a + 0x00e3, 0x0303, 0x0061, 0, // atilde, dead_tilde, a + 0x00e4, 0x0308, 0x0061, 0, // adiaeresis, dead_diaeresis, a + 0x00e5, 0x030a, 0x0061, 0, // aring, dead_abovering, a + 0x00e7, 0x0327, 0x0063, 0, // ccedilla, dead_cedilla, c + 0x00e8, 0x0300, 0x0065, 0, // egrave, dead_grave, e + 0x00e9, 0x0301, 0x0065, 0, // eacute, dead_acute, e + 0x00ea, 0x0302, 0x0065, 0, // ecircumflex, dead_circumflex, e + 0x00eb, 0x0308, 0x0065, 0, // ediaeresis, dead_diaeresis, e + 0x00ec, 0x0300, 0x0069, 0, // igrave, dead_grave, i + 0x00ed, 0x0301, 0x0069, 0, // iacute, dead_acute, i + 0x00ee, 0x0302, 0x0069, 0, // icircumflex, dead_circumflex, i + 0x00ef, 0x0308, 0x0069, 0, // idiaeresis, dead_diaeresis, i + 0x00f1, 0x0303, 0x006e, 0, // ntilde, dead_tilde, n + 0x00f2, 0x0300, 0x006f, 0, // ograve, dead_grave, o + 0x00f3, 0x0301, 0x006f, 0, // oacute, dead_acute, o + 0x00f4, 0x0302, 0x006f, 0, // ocircumflex, dead_circumflex, o + 0x00f5, 0x0303, 0x006f, 0, // otilde, dead_tilde, o + 0x00f6, 0x0308, 0x006f, 0, // odiaeresis, dead_diaeresis, o + 0x00f9, 0x0300, 0x0075, 0, // ugrave, dead_grave, u + 0x00fa, 0x0301, 0x0075, 0, // uacute, dead_acute, u + 0x00fb, 0x0302, 0x0075, 0, // ucircumflex, dead_circumflex, u + 0x00fc, 0x0308, 0x0075, 0, // udiaeresis, dead_diaeresis, u + 0x00fd, 0x0301, 0x0079, 0, // yacute, dead_acute, y + 0x00ff, 0x0308, 0x0079, 0, // ydiaeresis, dead_diaeresis, y + + // Latin-2 (ISO 8859-2) + 0x0104, 0x0328, 0x0041, 0, // Aogonek, dead_ogonek, A + 0x013d, 0x030c, 0x004c, 0, // Lcaron, dead_caron, L + 0x015a, 0x0301, 0x0053, 0, // Sacute, dead_acute, S + 0x0160, 0x030c, 0x0053, 0, // Scaron, dead_caron, S + 0x015e, 0x0327, 0x0053, 0, // Scedilla, dead_cedilla, S + 0x0164, 0x030c, 0x0054, 0, // Tcaron, dead_caron, T + 0x0179, 0x0301, 0x005a, 0, // Zacute, dead_acute, Z + 0x017d, 0x030c, 0x005a, 0, // Zcaron, dead_caron, Z + 0x017b, 0x0307, 0x005a, 0, // Zabovedot, dead_abovedot, Z + 0x0105, 0x0328, 0x0061, 0, // aogonek, dead_ogonek, a + 0x013e, 0x030c, 0x006c, 0, // lcaron, dead_caron, l + 0x015b, 0x0301, 0x0073, 0, // sacute, dead_acute, s + 0x0161, 0x030c, 0x0073, 0, // scaron, dead_caron, s + 0x015f, 0x0327, 0x0073, 0, // scedilla, dead_cedilla, s + 0x0165, 0x030c, 0x0074, 0, // tcaron, dead_caron, t + 0x017a, 0x0301, 0x007a, 0, // zacute, dead_acute, z + 0x017e, 0x030c, 0x007a, 0, // zcaron, dead_caron, z + 0x017c, 0x0307, 0x007a, 0, // zabovedot, dead_abovedot, z + 0x0154, 0x0301, 0x0052, 0, // Racute, dead_acute, R + 0x0102, 0x0306, 0x0041, 0, // Abreve, dead_breve, A + 0x0139, 0x0301, 0x004c, 0, // Lacute, dead_acute, L + 0x0106, 0x0301, 0x0043, 0, // Cacute, dead_acute, C + 0x010c, 0x030c, 0x0043, 0, // Ccaron, dead_caron, C + 0x0118, 0x0328, 0x0045, 0, // Eogonek, dead_ogonek, E + 0x011a, 0x030c, 0x0045, 0, // Ecaron, dead_caron, E + 0x010e, 0x030c, 0x0044, 0, // Dcaron, dead_caron, D + 0x0143, 0x0301, 0x004e, 0, // Nacute, dead_acute, N + 0x0147, 0x030c, 0x004e, 0, // Ncaron, dead_caron, N + 0x0150, 0x030b, 0x004f, 0, // Odoubleacute, dead_doubleacute, O + 0x0158, 0x030c, 0x0052, 0, // Rcaron, dead_caron, R + 0x016e, 0x030a, 0x0055, 0, // Uring, dead_abovering, U + 0x0170, 0x030b, 0x0055, 0, // Udoubleacute, dead_doubleacute, U + 0x0162, 0x0327, 0x0054, 0, // Tcedilla, dead_cedilla, T + 0x0155, 0x0301, 0x0072, 0, // racute, dead_acute, r + 0x0103, 0x0306, 0x0061, 0, // abreve, dead_breve, a + 0x013a, 0x0301, 0x006c, 0, // lacute, dead_acute, l + 0x0107, 0x0301, 0x0063, 0, // cacute, dead_acute, c + 0x010d, 0x030c, 0x0063, 0, // ccaron, dead_caron, c + 0x0119, 0x0328, 0x0065, 0, // eogonek, dead_ogonek, e + 0x011b, 0x030c, 0x0065, 0, // ecaron, dead_caron, e + 0x010f, 0x030c, 0x0064, 0, // dcaron, dead_caron, d + 0x0144, 0x0301, 0x006e, 0, // nacute, dead_acute, n + 0x0148, 0x030c, 0x006e, 0, // ncaron, dead_caron, n + 0x0151, 0x030b, 0x006f, 0, // odoubleacute, dead_doubleacute, o + 0x0159, 0x030c, 0x0072, 0, // rcaron, dead_caron, r + 0x016f, 0x030a, 0x0075, 0, // uring, dead_abovering, u + 0x0171, 0x030b, 0x0075, 0, // udoubleacute, dead_doubleacute, u + 0x0163, 0x0327, 0x0074, 0, // tcedilla, dead_cedilla, t + + // Latin-3 (ISO 8859-3) + 0x0124, 0x0302, 0x0048, 0, // Hcircumflex, dead_circumflex, H + 0x0130, 0x0307, 0x0049, 0, // Iabovedot, dead_abovedot, I + 0x011e, 0x0306, 0x0047, 0, // Gbreve, dead_breve, G + 0x0134, 0x0302, 0x004a, 0, // Jcircumflex, dead_circumflex, J + 0x0125, 0x0302, 0x0068, 0, // hcircumflex, dead_circumflex, h + 0x011f, 0x0306, 0x0067, 0, // gbreve, dead_breve, g + 0x0135, 0x0302, 0x006a, 0, // jcircumflex, dead_circumflex, j + 0x010a, 0x0307, 0x0043, 0, // Cabovedot, dead_abovedot, C + 0x0108, 0x0302, 0x0043, 0, // Ccircumflex, dead_circumflex, C + 0x0120, 0x0307, 0x0047, 0, // Gabovedot, dead_abovedot, G + 0x011c, 0x0302, 0x0047, 0, // Gcircumflex, dead_circumflex, G + 0x016c, 0x0306, 0x0055, 0, // Ubreve, dead_breve, U + 0x015c, 0x0302, 0x0053, 0, // Scircumflex, dead_circumflex, S + 0x010b, 0x0307, 0x0063, 0, // cabovedot, dead_abovedot, c + 0x0109, 0x0302, 0x0063, 0, // ccircumflex, dead_circumflex, c + 0x0121, 0x0307, 0x0067, 0, // gabovedot, dead_abovedot, g + 0x011d, 0x0302, 0x0067, 0, // gcircumflex, dead_circumflex, g + 0x016d, 0x0306, 0x0075, 0, // ubreve, dead_breve, u + 0x015d, 0x0302, 0x0073, 0, // scircumflex, dead_circumflex, s + + // Latin-4 (ISO 8859-4) + 0x0156, 0x0327, 0x0052, 0, // Rcedilla, dead_cedilla, R + 0x0128, 0x0303, 0x0049, 0, // Itilde, dead_tilde, I + 0x013b, 0x0327, 0x004c, 0, // Lcedilla, dead_cedilla, L + 0x0112, 0x0304, 0x0045, 0, // Emacron, dead_macron, E + 0x0122, 0x0327, 0x0047, 0, // Gcedilla, dead_cedilla, G + 0x0157, 0x0327, 0x0072, 0, // rcedilla, dead_cedilla, r + 0x0129, 0x0303, 0x0069, 0, // itilde, dead_tilde, i + 0x013c, 0x0327, 0x006c, 0, // lcedilla, dead_cedilla, l + 0x0113, 0x0304, 0x0065, 0, // emacron, dead_macron, e + 0x0123, 0x0327, 0x0067, 0, // gcedilla, dead_cedilla, g + 0x0100, 0x0304, 0x0041, 0, // Amacron, dead_macron, A + 0x012e, 0x0328, 0x0049, 0, // Iogonek, dead_ogonek, I + 0x0116, 0x0307, 0x0045, 0, // Eabovedot, dead_abovedot, E + 0x012a, 0x0304, 0x0049, 0, // Imacron, dead_macron, I + 0x0145, 0x0327, 0x004e, 0, // Ncedilla, dead_cedilla, N + 0x014c, 0x0304, 0x004f, 0, // Omacron, dead_macron, O + 0x0136, 0x0327, 0x004b, 0, // Kcedilla, dead_cedilla, K + 0x0172, 0x0328, 0x0055, 0, // Uogonek, dead_ogonek, U + 0x0168, 0x0303, 0x0055, 0, // Utilde, dead_tilde, U + 0x016a, 0x0304, 0x0055, 0, // Umacron, dead_macron, U + 0x0101, 0x0304, 0x0061, 0, // amacron, dead_macron, a + 0x012f, 0x0328, 0x0069, 0, // iogonek, dead_ogonek, i + 0x0117, 0x0307, 0x0065, 0, // eabovedot, dead_abovedot, e + 0x012b, 0x0304, 0x0069, 0, // imacron, dead_macron, i + 0x0146, 0x0327, 0x006e, 0, // ncedilla, dead_cedilla, n + 0x014d, 0x0304, 0x006f, 0, // omacron, dead_macron, o + 0x0137, 0x0327, 0x006b, 0, // kcedilla, dead_cedilla, k + 0x0173, 0x0328, 0x0075, 0, // uogonek, dead_ogonek, u + 0x0169, 0x0303, 0x0075, 0, // utilde, dead_tilde, u + 0x016b, 0x0304, 0x0075, 0, // umacron, dead_macron, u + + // Latin-8 (ISO 8859-14) + 0x1e02, 0x0307, 0x0042, 0, // Babovedot, dead_abovedot, B + 0x1e03, 0x0307, 0x0062, 0, // babovedot, dead_abovedot, b + 0x1e0a, 0x0307, 0x0044, 0, // Dabovedot, dead_abovedot, D + 0x1e80, 0x0300, 0x0057, 0, // Wgrave, dead_grave, W + 0x1e82, 0x0301, 0x0057, 0, // Wacute, dead_acute, W + 0x1e0b, 0x0307, 0x0064, 0, // dabovedot, dead_abovedot, d + 0x1ef2, 0x0300, 0x0059, 0, // Ygrave, dead_grave, Y + 0x1e1e, 0x0307, 0x0046, 0, // Fabovedot, dead_abovedot, F + 0x1e1f, 0x0307, 0x0066, 0, // fabovedot, dead_abovedot, f + 0x1e40, 0x0307, 0x004d, 0, // Mabovedot, dead_abovedot, M + 0x1e41, 0x0307, 0x006d, 0, // mabovedot, dead_abovedot, m + 0x1e56, 0x0307, 0x0050, 0, // Pabovedot, dead_abovedot, P + 0x1e81, 0x0300, 0x0077, 0, // wgrave, dead_grave, w + 0x1e57, 0x0307, 0x0070, 0, // pabovedot, dead_abovedot, p + 0x1e83, 0x0301, 0x0077, 0, // wacute, dead_acute, w + 0x1e60, 0x0307, 0x0053, 0, // Sabovedot, dead_abovedot, S + 0x1ef3, 0x0300, 0x0079, 0, // ygrave, dead_grave, y + 0x1e84, 0x0308, 0x0057, 0, // Wdiaeresis, dead_diaeresis, W + 0x1e85, 0x0308, 0x0077, 0, // wdiaeresis, dead_diaeresis, w + 0x1e61, 0x0307, 0x0073, 0, // sabovedot, dead_abovedot, s + 0x0174, 0x0302, 0x0057, 0, // Wcircumflex, dead_circumflex, W + 0x1e6a, 0x0307, 0x0054, 0, // Tabovedot, dead_abovedot, T + 0x0176, 0x0302, 0x0059, 0, // Ycircumflex, dead_circumflex, Y + 0x0175, 0x0302, 0x0077, 0, // wcircumflex, dead_circumflex, w + 0x1e6b, 0x0307, 0x0074, 0, // tabovedot, dead_abovedot, t + 0x0177, 0x0302, 0x0079, 0, // ycircumflex, dead_circumflex, y + + // Latin-9 (ISO 8859-15) + 0x0178, 0x0308, 0x0059, 0, // Ydiaeresis, dead_diaeresis, Y + + // Compose key sequences + 0x00c6, kKeyCompose, 0x0041, 0x0045, 0, // AE, A, E + 0x00c1, kKeyCompose, 0x0041, 0x0027, 0, // Aacute, A, apostrophe + 0x00c2, kKeyCompose, 0x0041, 0x0053, 0, // Acircumflex, A, asciicircum + 0x00c3, kKeyCompose, 0x0041, 0x0022, 0, // Adiaeresis, A, quotedbl + 0x00c0, kKeyCompose, 0x0041, 0x0060, 0, // Agrave, A, grave + 0x00c5, kKeyCompose, 0x0041, 0x002a, 0, // Aring, A, asterisk + 0x00c3, kKeyCompose, 0x0041, 0x007e, 0, // Atilde, A, asciitilde + 0x00c7, kKeyCompose, 0x0043, 0x002c, 0, // Ccedilla, C, comma + 0x00d0, kKeyCompose, 0x0044, 0x002d, 0, // ETH, D, minus + 0x00c9, kKeyCompose, 0x0045, 0x0027, 0, // Eacute, E, apostrophe + 0x00ca, kKeyCompose, 0x0045, 0x0053, 0, // Ecircumflex, E, asciicircum + 0x00cb, kKeyCompose, 0x0045, 0x0022, 0, // Ediaeresis, E, quotedbl + 0x00c8, kKeyCompose, 0x0045, 0x0060, 0, // Egrave, E, grave + 0x00cd, kKeyCompose, 0x0049, 0x0027, 0, // Iacute, I, apostrophe + 0x00ce, kKeyCompose, 0x0049, 0x0053, 0, // Icircumflex, I, asciicircum + 0x00cf, kKeyCompose, 0x0049, 0x0022, 0, // Idiaeresis, I, quotedbl + 0x00cc, kKeyCompose, 0x0049, 0x0060, 0, // Igrave, I, grave + 0x00d1, kKeyCompose, 0x004e, 0x007e, 0, // Ntilde, N, asciitilde + 0x00d3, kKeyCompose, 0x004f, 0x0027, 0, // Oacute, O, apostrophe + 0x00d4, kKeyCompose, 0x004f, 0x0053, 0, // Ocircumflex, O, asciicircum + 0x00d6, kKeyCompose, 0x004f, 0x0022, 0, // Odiaeresis, O, quotedbl + 0x00d2, kKeyCompose, 0x004f, 0x0060, 0, // Ograve, O, grave + 0x00d8, kKeyCompose, 0x004f, 0x002f, 0, // Ooblique, O, slash + 0x00d5, kKeyCompose, 0x004f, 0x007e, 0, // Otilde, O, asciitilde + 0x00de, kKeyCompose, 0x0054, 0x0048, 0, // THORN, T, H + 0x00da, kKeyCompose, 0x0055, 0x0027, 0, // Uacute, U, apostrophe + 0x00db, kKeyCompose, 0x0055, 0x0053, 0, // Ucircumflex, U, asciicircum + 0x00dc, kKeyCompose, 0x0055, 0x0022, 0, // Udiaeresis, U, quotedbl + 0x00d9, kKeyCompose, 0x0055, 0x0060, 0, // Ugrave, U, grave + 0x00dd, kKeyCompose, 0x0059, 0x0027, 0, // Yacute, Y, apostrophe + 0x00e1, kKeyCompose, 0x0061, 0x0027, 0, // aacute, a, apostrophe + 0x00e2, kKeyCompose, 0x0061, 0x0053, 0, // acircumflex, a, asciicircum + 0x00b4, kKeyCompose, 0x0027, 0x0027, 0, // acute, apostrophe, apostrophe + 0x00e4, kKeyCompose, 0x0061, 0x0022, 0, // adiaeresis, a, quotedbl + 0x00e6, kKeyCompose, 0x0061, 0x0065, 0, // ae, a, e + 0x00e0, kKeyCompose, 0x0061, 0x0060, 0, // agrave, a, grave + 0x00e5, kKeyCompose, 0x0061, 0x002a, 0, // aring, a, asterisk + 0x0040, kKeyCompose, 0x0041, 0x0054, 0, // at, A, T + 0x00e3, kKeyCompose, 0x0061, 0x007e, 0, // atilde, a, asciitilde + 0x005c, kKeyCompose, 0x002f, 0x002f, 0, // backslash, slash, slash + 0x007c, kKeyCompose, 0x004c, 0x0056, 0, // bar, L, V + 0x007b, kKeyCompose, 0x0028, 0x002d, 0, // braceleft, parenleft, minus + 0x007d, kKeyCompose, 0x0029, 0x002d, 0, // braceright, parenright, minus + 0x005b, kKeyCompose, 0x0028, 0x0028, 0, // bracketleft, parenleft, parenleft + 0x005d, kKeyCompose, 0x0029, 0x0029, 0, // bracketright, parenright, parenright + 0x00a6, kKeyCompose, 0x0042, 0x0056, 0, // brokenbar, B, V + 0x00e7, kKeyCompose, 0x0063, 0x002c, 0, // ccedilla, c, comma + 0x00b8, kKeyCompose, 0x002c, 0x002c, 0, // cedilla, comma, comma + 0x00a2, kKeyCompose, 0x0063, 0x002f, 0, // cent, c, slash + 0x00a9, kKeyCompose, 0x0028, 0x0063, 0, // copyright, parenleft, c + 0x00a4, kKeyCompose, 0x006f, 0x0078, 0, // currency, o, x + 0x00b0, kKeyCompose, 0x0030, 0x0053, 0, // degree, 0, asciicircum + 0x00a8, kKeyCompose, 0x0022, 0x0022, 0, // diaeresis, quotedbl, quotedbl + 0x00f7, kKeyCompose, 0x003a, 0x002d, 0, // division, colon, minus + 0x00e9, kKeyCompose, 0x0065, 0x0027, 0, // eacute, e, apostrophe + 0x00ea, kKeyCompose, 0x0065, 0x0053, 0, // ecircumflex, e, asciicircum + 0x00eb, kKeyCompose, 0x0065, 0x0022, 0, // ediaeresis, e, quotedbl + 0x00e8, kKeyCompose, 0x0065, 0x0060, 0, // egrave, e, grave + 0x00f0, kKeyCompose, 0x0064, 0x002d, 0, // eth, d, minus + 0x00a1, kKeyCompose, 0x0021, 0x0021, 0, // exclamdown, exclam, exclam + 0x00ab, kKeyCompose, 0x003c, 0x003c, 0, // guillemotleft, less, less + 0x00bb, kKeyCompose, 0x003e, 0x003e, 0, // guillemotright, greater, greater + 0x0023, kKeyCompose, 0x002b, 0x002b, 0, // numbersign, plus, plus + 0x00ad, kKeyCompose, 0x002d, 0x002d, 0, // hyphen, minus, minus + 0x00ed, kKeyCompose, 0x0069, 0x0027, 0, // iacute, i, apostrophe + 0x00ee, kKeyCompose, 0x0069, 0x0053, 0, // icircumflex, i, asciicircum + 0x00ef, kKeyCompose, 0x0069, 0x0022, 0, // idiaeresis, i, quotedbl + 0x00ec, kKeyCompose, 0x0069, 0x0060, 0, // igrave, i, grave + 0x00af, kKeyCompose, 0x002d, 0x0053, 0, // macron, minus, asciicircum + 0x00ba, kKeyCompose, 0x006f, 0x005f, 0, // masculine, o, underscore + 0x00b5, kKeyCompose, 0x0075, 0x002f, 0, // mu, u, slash + 0x00d7, kKeyCompose, 0x0078, 0x0078, 0, // multiply, x, x + 0x00a0, kKeyCompose, 0x0020, 0x0020, 0, // nobreakspace, space, space + 0x00ac, kKeyCompose, 0x002c, 0x002d, 0, // notsign, comma, minus + 0x00f1, kKeyCompose, 0x006e, 0x007e, 0, // ntilde, n, asciitilde + 0x00f3, kKeyCompose, 0x006f, 0x0027, 0, // oacute, o, apostrophe + 0x00f4, kKeyCompose, 0x006f, 0x0053, 0, // ocircumflex, o, asciicircum + 0x00f6, kKeyCompose, 0x006f, 0x0022, 0, // odiaeresis, o, quotedbl + 0x00f2, kKeyCompose, 0x006f, 0x0060, 0, // ograve, o, grave + 0x00bd, kKeyCompose, 0x0031, 0x0032, 0, // onehalf, 1, 2 + 0x00bc, kKeyCompose, 0x0031, 0x0034, 0, // onequarter, 1, 4 + 0x00b9, kKeyCompose, 0x0031, 0x0053, 0, // onesuperior, 1, asciicircum + 0x00aa, kKeyCompose, 0x0061, 0x005f, 0, // ordfeminine, a, underscore + 0x00f8, kKeyCompose, 0x006f, 0x002f, 0, // oslash, o, slash + 0x00f5, kKeyCompose, 0x006f, 0x007e, 0, // otilde, o, asciitilde + 0x00b6, kKeyCompose, 0x0070, 0x0021, 0, // paragraph, p, exclam + 0x00b7, kKeyCompose, 0x002e, 0x002e, 0, // periodcentered, period, period + 0x00b1, kKeyCompose, 0x002b, 0x002d, 0, // plusminus, plus, minus + 0x00bf, kKeyCompose, 0x003f, 0x003f, 0, // questiondown, question, question + 0x00ae, kKeyCompose, 0x0028, 0x0072, 0, // registered, parenleft, r + 0x00a7, kKeyCompose, 0x0073, 0x006f, 0, // section, s, o + 0x00df, kKeyCompose, 0x0073, 0x0073, 0, // ssharp, s, s + 0x00a3, kKeyCompose, 0x004c, 0x002d, 0, // sterling, L, minus + 0x00fe, kKeyCompose, 0x0074, 0x0068, 0, // thorn, t, h + 0x00be, kKeyCompose, 0x0033, 0x0034, 0, // threequarters, 3, 4 + 0x00b3, kKeyCompose, 0x0033, 0x0053, 0, // threesuperior, 3, asciicircum + 0x00b2, kKeyCompose, 0x0032, 0x0053, 0, // twosuperior, 2, asciicircum + 0x00fa, kKeyCompose, 0x0075, 0x0027, 0, // uacute, u, apostrophe + 0x00fb, kKeyCompose, 0x0075, 0x0053, 0, // ucircumflex, u, asciicircum + 0x00fc, kKeyCompose, 0x0075, 0x0022, 0, // udiaeresis, u, quotedbl + 0x00f9, kKeyCompose, 0x0075, 0x0060, 0, // ugrave, u, grave + 0x00fd, kKeyCompose, 0x0079, 0x0027, 0, // yacute, y, apostrophe + 0x00ff, kKeyCompose, 0x0079, 0x0022, 0, // ydiaeresis, y, quotedbl + 0x00a5, kKeyCompose, 0x0079, 0x003d, 0, // yen, y, equal + + // end of table + 0 +}; + +static const KeyID s_numpadTable[] = { + kKeyKP_Space, 0x0020, + kKeyKP_Tab, kKeyTab, + kKeyKP_Enter, kKeyReturn, + kKeyKP_F1, kKeyF1, + kKeyKP_F2, kKeyF2, + kKeyKP_F3, kKeyF3, + kKeyKP_F4, kKeyF4, + kKeyKP_Home, kKeyHome, + kKeyKP_Left, kKeyLeft, + kKeyKP_Up, kKeyUp, + kKeyKP_Right, kKeyRight, + kKeyKP_Down, kKeyDown, + kKeyKP_PageUp, kKeyPageUp, + kKeyKP_PageDown, kKeyPageDown, + kKeyKP_End, kKeyEnd, + kKeyKP_Begin, kKeyBegin, + kKeyKP_Insert, kKeyInsert, + kKeyKP_Delete, kKeyDelete, + kKeyKP_Equal, 0x003d, + kKeyKP_Multiply, 0x002a, + kKeyKP_Add, 0x002b, + kKeyKP_Separator, 0x002c, + kKeyKP_Subtract, 0x002d, + kKeyKP_Decimal, 0x002e, + kKeyKP_Divide, 0x002f, + kKeyKP_0, 0x0030, + kKeyKP_1, 0x0031, + kKeyKP_2, 0x0032, + kKeyKP_3, 0x0033, + kKeyKP_4, 0x0034, + kKeyKP_5, 0x0035, + kKeyKP_6, 0x0036, + kKeyKP_7, 0x0037, + kKeyKP_8, 0x0038, + kKeyKP_9, 0x0039 +}; + +// +// CKeyState +// + +CKeyState::CKeyState() : + m_mask(0) +{ + memset(&m_keys, 0, sizeof(m_keys)); + memset(&m_syntheticKeys, 0, sizeof(m_syntheticKeys)); + memset(&m_keyClientData, 0, sizeof(m_keyClientData)); + memset(&m_serverKeys, 0, sizeof(m_serverKeys)); +} + +CKeyState::~CKeyState() +{ + // do nothing +} + +void +CKeyState::onKey(KeyButton button, bool down, KeyModifierMask newState) +{ + // update modifier state + m_mask = newState; + LOG((CLOG_DEBUG1 "new mask: 0x%04x", m_mask)); + + // ignore bogus buttons + button &= kButtonMask; + if (button == 0) { + return; + } + + // update key state + if (down) { + m_keys[button] = 1; + m_syntheticKeys[button] = 1; + } + else { + m_keys[button] = 0; + m_syntheticKeys[button] = 0; + } +} + +void +CKeyState::sendKeyEvent( + void* target, bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + if (m_keyMap.isHalfDuplex(key, button)) { + if (isAutoRepeat) { + // ignore auto-repeat on half-duplex keys + } + else { + EVENTQUEUE->addEvent(CEvent(getKeyDownEvent(), target, + CKeyInfo::alloc(key, mask, button, 1))); + EVENTQUEUE->addEvent(CEvent(getKeyUpEvent(), target, + CKeyInfo::alloc(key, mask, button, 1))); + } + } + else { + if (isAutoRepeat) { + EVENTQUEUE->addEvent(CEvent(getKeyRepeatEvent(), target, + CKeyInfo::alloc(key, mask, button, count))); + } + else if (press) { + EVENTQUEUE->addEvent(CEvent(getKeyDownEvent(), target, + CKeyInfo::alloc(key, mask, button, 1))); + } + else { + EVENTQUEUE->addEvent(CEvent(getKeyUpEvent(), target, + CKeyInfo::alloc(key, mask, button, 1))); + } + } +} + +void +CKeyState::updateKeyMap() +{ + // get the current keyboard map + CKeyMap keyMap; + getKeyMap(keyMap); + m_keyMap.swap(keyMap); + m_keyMap.finish(); + + // add special keys + addCombinationEntries(); + addKeypadEntries(); + addAliasEntries(); +} + +void +CKeyState::updateKeyState() +{ + // reset our state + memset(&m_keys, 0, sizeof(m_keys)); + memset(&m_syntheticKeys, 0, sizeof(m_syntheticKeys)); + memset(&m_keyClientData, 0, sizeof(m_keyClientData)); + memset(&m_serverKeys, 0, sizeof(m_serverKeys)); + m_activeModifiers.clear(); + + // get the current keyboard state + KeyButtonSet keysDown; + pollPressedKeys(keysDown); + for (KeyButtonSet::const_iterator i = keysDown.begin(); + i != keysDown.end(); ++i) { + m_keys[*i] = 1; + } + + // get the current modifier state + m_mask = pollActiveModifiers(); + + // set active modifiers + CAddActiveModifierContext addModifierContext(pollActiveGroup(), m_mask, + m_activeModifiers); + m_keyMap.foreachKey(&CKeyState::addActiveModifierCB, &addModifierContext); + + LOG((CLOG_DEBUG1 "modifiers on update: 0x%04x", m_mask)); +} + +void +CKeyState::addActiveModifierCB(KeyID, SInt32 group, + CKeyMap::KeyItem& keyItem, void* vcontext) +{ + CAddActiveModifierContext* context = + reinterpret_cast(vcontext); + if (group == context->m_activeGroup && + (keyItem.m_generates & context->m_mask) != 0) { + context->m_activeModifiers.insert(std::make_pair( + keyItem.m_generates, keyItem)); + } +} + +void +CKeyState::setHalfDuplexMask(KeyModifierMask mask) +{ + m_keyMap.clearHalfDuplexModifiers(); + if ((mask & KeyModifierCapsLock) != 0) { + m_keyMap.addHalfDuplexModifier(kKeyCapsLock); + } + if ((mask & KeyModifierNumLock) != 0) { + m_keyMap.addHalfDuplexModifier(kKeyNumLock); + } + if ((mask & KeyModifierScrollLock) != 0) { + m_keyMap.addHalfDuplexModifier(kKeyScrollLock); + } +} + +void +CKeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton serverID) +{ + // if this server key is already down then this is probably a + // mis-reported autorepeat. + serverID &= kButtonMask; + if (m_serverKeys[serverID] != 0) { + fakeKeyRepeat(id, mask, 1, serverID); + return; + } + + // ignore certain keys + if (isIgnoredKey(id, mask)) { + LOG((CLOG_DEBUG1 "ignored key %04x %04x", id, mask)); + return; + } + + // get keys for key press + Keystrokes keys; + ModifierToKeys oldActiveModifiers = m_activeModifiers; + const CKeyMap::KeyItem* keyItem = + m_keyMap.mapKey(keys, id, pollActiveGroup(), m_activeModifiers, + getActiveModifiersRValue(), mask, false); + if (keyItem == NULL) { + return; + } + KeyButton localID = (KeyButton)(keyItem->m_button & kButtonMask); + updateModifierKeyState(localID, oldActiveModifiers, m_activeModifiers); + if (localID != 0) { + // note keys down + ++m_keys[localID]; + ++m_syntheticKeys[localID]; + m_keyClientData[localID] = keyItem->m_client; + m_serverKeys[serverID] = localID; + } + + // generate key events + fakeKeys(keys, 1); +} + +void +CKeyState::fakeKeyRepeat( + KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton serverID) +{ + serverID &= kButtonMask; + + // if we haven't seen this button go down then ignore it + KeyButton oldLocalID = m_serverKeys[serverID]; + if (oldLocalID == 0) { + return; + } + + // get keys for key repeat + Keystrokes keys; + ModifierToKeys oldActiveModifiers = m_activeModifiers; + const CKeyMap::KeyItem* keyItem = + m_keyMap.mapKey(keys, id, pollActiveGroup(), m_activeModifiers, + getActiveModifiersRValue(), mask, true); + if (keyItem == NULL) { + return; + } + KeyButton localID = (KeyButton)(keyItem->m_button & kButtonMask); + if (localID == 0) { + return; + } + + // if the KeyButton for the auto-repeat is not the same as for the + // initial press then mark the initial key as released and the new + // key as pressed. this can happen when we auto-repeat after a + // dead key. for example, a dead accent followed by 'a' will + // generate an 'a with accent' followed by a repeating 'a'. the + // KeyButtons for the two KeyIDs might be different. + if (localID != oldLocalID) { + // replace key up with previous KeyButton but leave key down + // alone so it uses the new KeyButton. + for (Keystrokes::iterator index = keys.begin(); + index != keys.end(); ++index) { + if (index->m_type == Keystroke::kButton && + index->m_data.m_button.m_button == localID) { + index->m_data.m_button.m_button = oldLocalID; + break; + } + } + + // note that old key is now up + --m_keys[oldLocalID]; + --m_syntheticKeys[oldLocalID]; + + // note keys down + updateModifierKeyState(localID, oldActiveModifiers, m_activeModifiers); + ++m_keys[localID]; + ++m_syntheticKeys[localID]; + m_keyClientData[localID] = keyItem->m_client; + m_serverKeys[serverID] = localID; + } + + // generate key events + fakeKeys(keys, count); +} + +void +CKeyState::fakeKeyUp(KeyButton serverID) +{ + // if we haven't seen this button go down then ignore it + KeyButton localID = m_serverKeys[serverID & kButtonMask]; + if (localID == 0) { + return; + } + + // get the sequence of keys to simulate key release + Keystrokes keys; + keys.push_back(Keystroke(localID, false, false, m_keyClientData[localID])); + + // note keys down + --m_keys[localID]; + --m_syntheticKeys[localID]; + m_serverKeys[serverID] = 0; + + // check if this is a modifier + for (ModifierToKeys::iterator i = m_activeModifiers.begin(); + i != m_activeModifiers.end(); ++i) { + if (i->second.m_button == localID && !i->second.m_lock) { + // modifier is no longer down + KeyModifierMask mask = i->first; + m_activeModifiers.erase(i); + + if (m_activeModifiers.count(mask) == 0) { + // no key for modifier is down so deactivate modifier + m_mask &= ~mask; + LOG((CLOG_DEBUG1 "new state %04x", m_mask)); + } + + break; + } + } + + // generate key events + fakeKeys(keys, 1); +} + +void +CKeyState::fakeAllKeysUp() +{ + Keystrokes keys; + for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { + if (m_syntheticKeys[i] > 0) { + keys.push_back(Keystroke(i, false, false, m_keyClientData[i])); + m_keys[i] = 0; + m_syntheticKeys[i] = 0; + } + } + fakeKeys(keys, 1); + memset(&m_serverKeys, 0, sizeof(m_serverKeys)); + m_activeModifiers.clear(); + m_mask = pollActiveModifiers(); +} + +bool +CKeyState::isKeyDown(KeyButton button) const +{ + return (m_keys[button & kButtonMask] > 0); +} + +KeyModifierMask +CKeyState::getActiveModifiers() const +{ + return m_mask; +} + +KeyModifierMask& +CKeyState::getActiveModifiersRValue() +{ + return m_mask; +} + +SInt32 +CKeyState::getEffectiveGroup(SInt32 group, SInt32 offset) const +{ + return m_keyMap.getEffectiveGroup(group, offset); +} + +bool +CKeyState::isIgnoredKey(KeyID key, KeyModifierMask) const +{ + switch (key) { + case kKeyCapsLock: + case kKeyNumLock: + case kKeyScrollLock: + return true; + + default: + return false; + } +} + +KeyButton +CKeyState::getButton(KeyID id, SInt32 group) const +{ + const CKeyMap::KeyItemList* items = + m_keyMap.findCompatibleKey(id, group, 0, 0); + if (items == NULL) { + return 0; + } + else { + return items->back().m_button; + } +} + +void +CKeyState::addAliasEntries() +{ + for (SInt32 g = 0, n = m_keyMap.getNumGroups(); g < n; ++g) { + // if we can't shift any kKeyTab key in a particular group but we can + // shift kKeyLeftTab then add a shifted kKeyTab entry that matches a + // shifted kKeyLeftTab entry. + m_keyMap.addKeyAliasEntry(kKeyTab, g, + KeyModifierShift, KeyModifierShift, + kKeyLeftTab, + KeyModifierShift, KeyModifierShift); + + // if we have no kKeyLeftTab but we do have a kKeyTab that can be + // shifted then add kKeyLeftTab that matches a kKeyTab. + m_keyMap.addKeyAliasEntry(kKeyLeftTab, g, + KeyModifierShift, KeyModifierShift, + kKeyTab, + 0, KeyModifierShift); + + // map non-breaking space to space + m_keyMap.addKeyAliasEntry(0x20, g, 0, 0, 0xa0, 0, 0); + } +} + +void +CKeyState::addKeypadEntries() +{ + // map every numpad key to its equivalent non-numpad key if it's not + // on the keyboard. + for (SInt32 g = 0, n = m_keyMap.getNumGroups(); g < n; ++g) { + for (size_t i = 0; i < sizeof(s_numpadTable) / + sizeof(s_numpadTable[0]); i += 2) { + m_keyMap.addKeyCombinationEntry(s_numpadTable[i], g, + s_numpadTable + i + 1, 1); + } + } +} + +void +CKeyState::addCombinationEntries() +{ + for (SInt32 g = 0, n = m_keyMap.getNumGroups(); g < n; ++g) { + // add dead and compose key composition sequences + for (const KeyID* i = s_decomposeTable; *i != 0; ++i) { + // count the decomposed keys for this key + UInt32 numKeys = 0; + for (const KeyID* j = i; *++j != 0; ) { + ++numKeys; + } + + // add an entry for this key + m_keyMap.addKeyCombinationEntry(*i, g, i + 1, numKeys); + + // next key + i += numKeys + 1; + } + } +} + +void +CKeyState::fakeKeys(const Keystrokes& keys, UInt32 count) +{ + // do nothing if no keys or no repeats + if (count == 0 || keys.empty()) { + return; + } + + // generate key events + LOG((CLOG_DEBUG1 "keystrokes:")); + for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) { + if (k->m_type == Keystroke::kButton && k->m_data.m_button.m_repeat) { + // repeat from here up to but not including the next key + // with m_repeat == false count times. + Keystrokes::const_iterator start = k; + while (count-- > 0) { + // send repeating events + for (k = start; k != keys.end() && + k->m_type == Keystroke::kButton && + k->m_data.m_button.m_repeat; ++k) { + fakeKey(*k); + } + } + + // note -- k is now on the first non-repeat key after the + // repeat keys, exactly where we'd like to continue from. + } + else { + // send event + fakeKey(*k); + + // next key + ++k; + } + } +} + +void +CKeyState::updateModifierKeyState(KeyButton button, + const ModifierToKeys& oldModifiers, + const ModifierToKeys& newModifiers) +{ + // get the pressed modifier buttons before and after + CKeyMap::ButtonToKeyMap oldKeys, newKeys; + for (ModifierToKeys::const_iterator i = oldModifiers.begin(); + i != oldModifiers.end(); ++i) { + oldKeys.insert(std::make_pair(i->second.m_button, &i->second)); + } + for (ModifierToKeys::const_iterator i = newModifiers.begin(); + i != newModifiers.end(); ++i) { + newKeys.insert(std::make_pair(i->second.m_button, &i->second)); + } + + // get the modifier buttons that were pressed or released + CKeyMap::ButtonToKeyMap pressed, released; + std::set_difference(oldKeys.begin(), oldKeys.end(), + newKeys.begin(), newKeys.end(), + std::inserter(released, released.end()), + ButtonToKeyLess()); + std::set_difference(newKeys.begin(), newKeys.end(), + oldKeys.begin(), oldKeys.end(), + std::inserter(pressed, pressed.end()), + ButtonToKeyLess()); + + // update state + for (CKeyMap::ButtonToKeyMap::const_iterator i = released.begin(); + i != released.end(); ++i) { + if (i->first != button) { + m_keys[i->first] = 0; + m_syntheticKeys[i->first] = 0; + } + } + for (CKeyMap::ButtonToKeyMap::const_iterator i = pressed.begin(); + i != pressed.end(); ++i) { + if (i->first != button) { + m_keys[i->first] = 1; + m_syntheticKeys[i->first] = 1; + m_keyClientData[i->first] = i->second->m_client; + } + } +} + + +// +// CKeyState::CAddActiveModifierContext +// + +CKeyState::CAddActiveModifierContext::CAddActiveModifierContext( + SInt32 group, KeyModifierMask mask, + ModifierToKeys& activeModifiers) : + m_activeGroup(group), + m_mask(mask), + m_activeModifiers(activeModifiers) +{ + // do nothing +} diff --git a/lib/synergy/CKeyState.h b/lib/synergy/CKeyState.h new file mode 100644 index 00000000..6bfd9d8b --- /dev/null +++ b/lib/synergy/CKeyState.h @@ -0,0 +1,216 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CKEYSTATE_H +#define CKEYSTATE_H + +#include "IKeyState.h" +#include "CKeyMap.h" + +//! Core key state +/*! +This class provides key state services. Subclasses must implement a few +platform specific methods. +*/ +class CKeyState : public IKeyState { +public: + CKeyState(); + virtual ~CKeyState(); + + //! @name manipulators + //@{ + + //! Handle key event + /*! + Sets the state of \p button to down or up and updates the current + modifier state to \p newState. This method should be called by + primary screens only in response to local events. For auto-repeat + set \p down to \c true. Overrides must forward to the superclass. + */ + virtual void onKey(KeyButton button, bool down, + KeyModifierMask newState); + + //! Post a key event + /*! + Posts a key event. This may adjust the event or post additional + events in some circumstances. If this is overridden it must forward + to the superclass. + */ + virtual void sendKeyEvent(void* target, + bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button); + + //@} + //! @name accessors + //@{ + + //@} + + // IKeyState overrides + virtual void updateKeyMap(); + virtual void updateKeyState(); + virtual void setHalfDuplexMask(KeyModifierMask); + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button); + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual void fakeKeyUp(KeyButton button); + virtual void fakeAllKeysUp(); + virtual bool fakeCtrlAltDel() = 0; + virtual bool isKeyDown(KeyButton) const; + virtual KeyModifierMask + getActiveModifiers() const; + virtual KeyModifierMask + pollActiveModifiers() const = 0; + virtual SInt32 pollActiveGroup() const = 0; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const = 0; + +protected: + typedef CKeyMap::Keystroke Keystroke; + + //! @name protected manipulators + //@{ + + //! Get the keyboard map + /*! + Fills \p keyMap with the current keyboard map. + */ + virtual void getKeyMap(CKeyMap& keyMap) = 0; + + //! Fake a key event + /*! + Synthesize an event for \p keystroke. + */ + virtual void fakeKey(const Keystroke& keystroke) = 0; + + //! Get the active modifiers + /*! + Returns the modifiers that are currently active according to our + shadowed state. The state may be modified. + */ + virtual KeyModifierMask& + getActiveModifiersRValue(); + + //@} + //! @name protected accessors + //@{ + + //! Compute a group number + /*! + Returns the number of the group \p offset groups after group \p group. + */ + SInt32 getEffectiveGroup(SInt32 group, SInt32 offset) const; + + //! Check if key is ignored + /*! + Returns \c true if and only if the key should always be ignored. + The default returns \c true only for the toggle keys. + */ + virtual bool isIgnoredKey(KeyID key, KeyModifierMask mask) const; + + //! Get button for a KeyID + /*! + Return the button mapped to key \p id in group \p group if any, + otherwise returns 0. + */ + KeyButton getButton(KeyID id, SInt32 group) const; + + //@} + +private: + typedef CKeyMap::Keystrokes Keystrokes; + typedef CKeyMap::ModifierToKeys ModifierToKeys; + struct CAddActiveModifierContext { + public: + CAddActiveModifierContext(SInt32 group, KeyModifierMask mask, + ModifierToKeys& activeModifiers); + + public: + SInt32 m_activeGroup; + KeyModifierMask m_mask; + ModifierToKeys& m_activeModifiers; + + private: + // not implemented + CAddActiveModifierContext(const CAddActiveModifierContext&); + CAddActiveModifierContext& operator=(const CAddActiveModifierContext&); + }; + + class ButtonToKeyLess { + public: + bool operator()(const CKeyMap::ButtonToKeyMap::value_type& a, + const CKeyMap::ButtonToKeyMap::value_type b) const + { + return (a.first < b.first); + } + }; + + // not implemented + CKeyState(const CKeyState&); + CKeyState& operator=(const CKeyState&); + + // adds alias key sequences. these are sequences that are equivalent + // to other sequences. + void addAliasEntries(); + + // adds non-keypad key sequences for keypad KeyIDs + void addKeypadEntries(); + + // adds key sequences for combination KeyIDs (those built using + // dead keys) + void addCombinationEntries(); + + // synthesize key events. synthesize auto-repeat events count times. + void fakeKeys(const Keystrokes&, UInt32 count); + + // update key state to match changes to modifiers + void updateModifierKeyState(KeyButton button, + const ModifierToKeys& oldModifiers, + const ModifierToKeys& newModifiers); + + // active modifiers collection callback + static void addActiveModifierCB(KeyID id, SInt32 group, + CKeyMap::KeyItem& keyItem, void* vcontext); + +private: + // the keyboard map + CKeyMap m_keyMap; + + // current modifier state + KeyModifierMask m_mask; + + // the active modifiers and the buttons activating them + ModifierToKeys m_activeModifiers; + + // current keyboard state (> 0 if pressed, 0 otherwise). this is + // initialized to the keyboard state according to the system then + // it tracks synthesized events. + SInt32 m_keys[kNumButtons]; + + // synthetic keyboard state (> 0 if pressed, 0 otherwise). this + // tracks the synthesized keyboard state. if m_keys[n] > 0 but + // m_syntheticKeys[n] == 0 then the key was pressed locally and + // not synthesized yet. + SInt32 m_syntheticKeys[kNumButtons]; + + // client data for each pressed key + UInt32 m_keyClientData[kNumButtons]; + + // server keyboard state. an entry is 0 if not the key isn't pressed + // otherwise it's the local KeyButton synthesized for the server key. + KeyButton m_serverKeys[kNumButtons]; +}; + +#endif diff --git a/lib/synergy/CPacketStreamFilter.cpp b/lib/synergy/CPacketStreamFilter.cpp new file mode 100644 index 00000000..4aad9c02 --- /dev/null +++ b/lib/synergy/CPacketStreamFilter.cpp @@ -0,0 +1,190 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CPacketStreamFilter.h" +#include "IEventQueue.h" +#include "CLock.h" +#include "TMethodEventJob.h" + +// +// CPacketStreamFilter +// + +CPacketStreamFilter::CPacketStreamFilter(IStream* stream, bool adoptStream) : + CStreamFilter(stream, adoptStream), + m_size(0), + m_inputShutdown(false) +{ + // do nothing +} + +CPacketStreamFilter::~CPacketStreamFilter() +{ + // do nothing +} + +void +CPacketStreamFilter::close() +{ + CLock lock(&m_mutex); + m_size = 0; + m_buffer.pop(m_buffer.getSize()); + CStreamFilter::close(); +} + +UInt32 +CPacketStreamFilter::read(void* buffer, UInt32 n) +{ + if (n == 0) { + return 0; + } + + CLock lock(&m_mutex); + + // if not enough data yet then give up + if (!isReadyNoLock()) { + return 0; + } + + // read no more than what's left in the buffered packet + if (n > m_size) { + n = m_size; + } + + // read it + if (buffer != NULL) { + memcpy(buffer, m_buffer.peek(n), n); + } + m_buffer.pop(n); + m_size -= n; + + // get next packet's size if we've finished with this packet and + // there's enough data to do so. + readPacketSize(); + + if (m_inputShutdown && m_size == 0) { + EVENTQUEUE->addEvent(CEvent(getInputShutdownEvent(), + getEventTarget(), NULL)); + } + + return n; +} + +void +CPacketStreamFilter::write(const void* buffer, UInt32 count) +{ + // write the length of the payload + UInt8 length[4]; + length[0] = (UInt8)((count >> 24) & 0xff); + length[1] = (UInt8)((count >> 16) & 0xff); + length[2] = (UInt8)((count >> 8) & 0xff); + length[3] = (UInt8)( count & 0xff); + getStream()->write(length, sizeof(length)); + + // write the payload + getStream()->write(buffer, count); +} + +void +CPacketStreamFilter::shutdownInput() +{ + CLock lock(&m_mutex); + m_size = 0; + m_buffer.pop(m_buffer.getSize()); + CStreamFilter::shutdownInput(); +} + +bool +CPacketStreamFilter::isReady() const +{ + CLock lock(&m_mutex); + return isReadyNoLock(); +} + +UInt32 +CPacketStreamFilter::getSize() const +{ + CLock lock(&m_mutex); + return isReadyNoLock() ? m_size : 0; +} + +bool +CPacketStreamFilter::isReadyNoLock() const +{ + return (m_size != 0 && m_buffer.getSize() >= m_size); +} + +void +CPacketStreamFilter::readPacketSize() +{ + // note -- m_mutex must be locked on entry + + if (m_size == 0 && m_buffer.getSize() >= 4) { + UInt8 buffer[4]; + memcpy(buffer, m_buffer.peek(sizeof(buffer)), sizeof(buffer)); + m_buffer.pop(sizeof(buffer)); + m_size = ((UInt32)buffer[0] << 24) | + ((UInt32)buffer[1] << 16) | + ((UInt32)buffer[2] << 8) | + (UInt32)buffer[3]; + } +} + +bool +CPacketStreamFilter::readMore() +{ + // note if we have whole packet + bool wasReady = isReadyNoLock(); + + // read more data + char buffer[4096]; + UInt32 n = getStream()->read(buffer, sizeof(buffer)); + while (n > 0) { + m_buffer.write(buffer, n); + n = getStream()->read(buffer, sizeof(buffer)); + } + + // if we don't yet have the next packet size then get it, + // if possible. + readPacketSize(); + + // note if we now have a whole packet + bool isReady = isReadyNoLock(); + + // if we weren't ready before but now we are then send a + // input ready event apparently from the filtered stream. + return (wasReady != isReady); +} + +void +CPacketStreamFilter::filterEvent(const CEvent& event) +{ + if (event.getType() == getInputReadyEvent()) { + CLock lock(&m_mutex); + if (!readMore()) { + return; + } + } + else if (event.getType() == getInputShutdownEvent()) { + // discard this if we have buffered data + CLock lock(&m_mutex); + m_inputShutdown = true; + if (m_size != 0) { + return; + } + } + + // pass event + CStreamFilter::filterEvent(event); +} diff --git a/lib/synergy/CPacketStreamFilter.h b/lib/synergy/CPacketStreamFilter.h new file mode 100644 index 00000000..93ddd8fa --- /dev/null +++ b/lib/synergy/CPacketStreamFilter.h @@ -0,0 +1,55 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CPACKETSTREAMFILTER_H +#define CPACKETSTREAMFILTER_H + +#include "CStreamFilter.h" +#include "CStreamBuffer.h" +#include "CMutex.h" + +//! Packetizing stream filter +/*! +Filters a stream to read and write packets. +*/ +class CPacketStreamFilter : public CStreamFilter { +public: + CPacketStreamFilter(IStream* stream, bool adoptStream = true); + ~CPacketStreamFilter(); + + // IStream overrides + virtual void close(); + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void shutdownInput(); + virtual bool isReady() const; + virtual UInt32 getSize() const; + +protected: + // CStreamFilter overrides + virtual void filterEvent(const CEvent&); + +private: + bool isReadyNoLock() const; + void readPacketSize(); + bool readMore(); + +private: + CMutex m_mutex; + UInt32 m_size; + CStreamBuffer m_buffer; + bool m_inputShutdown; +}; + +#endif diff --git a/lib/synergy/CPlatformScreen.cpp b/lib/synergy/CPlatformScreen.cpp new file mode 100644 index 00000000..ca33d5a9 --- /dev/null +++ b/lib/synergy/CPlatformScreen.cpp @@ -0,0 +1,106 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "CPlatformScreen.h" + +CPlatformScreen::CPlatformScreen() +{ + // do nothing +} + +CPlatformScreen::~CPlatformScreen() +{ + // do nothing +} + +void +CPlatformScreen::updateKeyMap() +{ + getKeyState()->updateKeyMap(); +} + +void +CPlatformScreen::updateKeyState() +{ + getKeyState()->updateKeyState(); + updateButtons(); +} + +void +CPlatformScreen::setHalfDuplexMask(KeyModifierMask mask) +{ + getKeyState()->setHalfDuplexMask(mask); +} + +void +CPlatformScreen::fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) +{ + getKeyState()->fakeKeyDown(id, mask, button); +} + +void +CPlatformScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + getKeyState()->fakeKeyRepeat(id, mask, count, button); +} + +void +CPlatformScreen::fakeKeyUp(KeyButton button) +{ + getKeyState()->fakeKeyUp(button); +} + +void +CPlatformScreen::fakeAllKeysUp() +{ + getKeyState()->fakeAllKeysUp(); +} + +bool +CPlatformScreen::fakeCtrlAltDel() +{ + return getKeyState()->fakeCtrlAltDel(); +} + +bool +CPlatformScreen::isKeyDown(KeyButton button) const +{ + return getKeyState()->isKeyDown(button); +} + +KeyModifierMask +CPlatformScreen::getActiveModifiers() const +{ + return getKeyState()->getActiveModifiers(); +} + +KeyModifierMask +CPlatformScreen::pollActiveModifiers() const +{ + return getKeyState()->pollActiveModifiers(); +} + +SInt32 +CPlatformScreen::pollActiveGroup() const +{ + return getKeyState()->pollActiveGroup(); +} + +void +CPlatformScreen::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + getKeyState()->pollPressedKeys(pressedKeys); +} diff --git a/lib/synergy/CPlatformScreen.h b/lib/synergy/CPlatformScreen.h new file mode 100644 index 00000000..2e5c87f1 --- /dev/null +++ b/lib/synergy/CPlatformScreen.h @@ -0,0 +1,109 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CPLATFORMSCREEN_H +#define CPLATFORMSCREEN_H + +#include "IPlatformScreen.h" + +//! Base screen implementation +/*! +This screen implementation is the superclass of all other screen +implementations. It implements a handful of methods and requires +subclasses to implement the rest. +*/ +class CPlatformScreen : public IPlatformScreen { +public: + CPlatformScreen(); + virtual ~CPlatformScreen(); + + // IScreen overrides + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides) = 0; + virtual void warpCursor(SInt32 x, SInt32 y) = 0; + virtual UInt32 registerHotKey(KeyID key, + KeyModifierMask mask) = 0; + virtual void unregisterHotKey(UInt32 id) = 0; + virtual void fakeInputBegin() = 0; + virtual void fakeInputEnd() = 0; + virtual SInt32 getJumpZoneSize() const = 0; + virtual bool isAnyMouseButtonDown() const = 0; + virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press) const = 0; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const = 0; + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const = 0; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const = 0; + + // IKeyState overrides + virtual void updateKeyMap(); + virtual void updateKeyState(); + virtual void setHalfDuplexMask(KeyModifierMask); + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button); + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual void fakeKeyUp(KeyButton button); + virtual void fakeAllKeysUp(); + virtual bool fakeCtrlAltDel(); + virtual bool isKeyDown(KeyButton) const; + virtual KeyModifierMask + getActiveModifiers() const; + virtual KeyModifierMask + pollActiveModifiers() const; + virtual SInt32 pollActiveGroup() const; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const; + + // IPlatformScreen overrides + virtual void enable() = 0; + virtual void disable() = 0; + virtual void enter() = 0; + virtual bool leave() = 0; + virtual bool setClipboard(ClipboardID, const IClipboard*) = 0; + virtual void checkClipboards() = 0; + virtual void openScreensaver(bool notify) = 0; + virtual void closeScreensaver() = 0; + virtual void screensaver(bool activate) = 0; + virtual void resetOptions() = 0; + virtual void setOptions(const COptionsList& options) = 0; + virtual void setSequenceNumber(UInt32) = 0; + virtual bool isPrimary() const = 0; + +protected: + //! Update mouse buttons + /*! + Subclasses must implement this method to update their internal mouse + button mapping and, if desired, state tracking. + */ + virtual void updateButtons() = 0; + + //! Get the key state + /*! + Subclasses must implement this method to return the platform specific + key state object that each subclass must have. + */ + virtual IKeyState* getKeyState() const = 0; + + // IPlatformScreen overrides + virtual void handleSystemEvent(const CEvent& event, void*) = 0; +}; + +#endif diff --git a/lib/synergy/CProtocolUtil.cpp b/lib/synergy/CProtocolUtil.cpp new file mode 100644 index 00000000..f0f012a1 --- /dev/null +++ b/lib/synergy/CProtocolUtil.cpp @@ -0,0 +1,538 @@ +/* + * 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 "CProtocolUtil.h" +#include "IStream.h" +#include "CLog.h" +#include "stdvector.h" +#include +#include + +// +// CProtocolUtil +// + +void +CProtocolUtil::writef(IStream* stream, const char* fmt, ...) +{ + assert(stream != NULL); + assert(fmt != NULL); + LOG((CLOG_DEBUG2 "writef(%s)", fmt)); + + va_list args; + va_start(args, fmt); + UInt32 size = getLength(fmt, args); + va_end(args); + va_start(args, fmt); + vwritef(stream, fmt, size, args); + va_end(args); +} + +bool +CProtocolUtil::readf(IStream* stream, const char* fmt, ...) +{ + assert(stream != NULL); + assert(fmt != NULL); + LOG((CLOG_DEBUG2 "readf(%s)", fmt)); + + bool result; + va_list args; + va_start(args, fmt); + try { + vreadf(stream, fmt, args); + result = true; + } + catch (XIO&) { + result = false; + } + va_end(args); + return result; +} + +void +CProtocolUtil::vwritef(IStream* stream, + const char* fmt, UInt32 size, va_list args) +{ + assert(stream != NULL); + assert(fmt != NULL); + + // done if nothing to write + if (size == 0) { + return; + } + + // fill buffer + UInt8* buffer = new UInt8[size]; + writef(buffer, fmt, args); + + try { + // write buffer + stream->write(buffer, size); + LOG((CLOG_DEBUG2 "wrote %d bytes", size)); + + delete[] buffer; + } + catch (XBase&) { + delete[] buffer; + throw; + } +} + +void +CProtocolUtil::vreadf(IStream* stream, const char* fmt, va_list args) +{ + assert(stream != NULL); + assert(fmt != NULL); + + // begin scanning + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': { + // check for valid length + assert(len == 1 || len == 2 || len == 4); + + // read the data + UInt8 buffer[4]; + read(stream, buffer, len); + + // convert it + void* v = va_arg(args, void*); + switch (len) { + case 1: + // 1 byte integer + *reinterpret_cast(v) = buffer[0]; + LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *reinterpret_cast(v), *reinterpret_cast(v))); + break; + + case 2: + // 2 byte integer + *reinterpret_cast(v) = + static_cast( + (static_cast(buffer[0]) << 8) | + static_cast(buffer[1])); + LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *reinterpret_cast(v), *reinterpret_cast(v))); + break; + + case 4: + // 4 byte integer + *reinterpret_cast(v) = + (static_cast(buffer[0]) << 24) | + (static_cast(buffer[1]) << 16) | + (static_cast(buffer[2]) << 8) | + static_cast(buffer[3]); + LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *reinterpret_cast(v), *reinterpret_cast(v))); + break; + } + break; + } + + case 'I': { + // check for valid length + assert(len == 1 || len == 2 || len == 4); + + // read the vector length + UInt8 buffer[4]; + read(stream, buffer, 4); + UInt32 n = (static_cast(buffer[0]) << 24) | + (static_cast(buffer[1]) << 16) | + (static_cast(buffer[2]) << 8) | + static_cast(buffer[3]); + + // convert it + void* v = va_arg(args, void*); + switch (len) { + case 1: + // 1 byte integer + for (UInt32 i = 0; i < n; ++i) { + read(stream, buffer, 1); + reinterpret_cast*>(v)->push_back( + buffer[0]); + LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, reinterpret_cast*>(v)->back(), reinterpret_cast*>(v)->back())); + } + break; + + case 2: + // 2 byte integer + for (UInt32 i = 0; i < n; ++i) { + read(stream, buffer, 2); + reinterpret_cast*>(v)->push_back( + static_cast( + (static_cast(buffer[0]) << 8) | + static_cast(buffer[1]))); + LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, reinterpret_cast*>(v)->back(), reinterpret_cast*>(v)->back())); + } + break; + + case 4: + // 4 byte integer + for (UInt32 i = 0; i < n; ++i) { + read(stream, buffer, 4); + reinterpret_cast*>(v)->push_back( + (static_cast(buffer[0]) << 24) | + (static_cast(buffer[1]) << 16) | + (static_cast(buffer[2]) << 8) | + static_cast(buffer[3])); + LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, reinterpret_cast*>(v)->back(), reinterpret_cast*>(v)->back())); + } + break; + } + break; + } + + case 's': { + assert(len == 0); + + // read the string length + UInt8 buffer[128]; + read(stream, buffer, 4); + UInt32 len = (static_cast(buffer[0]) << 24) | + (static_cast(buffer[1]) << 16) | + (static_cast(buffer[2]) << 8) | + static_cast(buffer[3]); + + // use a fixed size buffer if its big enough + const bool useFixed = (len <= sizeof(buffer)); + + // allocate a buffer to read the data + UInt8* sBuffer = buffer; + if (!useFixed) { + sBuffer = new UInt8[len]; + } + + // read the data + try { + read(stream, sBuffer, len); + } + catch (...) { + if (!useFixed) { + delete[] sBuffer; + } + throw; + } + LOG((CLOG_DEBUG2 "readf: read %d byte string: %.*s", len, len, sBuffer)); + + // save the data + CString* dst = va_arg(args, CString*); + dst->assign((const char*)sBuffer, len); + + // release the buffer + if (!useFixed) { + delete[] sBuffer; + } + break; + } + + case '%': + assert(len == 0); + break; + + default: + assert(0 && "invalid format specifier"); + } + + // next format character + ++fmt; + } + else { + // read next character + char buffer[1]; + read(stream, buffer, 1); + + // verify match + if (buffer[0] != *fmt) { + LOG((CLOG_DEBUG2 "readf: format mismatch: %c vs %c", *fmt, buffer[0])); + throw XIOReadMismatch(); + } + + // next format character + ++fmt; + } + } +} + +UInt32 +CProtocolUtil::getLength(const char* fmt, va_list args) +{ + UInt32 n = 0; + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': + assert(len == 1 || len == 2 || len == 4); + (void)va_arg(args, UInt32); + break; + + case 'I': + assert(len == 1 || len == 2 || len == 4); + switch (len) { + case 1: + len = (va_arg(args, std::vector*))->size() + 4; + break; + + case 2: + len = 2 * (va_arg(args, std::vector*))->size() + 4; + break; + + case 4: + len = 4 * (va_arg(args, std::vector*))->size() + 4; + break; + } + break; + + case 's': + assert(len == 0); + len = (va_arg(args, CString*))->size() + 4; + (void)va_arg(args, UInt8*); + break; + + case 'S': + assert(len == 0); + len = va_arg(args, UInt32) + 4; + (void)va_arg(args, UInt8*); + break; + + case '%': + assert(len == 0); + len = 1; + break; + + default: + assert(0 && "invalid format specifier"); + } + + // accumulate size + n += len; + ++fmt; + } + else { + // regular character + ++n; + ++fmt; + } + } + return n; +} + +void +CProtocolUtil::writef(void* buffer, const char* fmt, va_list args) +{ + UInt8* dst = reinterpret_cast(buffer); + + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': { + const UInt32 v = va_arg(args, UInt32); + switch (len) { + case 1: + // 1 byte integer + *dst++ = static_cast(v & 0xff); + break; + + case 2: + // 2 byte integer + *dst++ = static_cast((v >> 8) & 0xff); + *dst++ = static_cast( v & 0xff); + break; + + case 4: + // 4 byte integer + *dst++ = static_cast((v >> 24) & 0xff); + *dst++ = static_cast((v >> 16) & 0xff); + *dst++ = static_cast((v >> 8) & 0xff); + *dst++ = static_cast( v & 0xff); + break; + + default: + assert(0 && "invalid integer format length"); + return; + } + break; + } + + case 'I': { + switch (len) { + case 1: { + // 1 byte integers + const std::vector* list = + va_arg(args, const std::vector*); + const UInt32 n = list->size(); + *dst++ = static_cast((n >> 24) & 0xff); + *dst++ = static_cast((n >> 16) & 0xff); + *dst++ = static_cast((n >> 8) & 0xff); + *dst++ = static_cast( n & 0xff); + for (UInt32 i = 0; i < n; ++i) { + *dst++ = (*list)[i]; + } + break; + } + + case 2: { + // 2 byte integers + const std::vector* list = + va_arg(args, const std::vector*); + const UInt32 n = list->size(); + *dst++ = static_cast((n >> 24) & 0xff); + *dst++ = static_cast((n >> 16) & 0xff); + *dst++ = static_cast((n >> 8) & 0xff); + *dst++ = static_cast( n & 0xff); + for (UInt32 i = 0; i < n; ++i) { + const UInt16 v = (*list)[i]; + *dst++ = static_cast((v >> 8) & 0xff); + *dst++ = static_cast( v & 0xff); + } + break; + } + + case 4: { + // 4 byte integers + const std::vector* list = + va_arg(args, const std::vector*); + const UInt32 n = list->size(); + *dst++ = static_cast((n >> 24) & 0xff); + *dst++ = static_cast((n >> 16) & 0xff); + *dst++ = static_cast((n >> 8) & 0xff); + *dst++ = static_cast( n & 0xff); + for (UInt32 i = 0; i < n; ++i) { + const UInt32 v = (*list)[i]; + *dst++ = static_cast((v >> 24) & 0xff); + *dst++ = static_cast((v >> 16) & 0xff); + *dst++ = static_cast((v >> 8) & 0xff); + *dst++ = static_cast( v & 0xff); + } + break; + } + + default: + assert(0 && "invalid integer vector format length"); + return; + } + break; + } + + case 's': { + assert(len == 0); + const CString* src = va_arg(args, CString*); + const UInt32 len = (src != NULL) ? src->size() : 0; + *dst++ = static_cast((len >> 24) & 0xff); + *dst++ = static_cast((len >> 16) & 0xff); + *dst++ = static_cast((len >> 8) & 0xff); + *dst++ = static_cast( len & 0xff); + if (len != 0) { + memcpy(dst, src->data(), len); + dst += len; + } + break; + } + + case 'S': { + assert(len == 0); + const UInt32 len = va_arg(args, UInt32); + const UInt8* src = va_arg(args, UInt8*); + *dst++ = static_cast((len >> 24) & 0xff); + *dst++ = static_cast((len >> 16) & 0xff); + *dst++ = static_cast((len >> 8) & 0xff); + *dst++ = static_cast( len & 0xff); + memcpy(dst, src, len); + dst += len; + break; + } + + case '%': + assert(len == 0); + *dst++ = '%'; + break; + + default: + assert(0 && "invalid format specifier"); + } + + // next format character + ++fmt; + } + else { + // copy regular character + *dst++ = *fmt++; + } + } +} + +UInt32 +CProtocolUtil::eatLength(const char** pfmt) +{ + const char* fmt = *pfmt; + UInt32 n = 0; + for (;;) { + UInt32 d; + switch (*fmt) { + case '0': d = 0; break; + case '1': d = 1; break; + case '2': d = 2; break; + case '3': d = 3; break; + case '4': d = 4; break; + case '5': d = 5; break; + case '6': d = 6; break; + case '7': d = 7; break; + case '8': d = 8; break; + case '9': d = 9; break; + default: *pfmt = fmt; return n; + } + n = 10 * n + d; + ++fmt; + } +} + +void +CProtocolUtil::read(IStream* stream, void* vbuffer, UInt32 count) +{ + assert(stream != NULL); + assert(vbuffer != NULL); + + UInt8* buffer = reinterpret_cast(vbuffer); + while (count > 0) { + // read more + UInt32 n = stream->read(buffer, count); + + // bail if stream has hungup + if (n == 0) { + LOG((CLOG_DEBUG2 "unexpected disconnect in readf(), %d bytes left", count)); + throw XIOEndOfStream(); + } + + // prepare for next read + buffer += n; + count -= n; + } +} + + +// +// XIOReadMismatch +// + +CString +XIOReadMismatch::getWhat() const throw() +{ + return format("XIOReadMismatch", "CProtocolUtil::readf() mismatch"); +} diff --git a/lib/synergy/CProtocolUtil.h b/lib/synergy/CProtocolUtil.h new file mode 100644 index 00000000..d4019b0b --- /dev/null +++ b/lib/synergy/CProtocolUtil.h @@ -0,0 +1,95 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CPROTOCOLUTIL_H +#define CPROTOCOLUTIL_H + +#include "BasicTypes.h" +#include "XIO.h" +#include + +class IStream; + +//! Synergy protocol utilities +/*! +This class provides various functions for implementing the synergy +protocol. +*/ +class CProtocolUtil { +public: + //! Write formatted data + /*! + Write formatted binary data to a stream. \c fmt consists of + regular characters and format specifiers. Format specifiers + begin with \%. All characters not part of a format specifier + are regular and are transmitted unchanged. + + Format specifiers are: + - \%\% -- literal `\%' + - \%1i -- converts integer argument to 1 byte integer + - \%2i -- converts integer argument to 2 byte integer in NBO + - \%4i -- converts integer argument to 4 byte integer in NBO + - \%1I -- converts std::vector* to 1 byte integers + - \%2I -- converts std::vector* to 2 byte integers in NBO + - \%4I -- converts std::vector* to 4 byte integers in NBO + - \%s -- converts CString* to stream of bytes + - \%S -- converts integer N and const UInt8* to stream of N bytes + */ + static void writef(IStream*, + const char* fmt, ...); + + //! Read formatted data + /*! + Read formatted binary data from a buffer. This performs the + reverse operation of writef(). Returns true if the entire + format was successfully parsed, false otherwise. + + Format specifiers are: + - \%\% -- read (and discard) a literal `\%' + - \%1i -- reads a 1 byte integer; argument is a SInt32* or UInt32* + - \%2i -- reads an NBO 2 byte integer; arg is SInt32* or UInt32* + - \%4i -- reads an NBO 4 byte integer; arg is SInt32* or UInt32* + - \%1I -- reads 1 byte integers; arg is std::vector* + - \%2I -- reads NBO 2 byte integers; arg is std::vector* + - \%4I -- reads NBO 4 byte integers; arg is std::vector* + - \%s -- reads bytes; argument must be a CString*, \b not a char* + */ + static bool readf(IStream*, + const char* fmt, ...); + +private: + static void vwritef(IStream*, + const char* fmt, UInt32 size, va_list); + static void vreadf(IStream*, + const char* fmt, va_list); + + static UInt32 getLength(const char* fmt, va_list); + static void writef(void*, const char* fmt, va_list); + static UInt32 eatLength(const char** fmt); + static void read(IStream*, void*, UInt32); +}; + +//! Mismatched read exception +/*! +Thrown by CProtocolUtil::readf() when the data being read does not +match the format. +*/ +class XIOReadMismatch : public XIO { +public: + // XBase overrides + virtual CString getWhat() const throw(); +}; + +#endif + diff --git a/lib/synergy/CScreen.cpp b/lib/synergy/CScreen.cpp new file mode 100644 index 00000000..bff8daca --- /dev/null +++ b/lib/synergy/CScreen.cpp @@ -0,0 +1,488 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CScreen.h" +#include "IPlatformScreen.h" +#include "ProtocolTypes.h" +#include "CLog.h" +#include "IEventQueue.h" + +// +// CScreen +// + +CScreen::CScreen(IPlatformScreen* platformScreen) : + m_screen(platformScreen), + m_isPrimary(platformScreen->isPrimary()), + m_enabled(false), + m_entered(m_isPrimary), + m_screenSaverSync(true), + m_fakeInput(false) +{ + assert(m_screen != NULL); + + // reset options + resetOptions(); + + LOG((CLOG_DEBUG "opened display")); +} + +CScreen::~CScreen() +{ + if (m_enabled) { + disable(); + } + assert(!m_enabled); + assert(m_entered == m_isPrimary); + delete m_screen; + LOG((CLOG_DEBUG "closed display")); +} + +void +CScreen::enable() +{ + assert(!m_enabled); + + m_screen->updateKeyMap(); + m_screen->updateKeyState(); + m_screen->enable(); + if (m_isPrimary) { + enablePrimary(); + } + else { + enableSecondary(); + } + + // note activation + m_enabled = true; +} + +void +CScreen::disable() +{ + assert(m_enabled); + + if (!m_isPrimary && m_entered) { + leave(); + } + else if (m_isPrimary && !m_entered) { + enter(0); + } + m_screen->disable(); + if (m_isPrimary) { + disablePrimary(); + } + else { + disableSecondary(); + } + + // note deactivation + m_enabled = false; +} + +void +CScreen::enter(KeyModifierMask toggleMask) +{ + assert(m_entered == false); + LOG((CLOG_INFO "entering screen")); + + // now on screen + m_entered = true; + + m_screen->enter(); + if (m_isPrimary) { + enterPrimary(); + } + else { + enterSecondary(toggleMask); + } +} + +bool +CScreen::leave() +{ + assert(m_entered == true); + LOG((CLOG_INFO "leaving screen")); + + if (!m_screen->leave()) { + return false; + } + if (m_isPrimary) { + leavePrimary(); + } + else { + leaveSecondary(); + } + + // make sure our idea of clipboard ownership is correct + m_screen->checkClipboards(); + + // now not on screen + m_entered = false; + + return true; +} + +void +CScreen::reconfigure(UInt32 activeSides) +{ + assert(m_isPrimary); + m_screen->reconfigure(activeSides); +} + +void +CScreen::warpCursor(SInt32 x, SInt32 y) +{ + assert(m_isPrimary); + m_screen->warpCursor(x, y); +} + +void +CScreen::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + m_screen->setClipboard(id, clipboard); +} + +void +CScreen::grabClipboard(ClipboardID id) +{ + m_screen->setClipboard(id, NULL); +} + +void +CScreen::screensaver(bool activate) +{ + if (!m_isPrimary) { + // activate/deactivation screen saver iff synchronization enabled + if (m_screenSaverSync) { + m_screen->screensaver(activate); + } + } +} + +void +CScreen::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) +{ + assert(!m_isPrimary || m_fakeInput); + + // check for ctrl+alt+del emulation + if (id == kKeyDelete && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + LOG((CLOG_DEBUG "emulating ctrl+alt+del press")); + if (m_screen->fakeCtrlAltDel()) { + return; + } + } + m_screen->fakeKeyDown(id, mask, button); +} + +void +CScreen::keyRepeat(KeyID id, + KeyModifierMask mask, SInt32 count, KeyButton button) +{ + assert(!m_isPrimary); + m_screen->fakeKeyRepeat(id, mask, count, button); +} + +void +CScreen::keyUp(KeyID, KeyModifierMask, KeyButton button) +{ + assert(!m_isPrimary || m_fakeInput); + m_screen->fakeKeyUp(button); +} + +void +CScreen::mouseDown(ButtonID button) +{ + assert(!m_isPrimary); + m_screen->fakeMouseButton(button, true); +} + +void +CScreen::mouseUp(ButtonID button) +{ + assert(!m_isPrimary); + m_screen->fakeMouseButton(button, false); +} + +void +CScreen::mouseMove(SInt32 x, SInt32 y) +{ + assert(!m_isPrimary); + m_screen->fakeMouseMove(x, y); +} + +void +CScreen::mouseRelativeMove(SInt32 dx, SInt32 dy) +{ + assert(!m_isPrimary); + m_screen->fakeMouseRelativeMove(dx, dy); +} + +void +CScreen::mouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + assert(!m_isPrimary); + m_screen->fakeMouseWheel(xDelta, yDelta); +} + +void +CScreen::resetOptions() +{ + // reset options + m_halfDuplex = 0; + + // if screen saver synchronization was off then turn it on since + // that's the default option state. + if (!m_screenSaverSync) { + m_screenSaverSync = true; + if (!m_isPrimary) { + m_screen->openScreensaver(false); + } + } + + // let screen handle its own options + m_screen->resetOptions(); +} + +void +CScreen::setOptions(const COptionsList& options) +{ + // update options + bool oldScreenSaverSync = m_screenSaverSync; + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + if (options[i] == kOptionScreenSaverSync) { + m_screenSaverSync = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "screen saver synchronization %s", m_screenSaverSync ? "on" : "off")); + } + else if (options[i] == kOptionHalfDuplexCapsLock) { + if (options[i + 1] != 0) { + m_halfDuplex |= KeyModifierCapsLock; + } + else { + m_halfDuplex &= ~KeyModifierCapsLock; + } + LOG((CLOG_DEBUG1 "half-duplex caps-lock %s", ((m_halfDuplex & KeyModifierCapsLock) != 0) ? "on" : "off")); + } + else if (options[i] == kOptionHalfDuplexNumLock) { + if (options[i + 1] != 0) { + m_halfDuplex |= KeyModifierNumLock; + } + else { + m_halfDuplex &= ~KeyModifierNumLock; + } + LOG((CLOG_DEBUG1 "half-duplex num-lock %s", ((m_halfDuplex & KeyModifierNumLock) != 0) ? "on" : "off")); + } + else if (options[i] == kOptionHalfDuplexScrollLock) { + if (options[i + 1] != 0) { + m_halfDuplex |= KeyModifierScrollLock; + } + else { + m_halfDuplex &= ~KeyModifierScrollLock; + } + LOG((CLOG_DEBUG1 "half-duplex scroll-lock %s", ((m_halfDuplex & KeyModifierScrollLock) != 0) ? "on" : "off")); + } + } + + // update half-duplex options + m_screen->setHalfDuplexMask(m_halfDuplex); + + // update screen saver synchronization + if (!m_isPrimary && oldScreenSaverSync != m_screenSaverSync) { + if (m_screenSaverSync) { + m_screen->openScreensaver(false); + } + else { + m_screen->closeScreensaver(); + } + } + + // let screen handle its own options + m_screen->setOptions(options); +} + +void +CScreen::setSequenceNumber(UInt32 seqNum) +{ + m_screen->setSequenceNumber(seqNum); +} + +UInt32 +CScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + return m_screen->registerHotKey(key, mask); +} + +void +CScreen::unregisterHotKey(UInt32 id) +{ + m_screen->unregisterHotKey(id); +} + +void +CScreen::fakeInputBegin() +{ + assert(!m_fakeInput); + + m_fakeInput = true; + m_screen->fakeInputBegin(); +} + +void +CScreen::fakeInputEnd() +{ + assert(m_fakeInput); + + m_fakeInput = false; + m_screen->fakeInputEnd(); +} + +bool +CScreen::isOnScreen() const +{ + return m_entered; +} + +bool +CScreen::isLockedToScreen() const +{ + // check for pressed mouse buttons + if (m_screen->isAnyMouseButtonDown()) { + LOG((CLOG_DEBUG "locked by mouse button")); + return true; + } + + // not locked + return false; +} + +SInt32 +CScreen::getJumpZoneSize() const +{ + if (!m_isPrimary) { + return 0; + } + else { + return m_screen->getJumpZoneSize(); + } +} + +void +CScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + m_screen->getCursorCenter(x, y); +} + +KeyModifierMask +CScreen::getActiveModifiers() const +{ + return m_screen->getActiveModifiers(); +} + +KeyModifierMask +CScreen::pollActiveModifiers() const +{ + return m_screen->pollActiveModifiers(); +} + +void* +CScreen::getEventTarget() const +{ + return m_screen; +} + +bool +CScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + return m_screen->getClipboard(id, clipboard); +} + +void +CScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + m_screen->getShape(x, y, w, h); +} + +void +CScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + m_screen->getCursorPos(x, y); +} + +void +CScreen::enablePrimary() +{ + // get notified of screen saver activation/deactivation + m_screen->openScreensaver(true); + + // claim screen changed size + EVENTQUEUE->addEvent(CEvent(getShapeChangedEvent(), getEventTarget())); +} + +void +CScreen::enableSecondary() +{ + // assume primary has all clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + grabClipboard(id); + } + + // disable the screen saver if synchronization is enabled + if (m_screenSaverSync) { + m_screen->openScreensaver(false); + } +} + +void +CScreen::disablePrimary() +{ + // done with screen saver + m_screen->closeScreensaver(); +} + +void +CScreen::disableSecondary() +{ + // done with screen saver + m_screen->closeScreensaver(); +} + +void +CScreen::enterPrimary() +{ + // do nothing +} + +void +CScreen::enterSecondary(KeyModifierMask) +{ + // do nothing +} + +void +CScreen::leavePrimary() +{ + // we don't track keys while on the primary screen so update our + // idea of them now. this is particularly to update the state of + // the toggle modifiers. + m_screen->updateKeyState(); +} + +void +CScreen::leaveSecondary() +{ + // release any keys we think are still down + m_screen->fakeAllKeysUp(); +} diff --git a/lib/synergy/CScreen.h b/lib/synergy/CScreen.h new file mode 100644 index 00000000..4d216f8b --- /dev/null +++ b/lib/synergy/CScreen.h @@ -0,0 +1,304 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSCREEN_H +#define CSCREEN_H + +#include "IScreen.h" +#include "ClipboardTypes.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "OptionTypes.h" + +class IClipboard; +class IPlatformScreen; + +//! Platform independent screen +/*! +This is a platform independent screen. It can work as either a +primary or secondary screen. +*/ +class CScreen : public IScreen { +public: + CScreen(IPlatformScreen* platformScreen); + virtual ~CScreen(); + + //! @name manipulators + //@{ + + //! Activate screen + /*! + Activate the screen, preparing it to report system and user events. + For a secondary screen it also means disabling the screen saver if + synchronizing it and preparing to synthesize events. + */ + void enable(); + + //! Deactivate screen + /*! + Undoes the operations in activate() and events are no longer + reported. It also releases keys that are logically pressed. + */ + void disable(); + + //! Enter screen + /*! + Called when the user navigates to this screen. \p toggleMask has the + toggle keys that should be turned on on the secondary screen. + */ + void enter(KeyModifierMask toggleMask); + + //! Leave screen + /*! + Called when the user navigates off this screen. + */ + bool leave(); + + //! Update configuration + /*! + This is called when the configuration has changed. \c activeSides + is a bitmask of EDirectionMask indicating which sides of the + primary screen are linked to clients. + */ + void reconfigure(UInt32 activeSides); + + //! Warp cursor + /*! + Warps the cursor to the absolute coordinates \c x,y. Also + discards input events up to and including the warp before + returning. + */ + void warpCursor(SInt32 x, SInt32 y); + + //! Set clipboard + /*! + Sets the system's clipboard contents. This is usually called + soon after an enter(). + */ + void setClipboard(ClipboardID, const IClipboard*); + + //! Grab clipboard + /*! + Grabs (i.e. take ownership of) the system clipboard. + */ + void grabClipboard(ClipboardID); + + //! Activate/deactivate screen saver + /*! + Forcibly activates the screen saver if \c activate is true otherwise + forcibly deactivates it. + */ + void screensaver(bool activate); + + //! Notify of key press + /*! + Synthesize key events to generate a press of key \c id. If possible + match the given modifier mask. The KeyButton identifies the physical + key on the server that generated this key down. The client must + ensure that a key up or key repeat that uses the same KeyButton will + synthesize an up or repeat for the same client key synthesized by + keyDown(). + */ + void keyDown(KeyID id, KeyModifierMask, KeyButton); + + //! Notify of key repeat + /*! + Synthesize key events to generate a press and release of key \c id + \c count times. If possible match the given modifier mask. + */ + void keyRepeat(KeyID id, KeyModifierMask, + SInt32 count, KeyButton); + + //! Notify of key release + /*! + Synthesize key events to generate a release of key \c id. If possible + match the given modifier mask. + */ + void keyUp(KeyID id, KeyModifierMask, KeyButton); + + //! Notify of mouse press + /*! + Synthesize mouse events to generate a press of mouse button \c id. + */ + void mouseDown(ButtonID id); + + //! Notify of mouse release + /*! + Synthesize mouse events to generate a release of mouse button \c id. + */ + void mouseUp(ButtonID id); + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion to the absolute + screen position \c xAbs,yAbs. + */ + void mouseMove(SInt32 xAbs, SInt32 yAbs); + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion by the relative + amount \c xRel,yRel. + */ + void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + + //! Notify of mouse wheel motion + /*! + Synthesize mouse events to generate mouse wheel motion of \c xDelta + and \c yDelta. Deltas are positive for motion away from the user or + to the right and negative for motion towards the user or to the left. + Each wheel click should generate a delta of +/-120. + */ + void mouseWheel(SInt32 xDelta, SInt32 yDelta); + + //! Notify of options changes + /*! + Resets all options to their default values. + */ + void resetOptions(); + + //! Notify of options changes + /*! + Set options to given values. Ignores unknown options and doesn't + modify options that aren't given in \c options. + */ + void setOptions(const COptionsList& options); + + //! Set clipboard sequence number + /*! + Sets the sequence number to use in subsequent clipboard events. + */ + void setSequenceNumber(UInt32); + + //! Register a system hotkey + /*! + Registers a system-wide hotkey for key \p key with modifiers \p mask. + Returns an id used to unregister the hotkey. + */ + UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + + //! Unregister a system hotkey + /*! + Unregisters a previously registered hot key. + */ + void unregisterHotKey(UInt32 id); + + //! Prepare to synthesize input on primary screen + /*! + Prepares the primary screen to receive synthesized input. We do not + want to receive this synthesized input as user input so this method + ensures that we ignore it. Calls to \c fakeInputBegin() may not be + nested. + */ + void fakeInputBegin(); + + //! Done synthesizing input on primary screen + /*! + Undoes whatever \c fakeInputBegin() did. + */ + void fakeInputEnd(); + + //@} + //! @name accessors + //@{ + + //! Test if cursor on screen + /*! + Returns true iff the cursor is on the screen. + */ + bool isOnScreen() const; + + //! Get screen lock state + /*! + Returns true if there's any reason that the user should not be + allowed to leave the screen (usually because a button or key is + pressed). If this method returns true it logs a message as to + why at the CLOG_DEBUG level. + */ + bool isLockedToScreen() const; + + //! Get jump zone size + /*! + Return the jump zone size, the size of the regions on the edges of + the screen that cause the cursor to jump to another screen. + */ + SInt32 getJumpZoneSize() const; + + //! Get cursor center position + /*! + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. + */ + void getCursorCenter(SInt32& x, SInt32& y) const; + + //! Get the active modifiers + /*! + Returns the modifiers that are currently active according to our + shadowed state. + */ + KeyModifierMask getActiveModifiers() const; + + //! Get the active modifiers from OS + /*! + Returns the modifiers that are currently active according to the + operating system. + */ + KeyModifierMask pollActiveModifiers() const; + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + +protected: + void enablePrimary(); + void enableSecondary(); + void disablePrimary(); + void disableSecondary(); + + void enterPrimary(); + void enterSecondary(KeyModifierMask toggleMask); + void leavePrimary(); + void leaveSecondary(); + +private: + // our platform dependent screen + IPlatformScreen* m_screen; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if screen is enabled + bool m_enabled; + + // true if the cursor is on this screen + bool m_entered; + + // true if screen saver should be synchronized to server + bool m_screenSaverSync; + + // note toggle keys that toggles on up/down (false) or on + // transition (true) + KeyModifierMask m_halfDuplex; + + // true if we're faking input on a primary screen + bool m_fakeInput; +}; + +#endif diff --git a/lib/synergy/ClipboardTypes.h b/lib/synergy/ClipboardTypes.h new file mode 100644 index 00000000..a638e821 --- /dev/null +++ b/lib/synergy/ClipboardTypes.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CLIPBOARDTYPES_H +#define CLIPBOARDTYPES_H + +#include "BasicTypes.h" + +//! Clipboard ID +/*! +Type to hold a clipboard identifier. +*/ +typedef UInt8 ClipboardID; + +//! @name Clipboard identifiers +//@{ +// clipboard identifiers. kClipboardClipboard is what is normally +// considered the clipboard (e.g. the cut/copy/paste menu items +// affect it). kClipboardSelection is the selection on those +// platforms that can treat the selection as a clipboard (e.g. X +// windows). clipboard identifiers must be sequential starting +// at zero. +static const ClipboardID kClipboardClipboard = 0; +static const ClipboardID kClipboardSelection = 1; + +// the number of clipboards (i.e. one greater than the last clipboard id) +static const ClipboardID kClipboardEnd = 2; +//@} + +#endif diff --git a/lib/synergy/IClient.h b/lib/synergy/IClient.h new file mode 100644 index 00000000..81cbf06a --- /dev/null +++ b/lib/synergy/IClient.h @@ -0,0 +1,175 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ICLIENT_H +#define ICLIENT_H + +#include "IScreen.h" +#include "ClipboardTypes.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "OptionTypes.h" +#include "CString.h" + +//! Client interface +/*! +This interface defines the methods necessary for the server to +communicate with a client. +*/ +class IClient : public IScreen { +public: + //! @name manipulators + //@{ + + //! Enter screen + /*! + Enter the screen. The cursor should be warped to \p xAbs,yAbs. + \p mask is the expected toggle button state and the client should + update its state to match. \p forScreensaver is true iff the + screen is being entered because the screen saver is starting. + Subsequent clipboard events should report \p seqNum. + */ + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver) = 0; + + //! Leave screen + /*! + Leave the screen. Return false iff the user may not leave the + client's screen (because, for example, a button is down). + */ + virtual bool leave() = 0; + + //! Set clipboard + /*! + Update the client's clipboard. This implies that the client's + clipboard is now up to date. If the client's clipboard was + already known to be up to date then this may do nothing. \c data + has marshalled clipboard data. + */ + virtual void setClipboard(ClipboardID, const IClipboard*) = 0; + + //! Grab clipboard + /*! + Grab (i.e. take ownership of) the client's clipboard. Since this + is called when another client takes ownership of the clipboard it + implies that the client's clipboard is out of date. + */ + virtual void grabClipboard(ClipboardID) = 0; + + //! Mark clipboard dirty + /*! + Mark the client's clipboard as dirty (out of date) or clean (up to + date). + */ + virtual void setClipboardDirty(ClipboardID, bool dirty) = 0; + + //! Notify of key press + /*! + Synthesize key events to generate a press of key \c id. If possible + match the given modifier mask. The KeyButton identifies the physical + key on the server that generated this key down. The client must + ensure that a key up or key repeat that uses the same KeyButton will + synthesize an up or repeat for the same client key synthesized by + keyDown(). + */ + virtual void keyDown(KeyID id, KeyModifierMask, KeyButton) = 0; + + //! Notify of key repeat + /*! + Synthesize key events to generate a press and release of key \c id + \c count times. If possible match the given modifier mask. + */ + virtual void keyRepeat(KeyID id, KeyModifierMask, + SInt32 count, KeyButton) = 0; + + //! Notify of key release + /*! + Synthesize key events to generate a release of key \c id. If possible + match the given modifier mask. + */ + virtual void keyUp(KeyID id, KeyModifierMask, KeyButton) = 0; + + //! Notify of mouse press + /*! + Synthesize mouse events to generate a press of mouse button \c id. + */ + virtual void mouseDown(ButtonID id) = 0; + + //! Notify of mouse release + /*! + Synthesize mouse events to generate a release of mouse button \c id. + */ + virtual void mouseUp(ButtonID id) = 0; + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion to the absolute + screen position \c xAbs,yAbs. + */ + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0; + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion by the relative + amount \c xRel,yRel. + */ + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel) = 0; + + //! Notify of mouse wheel motion + /*! + Synthesize mouse events to generate mouse wheel motion of \c xDelta + and \c yDelta. Deltas are positive for motion away from the user or + to the right and negative for motion towards the user or to the left. + Each wheel click should generate a delta of +/-120. + */ + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta) = 0; + + //! Notify of screen saver change + virtual void screensaver(bool activate) = 0; + + //! Notify of options changes + /*! + Reset all options to their default values. + */ + virtual void resetOptions() = 0; + + //! Notify of options changes + /*! + Set options to given values. Ignore unknown options and don't + modify our options that aren't given in \c options. + */ + virtual void setOptions(const COptionsList& options) = 0; + + //@} + //! @name accessors + //@{ + + //! Get client name + /*! + Return the client's name. + */ + virtual CString getName() const = 0; + + //@} + + // IScreen overrides + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; +}; + +#endif diff --git a/lib/synergy/IClipboard.cpp b/lib/synergy/IClipboard.cpp new file mode 100644 index 00000000..7a055612 --- /dev/null +++ b/lib/synergy/IClipboard.cpp @@ -0,0 +1,155 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "IClipboard.h" +#include "stdvector.h" + +// +// IClipboard +// + +void +IClipboard::unmarshall(IClipboard* clipboard, const CString& data, Time time) +{ + assert(clipboard != NULL); + + const char* index = data.data(); + + // clear existing data + clipboard->open(time); + clipboard->empty(); + + // read the number of formats + const UInt32 numFormats = readUInt32(index); + index += 4; + + // read each format + for (UInt32 i = 0; i < numFormats; ++i) { + // get the format id + IClipboard::EFormat format = + static_cast(readUInt32(index)); + index += 4; + + // get the size of the format data + UInt32 size = readUInt32(index); + index += 4; + + // save the data if it's a known format. if either the client + // or server supports more clipboard formats than the other + // then one of them will get a format >= kNumFormats here. + if (format add(format, CString(index, size)); + } + index += size; + } + + // done + clipboard->close(); +} + +CString +IClipboard::marshall(const IClipboard* clipboard) +{ + assert(clipboard != NULL); + + CString data; + + std::vector formatData; + formatData.resize(IClipboard::kNumFormats); + // FIXME -- use current time + clipboard->open(0); + + // compute size of marshalled data + UInt32 size = 4; + UInt32 numFormats = 0; + for (UInt32 format = 0; format != IClipboard::kNumFormats; ++format) { + if (clipboard->has(static_cast(format))) { + ++numFormats; + formatData[format] = + clipboard->get(static_cast(format)); + size += 4 + 4 + formatData[format].size(); + } + } + + // allocate space + data.reserve(size); + + // marshall the data + writeUInt32(&data, numFormats); + for (UInt32 format = 0; format != IClipboard::kNumFormats; ++format) { + if (clipboard->has(static_cast(format))) { + writeUInt32(&data, format); + writeUInt32(&data, formatData[format].size()); + data += formatData[format]; + } + } + clipboard->close(); + + return data; +} + +bool +IClipboard::copy(IClipboard* dst, const IClipboard* src) +{ + assert(dst != NULL); + assert(src != NULL); + + return copy(dst, src, src->getTime()); +} + +bool +IClipboard::copy(IClipboard* dst, const IClipboard* src, Time time) +{ + assert(dst != NULL); + assert(src != NULL); + + bool success = false; + if (src->open(time)) { + if (dst->open(time)) { + if (dst->empty()) { + for (SInt32 format = 0; + format != IClipboard::kNumFormats; ++format) { + IClipboard::EFormat eFormat = (IClipboard::EFormat)format; + if (src->has(eFormat)) { + dst->add(eFormat, src->get(eFormat)); + } + } + success = true; + } + dst->close(); + } + src->close(); + } + + return success; +} + +UInt32 +IClipboard::readUInt32(const char* buf) +{ + const unsigned char* ubuf = reinterpret_cast(buf); + return (static_cast(ubuf[0]) << 24) | + (static_cast(ubuf[1]) << 16) | + (static_cast(ubuf[2]) << 8) | + static_cast(ubuf[3]); +} + +void +IClipboard::writeUInt32(CString* buf, UInt32 v) +{ + *buf += static_cast((v >> 24) & 0xff); + *buf += static_cast((v >> 16) & 0xff); + *buf += static_cast((v >> 8) & 0xff); + *buf += static_cast( v & 0xff); +} diff --git a/lib/synergy/IClipboard.h b/lib/synergy/IClipboard.h new file mode 100644 index 00000000..c883da8a --- /dev/null +++ b/lib/synergy/IClipboard.h @@ -0,0 +1,168 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ICLIPBOARD_H +#define ICLIPBOARD_H + +#include "IInterface.h" +#include "CString.h" +#include "BasicTypes.h" + +//! Clipboard interface +/*! +This interface defines the methods common to all clipboards. +*/ +class IClipboard : public IInterface { +public: + //! Timestamp type + /*! + Timestamp type. Timestamps are in milliseconds from some + arbitrary starting time. Timestamps will wrap around to 0 + after about 49 3/4 days. + */ + typedef UInt32 Time; + + //! Clipboard formats + /*! + The list of known clipboard formats. kNumFormats must be last and + formats must be sequential starting from zero. Clipboard data set + via add() and retrieved via get() must be in one of these formats. + Platform dependent clipboard subclasses can and should present any + suitable formats derivable from these formats. + + \c kText is a text format encoded in UTF-8. Newlines are LF (not + CR or LF/CR). + + \k kBitmap is an image format. The data is a BMP file without the + 14 byte header (i.e. starting at the INFOHEADER) and with the image + data immediately following the 40 byte INFOHEADER. + + \k kHTML is a text format encoded in UTF-8 and containing a valid + HTML fragment (but not necessarily a complete HTML document). + Newlines are LF. + */ + enum EFormat { + kText, //!< Text format, UTF-8, newline is LF + kBitmap, //!< Bitmap format, BMP 24/32bpp, BI_RGB + kHTML, //!< HTML format, HTML fragment, UTF-8, newline is LF + kNumFormats //!< The number of clipboard formats + }; + + //! @name manipulators + //@{ + + //! Empty clipboard + /*! + Take ownership of the clipboard and clear all data from it. + This must be called between a successful open() and close(). + Return false if the clipboard ownership could not be taken; + the clipboard should not be emptied in this case. + */ + virtual bool empty() = 0; + + //! Add data + /*! + Add data in the given format to the clipboard. May only be + called after a successful empty(). + */ + virtual void add(EFormat, const CString& data) = 0; + + //@} + //! @name accessors + //@{ + + //! Open clipboard + /*! + Open the clipboard. Return true iff the clipboard could be + opened. If open() returns true then the client must call + close() at some later time; if it returns false then close() + must not be called. \c time should be the current time or + a time in the past when the open should effectively have taken + place. + */ + virtual bool open(Time time) const = 0; + + //! Close clipboard + /*! + Close the clipboard. close() must match a preceding successful + open(). This signals that the clipboard has been filled with + all the necessary data or all data has been read. It does not + mean the clipboard ownership should be released (if it was + taken). + */ + virtual void close() const = 0; + + //! Get time + /*! + Return the timestamp passed to the last successful open(). + */ + virtual Time getTime() const = 0; + + //! Check for data + /*! + Return true iff the clipboard contains data in the given + format. Must be called between a successful open() and close(). + */ + virtual bool has(EFormat) const = 0; + + //! Get data + /*! + Return the data in the given format. Returns the empty string + if there is no data in that format. Must be called between + a successful open() and close(). + */ + virtual CString get(EFormat) const = 0; + + //! Marshall clipboard data + /*! + Merge \p clipboard's data into a single buffer that can be later + unmarshalled to restore the clipboard and return the buffer. + */ + static CString marshall(const IClipboard* clipboard); + + //! Unmarshall clipboard data + /*! + Extract marshalled clipboard data and store it in \p clipboard. + Sets the clipboard time to \c time. + */ + static void unmarshall(IClipboard* clipboard, + const CString& data, Time time); + + //! Copy clipboard + /*! + Transfers all the data in one clipboard to another. The + clipboards can be of any concrete clipboard type (and + they don't have to be the same type). This also sets + the destination clipboard's timestamp to source clipboard's + timestamp. Returns true iff the copy succeeded. + */ + static bool copy(IClipboard* dst, const IClipboard* src); + + //! Copy clipboard + /*! + Transfers all the data in one clipboard to another. The + clipboards can be of any concrete clipboard type (and they + don't have to be the same type). This also sets the + timestamp to \c time. Returns true iff the copy succeeded. + */ + static bool copy(IClipboard* dst, const IClipboard* src, Time); + + //@} + +private: + static UInt32 readUInt32(const char*); + static void writeUInt32(CString*, UInt32); +}; + +#endif diff --git a/lib/synergy/IKeyState.cpp b/lib/synergy/IKeyState.cpp new file mode 100644 index 00000000..d44e17d3 --- /dev/null +++ b/lib/synergy/IKeyState.cpp @@ -0,0 +1,176 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "IKeyState.h" +#include + +// +// IKeyState +// + +CEvent::Type IKeyState::s_keyDownEvent = CEvent::kUnknown; +CEvent::Type IKeyState::s_keyUpEvent = CEvent::kUnknown; +CEvent::Type IKeyState::s_keyRepeatEvent = CEvent::kUnknown; + +CEvent::Type +IKeyState::getKeyDownEvent() +{ + return CEvent::registerTypeOnce(s_keyDownEvent, + "IKeyState::keyDown"); +} + +CEvent::Type +IKeyState::getKeyUpEvent() +{ + return CEvent::registerTypeOnce(s_keyUpEvent, + "IKeyState::keyUp"); +} + +CEvent::Type +IKeyState::getKeyRepeatEvent() +{ + return CEvent::registerTypeOnce(s_keyRepeatEvent, + "IKeyState::keyRepeat"); +} + + +// +// IKeyState::CKeyInfo +// + +IKeyState::CKeyInfo* +IKeyState::CKeyInfo::alloc(KeyID id, + KeyModifierMask mask, KeyButton button, SInt32 count) +{ + CKeyInfo* info = (CKeyInfo*)malloc(sizeof(CKeyInfo)); + info->m_key = id; + info->m_mask = mask; + info->m_button = button; + info->m_count = count; + info->m_screens = NULL; + info->m_screensBuffer[0] = '\0'; + return info; +} + +IKeyState::CKeyInfo* +IKeyState::CKeyInfo::alloc(KeyID id, + KeyModifierMask mask, KeyButton button, SInt32 count, + const std::set& destinations) +{ + CString screens = join(destinations); + + // build structure + CKeyInfo* info = (CKeyInfo*)malloc(sizeof(CKeyInfo) + screens.size()); + info->m_key = id; + info->m_mask = mask; + info->m_button = button; + info->m_count = count; + info->m_screens = info->m_screensBuffer; + strcpy(info->m_screensBuffer, screens.c_str()); + return info; +} + +IKeyState::CKeyInfo* +IKeyState::CKeyInfo::alloc(const CKeyInfo& x) +{ + CKeyInfo* info = (CKeyInfo*)malloc(sizeof(CKeyInfo) + + strlen(x.m_screensBuffer)); + info->m_key = x.m_key; + info->m_mask = x.m_mask; + info->m_button = x.m_button; + info->m_count = x.m_count; + info->m_screens = x.m_screens ? info->m_screensBuffer : NULL; + strcpy(info->m_screensBuffer, x.m_screensBuffer); + return info; +} + +bool +IKeyState::CKeyInfo::isDefault(const char* screens) +{ + return (screens == NULL || screens[0] == '\0'); +} + +bool +IKeyState::CKeyInfo::contains(const char* screens, const CString& name) +{ + // special cases + if (isDefault(screens)) { + return false; + } + if (screens[0] == '*') { + return true; + } + + // search + CString match; + match.reserve(name.size() + 2); + match += ":"; + match += name; + match += ":"; + return (strstr(screens, match.c_str()) != NULL); +} + +bool +IKeyState::CKeyInfo::equal(const CKeyInfo* a, const CKeyInfo* b) +{ + return (a->m_key == b->m_key && + a->m_mask == b->m_mask && + a->m_button == b->m_button && + a->m_count == b->m_count && + strcmp(a->m_screensBuffer, b->m_screensBuffer) == 0); +} + +CString +IKeyState::CKeyInfo::join(const std::set& destinations) +{ + // collect destinations into a string. names are surrounded by ':' + // which makes searching easy. the string is empty if there are no + // destinations and "*" means all destinations. + CString screens; + for (std::set::const_iterator i = destinations.begin(); + i != destinations.end(); ++i) { + if (*i == "*") { + screens = "*"; + break; + } + else { + if (screens.empty()) { + screens = ":"; + } + screens += *i; + screens += ":"; + } + } + return screens; +} + +void +IKeyState::CKeyInfo::split(const char* screens, std::set& dst) +{ + dst.clear(); + if (isDefault(screens)) { + return; + } + if (screens[0] == '*') { + dst.insert("*"); + return; + } + + const char* i = screens + 1; + while (*i != '\0') { + const char* j = strchr(i, ':'); + dst.insert(CString(i, j - i)); + i = j + 1; + } +} diff --git a/lib/synergy/IKeyState.h b/lib/synergy/IKeyState.h new file mode 100644 index 00000000..2b282487 --- /dev/null +++ b/lib/synergy/IKeyState.h @@ -0,0 +1,174 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IKEYSTATE_H +#define IKEYSTATE_H + +#include "IInterface.h" +#include "KeyTypes.h" +#include "CEvent.h" +#include "CString.h" +#include "stdset.h" + +//! Key state interface +/*! +This interface provides access to set and query the keyboard state and +to synthesize key events. +*/ +class IKeyState : public IInterface { +public: + enum { + kNumButtons = 0x200 + }; + + //! Key event data + class CKeyInfo { + public: + static CKeyInfo* alloc(KeyID, KeyModifierMask, KeyButton, SInt32 count); + static CKeyInfo* alloc(KeyID, KeyModifierMask, KeyButton, SInt32 count, + const std::set& destinations); + static CKeyInfo* alloc(const CKeyInfo&); + + static bool isDefault(const char* screens); + static bool contains(const char* screens, const CString& name); + static bool equal(const CKeyInfo*, const CKeyInfo*); + static CString join(const std::set& destinations); + static void split(const char* screens, std::set&); + + public: + KeyID m_key; + KeyModifierMask m_mask; + KeyButton m_button; + SInt32 m_count; + char* m_screens; + char m_screensBuffer[1]; + }; + + typedef std::set KeyButtonSet; + + //! @name manipulators + //@{ + + //! Update the keyboard map + /*! + Causes the key state to get updated to reflect the current keyboard + mapping. + */ + virtual void updateKeyMap() = 0; + + //! Update the key state + /*! + Causes the key state to get updated to reflect the physical keyboard + state. + */ + virtual void updateKeyState() = 0; + + //! Set half-duplex mask + /*! + Sets which modifier toggle keys are half-duplex. A half-duplex + toggle key doesn't report a key release when toggled on and + doesn't report a key press when toggled off. + */ + virtual void setHalfDuplexMask(KeyModifierMask) = 0; + + //! Fake a key press + /*! + Synthesizes a key press event and updates the key state. + */ + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) = 0; + + //! Fake a key repeat + /*! + Synthesizes a key repeat event and updates the key state. + */ + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) = 0; + + //! Fake a key release + /*! + Synthesizes a key release event and updates the key state. + */ + virtual void fakeKeyUp(KeyButton button) = 0; + + //! Fake key releases for all fake pressed keys + /*! + Synthesizes a key release event for every key that is synthetically + pressed and updates the key state. + */ + virtual void fakeAllKeysUp() = 0; + + //! Fake ctrl+alt+del + /*! + Synthesize a press of ctrl+alt+del. Return true if processing is + complete and false if normal key processing should continue. + */ + virtual bool fakeCtrlAltDel() = 0; + + //@} + //! @name accessors + //@{ + + //! Test if key is pressed + /*! + Returns true iff the given key is down. Half-duplex toggles + always return false. + */ + virtual bool isKeyDown(KeyButton) const = 0; + + //! Get the active modifiers + /*! + Returns the modifiers that are currently active according to our + shadowed state. + */ + virtual KeyModifierMask + getActiveModifiers() const = 0; + + //! Get the active modifiers from OS + /*! + Returns the modifiers that are currently active according to the + operating system. + */ + virtual KeyModifierMask + pollActiveModifiers() const = 0; + + //! Get the active keyboard layout from OS + /*! + Returns the active keyboard layout according to the operating system. + */ + virtual SInt32 pollActiveGroup() const = 0; + + //! Get the keys currently pressed from OS + /*! + Adds any keys that are currently pressed according to the operating + system to \p pressedKeys. + */ + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const = 0; + + //! Get key down event type. Event data is CKeyInfo*, count == 1. + static CEvent::Type getKeyDownEvent(); + //! Get key up event type. Event data is CKeyInfo*, count == 1. + static CEvent::Type getKeyUpEvent(); + //! Get key repeat event type. Event data is CKeyInfo*. + static CEvent::Type getKeyRepeatEvent(); + + //@} + +private: + static CEvent::Type s_keyDownEvent; + static CEvent::Type s_keyUpEvent; + static CEvent::Type s_keyRepeatEvent; +}; + +#endif diff --git a/lib/synergy/IPlatformScreen.h b/lib/synergy/IPlatformScreen.h new file mode 100644 index 00000000..b4d1b785 --- /dev/null +++ b/lib/synergy/IPlatformScreen.h @@ -0,0 +1,210 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IPLATFORMSCREEN_H +#define IPLATFORMSCREEN_H + +#include "IScreen.h" +#include "IPrimaryScreen.h" +#include "ISecondaryScreen.h" +#include "IKeyState.h" +#include "ClipboardTypes.h" +#include "OptionTypes.h" + +class IClipboard; + +//! Screen interface +/*! +This interface defines the methods common to all platform dependent +screen implementations that are used by both primary and secondary +screens. +*/ +class IPlatformScreen : public IScreen, + public IPrimaryScreen, public ISecondaryScreen, + public IKeyState { +public: + //! @name manipulators + //@{ + + //! Enable screen + /*! + Enable the screen, preparing it to report system and user events. + For a secondary screen it also means preparing to synthesize events + and hiding the cursor. + */ + virtual void enable() = 0; + + //! Disable screen + /*! + Undoes the operations in enable() and events should no longer + be reported. + */ + virtual void disable() = 0; + + //! Enter screen + /*! + Called when the user navigates to this screen. + */ + virtual void enter() = 0; + + //! Leave screen + /*! + Called when the user navigates off the screen. Returns true on + success, false on failure. A typical reason for failure is being + unable to install the keyboard and mouse snoopers on a primary + screen. Secondary screens should not fail. + */ + virtual bool leave() = 0; + + //! Set clipboard + /*! + Set the contents of the system clipboard indicated by \c id. + */ + virtual bool setClipboard(ClipboardID id, const IClipboard*) = 0; + + //! Check clipboard owner + /*! + Check ownership of all clipboards and post grab events for any that + have changed. This is used as a backup in case the system doesn't + reliably report clipboard ownership changes. + */ + virtual void checkClipboards() = 0; + + //! Open screen saver + /*! + Open the screen saver. If \c notify is true then this object must + send events when the screen saver activates or deactivates until + \c closeScreensaver() is called. If \c notify is false then the + screen saver is disabled and restored on \c closeScreensaver(). + */ + virtual void openScreensaver(bool notify) = 0; + + //! Close screen saver + /*! + // Close the screen saver. Stop reporting screen saver activation + and deactivation and, if the screen saver was disabled by + openScreensaver(), enable the screen saver. + */ + virtual void closeScreensaver() = 0; + + //! Activate/deactivate screen saver + /*! + Forcibly activate the screen saver if \c activate is true otherwise + forcibly deactivate it. + */ + virtual void screensaver(bool activate) = 0; + + //! Notify of options changes + /*! + Reset all options to their default values. + */ + virtual void resetOptions() = 0; + + //! Notify of options changes + /*! + Set options to given values. Ignore unknown options and don't + modify options that aren't given in \c options. + */ + virtual void setOptions(const COptionsList& options) = 0; + + //! Set clipboard sequence number + /*! + Sets the sequence number to use in subsequent clipboard events. + */ + virtual void setSequenceNumber(UInt32) = 0; + + //@} + //! @name accessors + //@{ + + //! Test if is primary screen + /*! + Return true iff this screen is a primary screen. + */ + virtual bool isPrimary() const = 0; + + //@} + + // IScreen overrides + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides) = 0; + virtual void warpCursor(SInt32 x, SInt32 y) = 0; + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask) = 0; + virtual void unregisterHotKey(UInt32 id) = 0; + virtual void fakeInputBegin() = 0; + virtual void fakeInputEnd() = 0; + virtual SInt32 getJumpZoneSize() const = 0; + virtual bool isAnyMouseButtonDown() const = 0; + virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press) const = 0; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const = 0; + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const = 0; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const = 0; + + // IKeyState overrides + virtual void updateKeyMap() = 0; + virtual void updateKeyState() = 0; + virtual void setHalfDuplexMask(KeyModifierMask) = 0; + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) = 0; + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) = 0; + virtual void fakeKeyUp(KeyButton button) = 0; + virtual void fakeAllKeysUp() = 0; + virtual bool fakeCtrlAltDel() = 0; + virtual bool isKeyDown(KeyButton) const = 0; + virtual KeyModifierMask + getActiveModifiers() const = 0; + virtual KeyModifierMask + pollActiveModifiers() const = 0; + virtual SInt32 pollActiveGroup() const = 0; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const = 0; + +protected: + //! Handle system event + /*! + A platform screen is expected to install a handler for system + events in its c'tor like so: + \code + EVENTQUEUE->adoptHandler(CEvent::kSystem, + IEventQueue::getSystemTarget(), + new TMethodEventJob(this, + &CXXXPlatformScreen::handleSystemEvent)); + \endcode + It should remove the handler in its d'tor. Override the + \c handleSystemEvent() method to process system events. + It should post the events \c IScreen as appropriate. + + A primary screen has further responsibilities. It should post + the events in \c IPrimaryScreen as appropriate. It should also + call \c onKey() on its \c CKeyState whenever a key is pressed + or released (but not for key repeats). And it should call + \c updateKeyMap() on its \c CKeyState if necessary when the keyboard + mapping changes. + + The target of all events should be the value returned by + \c getEventTarget(). + */ + virtual void handleSystemEvent(const CEvent& event, void*) = 0; +}; + +#endif diff --git a/lib/synergy/IPrimaryScreen.cpp b/lib/synergy/IPrimaryScreen.cpp new file mode 100644 index 00000000..71b103f3 --- /dev/null +++ b/lib/synergy/IPrimaryScreen.cpp @@ -0,0 +1,178 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "IPrimaryScreen.h" + +// +// IPrimaryScreen +// + +CEvent::Type IPrimaryScreen::s_buttonDownEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_buttonUpEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_motionPrimaryEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_motionSecondaryEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_wheelEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_ssActivatedEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_ssDeactivatedEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_hotKeyDownEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_hotKeyUpEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_fakeInputBegin = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_fakeInputEnd = CEvent::kUnknown; + +CEvent::Type +IPrimaryScreen::getButtonDownEvent() +{ + return CEvent::registerTypeOnce(s_buttonDownEvent, + "IPrimaryScreen::buttonDown"); +} + +CEvent::Type +IPrimaryScreen::getButtonUpEvent() +{ + return CEvent::registerTypeOnce(s_buttonUpEvent, + "IPrimaryScreen::buttonUp"); +} + +CEvent::Type +IPrimaryScreen::getMotionOnPrimaryEvent() +{ + return CEvent::registerTypeOnce(s_motionPrimaryEvent, + "IPrimaryScreen::motionPrimary"); +} + +CEvent::Type +IPrimaryScreen::getMotionOnSecondaryEvent() +{ + return CEvent::registerTypeOnce(s_motionSecondaryEvent, + "IPrimaryScreen::motionSecondary"); +} + +CEvent::Type +IPrimaryScreen::getWheelEvent() +{ + return CEvent::registerTypeOnce(s_wheelEvent, + "IPrimaryScreen::wheel"); +} + +CEvent::Type +IPrimaryScreen::getScreensaverActivatedEvent() +{ + return CEvent::registerTypeOnce(s_ssActivatedEvent, + "IPrimaryScreen::screensaverActivated"); +} + +CEvent::Type +IPrimaryScreen::getScreensaverDeactivatedEvent() +{ + return CEvent::registerTypeOnce(s_ssDeactivatedEvent, + "IPrimaryScreen::screensaverDeactivated"); +} + +CEvent::Type +IPrimaryScreen::getHotKeyDownEvent() +{ + return CEvent::registerTypeOnce(s_hotKeyDownEvent, + "IPrimaryScreen::hotKeyDown"); +} + +CEvent::Type +IPrimaryScreen::getHotKeyUpEvent() +{ + return CEvent::registerTypeOnce(s_hotKeyUpEvent, + "IPrimaryScreen::hotKeyUp"); +} + +CEvent::Type +IPrimaryScreen::getFakeInputBeginEvent() +{ + return CEvent::registerTypeOnce(s_fakeInputBegin, + "IPrimaryScreen::fakeInputBegin"); +} + +CEvent::Type +IPrimaryScreen::getFakeInputEndEvent() +{ + return CEvent::registerTypeOnce(s_fakeInputEnd, + "IPrimaryScreen::fakeInputEnd"); +} + + +// +// IPrimaryScreen::CButtonInfo +// + +IPrimaryScreen::CButtonInfo* +IPrimaryScreen::CButtonInfo::alloc(ButtonID id, KeyModifierMask mask) +{ + CButtonInfo* info = (CButtonInfo*)malloc(sizeof(CButtonInfo)); + info->m_button = id; + info->m_mask = mask; + return info; +} + +IPrimaryScreen::CButtonInfo* +IPrimaryScreen::CButtonInfo::alloc(const CButtonInfo& x) +{ + CButtonInfo* info = (CButtonInfo*)malloc(sizeof(CButtonInfo)); + info->m_button = x.m_button; + info->m_mask = x.m_mask; + return info; +} + +bool +IPrimaryScreen::CButtonInfo::equal(const CButtonInfo* a, const CButtonInfo* b) +{ + return (a->m_button == b->m_button && a->m_mask == b->m_mask); +} + + +// +// IPrimaryScreen::CMotionInfo +// + +IPrimaryScreen::CMotionInfo* +IPrimaryScreen::CMotionInfo::alloc(SInt32 x, SInt32 y) +{ + CMotionInfo* info = (CMotionInfo*)malloc(sizeof(CMotionInfo)); + info->m_x = x; + info->m_y = y; + return info; +} + + +// +// IPrimaryScreen::CWheelInfo +// + +IPrimaryScreen::CWheelInfo* +IPrimaryScreen::CWheelInfo::alloc(SInt32 xDelta, SInt32 yDelta) +{ + CWheelInfo* info = (CWheelInfo*)malloc(sizeof(CWheelInfo)); + info->m_xDelta = xDelta; + info->m_yDelta = yDelta; + return info; +} + + +// +// IPrimaryScreen::CHotKeyInfo +// + +IPrimaryScreen::CHotKeyInfo* +IPrimaryScreen::CHotKeyInfo::alloc(UInt32 id) +{ + CHotKeyInfo* info = (CHotKeyInfo*)malloc(sizeof(CHotKeyInfo)); + info->m_id = id; + return info; +} diff --git a/lib/synergy/IPrimaryScreen.h b/lib/synergy/IPrimaryScreen.h new file mode 100644 index 00000000..93166d23 --- /dev/null +++ b/lib/synergy/IPrimaryScreen.h @@ -0,0 +1,206 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IPRIMARYSCREEN_H +#define IPRIMARYSCREEN_H + +#include "IInterface.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "CEvent.h" + +//! Primary screen interface +/*! +This interface defines the methods common to all platform dependent +primary screen implementations. +*/ +class IPrimaryScreen : public IInterface { +public: + //! Button event data + class CButtonInfo { + public: + static CButtonInfo* alloc(ButtonID, KeyModifierMask); + static CButtonInfo* alloc(const CButtonInfo&); + + static bool equal(const CButtonInfo*, const CButtonInfo*); + + public: + ButtonID m_button; + KeyModifierMask m_mask; + }; + //! Motion event data + class CMotionInfo { + public: + static CMotionInfo* alloc(SInt32 x, SInt32 y); + + public: + SInt32 m_x; + SInt32 m_y; + }; + //! Wheel motion event data + class CWheelInfo { + public: + static CWheelInfo* alloc(SInt32 xDelta, SInt32 yDelta); + + public: + SInt32 m_xDelta; + SInt32 m_yDelta; + }; + //! Hot key event data + class CHotKeyInfo { + public: + static CHotKeyInfo* alloc(UInt32 id); + + public: + UInt32 m_id; + }; + + //! @name manipulators + //@{ + + //! Update configuration + /*! + This is called when the configuration has changed. \c activeSides + is a bitmask of EDirectionMask indicating which sides of the + primary screen are linked to clients. Override to handle the + possible change in jump zones. + */ + virtual void reconfigure(UInt32 activeSides) = 0; + + //! Warp cursor + /*! + Warp the cursor to the absolute coordinates \c x,y. Also + discard input events up to and including the warp before + returning. + */ + virtual void warpCursor(SInt32 x, SInt32 y) = 0; + + //! Register a system hotkey + /*! + Registers a system-wide hotkey. The screen should arrange for an event + to be delivered to itself when the hot key is pressed or released. When + that happens the screen should post a \c getHotKeyDownEvent() or + \c getHotKeyUpEvent(), respectively. The hot key is key \p key with + exactly the modifiers \p mask. Returns 0 on failure otherwise an id + that can be used to unregister the hotkey. + + A hot key is a set of modifiers and a key, which may itself be a modifier. + The hot key is pressed when the hot key's modifiers and only those + modifiers are logically down (active) and the key is pressed. The hot + key is released when the key is released, regardless of the modifiers. + + The hot key event should be generated no matter what window or application + has the focus. No other window or application should receive the key + press or release events (they can and should see the modifier key events). + When the key is a modifier, it's acceptable to allow the user to press + the modifiers in any order or to require the user to press the given key + last. + */ + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask) = 0; + + //! Unregister a system hotkey + /*! + Unregisters a previously registered hot key. + */ + virtual void unregisterHotKey(UInt32 id) = 0; + + //! Prepare to synthesize input on primary screen + /*! + Prepares the primary screen to receive synthesized input. We do not + want to receive this synthesized input as user input so this method + ensures that we ignore it. Calls to \c fakeInputBegin() may not be + nested. + */ + virtual void fakeInputBegin() = 0; + + //! Done synthesizing input on primary screen + /*! + Undoes whatever \c fakeInputBegin() did. + */ + virtual void fakeInputEnd() = 0; + + //@} + //! @name accessors + //@{ + + //! Get jump zone size + /*! + Return the jump zone size, the size of the regions on the edges of + the screen that cause the cursor to jump to another screen. + */ + virtual SInt32 getJumpZoneSize() const = 0; + + //! Test if mouse is pressed + /*! + Return true if any mouse button is currently pressed. Ideally, + "current" means up to the last processed event but it can mean + the current physical mouse button state. + */ + virtual bool isAnyMouseButtonDown() const = 0; + + //! Get cursor center position + /*! + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. + */ + virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; + + //! Get button down event type. Event data is CButtonInfo*. + static CEvent::Type getButtonDownEvent(); + //! Get button up event type. Event data is CButtonInfo*. + static CEvent::Type getButtonUpEvent(); + //! Get mouse motion on the primary screen event type + /*! + Event data is CMotionInfo* and the values are an absolute position. + */ + static CEvent::Type getMotionOnPrimaryEvent(); + //! Get mouse motion on a secondary screen event type + /*! + Event data is CMotionInfo* and the values are motion deltas not + absolute coordinates. + */ + static CEvent::Type getMotionOnSecondaryEvent(); + //! Get mouse wheel event type. Event data is CWheelInfo*. + static CEvent::Type getWheelEvent(); + //! Get screensaver activated event type + static CEvent::Type getScreensaverActivatedEvent(); + //! Get screensaver deactivated event type + static CEvent::Type getScreensaverDeactivatedEvent(); + //! Get hot key down event type. Event data is CHotKeyInfo*. + static CEvent::Type getHotKeyDownEvent(); + //! Get hot key up event type. Event data is CHotKeyInfo*. + static CEvent::Type getHotKeyUpEvent(); + //! Get start of fake input event type + static CEvent::Type getFakeInputBeginEvent(); + //! Get end of fake input event type + static CEvent::Type getFakeInputEndEvent(); + + //@} + +private: + static CEvent::Type s_buttonDownEvent; + static CEvent::Type s_buttonUpEvent; + static CEvent::Type s_motionPrimaryEvent; + static CEvent::Type s_motionSecondaryEvent; + static CEvent::Type s_wheelEvent; + static CEvent::Type s_ssActivatedEvent; + static CEvent::Type s_ssDeactivatedEvent; + static CEvent::Type s_hotKeyDownEvent; + static CEvent::Type s_hotKeyUpEvent; + static CEvent::Type s_fakeInputBegin; + static CEvent::Type s_fakeInputEnd; +}; + +#endif diff --git a/lib/synergy/IScreen.cpp b/lib/synergy/IScreen.cpp new file mode 100644 index 00000000..3095b9d4 --- /dev/null +++ b/lib/synergy/IScreen.cpp @@ -0,0 +1,60 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 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 "IScreen.h" + +// +// IScreen +// + +CEvent::Type IScreen::s_errorEvent = CEvent::kUnknown; +CEvent::Type IScreen::s_shapeChangedEvent = CEvent::kUnknown; +CEvent::Type IScreen::s_clipboardGrabbedEvent = CEvent::kUnknown; +CEvent::Type IScreen::s_suspendEvent = CEvent::kUnknown; +CEvent::Type IScreen::s_resumeEvent = CEvent::kUnknown; + +CEvent::Type +IScreen::getErrorEvent() +{ + return CEvent::registerTypeOnce(s_errorEvent, + "IScreen::error"); +} + +CEvent::Type +IScreen::getShapeChangedEvent() +{ + return CEvent::registerTypeOnce(s_shapeChangedEvent, + "IScreen::shapeChanged"); +} + +CEvent::Type +IScreen::getClipboardGrabbedEvent() +{ + return CEvent::registerTypeOnce(s_clipboardGrabbedEvent, + "IScreen::clipboardGrabbed"); +} + +CEvent::Type +IScreen::getSuspendEvent() +{ + return CEvent::registerTypeOnce(s_suspendEvent, + "IScreen::suspend"); +} + +CEvent::Type +IScreen::getResumeEvent() +{ + return CEvent::registerTypeOnce(s_resumeEvent, + "IScreen::resume"); +} diff --git a/lib/synergy/IScreen.h b/lib/synergy/IScreen.h new file mode 100644 index 00000000..e1f94d59 --- /dev/null +++ b/lib/synergy/IScreen.h @@ -0,0 +1,112 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISCREEN_H +#define ISCREEN_H + +#include "IInterface.h" +#include "ClipboardTypes.h" +#include "CEvent.h" + +class IClipboard; + +//! Screen interface +/*! +This interface defines the methods common to all screens. +*/ +class IScreen : public IInterface { +public: + struct CClipboardInfo { + public: + ClipboardID m_id; + UInt32 m_sequenceNumber; + }; + + //! @name accessors + //@{ + + //! Get event target + /*! + Returns the target used for events created by this object. + */ + virtual void* getEventTarget() const = 0; + + //! Get clipboard + /*! + Save the contents of the clipboard indicated by \c id and return + true iff successful. + */ + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + + //! Get screen shape + /*! + Return the position of the upper-left corner of the screen in \c x and + \c y and the size of the screen in \c width and \c height. + */ + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + + //! Get cursor position + /*! + Return the current position of the cursor in \c x and \c y. + */ + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + //! Get error event type + /*! + Returns the error event type. This is sent whenever the screen has + failed for some reason (e.g. the X Windows server died). + */ + static CEvent::Type getErrorEvent(); + + //! Get shape changed event type + /*! + Returns the shape changed event type. This is sent whenever the + screen's shape changes. + */ + static CEvent::Type getShapeChangedEvent(); + + //! Get clipboard grabbed event type + /*! + Returns the clipboard grabbed event type. This is sent whenever the + clipboard is grabbed by some other application so we don't own it + anymore. The data is a pointer to a CClipboardInfo. + */ + static CEvent::Type getClipboardGrabbedEvent(); + + //! Get suspend event type + /*! + Returns the suspend event type. This is sent whenever the system goes + to sleep or a user session is deactivated (fast user switching). + */ + static CEvent::Type getSuspendEvent(); + + //! Get resume event type + /*! + Returns the suspend event type. This is sent whenever the system wakes + up or a user session is activated (fast user switching). + */ + static CEvent::Type getResumeEvent(); + + //@} + +private: + static CEvent::Type s_errorEvent; + static CEvent::Type s_shapeChangedEvent; + static CEvent::Type s_clipboardGrabbedEvent; + static CEvent::Type s_suspendEvent; + static CEvent::Type s_resumeEvent; +}; + +#endif diff --git a/lib/synergy/IScreenSaver.h b/lib/synergy/IScreenSaver.h new file mode 100644 index 00000000..9076b309 --- /dev/null +++ b/lib/synergy/IScreenSaver.h @@ -0,0 +1,74 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISCREENSAVER_H +#define ISCREENSAVER_H + +#include "IInterface.h" +#include "CEvent.h" + +//! Screen saver interface +/*! +This interface defines the methods common to all screen savers. +*/ +class IScreenSaver : public IInterface { +public: + // note -- the c'tor/d'tor must *not* enable/disable the screen saver + + //! @name manipulators + //@{ + + //! Enable screen saver + /*! + Enable the screen saver, restoring the screen saver settings to + what they were when disable() was previously called. If disable() + wasn't previously called then it should keep the current settings + or use reasonable defaults. + */ + virtual void enable() = 0; + + //! Disable screen saver + /*! + Disable the screen saver, saving the old settings for the next + call to enable(). + */ + virtual void disable() = 0; + + //! Activate screen saver + /*! + Activate (i.e. show) the screen saver. + */ + virtual void activate() = 0; + + //! Deactivate screen saver + /*! + Deactivate (i.e. hide) the screen saver, reseting the screen saver + timer. + */ + virtual void deactivate() = 0; + + //@} + //! @name accessors + //@{ + + //! Test if screen saver on + /*! + Returns true iff the screen saver is currently active (showing). + */ + virtual bool isActive() const = 0; + + //@} +}; + +#endif diff --git a/lib/synergy/ISecondaryScreen.h b/lib/synergy/ISecondaryScreen.h new file mode 100644 index 00000000..5b3a150d --- /dev/null +++ b/lib/synergy/ISecondaryScreen.h @@ -0,0 +1,58 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISECONDARYSCREEN_H +#define ISECONDARYSCREEN_H + +#include "IInterface.h" +#include "MouseTypes.h" + +//! Secondary screen interface +/*! +This interface defines the methods common to all platform dependent +secondary screen implementations. +*/ +class ISecondaryScreen : public IInterface { +public: + //! @name accessors + //@{ + + //! Fake mouse press/release + /*! + Synthesize a press or release of mouse button \c id. + */ + virtual void fakeMouseButton(ButtonID id, bool press) const = 0; + + //! Fake mouse move + /*! + Synthesize a mouse move to the absolute coordinates \c x,y. + */ + virtual void fakeMouseMove(SInt32 x, SInt32 y) const = 0; + + //! Fake mouse move + /*! + Synthesize a mouse move to the relative coordinates \c dx,dy. + */ + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const = 0; + + //! Fake mouse wheel + /*! + Synthesize a mouse wheel event of amount \c xDelta and \c yDelta. + */ + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const = 0; + + //@} +}; + +#endif diff --git a/lib/synergy/KeyTypes.cpp b/lib/synergy/KeyTypes.cpp new file mode 100644 index 00000000..b483745d --- /dev/null +++ b/lib/synergy/KeyTypes.cpp @@ -0,0 +1,204 @@ +/* + * 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 "KeyTypes.h" + +const KeyNameMapEntry kKeyNameMap[] = { + { "AltGr", kKeyAltGr }, + { "Alt_L", kKeyAlt_L }, + { "Alt_R", kKeyAlt_R }, + { "AppMail", kKeyAppMail }, + { "AppMedia", kKeyAppMedia }, + { "AppUser1", kKeyAppUser1 }, + { "AppUser2", kKeyAppUser2 }, + { "AudioDown", kKeyAudioDown }, + { "AudioMute", kKeyAudioMute }, + { "AudioNext", kKeyAudioNext }, + { "AudioPlay", kKeyAudioPlay }, + { "AudioPrev", kKeyAudioPrev }, + { "AudioStop", kKeyAudioStop }, + { "AudioUp", kKeyAudioUp }, + { "BackSpace", kKeyBackSpace }, + { "Begin", kKeyBegin }, + { "Break", kKeyBreak }, + { "Cancel", kKeyCancel }, + { "CapsLock", kKeyCapsLock }, + { "Clear", kKeyClear }, + { "Control_L", kKeyControl_L }, + { "Control_R", kKeyControl_R }, + { "Delete", kKeyDelete }, + { "Down", kKeyDown }, + { "Eject", kKeyEject }, + { "End", kKeyEnd }, + { "Escape", kKeyEscape }, + { "Execute", kKeyExecute }, + { "F1", kKeyF1 }, + { "F2", kKeyF2 }, + { "F3", kKeyF3 }, + { "F4", kKeyF4 }, + { "F5", kKeyF5 }, + { "F6", kKeyF6 }, + { "F7", kKeyF7 }, + { "F8", kKeyF8 }, + { "F9", kKeyF9 }, + { "F10", kKeyF10 }, + { "F11", kKeyF11 }, + { "F12", kKeyF12 }, + { "F13", kKeyF13 }, + { "F14", kKeyF14 }, + { "F15", kKeyF15 }, + { "F16", kKeyF16 }, + { "F17", kKeyF17 }, + { "F18", kKeyF18 }, + { "F19", kKeyF19 }, + { "F20", kKeyF20 }, + { "F21", kKeyF21 }, + { "F22", kKeyF22 }, + { "F23", kKeyF23 }, + { "F24", kKeyF24 }, + { "F25", kKeyF25 }, + { "F26", kKeyF26 }, + { "F27", kKeyF27 }, + { "F28", kKeyF28 }, + { "F29", kKeyF29 }, + { "F30", kKeyF30 }, + { "F31", kKeyF31 }, + { "F32", kKeyF32 }, + { "F33", kKeyF33 }, + { "F34", kKeyF34 }, + { "F35", kKeyF35 }, + { "Find", kKeyFind }, + { "Help", kKeyHelp }, + { "Henkan", kKeyHenkan }, + { "Home", kKeyHome }, + { "Hyper_L", kKeyHyper_L }, + { "Hyper_R", kKeyHyper_R }, + { "Insert", kKeyInsert }, + { "KP_0", kKeyKP_0 }, + { "KP_1", kKeyKP_1 }, + { "KP_2", kKeyKP_2 }, + { "KP_3", kKeyKP_3 }, + { "KP_4", kKeyKP_4 }, + { "KP_5", kKeyKP_5 }, + { "KP_6", kKeyKP_6 }, + { "KP_7", kKeyKP_7 }, + { "KP_8", kKeyKP_8 }, + { "KP_9", kKeyKP_9 }, + { "KP_Add", kKeyKP_Add }, + { "KP_Begin", kKeyKP_Begin }, + { "KP_Decimal", kKeyKP_Decimal }, + { "KP_Delete", kKeyKP_Delete }, + { "KP_Divide", kKeyKP_Divide }, + { "KP_Down", kKeyKP_Down }, + { "KP_End", kKeyKP_End }, + { "KP_Enter", kKeyKP_Enter }, + { "KP_Equal", kKeyKP_Equal }, + { "KP_F1", kKeyKP_F1 }, + { "KP_F2", kKeyKP_F2 }, + { "KP_F3", kKeyKP_F3 }, + { "KP_F4", kKeyKP_F4 }, + { "KP_Home", kKeyKP_Home }, + { "KP_Insert", kKeyKP_Insert }, + { "KP_Left", kKeyKP_Left }, + { "KP_Multiply", kKeyKP_Multiply }, + { "KP_PageDown", kKeyKP_PageDown }, + { "KP_PageUp", kKeyKP_PageUp }, + { "KP_Right", kKeyKP_Right }, + { "KP_Separator", kKeyKP_Separator }, + { "KP_Space", kKeyKP_Space }, + { "KP_Subtract", kKeyKP_Subtract }, + { "KP_Tab", kKeyKP_Tab }, + { "KP_Up", kKeyKP_Up }, + { "Left", kKeyLeft }, + { "LeftTab", kKeyLeftTab }, + { "Linefeed", kKeyLinefeed }, + { "Menu", kKeyMenu }, + { "Meta_L", kKeyMeta_L }, + { "Meta_R", kKeyMeta_R }, + { "NumLock", kKeyNumLock }, + { "PageDown", kKeyPageDown }, + { "PageUp", kKeyPageUp }, + { "Pause", kKeyPause }, + { "Print", kKeyPrint }, + { "Redo", kKeyRedo }, + { "Return", kKeyReturn }, + { "Right", kKeyRight }, + { "ScrollLock", kKeyScrollLock }, + { "Select", kKeySelect }, + { "ShiftLock", kKeyShiftLock }, + { "Shift_L", kKeyShift_L }, + { "Shift_R", kKeyShift_R }, + { "Sleep", kKeySleep }, + { "Super_L", kKeySuper_L }, + { "Super_R", kKeySuper_R }, + { "SysReq", kKeySysReq }, + { "Tab", kKeyTab }, + { "Undo", kKeyUndo }, + { "Up", kKeyUp }, + { "WWWBack", kKeyWWWBack }, + { "WWWFavorites", kKeyWWWFavorites }, + { "WWWForward", kKeyWWWForward }, + { "WWWHome", kKeyWWWHome }, + { "WWWRefresh", kKeyWWWRefresh }, + { "WWWSearch", kKeyWWWSearch }, + { "WWWStop", kKeyWWWStop }, + { "Zenkaku", kKeyZenkaku }, + { "Space", 0x0020 }, + { "Exclaim", 0x0021 }, + { "DoubleQuote", 0x0022 }, + { "Number", 0x0023 }, + { "Dollar", 0x0024 }, + { "Percent", 0x0025 }, + { "Ampersand", 0x0026 }, + { "Apostrophe", 0x0027 }, + { "ParenthesisL", 0x0028 }, + { "ParenthesisR", 0x0029 }, + { "Asterisk", 0x002a }, + { "Plus", 0x002b }, + { "Comma", 0x002c }, + { "Minus", 0x002d }, + { "Period", 0x002e }, + { "Slash", 0x002f }, + { "Colon", 0x003a }, + { "Semicolon", 0x003b }, + { "Less", 0x003c }, + { "Equal", 0x003d }, + { "Greater", 0x003e }, + { "Question", 0x003f }, + { "At", 0x0040 }, + { "BracketL", 0x005b }, + { "Backslash", 0x005c }, + { "BracketR", 0x005d }, + { "Circumflex", 0x005e }, + { "Underscore", 0x005f }, + { "Grave", 0x0060 }, + { "BraceL", 0x007b }, + { "Bar", 0x007c }, + { "BraceR", 0x007d }, + { "Tilde", 0x007e }, + { NULL, 0 }, +}; + +const KeyModifierNameMapEntry kModifierNameMap[] = { + { "Alt", KeyModifierAlt }, + { "AltGr", KeyModifierAltGr }, +// { "CapsLock", KeyModifierCapsLock }, + { "Control", KeyModifierControl }, + { "Meta", KeyModifierMeta }, +// { "NumLock", KeyModifierNumLock }, +// { "ScrollLock", KeyModifierScrollLock }, + { "Shift", KeyModifierShift }, + { "Super", KeyModifierSuper }, + { NULL, 0 }, +}; diff --git a/lib/synergy/KeyTypes.h b/lib/synergy/KeyTypes.h new file mode 100644 index 00000000..c8b4f6b8 --- /dev/null +++ b/lib/synergy/KeyTypes.h @@ -0,0 +1,306 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef KEYTYPES_H +#define KEYTYPES_H + +#include "BasicTypes.h" + +//! Key ID +/*! +Type to hold a key symbol identifier. The encoding is UTF-32, using +U+E000 through U+EFFF for the various control keys (e.g. arrow +keys, function keys, modifier keys, etc). +*/ +typedef UInt32 KeyID; + +//! Key Code +/*! +Type to hold a physical key identifier. That is, it identifies a +physical key on the keyboard. KeyButton 0 is reserved to be an +invalid key; platforms that use 0 as a physical key identifier +will have to remap that value to some arbitrary unused id. +*/ +typedef UInt16 KeyButton; + +//! Modifier key mask +/*! +Type to hold a bitmask of key modifiers (e.g. shift keys). +*/ +typedef UInt32 KeyModifierMask; + +//! Modifier key ID +/*! +Type to hold the id of a key modifier (e.g. a shift key). +*/ +typedef UInt32 KeyModifierID; + +//! @name Modifier key masks +//@{ +static const KeyModifierMask KeyModifierShift = 0x0001; +static const KeyModifierMask KeyModifierControl = 0x0002; +static const KeyModifierMask KeyModifierAlt = 0x0004; +static const KeyModifierMask KeyModifierMeta = 0x0008; +static const KeyModifierMask KeyModifierSuper = 0x0010; +static const KeyModifierMask KeyModifierAltGr = 0x0020; +static const KeyModifierMask KeyModifierCapsLock = 0x1000; +static const KeyModifierMask KeyModifierNumLock = 0x2000; +static const KeyModifierMask KeyModifierScrollLock = 0x4000; +//@} + +//! @name Modifier key bits +//@{ +static const UInt32 kKeyModifierBitNone = 16; +static const UInt32 kKeyModifierBitShift = 0; +static const UInt32 kKeyModifierBitControl = 1; +static const UInt32 kKeyModifierBitAlt = 2; +static const UInt32 kKeyModifierBitMeta = 3; +static const UInt32 kKeyModifierBitSuper = 4; +static const UInt32 kKeyModifierBitAltGr = 5; +static const UInt32 kKeyModifierBitCapsLock = 12; +static const UInt32 kKeyModifierBitNumLock = 13; +static const UInt32 kKeyModifierBitScrollLock = 14; +static const SInt32 kKeyModifierNumBits = 16; +//@} + +//! @name Modifier key identifiers +//@{ +static const KeyModifierID kKeyModifierIDNull = 0; +static const KeyModifierID kKeyModifierIDShift = 1; +static const KeyModifierID kKeyModifierIDControl = 2; +static const KeyModifierID kKeyModifierIDAlt = 3; +static const KeyModifierID kKeyModifierIDMeta = 4; +static const KeyModifierID kKeyModifierIDSuper = 5; +static const KeyModifierID kKeyModifierIDLast = 6; +//@} + +//! @name Key identifiers +//@{ +// all identifiers except kKeyNone and those in 0xE000 to 0xE0FF +// inclusive are equal to the corresponding X11 keysym - 0x1000. + +// no key +static const KeyID kKeyNone = 0x0000; + +// TTY functions +static const KeyID kKeyBackSpace = 0xEF08; /* back space, back char */ +static const KeyID kKeyTab = 0xEF09; +static const KeyID kKeyLinefeed = 0xEF0A; /* Linefeed, LF */ +static const KeyID kKeyClear = 0xEF0B; +static const KeyID kKeyReturn = 0xEF0D; /* Return, enter */ +static const KeyID kKeyPause = 0xEF13; /* Pause, hold */ +static const KeyID kKeyScrollLock = 0xEF14; +static const KeyID kKeySysReq = 0xEF15; +static const KeyID kKeyEscape = 0xEF1B; +static const KeyID kKeyHenkan = 0xEF23; /* Start/Stop Conversion */ +static const KeyID kKeyHangulKana = 0xEF26; /* Hangul, Kana */ +static const KeyID kKeyHiraganaKatakana = 0xEF27; /* Hiragana/Katakana toggle */ +static const KeyID kKeyZenkaku = 0xEF2A; /* Zenkaku/Hankaku */ +static const KeyID kKeyHanjaKanzi = 0xEF2A; /* Hanja, Kanzi */ +static const KeyID kKeyDelete = 0xEFFF; /* Delete, rubout */ + +// cursor control +static const KeyID kKeyHome = 0xEF50; +static const KeyID kKeyLeft = 0xEF51; /* Move left, left arrow */ +static const KeyID kKeyUp = 0xEF52; /* Move up, up arrow */ +static const KeyID kKeyRight = 0xEF53; /* Move right, right arrow */ +static const KeyID kKeyDown = 0xEF54; /* Move down, down arrow */ +static const KeyID kKeyPageUp = 0xEF55; +static const KeyID kKeyPageDown = 0xEF56; +static const KeyID kKeyEnd = 0xEF57; /* EOL */ +static const KeyID kKeyBegin = 0xEF58; /* BOL */ + +// misc functions +static const KeyID kKeySelect = 0xEF60; /* Select, mark */ +static const KeyID kKeyPrint = 0xEF61; +static const KeyID kKeyExecute = 0xEF62; /* Execute, run, do */ +static const KeyID kKeyInsert = 0xEF63; /* Insert, insert here */ +static const KeyID kKeyUndo = 0xEF65; /* Undo, oops */ +static const KeyID kKeyRedo = 0xEF66; /* redo, again */ +static const KeyID kKeyMenu = 0xEF67; +static const KeyID kKeyFind = 0xEF68; /* Find, search */ +static const KeyID kKeyCancel = 0xEF69; /* Cancel, stop, abort, exit */ +static const KeyID kKeyHelp = 0xEF6A; /* Help */ +static const KeyID kKeyBreak = 0xEF6B; +static const KeyID kKeyAltGr = 0xEF7E; /* Character set switch */ +static const KeyID kKeyNumLock = 0xEF7F; + +// keypad +static const KeyID kKeyKP_Space = 0xEF80; /* space */ +static const KeyID kKeyKP_Tab = 0xEF89; +static const KeyID kKeyKP_Enter = 0xEF8D; /* enter */ +static const KeyID kKeyKP_F1 = 0xEF91; /* PF1, KP_A, ... */ +static const KeyID kKeyKP_F2 = 0xEF92; +static const KeyID kKeyKP_F3 = 0xEF93; +static const KeyID kKeyKP_F4 = 0xEF94; +static const KeyID kKeyKP_Home = 0xEF95; +static const KeyID kKeyKP_Left = 0xEF96; +static const KeyID kKeyKP_Up = 0xEF97; +static const KeyID kKeyKP_Right = 0xEF98; +static const KeyID kKeyKP_Down = 0xEF99; +static const KeyID kKeyKP_PageUp = 0xEF9A; +static const KeyID kKeyKP_PageDown = 0xEF9B; +static const KeyID kKeyKP_End = 0xEF9C; +static const KeyID kKeyKP_Begin = 0xEF9D; +static const KeyID kKeyKP_Insert = 0xEF9E; +static const KeyID kKeyKP_Delete = 0xEF9F; +static const KeyID kKeyKP_Equal = 0xEFBD; /* equals */ +static const KeyID kKeyKP_Multiply = 0xEFAA; +static const KeyID kKeyKP_Add = 0xEFAB; +static const KeyID kKeyKP_Separator= 0xEFAC; /* separator, often comma */ +static const KeyID kKeyKP_Subtract = 0xEFAD; +static const KeyID kKeyKP_Decimal = 0xEFAE; +static const KeyID kKeyKP_Divide = 0xEFAF; +static const KeyID kKeyKP_0 = 0xEFB0; +static const KeyID kKeyKP_1 = 0xEFB1; +static const KeyID kKeyKP_2 = 0xEFB2; +static const KeyID kKeyKP_3 = 0xEFB3; +static const KeyID kKeyKP_4 = 0xEFB4; +static const KeyID kKeyKP_5 = 0xEFB5; +static const KeyID kKeyKP_6 = 0xEFB6; +static const KeyID kKeyKP_7 = 0xEFB7; +static const KeyID kKeyKP_8 = 0xEFB8; +static const KeyID kKeyKP_9 = 0xEFB9; + +// function keys +static const KeyID kKeyF1 = 0xEFBE; +static const KeyID kKeyF2 = 0xEFBF; +static const KeyID kKeyF3 = 0xEFC0; +static const KeyID kKeyF4 = 0xEFC1; +static const KeyID kKeyF5 = 0xEFC2; +static const KeyID kKeyF6 = 0xEFC3; +static const KeyID kKeyF7 = 0xEFC4; +static const KeyID kKeyF8 = 0xEFC5; +static const KeyID kKeyF9 = 0xEFC6; +static const KeyID kKeyF10 = 0xEFC7; +static const KeyID kKeyF11 = 0xEFC8; +static const KeyID kKeyF12 = 0xEFC9; +static const KeyID kKeyF13 = 0xEFCA; +static const KeyID kKeyF14 = 0xEFCB; +static const KeyID kKeyF15 = 0xEFCC; +static const KeyID kKeyF16 = 0xEFCD; +static const KeyID kKeyF17 = 0xEFCE; +static const KeyID kKeyF18 = 0xEFCF; +static const KeyID kKeyF19 = 0xEFD0; +static const KeyID kKeyF20 = 0xEFD1; +static const KeyID kKeyF21 = 0xEFD2; +static const KeyID kKeyF22 = 0xEFD3; +static const KeyID kKeyF23 = 0xEFD4; +static const KeyID kKeyF24 = 0xEFD5; +static const KeyID kKeyF25 = 0xEFD6; +static const KeyID kKeyF26 = 0xEFD7; +static const KeyID kKeyF27 = 0xEFD8; +static const KeyID kKeyF28 = 0xEFD9; +static const KeyID kKeyF29 = 0xEFDA; +static const KeyID kKeyF30 = 0xEFDB; +static const KeyID kKeyF31 = 0xEFDC; +static const KeyID kKeyF32 = 0xEFDD; +static const KeyID kKeyF33 = 0xEFDE; +static const KeyID kKeyF34 = 0xEFDF; +static const KeyID kKeyF35 = 0xEFE0; + +// modifiers +static const KeyID kKeyShift_L = 0xEFE1; /* Left shift */ +static const KeyID kKeyShift_R = 0xEFE2; /* Right shift */ +static const KeyID kKeyControl_L = 0xEFE3; /* Left control */ +static const KeyID kKeyControl_R = 0xEFE4; /* Right control */ +static const KeyID kKeyCapsLock = 0xEFE5; /* Caps lock */ +static const KeyID kKeyShiftLock = 0xEFE6; /* Shift lock */ +static const KeyID kKeyMeta_L = 0xEFE7; /* Left meta */ +static const KeyID kKeyMeta_R = 0xEFE8; /* Right meta */ +static const KeyID kKeyAlt_L = 0xEFE9; /* Left alt */ +static const KeyID kKeyAlt_R = 0xEFEA; /* Right alt */ +static const KeyID kKeySuper_L = 0xEFEB; /* Left super */ +static const KeyID kKeySuper_R = 0xEFEC; /* Right super */ +static const KeyID kKeyHyper_L = 0xEFED; /* Left hyper */ +static const KeyID kKeyHyper_R = 0xEFEE; /* Right hyper */ + +// multi-key character composition +static const KeyID kKeyCompose = 0xEF20; +static const KeyID kKeyDeadGrave = 0x0300; +static const KeyID kKeyDeadAcute = 0x0301; +static const KeyID kKeyDeadCircumflex = 0x0302; +static const KeyID kKeyDeadTilde = 0x0303; +static const KeyID kKeyDeadMacron = 0x0304; +static const KeyID kKeyDeadBreve = 0x0306; +static const KeyID kKeyDeadAbovedot = 0x0307; +static const KeyID kKeyDeadDiaeresis = 0x0308; +static const KeyID kKeyDeadAbovering = 0x030a; +static const KeyID kKeyDeadDoubleacute = 0x030b; +static const KeyID kKeyDeadCaron = 0x030c; +static const KeyID kKeyDeadCedilla = 0x0327; +static const KeyID kKeyDeadOgonek = 0x0328; + +// more function and modifier keys +static const KeyID kKeyLeftTab = 0xEE20; + +// update modifiers +static const KeyID kKeySetModifiers = 0xEE06; +static const KeyID kKeyClearModifiers = 0xEE07; + +// group change +static const KeyID kKeyNextGroup = 0xEE08; +static const KeyID kKeyPrevGroup = 0xEE0A; + +// extended keys +static const KeyID kKeyEject = 0xE001; +static const KeyID kKeySleep = 0xE05F; +static const KeyID kKeyWWWBack = 0xE0A6; +static const KeyID kKeyWWWForward = 0xE0A7; +static const KeyID kKeyWWWRefresh = 0xE0A8; +static const KeyID kKeyWWWStop = 0xE0A9; +static const KeyID kKeyWWWSearch = 0xE0AA; +static const KeyID kKeyWWWFavorites = 0xE0AB; +static const KeyID kKeyWWWHome = 0xE0AC; +static const KeyID kKeyAudioMute = 0xE0AD; +static const KeyID kKeyAudioDown = 0xE0AE; +static const KeyID kKeyAudioUp = 0xE0AF; +static const KeyID kKeyAudioNext = 0xE0B0; +static const KeyID kKeyAudioPrev = 0xE0B1; +static const KeyID kKeyAudioStop = 0xE0B2; +static const KeyID kKeyAudioPlay = 0xE0B3; +static const KeyID kKeyAppMail = 0xE0B4; +static const KeyID kKeyAppMedia = 0xE0B5; +static const KeyID kKeyAppUser1 = 0xE0B6; +static const KeyID kKeyAppUser2 = 0xE0B7; + +//@} + +struct KeyNameMapEntry { +public: + const char* m_name; + KeyID m_id; +}; +struct KeyModifierNameMapEntry { +public: + const char* m_name; + KeyModifierMask m_mask; +}; + +//! Key name to KeyID table +/*! +A table of key names to the corresponding KeyID. Only the keys listed +above plus non-alphanumeric ASCII characters are in the table. The end +of the table is the first pair with a NULL m_name. +*/ +extern const KeyNameMapEntry kKeyNameMap[]; + +//! Modifier key name to KeyModifierMask table +/*! +A table of modifier key names to the corresponding KeyModifierMask. +The end of the table is the first pair with a NULL m_name. +*/ +extern const KeyModifierNameMapEntry kModifierNameMap[]; + +#endif diff --git a/lib/synergy/Makefile.am b/lib/synergy/Makefile.am new file mode 100644 index 00000000..480102a4 --- /dev/null +++ b/lib/synergy/Makefile.am @@ -0,0 +1,71 @@ +# 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. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libsynergy.a +libsynergy_a_SOURCES = \ + CClipboard.cpp \ + CKeyMap.cpp \ + CKeyState.cpp \ + CPacketStreamFilter.cpp \ + CPlatformScreen.cpp \ + CProtocolUtil.cpp \ + CScreen.cpp \ + IClipboard.cpp \ + IKeyState.cpp \ + IPrimaryScreen.cpp \ + IScreen.cpp \ + KeyTypes.cpp \ + ProtocolTypes.cpp \ + XScreen.cpp \ + XSynergy.cpp \ + CClipboard.h \ + CKeyMap.h \ + CKeyState.h \ + CPacketStreamFilter.h \ + CPlatformScreen.h \ + CProtocolUtil.h \ + CScreen.h \ + ClipboardTypes.h \ + IClient.h \ + IClipboard.h \ + IKeyState.h \ + IPlatformScreen.h \ + IPrimaryScreen.h \ + IScreen.h \ + IScreenSaver.h \ + ISecondaryScreen.h \ + KeyTypes.h \ + MouseTypes.h \ + OptionTypes.h \ + ProtocolTypes.h \ + XScreen.h \ + XSynergy.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + $(NULL) diff --git a/lib/synergy/Makefile.win b/lib/synergy/Makefile.win new file mode 100644 index 00000000..e6d56d42 --- /dev/null +++ b/lib/synergy/Makefile.win @@ -0,0 +1,87 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 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. + +LIB_SYNERGY_SRC = lib\synergy +LIB_SYNERGY_DST = $(BUILD_DST)\$(LIB_SYNERGY_SRC) +LIB_SYNERGY_LIB = "$(LIB_SYNERGY_DST)\libsynergy.lib" +LIB_SYNERGY_CPP = \ + "CClipboard.cpp" \ + "CKeyMap.cpp" \ + "CKeyState.cpp" \ + "CPacketStreamFilter.cpp" \ + "CPlatformScreen.cpp" \ + "CProtocolUtil.cpp" \ + "CScreen.cpp" \ + "IClipboard.cpp" \ + "IKeyState.cpp" \ + "IPrimaryScreen.cpp" \ + "IScreen.cpp" \ + "KeyTypes.cpp" \ + "ProtocolTypes.cpp" \ + "XScreen.cpp" \ + "XSynergy.cpp" \ + $(NULL) +LIB_SYNERGY_OBJ = \ + "$(LIB_SYNERGY_DST)\CClipboard.obj" \ + "$(LIB_SYNERGY_DST)\CKeyMap.obj" \ + "$(LIB_SYNERGY_DST)\CKeyState.obj" \ + "$(LIB_SYNERGY_DST)\CPacketStreamFilter.obj" \ + "$(LIB_SYNERGY_DST)\CPlatformScreen.obj" \ + "$(LIB_SYNERGY_DST)\CProtocolUtil.obj" \ + "$(LIB_SYNERGY_DST)\CScreen.obj" \ + "$(LIB_SYNERGY_DST)\IClipboard.obj" \ + "$(LIB_SYNERGY_DST)\IKeyState.obj" \ + "$(LIB_SYNERGY_DST)\IPrimaryScreen.obj" \ + "$(LIB_SYNERGY_DST)\IScreen.obj" \ + "$(LIB_SYNERGY_DST)\KeyTypes.obj" \ + "$(LIB_SYNERGY_DST)\ProtocolTypes.obj" \ + "$(LIB_SYNERGY_DST)\XScreen.obj" \ + "$(LIB_SYNERGY_DST)\XSynergy.obj" \ + $(NULL) +LIB_SYNERGY_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_SYNERGY_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_SYNERGY_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_SYNERGY_LIB) + +# Dependency rules +$(LIB_SYNERGY_OBJ): $(AUTODEP) +!if EXIST($(LIB_SYNERGY_DST)\deps.mak) +!include $(LIB_SYNERGY_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_SYNERGY_SRC)\}.cpp{$(LIB_SYNERGY_DST)\}.obj:: +!else +{$(LIB_SYNERGY_SRC)\}.cpp{$(LIB_SYNERGY_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_SYNERGY_SRC) + -@$(MKDIR) $(LIB_SYNERGY_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_SYNERGY_INC) \ + /Fo$(LIB_SYNERGY_DST)\ \ + /Fd$(LIB_SYNERGY_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_SYNERGY_SRC) $(LIB_SYNERGY_DST) +$(LIB_SYNERGY_LIB): $(LIB_SYNERGY_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_SYNERGY_SRC) $(LIB_SYNERGY_DST) $(**:.obj=.d) diff --git a/lib/synergy/MouseTypes.h b/lib/synergy/MouseTypes.h new file mode 100644 index 00000000..e4988553 --- /dev/null +++ b/lib/synergy/MouseTypes.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MOUSETYPES_H +#define MOUSETYPES_H + +#include "BasicTypes.h" + +//! Mouse button ID +/*! +Type to hold a mouse button identifier. +*/ +typedef UInt8 ButtonID; + +//! @name Mouse button identifiers +//@{ +static const ButtonID kButtonNone = 0; +static const ButtonID kButtonLeft = 1; +static const ButtonID kButtonMiddle = 2; +static const ButtonID kButtonRight = 3; +static const ButtonID kButtonExtra0 = 4; +//@} + +#endif diff --git a/lib/synergy/OptionTypes.h b/lib/synergy/OptionTypes.h new file mode 100644 index 00000000..74beda00 --- /dev/null +++ b/lib/synergy/OptionTypes.h @@ -0,0 +1,92 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef OPTIONTYPES_H +#define OPTIONTYPES_H + +#include "BasicTypes.h" +#include "stdvector.h" + +//! Option ID +/*! +Type to hold an option identifier. +*/ +typedef UInt32 OptionID; + +//! Option Value +/*! +Type to hold an option value. +*/ +typedef SInt32 OptionValue; + +// for now, options are just pairs of integers +typedef std::vector COptionsList; + +// macro for packing 4 character strings into 4 byte integers +#define OPTION_CODE(_s) \ + (static_cast(static_cast(_s[0]) << 24) | \ + static_cast(static_cast(_s[1]) << 16) | \ + static_cast(static_cast(_s[2]) << 8) | \ + static_cast(static_cast(_s[3]) )) + +//! @name Option identifiers +//@{ +static const OptionID kOptionHalfDuplexCapsLock = OPTION_CODE("HDCL"); +static const OptionID kOptionHalfDuplexNumLock = OPTION_CODE("HDNL"); +static const OptionID kOptionHalfDuplexScrollLock = OPTION_CODE("HDSL"); +static const OptionID kOptionModifierMapForShift = OPTION_CODE("MMFS"); +static const OptionID kOptionModifierMapForControl = OPTION_CODE("MMFC"); +static const OptionID kOptionModifierMapForAlt = OPTION_CODE("MMFA"); +static const OptionID kOptionModifierMapForMeta = OPTION_CODE("MMFM"); +static const OptionID kOptionModifierMapForSuper = OPTION_CODE("MMFR"); +static const OptionID kOptionHeartbeat = OPTION_CODE("HART"); +static const OptionID kOptionScreenSwitchCorners = OPTION_CODE("SSCM"); +static const OptionID kOptionScreenSwitchCornerSize = OPTION_CODE("SSCS"); +static const OptionID kOptionScreenSwitchDelay = OPTION_CODE("SSWT"); +static const OptionID kOptionScreenSwitchTwoTap = OPTION_CODE("SSTT"); +static const OptionID kOptionScreenSaverSync = OPTION_CODE("SSVR"); +static const OptionID kOptionXTestXineramaUnaware = OPTION_CODE("XTXU"); +static const OptionID kOptionRelativeMouseMoves = OPTION_CODE("MDLT"); +static const OptionID kOptionWin32KeepForeground = OPTION_CODE("_KFW"); +//@} + +//! @name Screen switch corner enumeration +//@{ +enum EScreenSwitchCorners { + kNoCorner, + kTopLeft, + kTopRight, + kBottomLeft, + kBottomRight, + kFirstCorner = kTopLeft, + kLastCorner = kBottomRight +}; +//@} + +//! @name Screen switch corner masks +//@{ +enum EScreenSwitchCornerMasks { + kNoCornerMask = 0, + kTopLeftMask = 1 << (kTopLeft - kFirstCorner), + kTopRightMask = 1 << (kTopRight - kFirstCorner), + kBottomLeftMask = 1 << (kBottomLeft - kFirstCorner), + kBottomRightMask = 1 << (kBottomRight - kFirstCorner), + kAllCornersMask = kTopLeftMask | kTopRightMask | + kBottomLeftMask | kBottomRightMask +}; +//@} + +#undef OPTION_CODE + +#endif diff --git a/lib/synergy/ProtocolTypes.cpp b/lib/synergy/ProtocolTypes.cpp new file mode 100644 index 00000000..441b2f33 --- /dev/null +++ b/lib/synergy/ProtocolTypes.cpp @@ -0,0 +1,47 @@ +/* + * 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 "ProtocolTypes.h" + +const char* kMsgHello = "Synergy%2i%2i"; +const char* kMsgHelloBack = "Synergy%2i%2i%s"; +const char* kMsgCNoop = "CNOP"; +const char* kMsgCClose = "CBYE"; +const char* kMsgCEnter = "CINN%2i%2i%4i%2i"; +const char* kMsgCLeave = "COUT"; +const char* kMsgCClipboard = "CCLP%1i%4i"; +const char* kMsgCScreenSaver = "CSEC%1i"; +const char* kMsgCResetOptions = "CROP"; +const char* kMsgCInfoAck = "CIAK"; +const char* kMsgCKeepAlive = "CALV"; +const char* kMsgDKeyDown = "DKDN%2i%2i%2i"; +const char* kMsgDKeyDown1_0 = "DKDN%2i%2i"; +const char* kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i"; +const char* kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i"; +const char* kMsgDKeyUp = "DKUP%2i%2i%2i"; +const char* kMsgDKeyUp1_0 = "DKUP%2i%2i"; +const char* kMsgDMouseDown = "DMDN%1i"; +const char* kMsgDMouseUp = "DMUP%1i"; +const char* kMsgDMouseMove = "DMMV%2i%2i"; +const char* kMsgDMouseRelMove = "DMRM%2i%2i"; +const char* kMsgDMouseWheel = "DMWM%2i%2i"; +const char* kMsgDMouseWheel1_0 = "DMWM%2i"; +const char* kMsgDClipboard = "DCLP%1i%4i%s"; +const char* kMsgDInfo = "DINF%2i%2i%2i%2i%2i%2i%2i"; +const char* kMsgDSetOptions = "DSOP%4I"; +const char* kMsgQInfo = "QINF"; +const char* kMsgEIncompatible = "EICV%2i%2i"; +const char* kMsgEBusy = "EBSY"; +const char* kMsgEUnknown = "EUNK"; +const char* kMsgEBad = "EBAD"; diff --git a/lib/synergy/ProtocolTypes.h b/lib/synergy/ProtocolTypes.h new file mode 100644 index 00000000..c4d3c99d --- /dev/null +++ b/lib/synergy/ProtocolTypes.h @@ -0,0 +1,308 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PROTOCOLTYPES_H +#define PROTOCOLTYPES_H + +#include "BasicTypes.h" + +// protocol version number +// 1.0: initial protocol +// 1.1: adds KeyCode to key press, release, and repeat +// 1.2: adds mouse relative motion +// 1.3: adds keep alive and deprecates heartbeats, +// adds horizontal mouse scrolling +static const SInt16 kProtocolMajorVersion = 1; +static const SInt16 kProtocolMinorVersion = 3; + +// default contact port number +static const UInt16 kDefaultPort = 24800; + +// maximum total length for greeting returned by client +static const UInt32 kMaxHelloLength = 1024; + +// time between kMsgCKeepAlive (in seconds). a non-positive value disables +// keep alives. this is the default rate that can be overridden using an +// option. +static const double kKeepAliveRate = 3.0; + +// number of skipped kMsgCKeepAlive messages that indicates a problem +static const double kKeepAlivesUntilDeath = 3.0; + +// obsolete heartbeat stuff +static const double kHeartRate = -1.0; +static const double kHeartBeatsUntilDeath = 3.0; + +// direction constants +enum EDirection { + kNoDirection, + kLeft, + kRight, + kTop, + kBottom, + kFirstDirection = kLeft, + kLastDirection = kBottom, + kNumDirections = kLastDirection - kFirstDirection + 1 +}; +enum EDirectionMask { + kNoDirMask = 0, + kLeftMask = 1 << kLeft, + kRightMask = 1 << kRight, + kTopMask = 1 << kTop, + kBottomMask = 1 << kBottom +}; + + +// +// message codes (trailing NUL is not part of code). in comments, $n +// refers to the n'th argument (counting from one). message codes are +// always 4 bytes optionally followed by message specific parameters +// except those for the greeting handshake. +// + +// +// positions and sizes are signed 16 bit integers. +// + +// +// greeting handshake messages +// + +// say hello to client; primary -> secondary +// $1 = protocol major version number supported by server. $2 = +// protocol minor version number supported by server. +extern const char* kMsgHello; + +// respond to hello from server; secondary -> primary +// $1 = protocol major version number supported by client. $2 = +// protocol minor version number supported by client. $3 = client +// name. +extern const char* kMsgHelloBack; + + +// +// command codes +// + +// no operation; secondary -> primary +extern const char* kMsgCNoop; + +// close connection; primary -> secondary +extern const char* kMsgCClose; + +// enter screen: primary -> secondary +// entering screen at screen position $1 = x, $2 = y. x,y are +// absolute screen coordinates. $3 = sequence number, which is +// used to order messages between screens. the secondary screen +// must return this number with some messages. $4 = modifier key +// mask. this will have bits set for each toggle modifier key +// that is activated on entry to the screen. the secondary screen +// should adjust its toggle modifiers to reflect that state. +extern const char* kMsgCEnter; + +// leave screen: primary -> secondary +// leaving screen. the secondary screen should send clipboard +// data in response to this message for those clipboards that +// it has grabbed (i.e. has sent a kMsgCClipboard for and has +// not received a kMsgCClipboard for with a greater sequence +// number) and that were grabbed or have changed since the +// last leave. +extern const char* kMsgCLeave; + +// grab clipboard: primary <-> secondary +// sent by screen when some other app on that screen grabs a +// clipboard. $1 = the clipboard identifier, $2 = sequence number. +// secondary screens must use the sequence number passed in the +// most recent kMsgCEnter. the primary always sends 0. +extern const char* kMsgCClipboard; + +// screensaver change: primary -> secondary +// screensaver on primary has started ($1 == 1) or closed ($1 == 0) +extern const char* kMsgCScreenSaver; + +// reset options: primary -> secondary +// client should reset all of its options to their defaults. +extern const char* kMsgCResetOptions; + +// resolution change acknowledgment: primary -> secondary +// sent by primary in response to a secondary screen's kMsgDInfo. +// this is sent for every kMsgDInfo, whether or not the primary +// had sent a kMsgQInfo. +extern const char* kMsgCInfoAck; + +// keep connection alive: primary <-> secondary +// sent by the server periodically to verify that connections are still +// up and running. clients must reply in kind on receipt. if the server +// gets an error sending the message or does not receive a reply within +// a reasonable time then the server disconnects the client. if the +// client doesn't receive these (or any message) periodically then it +// should disconnect from the server. the appropriate interval is +// defined by an option. +extern const char* kMsgCKeepAlive; + + +// +// data codes +// + +// key pressed: primary -> secondary +// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton +// the KeyButton identifies the physical key on the primary used to +// generate this key. the secondary should note the KeyButton along +// with the physical key it uses to generate the key press. on +// release, the secondary can then use the primary's KeyButton to +// find its corresponding physical key and release it. this is +// necessary because the KeyID on release may not be the KeyID of +// the press. this can happen with combining (dead) keys or if +// the keyboard layouts are not identical and the user releases +// a modifier key before releasing the modified key. +extern const char* kMsgDKeyDown; + +// key pressed 1.0: same as above but without KeyButton +extern const char* kMsgDKeyDown1_0; + +// key auto-repeat: primary -> secondary +// $1 = KeyID, $2 = KeyModifierMask, $3 = number of repeats, $4 = KeyButton +extern const char* kMsgDKeyRepeat; + +// key auto-repeat 1.0: same as above but without KeyButton +extern const char* kMsgDKeyRepeat1_0; + +// key released: primary -> secondary +// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton +extern const char* kMsgDKeyUp; + +// key released 1.0: same as above but without KeyButton +extern const char* kMsgDKeyUp1_0; + +// mouse button pressed: primary -> secondary +// $1 = ButtonID +extern const char* kMsgDMouseDown; + +// mouse button released: primary -> secondary +// $1 = ButtonID +extern const char* kMsgDMouseUp; + +// mouse moved: primary -> secondary +// $1 = x, $2 = y. x,y are absolute screen coordinates. +extern const char* kMsgDMouseMove; + +// relative mouse move: primary -> secondary +// $1 = dx, $2 = dy. dx,dy are motion deltas. +extern const char* kMsgDMouseRelMove; + +// mouse scroll: primary -> secondary +// $1 = xDelta, $2 = yDelta. the delta should be +120 for one tick forward +// (away from the user) or right and -120 for one tick backward (toward +// the user) or left. +extern const char* kMsgDMouseWheel; + +// mouse vertical scroll: primary -> secondary +// like as kMsgDMouseWheel except only sends $1 = yDelta. +extern const char* kMsgDMouseWheel1_0; + +// clipboard data: primary <-> secondary +// $2 = sequence number, $3 = clipboard data. the sequence number +// is 0 when sent by the primary. secondary screens should use the +// sequence number from the most recent kMsgCEnter. $1 = clipboard +// identifier. +extern const char* kMsgDClipboard; + +// client data: secondary -> primary +// $1 = coordinate of leftmost pixel on secondary screen, +// $2 = coordinate of topmost pixel on secondary screen, +// $3 = width of secondary screen in pixels, +// $4 = height of secondary screen in pixels, +// $5 = size of warp zone, (obsolete) +// $6, $7 = the x,y position of the mouse on the secondary screen. +// +// the secondary screen must send this message in response to the +// kMsgQInfo message. it must also send this message when the +// screen's resolution changes. in this case, the secondary screen +// should ignore any kMsgDMouseMove messages until it receives a +// kMsgCInfoAck in order to prevent attempts to move the mouse off +// the new screen area. +extern const char* kMsgDInfo; + +// set options: primary -> secondary +// client should set the given option/value pairs. $1 = option/value +// pairs. +extern const char* kMsgDSetOptions; + + +// +// query codes +// + +// query screen info: primary -> secondary +// client should reply with a kMsgDInfo. +extern const char* kMsgQInfo; + + +// +// error codes +// + +// incompatible versions: primary -> secondary +// $1 = major version of primary, $2 = minor version of primary. +extern const char* kMsgEIncompatible; + +// name provided when connecting is already in use: primary -> secondary +extern const char* kMsgEBusy; + +// unknown client: primary -> secondary +// name provided when connecting is not in primary's screen +// configuration map. +extern const char* kMsgEUnknown; + +// protocol violation: primary -> secondary +// primary should disconnect after sending this message. +extern const char* kMsgEBad; + + +// +// structures +// + +//! Screen information +/*! +This class contains information about a screen. +*/ +class CClientInfo { +public: + //! Screen position + /*! + The position of the upper-left corner of the screen. This is + typically 0,0. + */ + SInt32 m_x, m_y; + + //! Screen size + /*! + The size of the screen in pixels. + */ + SInt32 m_w, m_h; + + //! Obsolete (jump zone size) + SInt32 obsolete1; + + //! Mouse position + /*! + The current location of the mouse cursor. + */ + SInt32 m_mx, m_my; +}; + +#endif + diff --git a/lib/synergy/XScreen.cpp b/lib/synergy/XScreen.cpp new file mode 100644 index 00000000..8a694bfa --- /dev/null +++ b/lib/synergy/XScreen.cpp @@ -0,0 +1,53 @@ +/* + * 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 "XScreen.h" + +// +// XScreenOpenFailure +// + +CString +XScreenOpenFailure::getWhat() const throw() +{ + return format("XScreenOpenFailure", "unable to open screen"); +} + + +// +// XScreenUnavailable +// + +XScreenUnavailable::XScreenUnavailable(double timeUntilRetry) : + m_timeUntilRetry(timeUntilRetry) +{ + // do nothing +} + +XScreenUnavailable::~XScreenUnavailable() +{ + // do nothing +} + +double +XScreenUnavailable::getRetryTime() const +{ + return m_timeUntilRetry; +} + +CString +XScreenUnavailable::getWhat() const throw() +{ + return format("XScreenUnavailable", "unable to open screen"); +} diff --git a/lib/synergy/XScreen.h b/lib/synergy/XScreen.h new file mode 100644 index 00000000..9966e6cf --- /dev/null +++ b/lib/synergy/XScreen.h @@ -0,0 +1,61 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XSCREEN_H +#define XSCREEN_H + +#include "XBase.h" + +//! Generic screen exception +XBASE_SUBCLASS(XScreen, XBase); + +//! Cannot open screen exception +/*! +Thrown when a screen cannot be opened or initialized. +*/ +XBASE_SUBCLASS_WHAT(XScreenOpenFailure, XScreen); + +//! Screen unavailable exception +/*! +Thrown when a screen cannot be opened or initialized but retrying later +may be successful. +*/ +class XScreenUnavailable : public XScreenOpenFailure { +public: + /*! + \c timeUntilRetry is the suggested time the caller should wait until + trying to open the screen again. + */ + XScreenUnavailable(double timeUntilRetry); + virtual ~XScreenUnavailable(); + + //! @name manipulators + //@{ + + //! Get retry time + /*! + Returns the suggested time to wait until retrying to open the screen. + */ + double getRetryTime() const; + + //@} + +protected: + virtual CString getWhat() const throw(); + +private: + double m_timeUntilRetry; +}; + +#endif diff --git a/lib/synergy/XSynergy.cpp b/lib/synergy/XSynergy.cpp new file mode 100644 index 00000000..1e19945f --- /dev/null +++ b/lib/synergy/XSynergy.cpp @@ -0,0 +1,104 @@ +/* + * 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 "XSynergy.h" +#include "CStringUtil.h" + +// +// XBadClient +// + +CString +XBadClient::getWhat() const throw() +{ + return "XBadClient"; +} + + +// +// XIncompatibleClient +// + +XIncompatibleClient::XIncompatibleClient(int major, int minor) : + m_major(major), + m_minor(minor) +{ + // do nothing +} + +int +XIncompatibleClient::getMajor() const throw() +{ + return m_major; +} + +int +XIncompatibleClient::getMinor() const throw() +{ + return m_minor; +} + +CString +XIncompatibleClient::getWhat() const throw() +{ + return format("XIncompatibleClient", "incompatible client %{1}.%{2}", + CStringUtil::print("%d", m_major).c_str(), + CStringUtil::print("%d", m_minor).c_str()); +} + + +// +// XDuplicateClient +// + +XDuplicateClient::XDuplicateClient(const CString& name) : + m_name(name) +{ + // do nothing +} + +const CString& +XDuplicateClient::getName() const throw() +{ + return m_name; +} + +CString +XDuplicateClient::getWhat() const throw() +{ + return format("XDuplicateClient", "duplicate client %{1}", m_name.c_str()); +} + + +// +// XUnknownClient +// + +XUnknownClient::XUnknownClient(const CString& name) : + m_name(name) +{ + // do nothing +} + +const CString& +XUnknownClient::getName() const throw() +{ + return m_name; +} + +CString +XUnknownClient::getWhat() const throw() +{ + return format("XUnknownClient", "unknown client %{1}", m_name.c_str()); +} diff --git a/lib/synergy/XSynergy.h b/lib/synergy/XSynergy.h new file mode 100644 index 00000000..23131194 --- /dev/null +++ b/lib/synergy/XSynergy.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. + */ + +#ifndef XSYNERGY_H +#define XSYNERGY_H + +#include "XBase.h" + +//! Generic synergy exception +XBASE_SUBCLASS(XSynergy, XBase); + +//! Client error exception +/*! +Thrown when the client fails to follow the protocol. +*/ +XBASE_SUBCLASS_WHAT(XBadClient, XSynergy); + +//! Incompatible client exception +/*! +Thrown when a client attempting to connect has an incompatible version. +*/ +class XIncompatibleClient : public XSynergy { +public: + XIncompatibleClient(int major, int minor); + + //! @name accessors + //@{ + + //! Get client's major version number + int getMajor() const throw(); + //! Get client's minor version number + int getMinor() const throw(); + + //@} + +protected: + virtual CString getWhat() const throw(); + +private: + int m_major; + int m_minor; +}; + +//! Client already connected exception +/*! +Thrown when a client attempting to connect is using the same name as +a client that is already connected. +*/ +class XDuplicateClient : public XSynergy { +public: + XDuplicateClient(const CString& name); + + //! @name accessors + //@{ + + //! Get client's name + virtual const CString& + getName() const throw(); + + //@} + +protected: + virtual CString getWhat() const throw(); + +private: + CString m_name; +}; + +//! Client not in map exception +/*! +Thrown when a client attempting to connect is using a name that is +unknown to the server. +*/ +class XUnknownClient : public XSynergy { +public: + XUnknownClient(const CString& name); + + //! @name accessors + //@{ + + //! Get the client's name + virtual const CString& + getName() const throw(); + + //@} + +protected: + virtual CString getWhat() const throw(); + +private: + CString m_name; +}; + +#endif diff --git a/win32util/autodep.cpp b/win32util/autodep.cpp new file mode 100644 index 00000000..2a2e9d50 --- /dev/null +++ b/win32util/autodep.cpp @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include + +using namespace std; + +static +string +baseName(const string& filename) +{ + return filename.substr(0, filename.rfind('.')); +} + +static +int +writeMakefile(const string& dstdir, const set& depFilenames) +{ + string makeFilename = dstdir + "\\deps.mak"; + ofstream makeFile(makeFilename.c_str()); + if (!makeFile) { + cerr << "Can't open '" << makeFilename << "' for writing" << endl; + return 1; + } + + for (set::const_iterator i = depFilenames.begin(); + i != depFilenames.end(); ++i) { + makeFile << "!if EXIST(\"" << *i << "\")" << endl; + makeFile << "!include \"" << *i << "\"" << endl; + makeFile << "!endif" << endl; + } + + return 0; +} + +static +void +writeDependencies( + const string& filename, + const string& srcdir, + const string& dstdir, + const set& paths) +{ + string basename = baseName(filename); + string depFilename = dstdir + "\\" + basename + ".d"; + ofstream depFile(depFilename.c_str()); + if (!depFile) { + cerr << "Can't open '" << depFilename << "' for writing" << endl; + return; + } + + // Write dependencies rule for filename + depFile << "\"" << dstdir << "\\" << basename << ".obj\": \"" << + srcdir << "\\" << filename << "\" \\" << endl; + for (set::const_iterator i = paths.begin(); i != paths.end(); ++i) { + depFile << "\t\"" << *i << "\" \\" << endl; + } + depFile << "\t$(NULL)" << endl; +} + +static +int +writeDepfiles(const string& srcdir, const string& dstdir) +{ + const string includeLine = "Note: including file:"; + + // Parse stdin + string line; + string filename; + set paths; + locale loc = locale::classic(); + const ctype& ct = use_facet >(loc); + while (getline(cin, line)) { + bool echo = true; + + // Check for include line + if (line.compare(0, includeLine.length(), includeLine) == 0) { + // Strip includeLine and leading spaces + line.erase(0, line.find_first_not_of(" ", includeLine.length())); + if (line.length() == 0) { + continue; + } + + // Uppercase all drive letters + if (line.length() > 2 && line[1] == ':') { + line[0] = ct.toupper(line[0]); + } + + // Record path + paths.insert(line); + echo = false; + } + + // Maybe a source filename + else if (line.rfind(".cpp") == line.length() - 4) { + // Write dependencies for previous source file + if (filename.length() != 0) { + writeDependencies(filename, srcdir, dstdir, paths); + paths.clear(); + } + filename = line; + } + + // Otherwise other output + else { + // do nothing + } + + if (echo) { + cout << line << endl; + } + } + + // Write dependencies for last source file + if (filename.length() != 0) { + writeDependencies(filename, srcdir, dstdir, paths); + paths.clear(); + } + + return 0; +} + +int +main(int argc, char** argv) +{ + if (argc < 3) { + cerr << "usage: " << argv[0] << + " []" << endl; + return 1; + } + string srcdir = argv[1]; + string dstdir = argv[2]; + + // If depfiles were supplied then create a makefile in outdir to load + // all of them. + int result; + if (argc > 3) { + set depFilenames(argv + 3, argv + argc); + result = writeMakefile(dstdir, depFilenames); + } + + // Otherwise parse stdin and create a depfile for each listed file + else { + result = writeDepfiles(srcdir, dstdir); + } + + return result; +}