aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/CMakeLists.txt27
-rw-r--r--src/lib/arch/Arch.cpp60
-rw-r--r--src/lib/arch/Arch.h144
-rw-r--r--src/lib/arch/ArchConsoleStd.cpp33
-rw-r--r--src/lib/arch/ArchConsoleStd.h34
-rw-r--r--src/lib/arch/ArchDaemonNone.cpp85
-rw-r--r--src/lib/arch/ArchDaemonNone.h50
-rw-r--r--src/lib/arch/CMakeLists.txt47
-rw-r--r--src/lib/arch/IArchConsole.h66
-rw-r--r--src/lib/arch/IArchDaemon.h128
-rw-r--r--src/lib/arch/IArchFile.h105
-rw-r--r--src/lib/arch/IArchLog.h63
-rw-r--r--src/lib/arch/IArchMultithread.h273
-rw-r--r--src/lib/arch/IArchNetwork.h283
-rw-r--r--src/lib/arch/IArchSleep.h44
-rw-r--r--src/lib/arch/IArchString.cpp190
-rw-r--r--src/lib/arch/IArchString.h72
-rw-r--r--src/lib/arch/IArchSystem.h66
-rw-r--r--src/lib/arch/IArchTaskBar.h63
-rw-r--r--src/lib/arch/IArchTaskBarReceiver.h98
-rw-r--r--src/lib/arch/IArchTime.h41
-rw-r--r--src/lib/arch/XArch.h161
-rw-r--r--src/lib/arch/multibyte.h56
-rw-r--r--src/lib/arch/unix/ArchConsoleUnix.cpp23
-rw-r--r--src/lib/arch/unix/ArchConsoleUnix.h29
-rw-r--r--src/lib/arch/unix/ArchDaemonUnix.cpp132
-rw-r--r--src/lib/arch/unix/ArchDaemonUnix.h36
-rw-r--r--src/lib/arch/unix/ArchFileUnix.cpp163
-rw-r--r--src/lib/arch/unix/ArchFileUnix.h47
-rw-r--r--src/lib/arch/unix/ArchInternetUnix.cpp126
-rw-r--r--src/lib/arch/unix/ArchInternetUnix.h28
-rw-r--r--src/lib/arch/unix/ArchLogUnix.cpp84
-rw-r--r--src/lib/arch/unix/ArchLogUnix.h36
-rw-r--r--src/lib/arch/unix/ArchMultithreadPosix.cpp812
-rw-r--r--src/lib/arch/unix/ArchMultithreadPosix.h115
-rw-r--r--src/lib/arch/unix/ArchNetworkBSD.cpp1005
-rw-r--r--src/lib/arch/unix/ArchNetworkBSD.h105
-rw-r--r--src/lib/arch/unix/ArchSleepUnix.cpp94
-rw-r--r--src/lib/arch/unix/ArchSleepUnix.h33
-rw-r--r--src/lib/arch/unix/ArchStringUnix.cpp42
-rw-r--r--src/lib/arch/unix/ArchStringUnix.h34
-rw-r--r--src/lib/arch/unix/ArchSystemUnix.cpp80
-rw-r--r--src/lib/arch/unix/ArchSystemUnix.h38
-rw-r--r--src/lib/arch/unix/ArchTaskBarXWindows.cpp51
-rw-r--r--src/lib/arch/unix/ArchTaskBarXWindows.h35
-rw-r--r--src/lib/arch/unix/ArchTimeUnix.cpp52
-rw-r--r--src/lib/arch/unix/ArchTimeUnix.h33
-rw-r--r--src/lib/arch/unix/XArchUnix.cpp32
-rw-r--r--src/lib/arch/unix/XArchUnix.h33
-rw-r--r--src/lib/arch/vsnprintf.h67
-rw-r--r--src/lib/arch/win32/ArchConsoleWindows.cpp23
-rw-r--r--src/lib/arch/win32/ArchConsoleWindows.h29
-rw-r--r--src/lib/arch/win32/ArchDaemonWindows.cpp704
-rw-r--r--src/lib/arch/win32/ArchDaemonWindows.h151
-rw-r--r--src/lib/arch/win32/ArchFileWindows.cpp203
-rw-r--r--src/lib/arch/win32/ArchFileWindows.h47
-rw-r--r--src/lib/arch/win32/ArchInternetWindows.cpp224
-rw-r--r--src/lib/arch/win32/ArchInternetWindows.h28
-rw-r--r--src/lib/arch/win32/ArchLogWindows.cpp95
-rw-r--r--src/lib/arch/win32/ArchLogWindows.h42
-rw-r--r--src/lib/arch/win32/ArchMiscWindows.cpp524
-rw-r--r--src/lib/arch/win32/ArchMiscWindows.h202
-rw-r--r--src/lib/arch/win32/ArchMultithreadWindows.cpp705
-rw-r--r--src/lib/arch/win32/ArchMultithreadWindows.h116
-rw-r--r--src/lib/arch/win32/ArchNetworkWinsock.cpp985
-rw-r--r--src/lib/arch/win32/ArchNetworkWinsock.h111
-rw-r--r--src/lib/arch/win32/ArchSleepWindows.cpp61
-rw-r--r--src/lib/arch/win32/ArchSleepWindows.h33
-rw-r--r--src/lib/arch/win32/ArchStringWindows.cpp46
-rw-r--r--src/lib/arch/win32/ArchStringWindows.h34
-rw-r--r--src/lib/arch/win32/ArchSystemWindows.cpp166
-rw-r--r--src/lib/arch/win32/ArchSystemWindows.h39
-rw-r--r--src/lib/arch/win32/ArchTaskBarWindows.cpp514
-rw-r--r--src/lib/arch/win32/ArchTaskBarWindows.h114
-rw-r--r--src/lib/arch/win32/ArchTimeWindows.cpp89
-rw-r--r--src/lib/arch/win32/ArchTimeWindows.h33
-rw-r--r--src/lib/arch/win32/XArchWindows.cpp120
-rw-r--r--src/lib/arch/win32/XArchWindows.h49
-rw-r--r--src/lib/barrier/App.cpp329
-rw-r--r--src/lib/barrier/App.h200
-rw-r--r--src/lib/barrier/AppUtil.cpp52
-rw-r--r--src/lib/barrier/AppUtil.h40
-rw-r--r--src/lib/barrier/ArgParser.cpp519
-rw-r--r--src/lib/barrier/ArgParser.h63
-rw-r--r--src/lib/barrier/ArgsBase.cpp53
-rw-r--r--src/lib/barrier/ArgsBase.h54
-rw-r--r--src/lib/barrier/CMakeLists.txt40
-rw-r--r--src/lib/barrier/Chunk.cpp30
-rw-r--r--src/lib/barrier/Chunk.h30
-rw-r--r--src/lib/barrier/ClientApp.cpp560
-rw-r--r--src/lib/barrier/ClientApp.h83
-rw-r--r--src/lib/barrier/ClientArgs.cpp23
-rw-r--r--src/lib/barrier/ClientArgs.h30
-rw-r--r--src/lib/barrier/ClientTaskBarReceiver.cpp141
-rw-r--r--src/lib/barrier/ClientTaskBarReceiver.h95
-rw-r--r--src/lib/barrier/Clipboard.cpp118
-rw-r--r--src/lib/barrier/Clipboard.h71
-rw-r--r--src/lib/barrier/ClipboardChunk.cpp154
-rw-r--r--src/lib/barrier/ClipboardChunk.h60
-rw-r--r--src/lib/barrier/DragInformation.cpp158
-rw-r--r--src/lib/barrier/DragInformation.h53
-rw-r--r--src/lib/barrier/DropHelper.cpp53
-rw-r--r--src/lib/barrier/DropHelper.h27
-rw-r--r--src/lib/barrier/FileChunk.cpp156
-rw-r--r--src/lib/barrier/FileChunk.h46
-rw-r--r--src/lib/barrier/IApp.h47
-rw-r--r--src/lib/barrier/IAppUtil.h31
-rw-r--r--src/lib/barrier/IClient.h176
-rw-r--r--src/lib/barrier/IClipboard.cpp168
-rw-r--r--src/lib/barrier/IClipboard.h169
-rw-r--r--src/lib/barrier/IKeyState.cpp161
-rw-r--r--src/lib/barrier/IKeyState.h174
-rw-r--r--src/lib/barrier/INode.h25
-rw-r--r--src/lib/barrier/IPlatformScreen.cpp24
-rw-r--r--src/lib/barrier/IPlatformScreen.h227
-rw-r--r--src/lib/barrier/IPrimaryScreen.cpp91
-rw-r--r--src/lib/barrier/IPrimaryScreen.h165
-rw-r--r--src/lib/barrier/IScreen.h71
-rw-r--r--src/lib/barrier/IScreenSaver.h75
-rw-r--r--src/lib/barrier/ISecondaryScreen.h61
-rw-r--r--src/lib/barrier/KeyMap.cpp1344
-rw-r--r--src/lib/barrier/KeyMap.h512
-rw-r--r--src/lib/barrier/KeyState.cpp936
-rw-r--r--src/lib/barrier/KeyState.h232
-rw-r--r--src/lib/barrier/PacketStreamFilter.cpp198
-rw-r--r--src/lib/barrier/PacketStreamFilter.h59
-rw-r--r--src/lib/barrier/PlatformScreen.cpp123
-rw-r--r--src/lib/barrier/PlatformScreen.h127
-rw-r--r--src/lib/barrier/PortableTaskBarReceiver.cpp121
-rw-r--r--src/lib/barrier/PortableTaskBarReceiver.h96
-rw-r--r--src/lib/barrier/ProtocolUtil.cpp544
-rw-r--r--src/lib/barrier/ProtocolUtil.h96
-rw-r--r--src/lib/barrier/Screen.cpp559
-rw-r--r--src/lib/barrier/Screen.h345
-rw-r--r--src/lib/barrier/ServerApp.cpp859
-rw-r--r--src/lib/barrier/ServerApp.h127
-rw-r--r--src/lib/barrier/ServerArgs.cpp25
-rw-r--r--src/lib/barrier/ServerArgs.h32
-rw-r--r--src/lib/barrier/ServerTaskBarReceiver.cpp138
-rw-r--r--src/lib/barrier/ServerTaskBarReceiver.h98
-rw-r--r--src/lib/barrier/StreamChunker.cpp166
-rw-r--r--src/lib/barrier/StreamChunker.h45
-rw-r--r--src/lib/barrier/ToolApp.cpp205
-rw-r--r--src/lib/barrier/ToolApp.h37
-rw-r--r--src/lib/barrier/ToolArgs.cpp29
-rw-r--r--src/lib/barrier/ToolArgs.h34
-rw-r--r--src/lib/barrier/XBarrier.cpp133
-rw-r--r--src/lib/barrier/XBarrier.h135
-rw-r--r--src/lib/barrier/XScreen.cpp68
-rw-r--r--src/lib/barrier/XScreen.h68
-rw-r--r--src/lib/barrier/clipboard_types.h42
-rw-r--r--src/lib/barrier/key_types.cpp208
-rw-r--r--src/lib/barrier/key_types.h314
-rw-r--r--src/lib/barrier/mouse_types.h41
-rw-r--r--src/lib/barrier/option_types.h99
-rw-r--r--src/lib/barrier/protocol_types.cpp53
-rw-r--r--src/lib/barrier/protocol_types.h337
-rw-r--r--src/lib/barrier/unix/AppUtilUnix.cpp46
-rw-r--r--src/lib/barrier/unix/AppUtilUnix.h34
-rw-r--r--src/lib/barrier/win32/AppUtilWindows.cpp185
-rw-r--r--src/lib/barrier/win32/AppUtilWindows.h62
-rw-r--r--src/lib/barrier/win32/DaemonApp.cpp361
-rw-r--r--src/lib/barrier/win32/DaemonApp.h58
-rw-r--r--src/lib/base/CMakeLists.txt28
-rw-r--r--src/lib/base/ELevel.h38
-rw-r--r--src/lib/base/Event.cpp100
-rw-r--r--src/lib/base/Event.h126
-rw-r--r--src/lib/base/EventQueue.cpp658
-rw-r--r--src/lib/base/EventQueue.h200
-rw-r--r--src/lib/base/EventTypes.cpp203
-rw-r--r--src/lib/base/EventTypes.h754
-rw-r--r--src/lib/base/FunctionEventJob.cpp44
-rw-r--r--src/lib/base/FunctionEventJob.h39
-rw-r--r--src/lib/base/FunctionJob.cpp43
-rw-r--r--src/lib/base/FunctionJob.h39
-rw-r--r--src/lib/base/IEventJob.h33
-rw-r--r--src/lib/base/IEventQueue.h251
-rw-r--r--src/lib/base/IEventQueueBuffer.h101
-rw-r--r--src/lib/base/IJob.h31
-rw-r--r--src/lib/base/ILogOutputter.h69
-rw-r--r--src/lib/base/Log.cpp309
-rw-r--r--src/lib/base/Log.h211
-rw-r--r--src/lib/base/NonBlockingStream.cpp60
-rw-r--r--src/lib/base/NonBlockingStream.h49
-rw-r--r--src/lib/base/PriorityQueue.h138
-rw-r--r--src/lib/base/SimpleEventQueueBuffer.cpp101
-rw-r--r--src/lib/base/SimpleEventQueueBuffer.h52
-rw-r--r--src/lib/base/Stopwatch.cpp130
-rw-r--r--src/lib/base/Stopwatch.h109
-rw-r--r--src/lib/base/String.cpp295
-rw-r--r--src/lib/base/String.h135
-rw-r--r--src/lib/base/TMethodEventJob.h71
-rw-r--r--src/lib/base/TMethodJob.h68
-rw-r--r--src/lib/base/Unicode.cpp784
-rw-r--r--src/lib/base/Unicode.h144
-rw-r--r--src/lib/base/XBase.cpp76
-rw-r--r--src/lib/base/XBase.h125
-rw-r--r--src/lib/base/log_outputters.cpp337
-rw-r--r--src/lib/base/log_outputters.h172
-rw-r--r--src/lib/client/CMakeLists.txt28
-rw-r--r--src/lib/client/Client.cpp836
-rw-r--r--src/lib/client/Client.h227
-rw-r--r--src/lib/client/ServerProxy.cpp908
-rw-r--r--src/lib/client/ServerProxy.h133
-rw-r--r--src/lib/common/CMakeLists.txt24
-rw-r--r--src/lib/common/IInterface.h32
-rw-r--r--src/lib/common/MacOSXPrecomp.h23
-rw-r--r--src/lib/common/Version.cpp29
-rw-r--r--src/lib/common/Version.h39
-rw-r--r--src/lib/common/basic_types.h92
-rw-r--r--src/lib/common/common.h58
-rw-r--r--src/lib/common/stdbitset.h21
-rw-r--r--src/lib/common/stddeque.h21
-rw-r--r--src/lib/common/stdexcept.h23
-rw-r--r--src/lib/common/stdfstream.h22
-rw-r--r--src/lib/common/stdistream.h47
-rw-r--r--src/lib/common/stdlist.h21
-rw-r--r--src/lib/common/stdmap.h21
-rw-r--r--src/lib/common/stdostream.h25
-rw-r--r--src/lib/common/stdpost.h21
-rw-r--r--src/lib/common/stdpre.h31
-rw-r--r--src/lib/common/stdset.h21
-rw-r--r--src/lib/common/stdsstream.h376
-rw-r--r--src/lib/common/stdstring.h21
-rw-r--r--src/lib/common/stdvector.h21
-rw-r--r--src/lib/io/CMakeLists.txt24
-rw-r--r--src/lib/io/IStream.h120
-rw-r--r--src/lib/io/StreamBuffer.cpp146
-rw-r--r--src/lib/io/StreamBuffer.h79
-rw-r--r--src/lib/io/StreamFilter.cpp118
-rw-r--r--src/lib/io/StreamFilter.h73
-rw-r--r--src/lib/io/XIO.cpp51
-rw-r--r--src/lib/io/XIO.h49
-rw-r--r--src/lib/ipc/CMakeLists.txt28
-rw-r--r--src/lib/ipc/Ipc.cpp24
-rw-r--r--src/lib/ipc/Ipc.h52
-rw-r--r--src/lib/ipc/IpcClient.cpp108
-rw-r--r--src/lib/ipc/IpcClient.h64
-rw-r--r--src/lib/ipc/IpcClientProxy.cpp194
-rw-r--r--src/lib/ipc/IpcClientProxy.h55
-rw-r--r--src/lib/ipc/IpcLogOutputter.cpp228
-rw-r--r--src/lib/ipc/IpcLogOutputter.h119
-rw-r--r--src/lib/ipc/IpcMessage.cpp69
-rw-r--r--src/lib/ipc/IpcMessage.h85
-rw-r--r--src/lib/ipc/IpcServer.cpp187
-rw-r--r--src/lib/ipc/IpcServer.h92
-rw-r--r--src/lib/ipc/IpcServerProxy.cpp123
-rw-r--r--src/lib/ipc/IpcServerProxy.h46
-rw-r--r--src/lib/mt/CMakeLists.txt24
-rw-r--r--src/lib/mt/CondVar.cpp91
-rw-r--r--src/lib/mt/CondVar.h225
-rw-r--r--src/lib/mt/Lock.cpp42
-rw-r--r--src/lib/mt/Lock.h49
-rw-r--r--src/lib/mt/Mutex.cpp58
-rw-r--r--src/lib/mt/Mutex.h79
-rw-r--r--src/lib/mt/Thread.cpp187
-rw-r--r--src/lib/mt/Thread.h210
-rw-r--r--src/lib/mt/XMT.cpp29
-rw-r--r--src/lib/mt/XMT.h30
-rw-r--r--src/lib/mt/XThread.h37
-rw-r--r--src/lib/net/CMakeLists.txt28
-rw-r--r--src/lib/net/IDataSocket.cpp39
-rw-r--r--src/lib/net/IDataSocket.h73
-rw-r--r--src/lib/net/IListenSocket.h51
-rw-r--r--src/lib/net/ISocket.h60
-rw-r--r--src/lib/net/ISocketFactory.h48
-rw-r--r--src/lib/net/ISocketMultiplexerJob.h76
-rw-r--r--src/lib/net/NetworkAddress.cpp214
-rw-r--r--src/lib/net/NetworkAddress.h123
-rw-r--r--src/lib/net/SecureListenSocket.cpp85
-rw-r--r--src/lib/net/SecureListenSocket.h36
-rw-r--r--src/lib/net/SecureSocket.cpp867
-rw-r--r--src/lib/net/SecureSocket.h95
-rw-r--r--src/lib/net/SocketMultiplexer.cpp352
-rw-r--r--src/lib/net/SocketMultiplexer.h111
-rw-r--r--src/lib/net/TCPListenSocket.cpp158
-rw-r--r--src/lib/net/TCPListenSocket.h60
-rw-r--r--src/lib/net/TCPSocket.cpp596
-rw-r--r--src/lib/net/TCPSocket.h116
-rw-r--r--src/lib/net/TCPSocketFactory.cpp68
-rw-r--r--src/lib/net/TCPSocketFactory.h44
-rw-r--r--src/lib/net/TSocketMultiplexerMethodJob.h109
-rw-r--r--src/lib/net/XSocket.cpp117
-rw-r--r--src/lib/net/XSocket.h98
-rw-r--r--src/lib/platform/CMakeLists.txt49
-rw-r--r--src/lib/platform/IMSWindowsClipboardFacade.h36
-rw-r--r--src/lib/platform/IOSXKeyResource.cpp189
-rw-r--r--src/lib/platform/IOSXKeyResource.h36
-rw-r--r--src/lib/platform/ImmuneKeysReader.cpp53
-rw-r--r--src/lib/platform/ImmuneKeysReader.h34
-rw-r--r--src/lib/platform/MSWindowsClipboard.cpp232
-rw-r--r--src/lib/platform/MSWindowsClipboard.h113
-rw-r--r--src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp149
-rw-r--r--src/lib/platform/MSWindowsClipboardAnyTextConverter.h57
-rw-r--r--src/lib/platform/MSWindowsClipboardBitmapConverter.cpp152
-rw-r--r--src/lib/platform/MSWindowsClipboardBitmapConverter.h36
-rw-r--r--src/lib/platform/MSWindowsClipboardFacade.cpp31
-rw-r--r--src/lib/platform/MSWindowsClipboardFacade.h29
-rw-r--r--src/lib/platform/MSWindowsClipboardHTMLConverter.cpp120
-rw-r--r--src/lib/platform/MSWindowsClipboardHTMLConverter.h45
-rw-r--r--src/lib/platform/MSWindowsClipboardTextConverter.cpp60
-rw-r--r--src/lib/platform/MSWindowsClipboardTextConverter.h37
-rw-r--r--src/lib/platform/MSWindowsClipboardUTF16Converter.cpp60
-rw-r--r--src/lib/platform/MSWindowsClipboardUTF16Converter.h37
-rw-r--r--src/lib/platform/MSWindowsDebugOutputter.cpp58
-rw-r--r--src/lib/platform/MSWindowsDebugOutputter.h39
-rw-r--r--src/lib/platform/MSWindowsDesks.cpp923
-rw-r--r--src/lib/platform/MSWindowsDesks.h297
-rw-r--r--src/lib/platform/MSWindowsDropTarget.cpp178
-rw-r--r--src/lib/platform/MSWindowsDropTarget.h59
-rw-r--r--src/lib/platform/MSWindowsEventQueueBuffer.cpp146
-rw-r--r--src/lib/platform/MSWindowsEventQueueBuffer.h50
-rw-r--r--src/lib/platform/MSWindowsHook.cpp629
-rw-r--r--src/lib/platform/MSWindowsHook.h39
-rw-r--r--src/lib/platform/MSWindowsHookResource.cpp33
-rw-r--r--src/lib/platform/MSWindowsHookResource.h20
-rw-r--r--src/lib/platform/MSWindowsKeyState.cpp1406
-rw-r--r--src/lib/platform/MSWindowsKeyState.h233
-rw-r--r--src/lib/platform/MSWindowsScreen.cpp1959
-rw-r--r--src/lib/platform/MSWindowsScreen.h346
-rw-r--r--src/lib/platform/MSWindowsScreenSaver.cpp359
-rw-r--r--src/lib/platform/MSWindowsScreenSaver.h89
-rw-r--r--src/lib/platform/MSWindowsSession.cpp195
-rw-r--r--src/lib/platform/MSWindowsSession.h52
-rw-r--r--src/lib/platform/MSWindowsUtil.cpp64
-rw-r--r--src/lib/platform/MSWindowsUtil.h40
-rw-r--r--src/lib/platform/MSWindowsWatchdog.cpp611
-rw-r--r--src/lib/platform/MSWindowsWatchdog.h99
-rw-r--r--src/lib/platform/OSXClipboard.cpp259
-rw-r--r--src/lib/platform/OSXClipboard.h95
-rw-r--r--src/lib/platform/OSXClipboardAnyBitmapConverter.cpp48
-rw-r--r--src/lib/platform/OSXClipboardAnyBitmapConverter.h48
-rw-r--r--src/lib/platform/OSXClipboardAnyTextConverter.cpp90
-rw-r--r--src/lib/platform/OSXClipboardAnyTextConverter.h53
-rw-r--r--src/lib/platform/OSXClipboardBMPConverter.cpp134
-rw-r--r--src/lib/platform/OSXClipboardBMPConverter.h44
-rw-r--r--src/lib/platform/OSXClipboardHTMLConverter.cpp95
-rw-r--r--src/lib/platform/OSXClipboardHTMLConverter.h44
-rw-r--r--src/lib/platform/OSXClipboardTextConverter.cpp93
-rw-r--r--src/lib/platform/OSXClipboardTextConverter.h42
-rw-r--r--src/lib/platform/OSXClipboardUTF16Converter.cpp55
-rw-r--r--src/lib/platform/OSXClipboardUTF16Converter.h37
-rw-r--r--src/lib/platform/OSXDragSimulator.h34
-rw-r--r--src/lib/platform/OSXDragSimulator.m102
-rw-r--r--src/lib/platform/OSXDragView.h34
-rw-r--r--src/lib/platform/OSXDragView.m177
-rw-r--r--src/lib/platform/OSXEventQueueBuffer.cpp143
-rw-r--r--src/lib/platform/OSXEventQueueBuffer.h47
-rw-r--r--src/lib/platform/OSXKeyState.cpp912
-rw-r--r--src/lib/platform/OSXKeyState.h180
-rw-r--r--src/lib/platform/OSXMediaKeySimulator.h30
-rw-r--r--src/lib/platform/OSXMediaKeySimulator.m92
-rw-r--r--src/lib/platform/OSXMediaKeySupport.h33
-rw-r--r--src/lib/platform/OSXMediaKeySupport.m154
-rw-r--r--src/lib/platform/OSXPasteboardPeeker.h32
-rw-r--r--src/lib/platform/OSXPasteboardPeeker.m37
-rw-r--r--src/lib/platform/OSXScreen.h349
-rw-r--r--src/lib/platform/OSXScreen.mm2162
-rw-r--r--src/lib/platform/OSXScreenSaver.cpp201
-rw-r--r--src/lib/platform/OSXScreenSaver.h59
-rw-r--r--src/lib/platform/OSXScreenSaverControl.h54
-rw-r--r--src/lib/platform/OSXScreenSaverUtil.h40
-rw-r--r--src/lib/platform/OSXScreenSaverUtil.m83
-rw-r--r--src/lib/platform/OSXUchrKeyResource.cpp296
-rw-r--r--src/lib/platform/OSXUchrKeyResource.h55
-rw-r--r--src/lib/platform/XWindowsClipboard.cpp1525
-rw-r--r--src/lib/platform/XWindowsClipboard.h378
-rw-r--r--src/lib/platform/XWindowsClipboardAnyBitmapConverter.cpp191
-rw-r--r--src/lib/platform/XWindowsClipboardAnyBitmapConverter.h60
-rw-r--r--src/lib/platform/XWindowsClipboardBMPConverter.cpp143
-rw-r--r--src/lib/platform/XWindowsClipboardBMPConverter.h40
-rw-r--r--src/lib/platform/XWindowsClipboardHTMLConverter.cpp67
-rw-r--r--src/lib/platform/XWindowsClipboardHTMLConverter.h42
-rw-r--r--src/lib/platform/XWindowsClipboardTextConverter.cpp79
-rw-r--r--src/lib/platform/XWindowsClipboardTextConverter.h42
-rw-r--r--src/lib/platform/XWindowsClipboardUCS2Converter.cpp67
-rw-r--r--src/lib/platform/XWindowsClipboardUCS2Converter.h42
-rw-r--r--src/lib/platform/XWindowsClipboardUTF8Converter.cpp65
-rw-r--r--src/lib/platform/XWindowsClipboardUTF8Converter.h42
-rw-r--r--src/lib/platform/XWindowsEventQueueBuffer.cpp291
-rw-r--r--src/lib/platform/XWindowsEventQueueBuffer.h64
-rw-r--r--src/lib/platform/XWindowsKeyState.cpp867
-rw-r--r--src/lib/platform/XWindowsKeyState.h174
-rw-r--r--src/lib/platform/XWindowsScreen.cpp2096
-rw-r--r--src/lib/platform/XWindowsScreen.h252
-rw-r--r--src/lib/platform/XWindowsScreenSaver.cpp605
-rw-r--r--src/lib/platform/XWindowsScreenSaver.h169
-rw-r--r--src/lib/platform/XWindowsUtil.cpp1790
-rw-r--r--src/lib/platform/XWindowsUtil.h187
-rw-r--r--src/lib/platform/synwinhk.h66
-rw-r--r--src/lib/server/BaseClientProxy.cpp56
-rw-r--r--src/lib/server/BaseClientProxy.h99
-rw-r--r--src/lib/server/CMakeLists.txt30
-rw-r--r--src/lib/server/ClientListener.cpp256
-rw-r--r--src/lib/server/ClientListener.h91
-rw-r--r--src/lib/server/ClientProxy.cpp61
-rw-r--r--src/lib/server/ClientProxy.h91
-rw-r--r--src/lib/server/ClientProxy1_0.cpp484
-rw-r--r--src/lib/server/ClientProxy1_0.h107
-rw-r--r--src/lib/server/ClientProxy1_1.cpp61
-rw-r--r--src/lib/server/ClientProxy1_1.h34
-rw-r--r--src/lib/server/ClientProxy1_2.cpp44
-rw-r--r--src/lib/server/ClientProxy1_2.h33
-rw-r--r--src/lib/server/ClientProxy1_3.cpp129
-rw-r--r--src/lib/server/ClientProxy1_3.h48
-rw-r--r--src/lib/server/ClientProxy1_4.cpp66
-rw-r--r--src/lib/server/ClientProxy1_4.h46
-rw-r--r--src/lib/server/ClientProxy1_5.cpp110
-rw-r--r--src/lib/server/ClientProxy1_5.h41
-rw-r--r--src/lib/server/ClientProxy1_6.cpp100
-rw-r--r--src/lib/server/ClientProxy1_6.h39
-rw-r--r--src/lib/server/ClientProxyUnknown.cpp295
-rw-r--r--src/lib/server/ClientProxyUnknown.h71
-rw-r--r--src/lib/server/Config.cpp2335
-rw-r--r--src/lib/server/Config.h549
-rw-r--r--src/lib/server/InputFilter.cpp1090
-rw-r--r--src/lib/server/InputFilter.h361
-rw-r--r--src/lib/server/PrimaryClient.cpp274
-rw-r--r--src/lib/server/PrimaryClient.h156
-rw-r--r--src/lib/server/Server.cpp2405
-rw-r--r--src/lib/server/Server.h483
421 files changed, 76649 insertions, 0 deletions
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
new file mode 100644
index 0000000..70a0629
--- /dev/null
+++ b/src/lib/CMakeLists.txt
@@ -0,0 +1,27 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+add_subdirectory(arch)
+add_subdirectory(base)
+add_subdirectory(client)
+add_subdirectory(common)
+add_subdirectory(io)
+add_subdirectory(ipc)
+add_subdirectory(mt)
+add_subdirectory(net)
+add_subdirectory(platform)
+add_subdirectory(server)
+add_subdirectory(barrier)
diff --git a/src/lib/arch/Arch.cpp b/src/lib/arch/Arch.cpp
new file mode 100644
index 0000000..0a3b3e5
--- /dev/null
+++ b/src/lib/arch/Arch.cpp
@@ -0,0 +1,60 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/Arch.h"
+
+//
+// Arch
+//
+
+Arch* Arch::s_instance = NULL;
+
+Arch::Arch()
+{
+ assert(s_instance == NULL);
+ s_instance = this;
+}
+
+Arch::Arch(Arch* arch)
+{
+ s_instance = arch;
+}
+
+Arch::~Arch()
+{
+#if SYSAPI_WIN32
+ ArchMiscWindows::cleanup();
+#endif
+}
+
+void
+Arch::init()
+{
+ ARCH_NETWORK::init();
+#if SYSAPI_WIN32
+ ARCH_TASKBAR::init();
+ ArchMiscWindows::init();
+#endif
+}
+
+Arch*
+Arch::getInstance()
+{
+ assert(s_instance != NULL);
+ return s_instance;
+}
diff --git a/src/lib/arch/Arch.h b/src/lib/arch/Arch.h
new file mode 100644
index 0000000..42a73c2
--- /dev/null
+++ b/src/lib/arch/Arch.h
@@ -0,0 +1,144 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// TODO: consider whether or not to use either encapsulation (as below)
+// or inheritance (as it is now) for the ARCH stuff.
+//
+// case for encapsulation:
+// pros:
+// - compiler errors for missing pv implementations are not absolutely bonkers.
+// - function names don't have to be so verbose.
+// - easier to understand and debug.
+// - ctors in IArch implementations can call other implementations.
+// cons:
+// - slightly more code for calls to ARCH.
+// - you'll have to modify each ARCH call.
+//
+// also, we may want to consider making each encapsulated
+// class lazy-loaded so that apps like the daemon don't load
+// stuff when they don't need it.
+
+#pragma once
+
+#include "common/common.h"
+
+#if SYSAPI_WIN32
+# include "arch/win32/ArchConsoleWindows.h"
+# include "arch/win32/ArchDaemonWindows.h"
+# include "arch/win32/ArchFileWindows.h"
+# include "arch/win32/ArchLogWindows.h"
+# include "arch/win32/ArchMiscWindows.h"
+# include "arch/win32/ArchMultithreadWindows.h"
+# include "arch/win32/ArchNetworkWinsock.h"
+# include "arch/win32/ArchSleepWindows.h"
+# include "arch/win32/ArchStringWindows.h"
+# include "arch/win32/ArchSystemWindows.h"
+# include "arch/win32/ArchTaskBarWindows.h"
+# include "arch/win32/ArchTimeWindows.h"
+# include "arch/win32/ArchInternetWindows.h"
+#elif SYSAPI_UNIX
+# include "arch/unix/ArchConsoleUnix.h"
+# include "arch/unix/ArchDaemonUnix.h"
+# include "arch/unix/ArchFileUnix.h"
+# include "arch/unix/ArchLogUnix.h"
+# if HAVE_PTHREAD
+# include "arch/unix/ArchMultithreadPosix.h"
+# endif
+# include "arch/unix/ArchNetworkBSD.h"
+# include "arch/unix/ArchSleepUnix.h"
+# include "arch/unix/ArchStringUnix.h"
+# include "arch/unix/ArchSystemUnix.h"
+# include "arch/unix/ArchTaskBarXWindows.h"
+# include "arch/unix/ArchTimeUnix.h"
+# include "arch/unix/ArchInternetUnix.h"
+#endif
+
+/*!
+\def ARCH
+This macro evaluates to the singleton Arch object.
+*/
+#define ARCH (Arch::getInstance())
+
+//! Delegating implementation 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 Arch : public ARCH_CONSOLE,
+ public ARCH_DAEMON,
+ public ARCH_FILE,
+ public ARCH_LOG,
+ public ARCH_MULTITHREAD,
+ public ARCH_NETWORK,
+ public ARCH_SLEEP,
+ public ARCH_STRING,
+ public ARCH_SYSTEM,
+ public ARCH_TASKBAR,
+ public ARCH_TIME {
+public:
+ Arch();
+ Arch(Arch* arch);
+ virtual ~Arch();
+
+ //! Call init on other arch classes.
+ /*!
+ Some arch classes depend on others to exist first. When init is called
+ these clases will have ARCH available for use.
+ */
+ virtual void init();
+
+ //
+ // accessors
+ //
+
+ //! Return the singleton instance
+ /*!
+ The client must have instantiated exactly once Arch object before
+ calling this function.
+ */
+ static Arch* getInstance();
+
+ static void setInstance(Arch* s) { s_instance = s; }
+
+ ARCH_INTERNET& internet() const { return (ARCH_INTERNET&)m_internet; }
+
+private:
+ static Arch* s_instance;
+ ARCH_INTERNET m_internet;
+};
+
+//! Convenience object to lock/unlock an arch mutex
+class ArchMutexLock {
+public:
+ ArchMutexLock(ArchMutex mutex) : m_mutex(mutex)
+ {
+ ARCH->lockMutex(m_mutex);
+ }
+ ~ArchMutexLock()
+ {
+ ARCH->unlockMutex(m_mutex);
+ }
+
+private:
+ ArchMutex m_mutex;
+};
diff --git a/src/lib/arch/ArchConsoleStd.cpp b/src/lib/arch/ArchConsoleStd.cpp
new file mode 100644
index 0000000..f7f7691
--- /dev/null
+++ b/src/lib/arch/ArchConsoleStd.cpp
@@ -0,0 +1,33 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/ArchConsoleStd.h"
+#include "base/Log.h"
+
+#include <iostream>
+
+void
+ArchConsoleStd::writeConsole(ELevel level, const char* str)
+{
+ if ((level >= kFATAL) && (level <= kWARNING))
+ std::cerr << str << std::endl;
+ else
+ std::cout << str << std::endl;
+
+ std::cout.flush();
+} \ No newline at end of file
diff --git a/src/lib/arch/ArchConsoleStd.h b/src/lib/arch/ArchConsoleStd.h
new file mode 100644
index 0000000..8560fad
--- /dev/null
+++ b/src/lib/arch/ArchConsoleStd.h
@@ -0,0 +1,34 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchConsole.h"
+
+//! Cross platform implementation of IArchConsole
+class ArchConsoleStd : public IArchConsole {
+public:
+ ArchConsoleStd() { }
+ virtual ~ArchConsoleStd() { }
+
+ // IArchConsole overrides
+ virtual void openConsole(const char* title) { }
+ virtual void closeConsole() { }
+ virtual void showConsole(bool) { }
+ virtual void writeConsole(ELevel level, const char*);
+};
diff --git a/src/lib/arch/ArchDaemonNone.cpp b/src/lib/arch/ArchDaemonNone.cpp
new file mode 100644
index 0000000..1222549
--- /dev/null
+++ b/src/lib/arch/ArchDaemonNone.cpp
@@ -0,0 +1,85 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/ArchDaemonNone.h"
+
+//
+// ArchDaemonNone
+//
+
+ArchDaemonNone::ArchDaemonNone()
+{
+ // do nothing
+}
+
+ArchDaemonNone::~ArchDaemonNone()
+{
+ // do nothing
+}
+
+void
+ArchDaemonNone::installDaemon(const char*,
+ const char*,
+ const char*,
+ const char*,
+ const char*)
+{
+ // do nothing
+}
+
+void
+ArchDaemonNone::uninstallDaemon(const char*)
+{
+ // do nothing
+}
+
+int
+ArchDaemonNone::daemonize(const char* name, DaemonFunc func)
+{
+ // simply forward the call to func. obviously, this doesn't
+ // do any daemonizing.
+ return func(1, &name);
+}
+
+bool
+ArchDaemonNone::canInstallDaemon(const char*)
+{
+ return false;
+}
+
+bool
+ArchDaemonNone::isDaemonInstalled(const char*)
+{
+ return false;
+}
+
+void
+ArchDaemonNone::installDaemon()
+{
+}
+
+void
+ArchDaemonNone::uninstallDaemon()
+{
+}
+
+std::string
+ArchDaemonNone::commandLine() const
+{
+ return "";
+}
diff --git a/src/lib/arch/ArchDaemonNone.h b/src/lib/arch/ArchDaemonNone.h
new file mode 100644
index 0000000..fd59758
--- /dev/null
+++ b/src/lib/arch/ArchDaemonNone.h
@@ -0,0 +1,50 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchDaemon.h"
+
+#define ARCH_DAEMON ArchDaemonNone
+
+//! 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 ArchDaemonNone : public IArchDaemon {
+public:
+ ArchDaemonNone();
+ virtual ~ArchDaemonNone();
+
+ // IArchDaemon overrides
+ virtual void installDaemon(const char* name,
+ const char* description,
+ const char* pathname,
+ const char* commandLine,
+ const char* dependencies);
+ virtual void uninstallDaemon(const char* name);
+ virtual int daemonize(const char* name, DaemonFunc func);
+ virtual bool canInstallDaemon(const char* name);
+ virtual bool isDaemonInstalled(const char* name);
+ virtual void installDaemon();
+ virtual void uninstallDaemon();
+ virtual std::string commandLine() const;
+};
diff --git a/src/lib/arch/CMakeLists.txt b/src/lib/arch/CMakeLists.txt
new file mode 100644
index 0000000..113cdd9
--- /dev/null
+++ b/src/lib/arch/CMakeLists.txt
@@ -0,0 +1,47 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+file(GLOB headers "*.h")
+file(GLOB sources "*.cpp")
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+# arch
+if (WIN32)
+ file(GLOB arch_headers "win32/*.h")
+ file(GLOB arch_sources "win32/*.cpp")
+elseif (UNIX)
+ file(GLOB arch_headers "unix/*.h")
+ file(GLOB arch_sources "unix/*.cpp")
+endif()
+
+list(APPEND sources ${arch_sources})
+list(APPEND headers ${arch_headers})
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+add_library(arch STATIC ${sources})
+
+if (UNIX)
+ target_link_libraries(arch ${libs})
+ if (NOT CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+ target_link_libraries(arch dl)
+ endif()
+endif()
diff --git a/src/lib/arch/IArchConsole.h b/src/lib/arch/IArchConsole.h
new file mode 100644
index 0000000..d115c50
--- /dev/null
+++ b/src/lib/arch/IArchConsole.h
@@ -0,0 +1,66 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "base/ELevel.h"
+
+//! Interface for architecture dependent console output
+/*!
+This interface defines the console operations required by
+barrier. 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(ELevel, const char*) = 0;
+
+ //@}
+};
diff --git a/src/lib/arch/IArchDaemon.h b/src/lib/arch/IArchDaemon.h
new file mode 100644
index 0000000..a4983d3
--- /dev/null
+++ b/src/lib/arch/IArchDaemon.h
@@ -0,0 +1,128 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "base/String.h"
+
+//! Interface for architecture dependent daemonizing
+/*!
+This interface defines the operations required by barrier 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) = 0;
+
+ //! Uninstall daemon
+ /*!
+ Uninstall a daemon. Throws an \c XArchDaemon on failure.
+ */
+ virtual void uninstallDaemon(const char* name) = 0;
+
+ //! Install daemon
+ /*!
+ Installs the default daemon.
+ */
+ virtual void installDaemon() = 0;
+
+ //! Uninstall daemon
+ /*!
+ Uninstalls the default daemon.
+ */
+ virtual void uninstallDaemon() = 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.
+ <ul>
+ <li>unix:
+ Detaches from terminal. \c func gets passed one argument, the
+ name passed to daemonize().
+ <li>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 ArchMiscWindows::runDaemon() to finally
+ becoming a service. The \c runFunc function passed to \c runDaemon()
+ must call \c ArchMiscWindows::daemonRunning(true) when it
+ enters the main loop (i.e. after initialization) and
+ \c ArchMiscWindows::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 ArchMiscWindows::daemonFailed() to indicate startup failure.
+ </ul>
+ */
+ 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) = 0;
+
+ //! Check if the daemon is installed
+ /*!
+ Returns true iff the daemon is installed.
+ */
+ virtual bool isDaemonInstalled(const char* name) = 0;
+
+ //@}
+
+ //! Get the command line
+ /*!
+ Gets the command line with which the application was started.
+ */
+ virtual std::string commandLine() const = 0;
+
+ //@}
+};
diff --git a/src/lib/arch/IArchFile.h b/src/lib/arch/IArchFile.h
new file mode 100644
index 0000000..5fdd288
--- /dev/null
+++ b/src/lib/arch/IArchFile.h
@@ -0,0 +1,105 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "common/stdstring.h"
+#include "base/String.h"
+
+//! Interface for architecture dependent file system operations
+/*!
+This interface defines the file system operations required by
+barrier. 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;
+
+ //! Get installed directory
+ /*!
+ Returns the directory in which Barrier is installed.
+ */
+ virtual std::string getInstalledDirectory() = 0;
+
+ //! Get log directory
+ /*!
+ Returns the log file directory.
+ */
+ virtual std::string getLogDirectory() = 0;
+
+ //! Get plugins directory
+ /*!
+ Returns the plugin files directory. If no plugin directory is set,
+ this will return the plugin folder within the user's profile.
+ */
+ virtual std::string getPluginDirectory() = 0;
+
+ //! Get user's profile directory
+ /*!
+ Returns the user's profile directory. If no profile directory is set,
+ this will return the user's profile according to the operating system,
+ which will depend on which user launched the program.
+ */
+ virtual std::string getProfileDirectory() = 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;
+
+ //@}
+ //! Set the user's profile directory
+ /*
+ Returns the user's profile directory.
+ */
+ virtual void setProfileDirectory(const String& s) = 0;
+
+ //@}
+ //! Set the user's plugin directory
+ /*
+ Returns the user's plugin directory.
+ */
+ virtual void setPluginDirectory(const String& s) = 0;
+};
diff --git a/src/lib/arch/IArchLog.h b/src/lib/arch/IArchLog.h
new file mode 100644
index 0000000..165b1df
--- /dev/null
+++ b/src/lib/arch/IArchLog.h
@@ -0,0 +1,63 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "base/ELevel.h"
+
+//! Interface for architecture dependent logging
+/*!
+This interface defines the logging operations required by
+barrier. Each architecture must implement this interface.
+*/
+class IArchLog : public IInterface {
+public:
+ //! @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;
+
+ //@}
+};
diff --git a/src/lib/arch/IArchMultithread.h b/src/lib/arch/IArchMultithread.h
new file mode 100644
index 0000000..e8d358b
--- /dev/null
+++ b/src/lib/arch/IArchMultithread.h
@@ -0,0 +1,273 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+
+/*!
+\class ArchCondImpl
+\brief Internal condition variable data.
+An architecture dependent type holding the necessary data for a
+condition variable.
+*/
+class ArchCondImpl;
+
+/*!
+\var ArchCond
+\brief Opaque condition variable type.
+An opaque type representing a condition variable.
+*/
+typedef ArchCondImpl* ArchCond;
+
+/*!
+\class ArchMutexImpl
+\brief Internal mutex data.
+An architecture dependent type holding the necessary data for a mutex.
+*/
+class ArchMutexImpl;
+
+/*!
+\var ArchMutex
+\brief Opaque mutex type.
+An opaque type representing a mutex.
+*/
+typedef ArchMutexImpl* ArchMutex;
+
+/*!
+\class ArchThreadImpl
+\brief Internal thread data.
+An architecture dependent type holding the necessary data for a thread.
+*/
+class ArchThreadImpl;
+
+/*!
+\var ArchThread
+\brief Opaque thread type.
+An opaque type representing a thread.
+*/
+typedef ArchThreadImpl* ArchThread;
+
+//! Interface for architecture dependent multithreading
+/*!
+This interface defines the multithreading operations required by
+barrier. 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 ArchCond newCondVar() = 0;
+
+ //! Destroy a condition variable
+ virtual void closeCondVar(ArchCond) = 0;
+
+ //! Signal a condition variable
+ /*!
+ Signalling a condition variable releases one waiting thread.
+ */
+ virtual void signalCondVar(ArchCond) = 0;
+
+ //! Broadcast a condition variable
+ /*!
+ Broadcasting a condition variable releases all waiting threads.
+ */
+ virtual void broadcastCondVar(ArchCond) = 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(ArchCond, ArchMutex, 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 ArchMutex newMutex() = 0;
+
+ //! Destroy a mutex
+ virtual void closeMutex(ArchMutex) = 0;
+
+ //! Lock a mutex
+ virtual void lockMutex(ArchMutex) = 0;
+
+ //! Unlock a mutex
+ virtual void unlockMutex(ArchMutex) = 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 ArchThread newThread(ThreadFunc func, void* userData) = 0;
+
+ //! Get a reference to the calling thread
+ /*!
+ Returns a thread representing the current (i.e. calling) thread.
+ */
+ virtual ArchThread newCurrentThread() = 0;
+
+ //! Copy a thread object
+ /*!
+ Returns a reference to to thread referred to by \c thread.
+ */
+ virtual ArchThread copyThread(ArchThread 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(ArchThread) = 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(ArchThread 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(ArchThread, 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(ArchThread 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(ArchThread, ArchThread) = 0;
+
+ //! Test if thread exited
+ /*!
+ Returns true iff \c thread has exited.
+ */
+ virtual bool isExitedThread(ArchThread 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(ArchThread 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(ArchThread 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;
+
+ //@}
+};
diff --git a/src/lib/arch/IArchNetwork.h b/src/lib/arch/IArchNetwork.h
new file mode 100644
index 0000000..b859506
--- /dev/null
+++ b/src/lib/arch/IArchNetwork.h
@@ -0,0 +1,283 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "common/stdstring.h"
+
+class ArchThreadImpl;
+typedef ArchThreadImpl* ArchThread;
+
+/*!
+\class ArchSocketImpl
+\brief Internal socket data.
+An architecture dependent type holding the necessary data for a socket.
+*/
+class ArchSocketImpl;
+
+/*!
+\var ArchSocket
+\brief Opaque socket type.
+An opaque type representing a socket.
+*/
+typedef ArchSocketImpl* ArchSocket;
+
+/*!
+\class ArchNetAddressImpl
+\brief Internal network address data.
+An architecture dependent type holding the necessary data for a network
+address.
+*/
+class ArchNetAddressImpl;
+
+/*!
+\var ArchNetAddress
+\brief Opaque network address type.
+An opaque type representing a network address.
+*/
+typedef ArchNetAddressImpl* ArchNetAddress;
+
+//! Interface for architecture dependent networking
+/*!
+This interface defines the networking operations required by
+barrier. Each architecture must implement this interface.
+*/
+class IArchNetwork : public IInterface {
+public:
+ //! Supported address families
+ enum EAddressFamily {
+ kUNKNOWN,
+ kINET,
+ kINET6,
+ };
+
+ //! 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 PollEntry {
+ public:
+ //! The socket to query
+ ArchSocket 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 ArchSocket newSocket(EAddressFamily, ESocketType) = 0;
+
+ //! Copy a socket object
+ /*!
+ Returns a reference to to socket referred to by \c s.
+ */
+ virtual ArchSocket copySocket(ArchSocket 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(ArchSocket s) = 0;
+
+ //! Close socket for further reads
+ /*!
+ Calling this disallows future reads on socket \c s.
+ */
+ virtual void closeSocketForRead(ArchSocket s) = 0;
+
+ //! Close socket for further writes
+ /*!
+ Calling this disallows future writes on socket \c s.
+ */
+ virtual void closeSocketForWrite(ArchSocket s) = 0;
+
+ //! Bind socket to address
+ /*!
+ Binds socket \c s to the address \c addr.
+ */
+ virtual void bindSocket(ArchSocket s, ArchNetAddress addr) = 0;
+
+ //! Listen for connections on socket
+ /*!
+ Causes the socket \c s to begin listening for incoming connections.
+ */
+ virtual void listenOnSocket(ArchSocket 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 ArchSocket acceptSocket(ArchSocket s, ArchNetAddress* 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(ArchSocket s, ArchNetAddress 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(PollEntry[], 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(ArchThread 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(ArchSocket 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(ArchSocket 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(ArchSocket 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(ArchSocket, 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(ArchSocket, bool reuse) = 0;
+
+ //! Return local host's name
+ virtual std::string getHostName() = 0;
+
+ //! Create an "any" network address
+ virtual ArchNetAddress newAnyAddr(EAddressFamily) = 0;
+
+ //! Copy a network address
+ virtual ArchNetAddress copyAddr(ArchNetAddress) = 0;
+
+ //! Convert a name to a network address
+ virtual ArchNetAddress nameToAddr(const std::string&) = 0;
+
+ //! Destroy a network address
+ virtual void closeAddr(ArchNetAddress) = 0;
+
+ //! Convert an address to a host name
+ virtual std::string addrToName(ArchNetAddress) = 0;
+
+ //! Convert an address to a string
+ virtual std::string addrToString(ArchNetAddress) = 0;
+
+ //! Get an address's family
+ virtual EAddressFamily getAddrFamily(ArchNetAddress) = 0;
+
+ //! Set the port of an address
+ virtual void setAddrPort(ArchNetAddress, int port) = 0;
+
+ //! Get the port of an address
+ virtual int getAddrPort(ArchNetAddress) = 0;
+
+ //! Test addresses for equality
+ virtual bool isEqualAddr(ArchNetAddress, ArchNetAddress) = 0;
+
+ //! Test for the "any" address
+ /*!
+ Returns true if \c addr is the "any" address. \c newAnyAddr()
+ returns an "any" address.
+ */
+ virtual bool isAnyAddr(ArchNetAddress addr) = 0;
+
+ //@}
+
+ virtual void init() = 0;
+};
diff --git a/src/lib/arch/IArchSleep.h b/src/lib/arch/IArchSleep.h
new file mode 100644
index 0000000..9999d0e
--- /dev/null
+++ b/src/lib/arch/IArchSleep.h
@@ -0,0 +1,44 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+
+//! Interface for architecture dependent sleeping
+/*!
+This interface defines the sleep operations required by
+barrier. 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;
+
+ //@}
+};
diff --git a/src/lib/arch/IArchString.cpp b/src/lib/arch/IArchString.cpp
new file mode 100644
index 0000000..f618c12
--- /dev/null
+++ b/src/lib/arch/IArchString.cpp
@@ -0,0 +1,190 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2011 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/IArchString.h"
+#include "arch/Arch.h"
+#include "common/common.h"
+
+#include <climits>
+#include <cstring>
+#include <cstdlib>
+
+static ArchMutex s_mutex = NULL;
+
+//
+// use C library non-reentrant multibyte conversion with mutex
+//
+
+IArchString::~IArchString()
+{
+ if (s_mutex != NULL) {
+ ARCH->closeMutex(s_mutex);
+ s_mutex = NULL;
+ }
+}
+
+int
+IArchString::convStringWCToMB(char* dst,
+ const wchar_t* src, UInt32 n, bool* errors)
+{
+ ptrdiff_t len = 0;
+
+ bool dummyErrors;
+ if (errors == NULL) {
+ errors = &dummyErrors;
+ }
+
+ if (s_mutex == NULL) {
+ s_mutex = ARCH->newMutex();
+ }
+
+ ARCH->lockMutex(s_mutex);
+
+ if (dst == NULL) {
+ char dummy[MB_LEN_MAX];
+ for (const wchar_t* scan = src; n > 0; ++scan, --n) {
+ ptrdiff_t mblen = wctomb(dummy, *scan);
+ if (mblen == -1) {
+ *errors = true;
+ mblen = 1;
+ }
+ len += mblen;
+ }
+ ptrdiff_t 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) {
+ ptrdiff_t mblen = wctomb(dst, *scan);
+ if (mblen == -1) {
+ *errors = true;
+ *dst++ = '?';
+ }
+ else {
+ dst += mblen;
+ }
+ }
+ ptrdiff_t mblen = wctomb(dst, L'\0');
+ if (mblen != -1) {
+ // don't include nul terminator
+ dst += mblen - 1;
+ }
+ len = dst - dst0;
+ }
+ ARCH->unlockMutex(s_mutex);
+
+ return (int)len;
+}
+
+int
+IArchString::convStringMBToWC(wchar_t* dst,
+ const char* src, UInt32 n_param, bool* errors)
+{
+ ptrdiff_t n = (ptrdiff_t)n_param; // fix compiler warning
+ ptrdiff_t len = 0;
+ wchar_t dummy;
+
+ bool dummyErrors;
+ if (errors == NULL) {
+ errors = &dummyErrors;
+ }
+
+ if (s_mutex == NULL) {
+ s_mutex = ARCH->newMutex();
+ }
+
+ ARCH->lockMutex(s_mutex);
+
+ if (dst == NULL) {
+ for (const char* scan = src; n > 0; ) {
+ ptrdiff_t 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) {
+ ptrdiff_t 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 = dst - dst0;
+ }
+ ARCH->unlockMutex(s_mutex);
+
+ return (int)len;
+}
diff --git a/src/lib/arch/IArchString.h b/src/lib/arch/IArchString.h
new file mode 100644
index 0000000..ea10b65
--- /dev/null
+++ b/src/lib/arch/IArchString.h
@@ -0,0 +1,72 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "common/basic_types.h"
+
+#include <stdarg.h>
+
+//! Interface for architecture dependent string operations
+/*!
+This interface defines the string operations required by
+barrier. Each architecture must implement this interface.
+*/
+class IArchString : public IInterface {
+public:
+ virtual ~IArchString();
+
+ //! 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);
+
+ //! Convert multibyte string to wide character string
+ virtual int convStringMBToWC(wchar_t*,
+ const char*, UInt32 n, bool* errors);
+
+ //! Convert wide character string to multibyte string
+ virtual int convStringWCToMB(char*,
+ const wchar_t*, UInt32 n, bool* errors);
+
+ //! Return the architecture's native wide character encoding
+ virtual EWideCharEncoding
+ getWideCharEncoding() = 0;
+
+ //@}
+};
diff --git a/src/lib/arch/IArchSystem.h b/src/lib/arch/IArchSystem.h
new file mode 100644
index 0000000..9446505
--- /dev/null
+++ b/src/lib/arch/IArchSystem.h
@@ -0,0 +1,66 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "common/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;
+
+ //! Identify the platform
+ /*!
+ Returns a string identifying the platform this OS is running on.
+ */
+ virtual std::string getPlatformName() const = 0;
+ //@}
+
+ //! Get a Barrier setting
+ /*!
+ Reads a Barrier setting from the system.
+ */
+ virtual std::string setting(const std::string& valueName) const = 0;
+ //@}
+
+ //! Set a Barrier setting
+ /*!
+ Writes a Barrier setting from the system.
+ */
+ virtual void setting(const std::string& valueName, const std::string& valueString) const = 0;
+ //@}
+
+ //! Get the pathnames of the libraries used by Barrier
+ /*
+ Returns a string containing the full path names of all loaded libraries at the point it is called.
+ */
+ virtual std::string getLibsUsed(void) const = 0;
+ //@}
+};
diff --git a/src/lib/arch/IArchTaskBar.h b/src/lib/arch/IArchTaskBar.h
new file mode 100644
index 0000000..85a32d8
--- /dev/null
+++ b/src/lib/arch/IArchTaskBar.h
@@ -0,0 +1,63 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+
+class IArchTaskBarReceiver;
+
+//! Interface for architecture dependent task bar control
+/*!
+This interface defines the task bar icon operations required
+by barrier. 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;
+
+ //@}
+
+ virtual void init() = 0;
+};
diff --git a/src/lib/arch/IArchTaskBarReceiver.h b/src/lib/arch/IArchTaskBarReceiver.h
new file mode 100644
index 0000000..8a925b4
--- /dev/null
+++ b/src/lib/arch/IArchTaskBarReceiver.h
@@ -0,0 +1,98 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+#include "common/IInterface.h"
+
+class IScreen;
+class INode;
+
+//! Interface for architecture dependent task bar event handling
+/*!
+This interface defines the task bar icon event handlers required
+by barrier. 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;
+
+ virtual void updateStatus(INode*, const String& errorMsg) = 0;
+
+ virtual void cleanup() {}
+
+ //@}
+};
diff --git a/src/lib/arch/IArchTime.h b/src/lib/arch/IArchTime.h
new file mode 100644
index 0000000..abb3cdd
--- /dev/null
+++ b/src/lib/arch/IArchTime.h
@@ -0,0 +1,41 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+
+//! Interface for architecture dependent time operations
+/*!
+This interface defines the time operations required by
+barrier. 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;
+
+ //@}
+};
diff --git a/src/lib/arch/XArch.h b/src/lib/arch/XArch.h
new file mode 100644
index 0000000..457c620
--- /dev/null
+++ b/src/lib/arch/XArch.h
@@ -0,0 +1,161 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/common.h"
+#include "common/stdstring.h"
+#include "common/stdexcept.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() _NOEXCEPT { }
+
+ virtual std::string eval() const = 0;
+};
+
+//! Generic exception architecture dependent library
+class XArch : public std::runtime_error {
+public:
+ XArch(XArchEval* adopted) : std::runtime_error(adopted->eval()) { delete adopted; }
+ XArch(const std::string& msg) : std::runtime_error(msg) { }
+ virtual ~XArch() _NOEXCEPT { }
+};
+
+// 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);
diff --git a/src/lib/arch/multibyte.h b/src/lib/arch/multibyte.h
new file mode 100644
index 0000000..4a4e0ec
--- /dev/null
+++ b/src/lib/arch/multibyte.h
@@ -0,0 +1,56 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/common.h"
+#include "arch/Arch.h"
+
+#include <climits>
+#include <cstring>
+#include <cstdlib>
+#if HAVE_LOCALE_H
+# include <locale.h>
+#endif
+#if HAVE_WCHAR_H || defined(_MSC_VER)
+# include <wchar.h>
+#elif __APPLE__
+ // wtf? Darwin puts mbtowc() et al. in stdlib
+# include <cstdlib>
+#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<wchar_t>(*src);
+ return 1;
+}
+
+static inline
+int
+wctomb(char* dst, wchar_t src)
+{
+ *dst = static_cast<char>(src);
+ return 1;
+}
+
+#endif
diff --git a/src/lib/arch/unix/ArchConsoleUnix.cpp b/src/lib/arch/unix/ArchConsoleUnix.cpp
new file mode 100644
index 0000000..79a4634
--- /dev/null
+++ b/src/lib/arch/unix/ArchConsoleUnix.cpp
@@ -0,0 +1,23 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchConsoleUnix.h"
+
+ArchConsoleUnix::ArchConsoleUnix() { }
+
+ArchConsoleUnix::~ArchConsoleUnix() { }
diff --git a/src/lib/arch/unix/ArchConsoleUnix.h b/src/lib/arch/unix/ArchConsoleUnix.h
new file mode 100644
index 0000000..8326ab5
--- /dev/null
+++ b/src/lib/arch/unix/ArchConsoleUnix.h
@@ -0,0 +1,29 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/ArchConsoleStd.h"
+
+#define ARCH_CONSOLE ArchConsoleUnix
+
+class ArchConsoleUnix : public ArchConsoleStd {
+public:
+ ArchConsoleUnix();
+ virtual ~ArchConsoleUnix();
+};
diff --git a/src/lib/arch/unix/ArchDaemonUnix.cpp b/src/lib/arch/unix/ArchDaemonUnix.cpp
new file mode 100644
index 0000000..a03bf7a
--- /dev/null
+++ b/src/lib/arch/unix/ArchDaemonUnix.cpp
@@ -0,0 +1,132 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchDaemonUnix.h"
+
+#include "arch/unix/XArchUnix.h"
+#include "base/Log.h"
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <cstdlib>
+
+//
+// ArchDaemonUnix
+//
+
+ArchDaemonUnix::ArchDaemonUnix()
+{
+ // do nothing
+}
+
+ArchDaemonUnix::~ArchDaemonUnix()
+{
+ // do nothing
+}
+
+
+#ifdef __APPLE__
+
+// In Mac OS X, fork()'d child processes can't use most APIs (the frameworks
+// that Barrier uses in fact prevent it and make the process just up and die),
+// so need to exec a copy of the program that doesn't fork so isn't limited.
+int
+execSelfNonDaemonized()
+{
+ extern char** NXArgv;
+ char** selfArgv = NXArgv;
+
+ setenv("_BARRIER_DAEMONIZED", "", 1);
+
+ execvp(selfArgv[0], selfArgv);
+ return 0;
+}
+
+bool alreadyDaemonized() {
+ return getenv("_BARRIER_DAEMONIZED") != NULL;
+}
+
+#endif
+
+int
+ArchDaemonUnix::daemonize(const char* name, DaemonFunc func)
+{
+#ifdef __APPLE__
+ if (alreadyDaemonized())
+ return func(1, &name);
+#endif
+
+ // 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();
+
+#ifndef __APPLE__
+ // NB: don't run chdir on apple; causes strange behaviour.
+ // chdir to root so we don't keep mounted filesystems points busy
+ // TODO: this is a bit of a hack - can we find a better solution?
+ int chdirErr = chdir("/");
+ if (chdirErr)
+ // NB: file logging actually isn't working at this point!
+ LOG((CLOG_ERR "chdir error: %i", chdirErr));
+#endif
+
+ // 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);
+
+ int dupErr = dup(1);
+
+ if (dupErr < 0) {
+ // NB: file logging actually isn't working at this point!
+ LOG((CLOG_ERR "dup error: %i", dupErr));
+ }
+
+#ifdef __APPLE__
+ return execSelfNonDaemonized();
+#endif
+
+ // invoke function
+ return func(1, &name);
+}
diff --git a/src/lib/arch/unix/ArchDaemonUnix.h b/src/lib/arch/unix/ArchDaemonUnix.h
new file mode 100644
index 0000000..530159a
--- /dev/null
+++ b/src/lib/arch/unix/ArchDaemonUnix.h
@@ -0,0 +1,36 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/ArchDaemonNone.h"
+
+#undef ARCH_DAEMON
+#define ARCH_DAEMON ArchDaemonUnix
+
+//! Unix implementation of IArchDaemon
+class ArchDaemonUnix : public ArchDaemonNone {
+public:
+ ArchDaemonUnix();
+ virtual ~ArchDaemonUnix();
+
+ // IArchDaemon overrides
+ virtual int daemonize(const char* name, DaemonFunc func);
+};
+
+#define CONFIG_FILE "/etc/barrier/barrierd.conf"
diff --git a/src/lib/arch/unix/ArchFileUnix.cpp b/src/lib/arch/unix/ArchFileUnix.cpp
new file mode 100644
index 0000000..d9ae8b2
--- /dev/null
+++ b/src/lib/arch/unix/ArchFileUnix.cpp
@@ -0,0 +1,163 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchFileUnix.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <cstring>
+
+//
+// ArchFileUnix
+//
+
+ArchFileUnix::ArchFileUnix()
+{
+ // do nothing
+}
+
+ArchFileUnix::~ArchFileUnix()
+{
+ // do nothing
+}
+
+const char*
+ArchFileUnix::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
+ArchFileUnix::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
+ArchFileUnix::getSystemDirectory()
+{
+ return "/etc";
+}
+
+std::string
+ArchFileUnix::getInstalledDirectory()
+{
+#if WINAPI_XWINDOWS
+ return "/usr/bin";
+#else
+ return "/Applications/Barrier.app/Contents/MacOS";
+#endif
+}
+
+std::string
+ArchFileUnix::getLogDirectory()
+{
+ return "/var/log";
+}
+
+std::string
+ArchFileUnix::getPluginDirectory()
+{
+ if (!m_pluginDirectory.empty()) {
+ return m_pluginDirectory;
+ }
+
+#if WINAPI_XWINDOWS
+ return getProfileDirectory().append("/plugins");
+#else
+ return getProfileDirectory().append("/Plugins");
+#endif
+}
+
+std::string
+ArchFileUnix::getProfileDirectory()
+{
+ String dir;
+ if (!m_profileDirectory.empty()) {
+ dir = m_profileDirectory;
+ }
+ else {
+#if WINAPI_XWINDOWS
+ dir = getUserDirectory().append("/.barrier");
+#else
+ dir = getUserDirectory().append("/Library/Application Support/Barrier");
+#endif
+ }
+ return dir;
+
+}
+
+std::string
+ArchFileUnix::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;
+}
+
+void
+ArchFileUnix::setProfileDirectory(const String& s)
+{
+ m_profileDirectory = s;
+}
+
+void
+ArchFileUnix::setPluginDirectory(const String& s)
+{
+ m_pluginDirectory = s;
+}
diff --git a/src/lib/arch/unix/ArchFileUnix.h b/src/lib/arch/unix/ArchFileUnix.h
new file mode 100644
index 0000000..86dea0c
--- /dev/null
+++ b/src/lib/arch/unix/ArchFileUnix.h
@@ -0,0 +1,47 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchFile.h"
+
+#define ARCH_FILE ArchFileUnix
+
+//! Unix implementation of IArchFile
+class ArchFileUnix : public IArchFile {
+public:
+ ArchFileUnix();
+ virtual ~ArchFileUnix();
+
+ // IArchFile overrides
+ virtual const char* getBasename(const char* pathname);
+ virtual std::string getUserDirectory();
+ virtual std::string getSystemDirectory();
+ virtual std::string getInstalledDirectory();
+ virtual std::string getLogDirectory();
+ virtual std::string getPluginDirectory();
+ virtual std::string getProfileDirectory();
+ virtual std::string concatPath(const std::string& prefix,
+ const std::string& suffix);
+ virtual void setProfileDirectory(const String& s);
+ virtual void setPluginDirectory(const String& s);
+
+private:
+ String m_profileDirectory;
+ String m_pluginDirectory;
+};
diff --git a/src/lib/arch/unix/ArchInternetUnix.cpp b/src/lib/arch/unix/ArchInternetUnix.cpp
new file mode 100644
index 0000000..fd1e135
--- /dev/null
+++ b/src/lib/arch/unix/ArchInternetUnix.cpp
@@ -0,0 +1,126 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchInternetUnix.h"
+
+#include "arch/XArch.h"
+#include "common/Version.h"
+#include "base/Log.h"
+
+#include <sstream>
+#include <curl/curl.h>
+
+class CurlFacade {
+public:
+ CurlFacade();
+ ~CurlFacade();
+ String get(const String& url);
+ String urlEncode(const String& url);
+
+private:
+ CURL* m_curl;
+};
+
+//
+// ArchInternetUnix
+//
+
+String
+ArchInternetUnix::get(const String& url)
+{
+ CurlFacade curl;
+ return curl.get(url);
+}
+
+String
+ArchInternetUnix::urlEncode(const String& url)
+{
+ CurlFacade curl;
+ return curl.urlEncode(url);
+}
+
+//
+// CurlFacade
+//
+
+static size_t
+curlWriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
+{
+ ((std::string*)userp)->append((char*)contents, size * nmemb);
+ return size * nmemb;
+}
+
+CurlFacade::CurlFacade() :
+ m_curl(NULL)
+{
+ CURLcode init = curl_global_init(CURL_GLOBAL_ALL);
+ if (init != CURLE_OK) {
+ throw XArch("CURL global init failed.");
+ }
+
+ m_curl = curl_easy_init();
+ if (m_curl == NULL) {
+ throw XArch("CURL easy init failed.");
+ }
+}
+
+CurlFacade::~CurlFacade()
+{
+ if (m_curl != NULL) {
+ curl_easy_cleanup(m_curl);
+ }
+
+ curl_global_cleanup();
+}
+
+String
+CurlFacade::get(const String& url)
+{
+ curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);
+
+ std::stringstream userAgent;
+ userAgent << "Barrier ";
+ userAgent << kVersion;
+ curl_easy_setopt(m_curl, CURLOPT_USERAGENT, userAgent.str().c_str());
+
+ std::string result;
+ curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &result);
+
+ CURLcode code = curl_easy_perform(m_curl);
+ if (code != CURLE_OK) {
+ LOG((CLOG_ERR "curl perform error: %s", curl_easy_strerror(code)));
+ throw XArch("CURL perform failed.");
+ }
+
+ return result;
+}
+
+String
+CurlFacade::urlEncode(const String& url)
+{
+ char* resultCStr = curl_easy_escape(m_curl, url.c_str(), 0);
+
+ if (resultCStr == NULL) {
+ throw XArch("CURL escape failed.");
+ }
+
+ std::string result(resultCStr);
+ curl_free(resultCStr);
+
+ return result;
+}
diff --git a/src/lib/arch/unix/ArchInternetUnix.h b/src/lib/arch/unix/ArchInternetUnix.h
new file mode 100644
index 0000000..2413d8f
--- /dev/null
+++ b/src/lib/arch/unix/ArchInternetUnix.h
@@ -0,0 +1,28 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#define ARCH_INTERNET ArchInternetUnix
+
+#include "base/String.h"
+
+class ArchInternetUnix {
+public:
+ String get(const String& url);
+ String urlEncode(const String& url);
+};
diff --git a/src/lib/arch/unix/ArchLogUnix.cpp b/src/lib/arch/unix/ArchLogUnix.cpp
new file mode 100644
index 0000000..b1f9089
--- /dev/null
+++ b/src/lib/arch/unix/ArchLogUnix.cpp
@@ -0,0 +1,84 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchLogUnix.h"
+
+#include <syslog.h>
+
+//
+// ArchLogUnix
+//
+
+ArchLogUnix::ArchLogUnix()
+{
+ // do nothing
+}
+
+ArchLogUnix::~ArchLogUnix()
+{
+ // do nothing
+}
+
+void
+ArchLogUnix::openLog(const char* name)
+{
+ openlog(name, 0, LOG_DAEMON);
+}
+
+void
+ArchLogUnix::closeLog()
+{
+ closelog();
+}
+
+void
+ArchLogUnix::showLog(bool)
+{
+ // do nothing
+}
+
+void
+ArchLogUnix::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/src/lib/arch/unix/ArchLogUnix.h b/src/lib/arch/unix/ArchLogUnix.h
new file mode 100644
index 0000000..cdd733f
--- /dev/null
+++ b/src/lib/arch/unix/ArchLogUnix.h
@@ -0,0 +1,36 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchLog.h"
+
+#define ARCH_LOG ArchLogUnix
+
+//! Unix implementation of IArchLog
+class ArchLogUnix : public IArchLog {
+public:
+ ArchLogUnix();
+ virtual ~ArchLogUnix();
+
+ // IArchLog overrides
+ virtual void openLog(const char* name);
+ virtual void closeLog();
+ virtual void showLog(bool);
+ virtual void writeLog(ELevel, const char*);
+};
diff --git a/src/lib/arch/unix/ArchMultithreadPosix.cpp b/src/lib/arch/unix/ArchMultithreadPosix.cpp
new file mode 100644
index 0000000..ade6c51
--- /dev/null
+++ b/src/lib/arch/unix/ArchMultithreadPosix.cpp
@@ -0,0 +1,812 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchMultithreadPosix.h"
+
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+
+#include <signal.h>
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+#include <cerrno>
+
+#define SIGWAKEUP SIGUSR1
+
+#if !HAVE_PTHREAD_SIGNAL
+ // boy, is this platform broken. forget about pthread signal
+ // handling and let signals through to every process. barrier
+ // 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);
+}
+
+//
+// ArchThreadImpl
+//
+
+class ArchThreadImpl {
+public:
+ ArchThreadImpl();
+
+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;
+};
+
+ArchThreadImpl::ArchThreadImpl() :
+ 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
+}
+
+
+//
+// ArchMultithreadPosix
+//
+
+ArchMultithreadPosix* ArchMultithreadPosix::s_instance = NULL;
+
+ArchMultithreadPosix::ArchMultithreadPosix() :
+ 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 ArchThreadImpl;
+ 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);
+}
+
+ArchMultithreadPosix::~ArchMultithreadPosix()
+{
+ assert(s_instance != NULL);
+
+ closeMutex(m_threadMutex);
+ s_instance = NULL;
+}
+
+void
+ArchMultithreadPosix::setNetworkDataForCurrentThread(void* data)
+{
+ lockMutex(m_threadMutex);
+ ArchThreadImpl* thread = find(pthread_self());
+ thread->m_networkData = data;
+ unlockMutex(m_threadMutex);
+}
+
+void*
+ArchMultithreadPosix::getNetworkDataForThread(ArchThread thread)
+{
+ lockMutex(m_threadMutex);
+ void* data = thread->m_networkData;
+ unlockMutex(m_threadMutex);
+ return data;
+}
+
+ArchMultithreadPosix*
+ArchMultithreadPosix::getInstance()
+{
+ return s_instance;
+}
+
+ArchCond
+ArchMultithreadPosix::newCondVar()
+{
+ ArchCondImpl* cond = new ArchCondImpl;
+ int status = pthread_cond_init(&cond->m_cond, NULL);
+ (void)status;
+ assert(status == 0);
+ return cond;
+}
+
+void
+ArchMultithreadPosix::closeCondVar(ArchCond cond)
+{
+ int status = pthread_cond_destroy(&cond->m_cond);
+ (void)status;
+ assert(status == 0);
+ delete cond;
+}
+
+void
+ArchMultithreadPosix::signalCondVar(ArchCond cond)
+{
+ int status = pthread_cond_signal(&cond->m_cond);
+ (void)status;
+ assert(status == 0);
+}
+
+void
+ArchMultithreadPosix::broadcastCondVar(ArchCond cond)
+{
+ int status = pthread_cond_broadcast(&cond->m_cond);
+ (void)status;
+ assert(status == 0);
+}
+
+bool
+ArchMultithreadPosix::waitCondVar(ArchCond cond,
+ ArchMutex 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;
+ }
+}
+
+ArchMutex
+ArchMultithreadPosix::newMutex()
+{
+ pthread_mutexattr_t attr;
+ int status = pthread_mutexattr_init(&attr);
+ assert(status == 0);
+ ArchMutexImpl* mutex = new ArchMutexImpl;
+ status = pthread_mutex_init(&mutex->m_mutex, &attr);
+ assert(status == 0);
+ return mutex;
+}
+
+void
+ArchMultithreadPosix::closeMutex(ArchMutex mutex)
+{
+ int status = pthread_mutex_destroy(&mutex->m_mutex);
+ (void)status;
+ assert(status == 0);
+ delete mutex;
+}
+
+void
+ArchMultithreadPosix::lockMutex(ArchMutex 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
+ArchMultithreadPosix::unlockMutex(ArchMutex 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;
+ }
+}
+
+ArchThread
+ArchMultithreadPosix::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
+ ArchThreadImpl* thread = new ArchThreadImpl;
+ 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,
+ &ArchMultithreadPosix::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;
+}
+
+ArchThread
+ArchMultithreadPosix::newCurrentThread()
+{
+ lockMutex(m_threadMutex);
+ ArchThreadImpl* thread = find(pthread_self());
+ unlockMutex(m_threadMutex);
+ assert(thread != NULL);
+ return thread;
+}
+
+void
+ArchMultithreadPosix::closeThread(ArchThread 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;
+ }
+}
+
+ArchThread
+ArchMultithreadPosix::copyThread(ArchThread thread)
+{
+ refThread(thread);
+ return thread;
+}
+
+void
+ArchMultithreadPosix::cancelThread(ArchThread 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
+ArchMultithreadPosix::setPriorityOfThread(ArchThread thread, int /*n*/)
+{
+ assert(thread != NULL);
+
+ // FIXME
+}
+
+void
+ArchMultithreadPosix::testCancelThread()
+{
+ // find current thread
+ lockMutex(m_threadMutex);
+ ArchThreadImpl* thread = findNoRef(pthread_self());
+ unlockMutex(m_threadMutex);
+
+ // test cancel on thread
+ testCancelThreadImpl(thread);
+}
+
+bool
+ArchMultithreadPosix::wait(ArchThread target, double timeout)
+{
+ assert(target != NULL);
+
+ lockMutex(m_threadMutex);
+
+ // find current thread
+ ArchThreadImpl* 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
+ArchMultithreadPosix::isSameThread(ArchThread thread1, ArchThread thread2)
+{
+ return (thread1 == thread2);
+}
+
+bool
+ArchMultithreadPosix::isExitedThread(ArchThread thread)
+{
+ lockMutex(m_threadMutex);
+ bool exited = thread->m_exited;
+ unlockMutex(m_threadMutex);
+ return exited;
+}
+
+void*
+ArchMultithreadPosix::getResultOfThread(ArchThread thread)
+{
+ lockMutex(m_threadMutex);
+ void* result = thread->m_result;
+ unlockMutex(m_threadMutex);
+ return result;
+}
+
+IArchMultithread::ThreadID
+ArchMultithreadPosix::getIDOfThread(ArchThread thread)
+{
+ return thread->m_id;
+}
+
+void
+ArchMultithreadPosix::setSignalHandler(
+ ESignal signal, SignalFunc func, void* userData)
+{
+ lockMutex(m_threadMutex);
+ m_signalFunc[signal] = func;
+ m_signalUserData[signal] = userData;
+ unlockMutex(m_threadMutex);
+}
+
+void
+ArchMultithreadPosix::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
+ArchMultithreadPosix::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,
+ &ArchMultithreadPosix::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);
+ }
+}
+
+ArchThreadImpl*
+ArchMultithreadPosix::find(pthread_t thread)
+{
+ ArchThreadImpl* impl = findNoRef(thread);
+ if (impl != NULL) {
+ refThread(impl);
+ }
+ return impl;
+}
+
+ArchThreadImpl*
+ArchMultithreadPosix::findNoRef(pthread_t thread)
+{
+ // linear search
+ for (ThreadList::const_iterator index = m_threadList.begin();
+ index != m_threadList.end(); ++index) {
+ if ((*index)->m_thread == thread) {
+ return *index;
+ }
+ }
+ return NULL;
+}
+
+void
+ArchMultithreadPosix::insert(ArchThreadImpl* 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 barrier running that long is vanishingly
+ // small.
+ thread->m_id = ++m_nextID;
+
+ // append to list
+ m_threadList.push_back(thread);
+}
+
+void
+ArchMultithreadPosix::erase(ArchThreadImpl* thread)
+{
+ for (ThreadList::iterator index = m_threadList.begin();
+ index != m_threadList.end(); ++index) {
+ if (*index == thread) {
+ m_threadList.erase(index);
+ break;
+ }
+ }
+}
+
+void
+ArchMultithreadPosix::refThread(ArchThreadImpl* thread)
+{
+ assert(thread != NULL);
+ assert(findNoRef(thread->m_thread) != NULL);
+ ++thread->m_refCount;
+}
+
+void
+ArchMultithreadPosix::testCancelThreadImpl(ArchThreadImpl* 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*
+ArchMultithreadPosix::threadFunc(void* vrep)
+{
+ // get the thread
+ ArchThreadImpl* thread = static_cast<ArchThreadImpl*>(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
+ArchMultithreadPosix::doThreadFunc(ArchThread 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
+ArchMultithreadPosix::threadCancel(int)
+{
+ // do nothing
+}
+
+void*
+ArchMultithreadPosix::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/src/lib/arch/unix/ArchMultithreadPosix.h b/src/lib/arch/unix/ArchMultithreadPosix.h
new file mode 100644
index 0000000..98b5eda
--- /dev/null
+++ b/src/lib/arch/unix/ArchMultithreadPosix.h
@@ -0,0 +1,115 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchMultithread.h"
+#include "common/stdlist.h"
+
+#include <pthread.h>
+
+#define ARCH_MULTITHREAD ArchMultithreadPosix
+
+class ArchCondImpl {
+public:
+ pthread_cond_t m_cond;
+};
+
+class ArchMutexImpl {
+public:
+ pthread_mutex_t m_mutex;
+};
+
+//! Posix implementation of IArchMultithread
+class ArchMultithreadPosix : public IArchMultithread {
+public:
+ ArchMultithreadPosix();
+ virtual ~ArchMultithreadPosix();
+
+ //! @name manipulators
+ //@{
+
+ void setNetworkDataForCurrentThread(void*);
+
+ //@}
+ //! @name accessors
+ //@{
+
+ void* getNetworkDataForThread(ArchThread);
+
+ static ArchMultithreadPosix* getInstance();
+
+ //@}
+
+ // IArchMultithread overrides
+ virtual ArchCond newCondVar();
+ virtual void closeCondVar(ArchCond);
+ virtual void signalCondVar(ArchCond);
+ virtual void broadcastCondVar(ArchCond);
+ virtual bool waitCondVar(ArchCond, ArchMutex, double timeout);
+ virtual ArchMutex newMutex();
+ virtual void closeMutex(ArchMutex);
+ virtual void lockMutex(ArchMutex);
+ virtual void unlockMutex(ArchMutex);
+ virtual ArchThread newThread(ThreadFunc, void*);
+ virtual ArchThread newCurrentThread();
+ virtual ArchThread copyThread(ArchThread);
+ virtual void closeThread(ArchThread);
+ virtual void cancelThread(ArchThread);
+ virtual void setPriorityOfThread(ArchThread, int n);
+ virtual void testCancelThread();
+ virtual bool wait(ArchThread, double timeout);
+ virtual bool isSameThread(ArchThread, ArchThread);
+ virtual bool isExitedThread(ArchThread);
+ virtual void* getResultOfThread(ArchThread);
+ virtual ThreadID getIDOfThread(ArchThread);
+ virtual void setSignalHandler(ESignal, SignalFunc, void*);
+ virtual void raiseSignal(ESignal);
+
+private:
+ void startSignalHandler();
+
+ ArchThreadImpl* find(pthread_t thread);
+ ArchThreadImpl* findNoRef(pthread_t thread);
+ void insert(ArchThreadImpl* thread);
+ void erase(ArchThreadImpl* thread);
+
+ void refThread(ArchThreadImpl* rep);
+ void testCancelThreadImpl(ArchThreadImpl* rep);
+
+ void doThreadFunc(ArchThread thread);
+ static void* threadFunc(void* vrep);
+ static void threadCancel(int);
+ static void* threadSignalHandler(void* vrep);
+
+private:
+ typedef std::list<ArchThread> ThreadList;
+
+ static ArchMultithreadPosix* s_instance;
+
+ bool m_newThreadCalled;
+
+ ArchMutex m_threadMutex;
+ ArchThread m_mainThread;
+ ThreadList m_threadList;
+ ThreadID m_nextID;
+
+ pthread_t m_signalThread;
+ SignalFunc m_signalFunc[kNUM_SIGNALS];
+ void* m_signalUserData[kNUM_SIGNALS];
+};
diff --git a/src/lib/arch/unix/ArchNetworkBSD.cpp b/src/lib/arch/unix/ArchNetworkBSD.cpp
new file mode 100644
index 0000000..149218c
--- /dev/null
+++ b/src/lib/arch/unix/ArchNetworkBSD.cpp
@@ -0,0 +1,1005 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchNetworkBSD.h"
+
+#include "arch/unix/ArchMultithreadPosix.h"
+#include "arch/unix/XArchUnix.h"
+#include "arch/Arch.h"
+
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <netinet/in.h>
+#include <netdb.h>
+#if !defined(TCP_NODELAY)
+# include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#if HAVE_POLL
+# include <poll.h>
+#else
+# if HAVE_SYS_SELECT_H
+# include <sys/select.h>
+# endif
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# endif
+#endif
+
+#if !HAVE_INET_ATON
+# include <stdio.h>
+#endif
+
+static const int s_family[] = {
+ PF_UNSPEC,
+ PF_INET,
+ PF_INET6,
+};
+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
+
+//
+// ArchNetworkBSD
+//
+
+ArchNetworkBSD::ArchNetworkBSD()
+{
+}
+
+ArchNetworkBSD::~ArchNetworkBSD()
+{
+ ARCH->closeMutex(m_mutex);
+}
+
+void
+ArchNetworkBSD::init()
+{
+ // create mutex to make some calls thread safe
+ m_mutex = ARCH->newMutex();
+}
+
+ArchSocket
+ArchNetworkBSD::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
+ ArchSocketImpl* newSocket = new ArchSocketImpl;
+ newSocket->m_fd = fd;
+ newSocket->m_refCount = 1;
+ return newSocket;
+}
+
+ArchSocket
+ArchNetworkBSD::copySocket(ArchSocket s)
+{
+ assert(s != NULL);
+
+ // ref the socket and return it
+ ARCH->lockMutex(m_mutex);
+ ++s->m_refCount;
+ ARCH->unlockMutex(m_mutex);
+ return s;
+}
+
+void
+ArchNetworkBSD::closeSocket(ArchSocket 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
+ArchNetworkBSD::closeSocketForRead(ArchSocket s)
+{
+ assert(s != NULL);
+
+ if (shutdown(s->m_fd, 0) == -1) {
+ if (errno != ENOTCONN) {
+ throwError(errno);
+ }
+ }
+}
+
+void
+ArchNetworkBSD::closeSocketForWrite(ArchSocket s)
+{
+ assert(s != NULL);
+
+ if (shutdown(s->m_fd, 1) == -1) {
+ if (errno != ENOTCONN) {
+ throwError(errno);
+ }
+ }
+}
+
+void
+ArchNetworkBSD::bindSocket(ArchSocket s, ArchNetAddress addr)
+{
+ assert(s != NULL);
+ assert(addr != NULL);
+
+ if (bind(s->m_fd, TYPED_ADDR(struct sockaddr, addr), addr->m_len) == -1) {
+ throwError(errno);
+ }
+}
+
+void
+ArchNetworkBSD::listenOnSocket(ArchSocket s)
+{
+ assert(s != NULL);
+
+ // hardcoding backlog
+ if (listen(s->m_fd, 3) == -1) {
+ throwError(errno);
+ }
+}
+
+ArchSocket
+ArchNetworkBSD::acceptSocket(ArchSocket s, ArchNetAddress* addr)
+{
+ assert(s != NULL);
+
+ // if user passed NULL in addr then use scratch space
+ ArchNetAddress dummy;
+ if (addr == NULL) {
+ addr = &dummy;
+ }
+
+ // create new socket and address
+ ArchSocketImpl* newSocket = new ArchSocketImpl;
+ *addr = new ArchNetAddressImpl;
+
+ // accept on socket
+ ACCEPT_TYPE_ARG3 len = (ACCEPT_TYPE_ARG3)((*addr)->m_len);
+ int fd = accept(s->m_fd, TYPED_ADDR(struct sockaddr, (*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
+ArchNetworkBSD::connectSocket(ArchSocket s, ArchNetAddress addr)
+{
+ assert(s != NULL);
+ assert(addr != NULL);
+
+ if (connect(s->m_fd, TYPED_ADDR(struct sockaddr, addr), addr->m_len) == -1) {
+ if (errno == EISCONN) {
+ return true;
+ }
+ if (errno == EINPROGRESS) {
+ return false;
+ }
+ throwError(errno);
+ }
+ return true;
+}
+
+#if HAVE_POLL
+
+int
+ArchNetworkBSD::pollSocket(PollEntry 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<int>(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];
+ int ignore;
+
+ do {
+ ignore = 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
+ArchNetworkBSD::pollSocket(PollEntry 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<int>(timeout);
+ timeout2.tv_usec = static_cast<int>(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
+ArchNetworkBSD::unblockPollSocket(ArchThread thread)
+{
+ const int* unblockPipe = getUnblockPipeForThread(thread);
+ if (unblockPipe != NULL) {
+ char dummy = 0;
+ int ignore;
+
+ ignore = write(unblockPipe[1], &dummy, 1);
+ }
+}
+
+size_t
+ArchNetworkBSD::readSocket(ArchSocket 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
+ArchNetworkBSD::writeSocket(ArchSocket 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
+ArchNetworkBSD::throwErrorOnSocket(ArchSocket 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
+ArchNetworkBSD::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
+ArchNetworkBSD::setNoDelayOnSocket(ArchSocket 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
+ArchNetworkBSD::setReuseAddrOnSocket(ArchSocket 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
+ArchNetworkBSD::getHostName()
+{
+ char name[256];
+ if (gethostname(name, sizeof(name)) == -1) {
+ name[0] = '\0';
+ }
+ else {
+ name[sizeof(name) - 1] = '\0';
+ }
+ return name;
+}
+
+ArchNetAddress
+ArchNetworkBSD::newAnyAddr(EAddressFamily family)
+{
+ // allocate address
+ ArchNetAddressImpl* addr = new ArchNetAddressImpl;
+
+ // fill it in
+ switch (family) {
+ case kINET: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in, 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;
+ }
+
+ case kINET6: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
+ ipAddr->sin6_family = AF_INET6;
+ ipAddr->sin6_port = 0;
+ memcpy(&ipAddr->sin6_addr, &in6addr_any, sizeof(in6addr_any));
+ addr->m_len = (socklen_t)sizeof(struct sockaddr_in6);
+ break;
+ }
+ default:
+ delete addr;
+ assert(0 && "invalid family");
+ }
+
+ return addr;
+}
+
+ArchNetAddress
+ArchNetworkBSD::copyAddr(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ // allocate and copy address
+ return new ArchNetAddressImpl(*addr);
+}
+
+ArchNetAddress
+ArchNetworkBSD::nameToAddr(const std::string& name)
+{
+ // allocate address
+ ArchNetAddressImpl* addr = new ArchNetAddressImpl;
+
+ char ipstr[INET6_ADDRSTRLEN];
+ struct addrinfo hints;
+ struct addrinfo *p;
+ int ret;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+
+ ARCH->lockMutex(m_mutex);
+ if ((ret = getaddrinfo(name.c_str(), NULL, &hints, &p)) != 0) {
+ ARCH->unlockMutex(m_mutex);
+ delete addr;
+ throwNameError(ret);
+ }
+
+ if (p->ai_family == AF_INET) {
+ addr->m_len = (socklen_t)sizeof(struct sockaddr_in);
+ } else {
+ addr->m_len = (socklen_t)sizeof(struct sockaddr_in6);
+ }
+
+ memcpy(&addr->m_addr, p->ai_addr, addr->m_len);
+ freeaddrinfo(p);
+ ARCH->unlockMutex(m_mutex);
+
+ return addr;
+}
+
+void
+ArchNetworkBSD::closeAddr(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ delete addr;
+}
+
+std::string
+ArchNetworkBSD::addrToName(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ // mutexed name lookup (ugh)
+ ARCH->lockMutex(m_mutex);
+ char host[1024];
+ char service[20];
+ int ret = getnameinfo(TYPED_ADDR(struct sockaddr, addr), addr->m_len, host,
+ sizeof(host), service, sizeof(service), 0);
+ if (ret != 0) {
+ ARCH->unlockMutex(m_mutex);
+ throwNameError(ret);
+ }
+
+ // save (primary) name
+ std::string name = host;
+
+ // done with static buffer
+ ARCH->unlockMutex(m_mutex);
+
+ return name;
+}
+
+std::string
+ArchNetworkBSD::addrToString(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ switch (getAddrFamily(addr)) {
+ case kINET: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
+ ARCH->lockMutex(m_mutex);
+ std::string s = inet_ntoa(ipAddr->sin_addr);
+ ARCH->unlockMutex(m_mutex);
+ return s;
+ }
+
+ case kINET6: {
+ char strAddr[INET6_ADDRSTRLEN];
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
+ ARCH->lockMutex(m_mutex);
+ inet_ntop(AF_INET6, &ipAddr->sin6_addr, strAddr, INET6_ADDRSTRLEN);
+ ARCH->unlockMutex(m_mutex);
+ return strAddr;
+ }
+
+ default:
+ assert(0 && "unknown address family");
+ return "";
+ }
+}
+
+IArchNetwork::EAddressFamily
+ArchNetworkBSD::getAddrFamily(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ switch (addr->m_addr.ss_family) {
+ case AF_INET:
+ return kINET;
+ case AF_INET6:
+ return kINET6;
+
+ default:
+ return kUNKNOWN;
+ }
+}
+
+void
+ArchNetworkBSD::setAddrPort(ArchNetAddress addr, int port)
+{
+ assert(addr != NULL);
+
+ switch (getAddrFamily(addr)) {
+ case kINET: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
+ ipAddr->sin_port = htons(port);
+ break;
+ }
+
+ case kINET6: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
+ ipAddr->sin6_port = htons(port);
+ break;
+ }
+
+ default:
+ assert(0 && "unknown address family");
+ break;
+ }
+}
+
+int
+ArchNetworkBSD::getAddrPort(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ switch (getAddrFamily(addr)) {
+ case kINET: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
+ return ntohs(ipAddr->sin_port);
+ }
+
+ case kINET6: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
+ return ntohs(ipAddr->sin6_port);
+ }
+
+ default:
+ assert(0 && "unknown address family");
+ return 0;
+ }
+}
+
+bool
+ArchNetworkBSD::isAnyAddr(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ switch (getAddrFamily(addr)) {
+ case kINET: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
+ return (ipAddr->sin_addr.s_addr == INADDR_ANY &&
+ addr->m_len == (socklen_t)sizeof(struct sockaddr_in));
+ }
+
+ case kINET6: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
+ return (memcmp(&ipAddr->sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 &&
+ addr->m_len == (socklen_t)sizeof(struct sockaddr_in6));
+ }
+
+ default:
+ assert(0 && "unknown address family");
+ return true;
+ }
+}
+
+bool
+ArchNetworkBSD::isEqualAddr(ArchNetAddress a, ArchNetAddress b)
+{
+ return (a->m_len == b->m_len &&
+ memcmp(&a->m_addr, &b->m_addr, a->m_len) == 0);
+}
+
+const int*
+ArchNetworkBSD::getUnblockPipe()
+{
+ ArchMultithreadPosix* mt = ArchMultithreadPosix::getInstance();
+ ArchThread thread = mt->newCurrentThread();
+ const int* p = getUnblockPipeForThread(thread);
+ ARCH->closeThread(thread);
+ return p;
+}
+
+const int*
+ArchNetworkBSD::getUnblockPipeForThread(ArchThread thread)
+{
+ ArchMultithreadPosix* mt = ArchMultithreadPosix::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
+ArchNetworkBSD::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
+ArchNetworkBSD::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/src/lib/arch/unix/ArchNetworkBSD.h b/src/lib/arch/unix/ArchNetworkBSD.h
new file mode 100644
index 0000000..3f5679a
--- /dev/null
+++ b/src/lib/arch/unix/ArchNetworkBSD.h
@@ -0,0 +1,105 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchNetwork.h"
+#include "arch/IArchMultithread.h"
+
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#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 ArchNetworkBSD
+#define TYPED_ADDR(type_, addr_) (reinterpret_cast<type_*>(&addr_->m_addr))
+
+class ArchSocketImpl {
+public:
+ int m_fd;
+ int m_refCount;
+};
+
+class ArchNetAddressImpl {
+public:
+ ArchNetAddressImpl() : m_len(sizeof(m_addr)) { }
+
+public:
+ struct sockaddr_storage m_addr;
+ socklen_t m_len;
+};
+
+//! Berkeley (BSD) sockets implementation of IArchNetwork
+class ArchNetworkBSD : public IArchNetwork {
+public:
+ ArchNetworkBSD();
+ virtual ~ArchNetworkBSD();
+
+ virtual void init();
+
+ // IArchNetwork overrides
+ virtual ArchSocket newSocket(EAddressFamily, ESocketType);
+ virtual ArchSocket copySocket(ArchSocket s); virtual void closeSocket(ArchSocket s);
+ virtual void closeSocketForRead(ArchSocket s);
+ virtual void closeSocketForWrite(ArchSocket s);
+ virtual void bindSocket(ArchSocket s, ArchNetAddress addr);
+ virtual void listenOnSocket(ArchSocket s);
+ virtual ArchSocket acceptSocket(ArchSocket s, ArchNetAddress* addr);
+ virtual bool connectSocket(ArchSocket s, ArchNetAddress name);
+ virtual int pollSocket(PollEntry[], int num, double timeout);
+ virtual void unblockPollSocket(ArchThread thread);
+ virtual size_t readSocket(ArchSocket s, void* buf, size_t len);
+ virtual size_t writeSocket(ArchSocket s,
+ const void* buf, size_t len);
+ virtual void throwErrorOnSocket(ArchSocket);
+ virtual bool setNoDelayOnSocket(ArchSocket, bool noDelay);
+ virtual bool setReuseAddrOnSocket(ArchSocket, bool reuse);
+ virtual std::string getHostName();
+ virtual ArchNetAddress newAnyAddr(EAddressFamily);
+ virtual ArchNetAddress copyAddr(ArchNetAddress);
+ virtual ArchNetAddress nameToAddr(const std::string&);
+ virtual void closeAddr(ArchNetAddress);
+ virtual std::string addrToName(ArchNetAddress);
+ virtual std::string addrToString(ArchNetAddress);
+ virtual EAddressFamily getAddrFamily(ArchNetAddress);
+ virtual void setAddrPort(ArchNetAddress, int port);
+ virtual int getAddrPort(ArchNetAddress);
+ virtual bool isAnyAddr(ArchNetAddress);
+ virtual bool isEqualAddr(ArchNetAddress, ArchNetAddress);
+
+private:
+ const int* getUnblockPipe();
+ const int* getUnblockPipeForThread(ArchThread);
+ void setBlockingOnSocket(int fd, bool blocking);
+ void throwError(int);
+ void throwNameError(int);
+
+private:
+ ArchMutex m_mutex;
+};
diff --git a/src/lib/arch/unix/ArchSleepUnix.cpp b/src/lib/arch/unix/ArchSleepUnix.cpp
new file mode 100644
index 0000000..48e2600
--- /dev/null
+++ b/src/lib/arch/unix/ArchSleepUnix.cpp
@@ -0,0 +1,94 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchSleepUnix.h"
+
+#include "arch/Arch.h"
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+#if !HAVE_NANOSLEEP
+# if HAVE_SYS_SELECT_H
+# include <sys/select.h>
+# endif
+# if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+# endif
+# if HAVE_UNISTD_H
+# include <unistd.h>
+# endif
+#endif
+
+//
+// ArchSleepUnix
+//
+
+ArchSleepUnix::ArchSleepUnix()
+{
+ // do nothing
+}
+
+ArchSleepUnix::~ArchSleepUnix()
+{
+ // do nothing
+}
+
+void
+ArchSleepUnix::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<int>(timeLeft);
+ timeout2.tv_usec = static_cast<int>(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/src/lib/arch/unix/ArchSleepUnix.h b/src/lib/arch/unix/ArchSleepUnix.h
new file mode 100644
index 0000000..3e307a5
--- /dev/null
+++ b/src/lib/arch/unix/ArchSleepUnix.h
@@ -0,0 +1,33 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchSleep.h"
+
+#define ARCH_SLEEP ArchSleepUnix
+
+//! Unix implementation of IArchSleep
+class ArchSleepUnix : public IArchSleep {
+public:
+ ArchSleepUnix();
+ virtual ~ArchSleepUnix();
+
+ // IArchSleep overrides
+ virtual void sleep(double timeout);
+};
diff --git a/src/lib/arch/unix/ArchStringUnix.cpp b/src/lib/arch/unix/ArchStringUnix.cpp
new file mode 100644
index 0000000..591c826
--- /dev/null
+++ b/src/lib/arch/unix/ArchStringUnix.cpp
@@ -0,0 +1,42 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchStringUnix.h"
+
+#include <stdio.h>
+
+//
+// ArchStringUnix
+//
+
+#include "arch/multibyte.h"
+#include "arch/vsnprintf.h"
+
+ArchStringUnix::ArchStringUnix()
+{
+}
+
+ArchStringUnix::~ArchStringUnix()
+{
+}
+
+IArchString::EWideCharEncoding
+ArchStringUnix::getWideCharEncoding()
+{
+ return kUCS4;
+}
diff --git a/src/lib/arch/unix/ArchStringUnix.h b/src/lib/arch/unix/ArchStringUnix.h
new file mode 100644
index 0000000..f7d0035
--- /dev/null
+++ b/src/lib/arch/unix/ArchStringUnix.h
@@ -0,0 +1,34 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchString.h"
+
+#define ARCH_STRING ArchStringUnix
+
+//! Unix implementation of IArchString
+class ArchStringUnix : public IArchString {
+public:
+ ArchStringUnix();
+ virtual ~ArchStringUnix();
+
+ // IArchString overrides
+ virtual EWideCharEncoding
+ getWideCharEncoding();
+};
diff --git a/src/lib/arch/unix/ArchSystemUnix.cpp b/src/lib/arch/unix/ArchSystemUnix.cpp
new file mode 100644
index 0000000..f51e47f
--- /dev/null
+++ b/src/lib/arch/unix/ArchSystemUnix.cpp
@@ -0,0 +1,80 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchSystemUnix.h"
+
+#include <sys/utsname.h>
+
+//
+// ArchSystemUnix
+//
+
+ArchSystemUnix::ArchSystemUnix()
+{
+ // do nothing
+}
+
+ArchSystemUnix::~ArchSystemUnix()
+{
+ // do nothing
+}
+
+std::string
+ArchSystemUnix::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;
+ return msg;
+ }
+#endif
+ return "Unix";
+}
+
+std::string
+ArchSystemUnix::getPlatformName() const
+{
+#if defined(HAVE_SYS_UTSNAME_H)
+ struct utsname info;
+ if (uname(&info) == 0) {
+ return std::string(info.machine);
+ }
+#endif
+ return "unknown";
+}
+
+std::string
+ArchSystemUnix::setting(const std::string&) const
+{
+ return "";
+}
+
+void
+ArchSystemUnix::setting(const std::string&, const std::string&) const
+{
+}
+
+std::string
+ArchSystemUnix::getLibsUsed(void) const
+{
+ return "not implemented.\nuse lsof on shell";
+}
diff --git a/src/lib/arch/unix/ArchSystemUnix.h b/src/lib/arch/unix/ArchSystemUnix.h
new file mode 100644
index 0000000..aa9c564
--- /dev/null
+++ b/src/lib/arch/unix/ArchSystemUnix.h
@@ -0,0 +1,38 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchSystem.h"
+
+#define ARCH_SYSTEM ArchSystemUnix
+
+//! Unix implementation of IArchString
+class ArchSystemUnix : public IArchSystem {
+public:
+ ArchSystemUnix();
+ virtual ~ArchSystemUnix();
+
+ // IArchSystem overrides
+ virtual std::string getOSName() const;
+ virtual std::string getPlatformName() const;
+ virtual std::string setting(const std::string&) const;
+ virtual void setting(const std::string&, const std::string&) const;
+ virtual std::string getLibsUsed(void) const;
+
+};
diff --git a/src/lib/arch/unix/ArchTaskBarXWindows.cpp b/src/lib/arch/unix/ArchTaskBarXWindows.cpp
new file mode 100644
index 0000000..c3577ad
--- /dev/null
+++ b/src/lib/arch/unix/ArchTaskBarXWindows.cpp
@@ -0,0 +1,51 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchTaskBarXWindows.h"
+
+//
+// ArchTaskBarXWindows
+//
+
+ArchTaskBarXWindows::ArchTaskBarXWindows()
+{
+ // do nothing
+}
+
+ArchTaskBarXWindows::~ArchTaskBarXWindows()
+{
+ // do nothing
+}
+
+void
+ArchTaskBarXWindows::addReceiver(IArchTaskBarReceiver* /*receiver*/)
+{
+ // do nothing
+}
+
+void
+ArchTaskBarXWindows::removeReceiver(IArchTaskBarReceiver* /*receiver*/)
+{
+ // do nothing
+}
+
+void
+ArchTaskBarXWindows::updateReceiver(IArchTaskBarReceiver* /*receiver*/)
+{
+ // do nothing
+}
diff --git a/src/lib/arch/unix/ArchTaskBarXWindows.h b/src/lib/arch/unix/ArchTaskBarXWindows.h
new file mode 100644
index 0000000..f2c8977
--- /dev/null
+++ b/src/lib/arch/unix/ArchTaskBarXWindows.h
@@ -0,0 +1,35 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchTaskBar.h"
+
+#define ARCH_TASKBAR ArchTaskBarXWindows
+
+//! X11 implementation of IArchTaskBar
+class ArchTaskBarXWindows : public IArchTaskBar {
+public:
+ ArchTaskBarXWindows();
+ virtual ~ArchTaskBarXWindows();
+
+ // IArchTaskBar overrides
+ virtual void addReceiver(IArchTaskBarReceiver*);
+ virtual void removeReceiver(IArchTaskBarReceiver*);
+ virtual void updateReceiver(IArchTaskBarReceiver*);
+};
diff --git a/src/lib/arch/unix/ArchTimeUnix.cpp b/src/lib/arch/unix/ArchTimeUnix.cpp
new file mode 100644
index 0000000..24685aa
--- /dev/null
+++ b/src/lib/arch/unix/ArchTimeUnix.cpp
@@ -0,0 +1,52 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/ArchTimeUnix.h"
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+//
+// ArchTimeUnix
+//
+
+ArchTimeUnix::ArchTimeUnix()
+{
+ // do nothing
+}
+
+ArchTimeUnix::~ArchTimeUnix()
+{
+ // do nothing
+}
+
+double
+ArchTimeUnix::time()
+{
+ struct timeval t;
+ gettimeofday(&t, NULL);
+ return (double)t.tv_sec + 1.0e-6 * (double)t.tv_usec;
+}
diff --git a/src/lib/arch/unix/ArchTimeUnix.h b/src/lib/arch/unix/ArchTimeUnix.h
new file mode 100644
index 0000000..3c5c0f8
--- /dev/null
+++ b/src/lib/arch/unix/ArchTimeUnix.h
@@ -0,0 +1,33 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchTime.h"
+
+#define ARCH_TIME ArchTimeUnix
+
+//! Generic Unix implementation of IArchTime
+class ArchTimeUnix : public IArchTime {
+public:
+ ArchTimeUnix();
+ virtual ~ArchTimeUnix();
+
+ // IArchTime overrides
+ virtual double time();
+};
diff --git a/src/lib/arch/unix/XArchUnix.cpp b/src/lib/arch/unix/XArchUnix.cpp
new file mode 100644
index 0000000..fc7ff65
--- /dev/null
+++ b/src/lib/arch/unix/XArchUnix.cpp
@@ -0,0 +1,32 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/unix/XArchUnix.h"
+
+#include <cstring>
+
+//
+// XArchEvalUnix
+//
+
+std::string
+XArchEvalUnix::eval() const
+{
+ // FIXME -- not thread safe
+ return strerror(m_error);
+}
diff --git a/src/lib/arch/unix/XArchUnix.h b/src/lib/arch/unix/XArchUnix.h
new file mode 100644
index 0000000..ae62f4c
--- /dev/null
+++ b/src/lib/arch/unix/XArchUnix.h
@@ -0,0 +1,33 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/XArch.h"
+
+//! Lazy error message string evaluation for unix
+class XArchEvalUnix : public XArchEval {
+public:
+ XArchEvalUnix(int error) : m_error(error) { }
+ virtual ~XArchEvalUnix() _NOEXCEPT { }
+
+ virtual std::string eval() const;
+
+private:
+ int m_error;
+};
diff --git a/src/lib/arch/vsnprintf.h b/src/lib/arch/vsnprintf.h
new file mode 100644
index 0000000..5a4e3dc
--- /dev/null
+++ b/src/lib/arch/vsnprintf.h
@@ -0,0 +1,67 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/IArchString.h"
+
+#if HAVE_VSNPRINTF
+
+#if !defined(ARCH_VSNPRINTF)
+# define ARCH_VSNPRINTF vsnprintf
+#endif
+
+int
+IArchString::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 <stdio.h>
+
+int
+IArchString::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/src/lib/arch/win32/ArchConsoleWindows.cpp b/src/lib/arch/win32/ArchConsoleWindows.cpp
new file mode 100644
index 0000000..4514555
--- /dev/null
+++ b/src/lib/arch/win32/ArchConsoleWindows.cpp
@@ -0,0 +1,23 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchConsoleWindows.h"
+
+ArchConsoleWindows::ArchConsoleWindows() { }
+
+ArchConsoleWindows::~ArchConsoleWindows() { }
diff --git a/src/lib/arch/win32/ArchConsoleWindows.h b/src/lib/arch/win32/ArchConsoleWindows.h
new file mode 100644
index 0000000..f1f0cc9
--- /dev/null
+++ b/src/lib/arch/win32/ArchConsoleWindows.h
@@ -0,0 +1,29 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/ArchConsoleStd.h"
+
+#define ARCH_CONSOLE ArchConsoleWindows
+
+class ArchConsoleWindows : public ArchConsoleStd {
+public:
+ ArchConsoleWindows();
+ virtual ~ArchConsoleWindows();
+};
diff --git a/src/lib/arch/win32/ArchDaemonWindows.cpp b/src/lib/arch/win32/ArchDaemonWindows.cpp
new file mode 100644
index 0000000..efcf235
--- /dev/null
+++ b/src/lib/arch/win32/ArchDaemonWindows.cpp
@@ -0,0 +1,704 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchDaemonWindows.h"
+#include "arch/win32/ArchMiscWindows.h"
+#include "arch/win32/XArchWindows.h"
+#include "arch/Arch.h"
+#include "common/stdvector.h"
+
+#include <sstream>
+
+//
+// ArchDaemonWindows
+//
+
+ArchDaemonWindows* ArchDaemonWindows::s_daemon = NULL;
+
+ArchDaemonWindows::ArchDaemonWindows() :
+m_daemonThreadID(0)
+{
+ m_quitMessage = RegisterWindowMessage("BarrierDaemonExit");
+}
+
+ArchDaemonWindows::~ArchDaemonWindows()
+{
+ // do nothing
+}
+
+int
+ArchDaemonWindows::runDaemon(RunFunc runFunc)
+{
+ assert(s_daemon != NULL);
+ return s_daemon->doRunDaemon(runFunc);
+}
+
+void
+ArchDaemonWindows::daemonRunning(bool running)
+{
+ if (s_daemon != NULL) {
+ s_daemon->doDaemonRunning(running);
+ }
+}
+
+UINT
+ArchDaemonWindows::getDaemonQuitMessage()
+{
+ if (s_daemon != NULL) {
+ return s_daemon->doGetDaemonQuitMessage();
+ }
+ else {
+ return 0;
+ }
+}
+
+void
+ArchDaemonWindows::daemonFailed(int result)
+{
+ assert(s_daemon != NULL);
+ throw XArchDaemonRunFailed(result);
+}
+
+void
+ArchDaemonWindows::installDaemon(const char* name,
+ const char* description,
+ const char* pathname,
+ const char* commandLine,
+ const char* dependencies)
+{
+ // 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));
+ }
+ }
+ else {
+ // done with service (but only try to close if not null)
+ CloseServiceHandle(service);
+ }
+
+ // done with manager
+ CloseServiceHandle(mgr);
+
+ // open the registry key for this service
+ HKEY key = openNTServicesKey();
+ key = ArchMiscWindows::addKey(key, name);
+ if (key == NULL) {
+ // can't open key
+ DWORD err = GetLastError();
+ try {
+ uninstallDaemon(name);
+ }
+ catch (...) {
+ // ignore
+ }
+ throw XArchDaemonInstallFailed(new XArchEvalWindows(err));
+ }
+
+ // set the description
+ ArchMiscWindows::setValue(key, _T("Description"), description);
+
+ // set command line
+ key = ArchMiscWindows::addKey(key, _T("Parameters"));
+ if (key == NULL) {
+ // can't open key
+ DWORD err = GetLastError();
+ ArchMiscWindows::closeKey(key);
+ try {
+ uninstallDaemon(name);
+ }
+ catch (...) {
+ // ignore
+ }
+ throw XArchDaemonInstallFailed(new XArchEvalWindows(err));
+ }
+ ArchMiscWindows::setValue(key, _T("CommandLine"), commandLine);
+
+ // done with registry
+ ArchMiscWindows::closeKey(key);
+}
+
+void
+ArchDaemonWindows::uninstallDaemon(const char* name)
+{
+ // remove parameters for this service. ignore failures.
+ HKEY key = openNTServicesKey();
+ key = ArchMiscWindows::openKey(key, name);
+ if (key != NULL) {
+ ArchMiscWindows::deleteKey(key, _T("Parameters"));
+ ArchMiscWindows::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);
+
+ // give windows a chance to remove the service before
+ // we check if it still exists.
+ ARCH->sleep(1);
+
+ // handle failure. ignore error if service isn't installed anymore.
+ if (!okay && isDaemonInstalled(name)) {
+ if (err == ERROR_SUCCESS) {
+ // this seems to occur even though the uninstall was successful.
+ // it could be a timing issue, i.e., isDaemonInstalled is
+ // called too soon. i've added a sleep to try and stop this.
+ return;
+ }
+ 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
+ArchDaemonWindows::daemonize(const char* name, DaemonFunc func)
+{
+ assert(name != NULL);
+ assert(func != NULL);
+
+ // save daemon function
+ m_daemonFunc = func;
+
+ // construct the service entry
+ SERVICE_TABLE_ENTRY entry[2];
+ entry[0].lpServiceName = const_cast<char*>(name);
+ entry[0].lpServiceProc = &ArchDaemonWindows::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
+ArchDaemonWindows::canInstallDaemon(const char* /*name*/)
+{
+ // 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();
+ ArchMiscWindows::closeKey(key);
+
+ return (key != NULL);
+}
+
+bool
+ArchDaemonWindows::isDaemonInstalled(const char* name)
+{
+ // 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
+ArchDaemonWindows::openNTServicesKey()
+{
+ static const char* s_keyNames[] = {
+ _T("SYSTEM"),
+ _T("CurrentControlSet"),
+ _T("Services"),
+ NULL
+ };
+
+ return ArchMiscWindows::addKey(HKEY_LOCAL_MACHINE, s_keyNames);
+}
+
+bool
+ArchDaemonWindows::isRunState(DWORD state)
+{
+ switch (state) {
+ case SERVICE_START_PENDING:
+ case SERVICE_CONTINUE_PENDING:
+ case SERVICE_RUNNING:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+int
+ArchDaemonWindows::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
+ArchDaemonWindows::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
+ArchDaemonWindows::doGetDaemonQuitMessage()
+{
+ return m_quitMessage;
+}
+
+void
+ArchDaemonWindows::setStatus(DWORD state)
+{
+ setStatus(state, 0, 0);
+}
+
+void
+ArchDaemonWindows::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
+ArchDaemonWindows::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
+ArchDaemonWindows::serviceMain(DWORD argc, LPTSTR* argvIn)
+{
+ typedef std::vector<LPCTSTR> ArgList;
+ typedef std::vector<std::string> Arguments;
+ const char** argv = const_cast<const char**>(argvIn);
+
+ // create synchronization objects
+ m_serviceMutex = ARCH->newMutex();
+ m_serviceCondVar = ARCH->newCondVar();
+
+ // register our service handler function
+ m_statusHandle = RegisterServiceCtrlHandler(argv[0],
+ &ArchDaemonWindows::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);
+
+ std::string commandLine;
+
+ // 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
+ HKEY key = openNTServicesKey();
+ key = ArchMiscWindows::openKey(key, argvIn[0]);
+ key = ArchMiscWindows::openKey(key, _T("Parameters"));
+ if (key != NULL) {
+ commandLine = ArchMiscWindows::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 j = 0; j < args.size(); ++j) {
+ myArgv.push_back(args[j].c_str());
+ }
+
+ // adjust argc/argv
+ argc = (DWORD)myArgv.size();
+ argv = &myArgv[0];
+ }
+ }
+
+ m_commandLine = commandLine;
+
+ try {
+ // invoke daemon function
+ m_daemonResult = m_daemonFunc(static_cast<int>(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);
+
+ // we're going to exit now, so set status to stopped
+ m_serviceState = SERVICE_STOPPED;
+ setStatus(m_serviceState, 0, 10000);
+}
+
+void WINAPI
+ArchDaemonWindows::serviceMainEntry(DWORD argc, LPTSTR* argv)
+{
+ s_daemon->serviceMain(argc, argv);
+}
+
+void
+ArchDaemonWindows::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
+ArchDaemonWindows::serviceHandlerEntry(DWORD ctrl)
+{
+ s_daemon->serviceHandler(ctrl);
+}
+
+void
+ArchDaemonWindows::start(const char* name)
+{
+ // open service manager
+ SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ);
+ if (mgr == NULL) {
+ throw XArchDaemonFailed(new XArchEvalWindows());
+ }
+
+ // open the service
+ SC_HANDLE service = OpenService(
+ mgr, name, SERVICE_START);
+
+ if (service == NULL) {
+ CloseServiceHandle(mgr);
+ throw XArchDaemonFailed(new XArchEvalWindows());
+ }
+
+ // start the service
+ if (!StartService(service, 0, NULL)) {
+ throw XArchDaemonFailed(new XArchEvalWindows());
+ }
+}
+
+void
+ArchDaemonWindows::stop(const char* name)
+{
+ // open service manager
+ SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ);
+ if (mgr == NULL) {
+ throw XArchDaemonFailed(new XArchEvalWindows());
+ }
+
+ // open the service
+ SC_HANDLE service = OpenService(
+ mgr, name,
+ SERVICE_STOP | SERVICE_QUERY_STATUS);
+
+ if (service == NULL) {
+ CloseServiceHandle(mgr);
+ throw XArchDaemonFailed(new XArchEvalWindows());
+ }
+
+ // ask the service to stop, asynchronously
+ SERVICE_STATUS ss;
+ if (!ControlService(service, SERVICE_CONTROL_STOP, &ss)) {
+ DWORD dwErrCode = GetLastError();
+ if (dwErrCode != ERROR_SERVICE_NOT_ACTIVE) {
+ throw XArchDaemonFailed(new XArchEvalWindows());
+ }
+ }
+}
+
+void
+ArchDaemonWindows::installDaemon()
+{
+ // install default daemon if not already installed.
+ if (!isDaemonInstalled(DEFAULT_DAEMON_NAME)) {
+ char path[MAX_PATH];
+ GetModuleFileName(ArchMiscWindows::instanceWin32(), path, MAX_PATH);
+
+ // wrap in quotes so a malicious user can't start \Program.exe as admin.
+ std::stringstream ss;
+ ss << '"';
+ ss << path;
+ ss << '"';
+
+ installDaemon(DEFAULT_DAEMON_NAME, DEFAULT_DAEMON_INFO, ss.str().c_str(), "", "");
+ }
+
+ start(DEFAULT_DAEMON_NAME);
+}
+
+void
+ArchDaemonWindows::uninstallDaemon()
+{
+ // remove service if installed.
+ if (isDaemonInstalled(DEFAULT_DAEMON_NAME)) {
+ uninstallDaemon(DEFAULT_DAEMON_NAME);
+ }
+}
diff --git a/src/lib/arch/win32/ArchDaemonWindows.h b/src/lib/arch/win32/ArchDaemonWindows.h
new file mode 100644
index 0000000..2db9792
--- /dev/null
+++ b/src/lib/arch/win32/ArchDaemonWindows.h
@@ -0,0 +1,151 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchDaemon.h"
+#include "arch/IArchMultithread.h"
+#include "common/stdstring.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <tchar.h>
+
+#define ARCH_DAEMON ArchDaemonWindows
+
+//! Win32 implementation of IArchDaemon
+class ArchDaemonWindows : public IArchDaemon {
+public:
+ typedef int (*RunFunc)(void);
+
+ ArchDaemonWindows();
+ virtual ~ArchDaemonWindows();
+
+ //! 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);
+ virtual void uninstallDaemon(const char* name);
+ virtual void installDaemon();
+ virtual void uninstallDaemon();
+ virtual int daemonize(const char* name, DaemonFunc func);
+ virtual bool canInstallDaemon(const char* name);
+ virtual bool isDaemonInstalled(const char* name);
+ std::string commandLine() const { return m_commandLine; }
+
+private:
+ static HKEY openNTServicesKey();
+
+ 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);
+
+ void start(const char* name);
+ void stop(const char* name);
+
+private:
+ class XArchDaemonRunFailed {
+ public:
+ XArchDaemonRunFailed(int result) : m_result(result) { }
+
+ public:
+ int m_result;
+ };
+
+private:
+ static ArchDaemonWindows* s_daemon;
+
+ ArchMutex m_serviceMutex;
+ ArchCond 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;
+
+ std::string m_commandLine;
+};
+
+#define DEFAULT_DAEMON_NAME _T("Barrier")
+#define DEFAULT_DAEMON_INFO _T("Manages the Barrier foreground processes.")
+
+static const TCHAR* const g_daemonKeyPath[] = {
+ _T("SOFTWARE"),
+ _T("The Barrier Project"),
+ _T("Barrier"),
+ _T("Service"),
+ NULL
+};
diff --git a/src/lib/arch/win32/ArchFileWindows.cpp b/src/lib/arch/win32/ArchFileWindows.cpp
new file mode 100644
index 0000000..53b4b59
--- /dev/null
+++ b/src/lib/arch/win32/ArchFileWindows.cpp
@@ -0,0 +1,203 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchFileWindows.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <shlobj.h>
+#include <tchar.h>
+#include <string.h>
+
+//
+// ArchFileWindows
+//
+
+ArchFileWindows::ArchFileWindows()
+{
+ // do nothing
+}
+
+ArchFileWindows::~ArchFileWindows()
+{
+ // do nothing
+}
+
+const char*
+ArchFileWindows::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
+ArchFileWindows::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
+ArchFileWindows::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
+ArchFileWindows::getInstalledDirectory()
+{
+ char fileNameBuffer[MAX_PATH];
+ GetModuleFileName(NULL, fileNameBuffer, MAX_PATH);
+ std::string fileName(fileNameBuffer);
+ size_t lastSlash = fileName.find_last_of("\\");
+ fileName = fileName.substr(0, lastSlash);
+
+ return fileName;
+}
+
+std::string
+ArchFileWindows::getLogDirectory()
+{
+ return getInstalledDirectory();
+}
+
+std::string
+ArchFileWindows::getPluginDirectory()
+{
+ if (!m_pluginDirectory.empty()) {
+ return m_pluginDirectory;
+ }
+
+ std::string dir = getProfileDirectory();
+ dir.append("\\Plugins");
+ return dir;
+}
+
+std::string
+ArchFileWindows::getProfileDirectory()
+{
+ String dir;
+ if (!m_profileDirectory.empty()) {
+ dir = m_profileDirectory;
+ }
+ else {
+ TCHAR result[MAX_PATH];
+ if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, result))) {
+ dir = result;
+ }
+ else {
+ dir = getUserDirectory();
+ }
+ }
+
+ // HACK: append program name, this seems wrong.
+ dir.append("\\Barrier");
+
+ return dir;
+}
+
+std::string
+ArchFileWindows::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;
+}
+
+void
+ArchFileWindows::setProfileDirectory(const String& s)
+{
+ m_profileDirectory = s;
+}
+
+void
+ArchFileWindows::setPluginDirectory(const String& s)
+{
+ m_pluginDirectory = s;
+}
diff --git a/src/lib/arch/win32/ArchFileWindows.h b/src/lib/arch/win32/ArchFileWindows.h
new file mode 100644
index 0000000..4747b9c
--- /dev/null
+++ b/src/lib/arch/win32/ArchFileWindows.h
@@ -0,0 +1,47 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchFile.h"
+
+#define ARCH_FILE ArchFileWindows
+
+//! Win32 implementation of IArchFile
+class ArchFileWindows : public IArchFile {
+public:
+ ArchFileWindows();
+ virtual ~ArchFileWindows();
+
+ // IArchFile overrides
+ virtual const char* getBasename(const char* pathname);
+ virtual std::string getUserDirectory();
+ virtual std::string getSystemDirectory();
+ virtual std::string getInstalledDirectory();
+ virtual std::string getLogDirectory();
+ virtual std::string getPluginDirectory();
+ virtual std::string getProfileDirectory();
+ virtual std::string concatPath(const std::string& prefix,
+ const std::string& suffix);
+ virtual void setProfileDirectory(const String& s);
+ virtual void setPluginDirectory(const String& s);
+
+private:
+ String m_profileDirectory;
+ String m_pluginDirectory;
+};
diff --git a/src/lib/arch/win32/ArchInternetWindows.cpp b/src/lib/arch/win32/ArchInternetWindows.cpp
new file mode 100644
index 0000000..7f69c7f
--- /dev/null
+++ b/src/lib/arch/win32/ArchInternetWindows.cpp
@@ -0,0 +1,224 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchInternetWindows.h"
+#include "arch/win32/XArchWindows.h"
+#include "arch/Arch.h"
+#include "common/Version.h"
+
+#include <sstream>
+#include <Wininet.h>
+#include <Shlwapi.h>
+
+struct WinINetUrl {
+ String m_scheme;
+ String m_host;
+ String m_path;
+ INTERNET_PORT m_port;
+ DWORD m_flags;
+};
+
+class WinINetRequest {
+public:
+ WinINetRequest(const String& url);
+ ~WinINetRequest();
+
+ String send();
+ void openSession();
+ void connect();
+ void openRequest();
+
+private:
+ HINTERNET m_session;
+ HINTERNET m_connect;
+ HINTERNET m_request;
+ WinINetUrl m_url;
+ bool m_used;
+};
+
+//
+// ArchInternetWindows
+//
+
+String
+ArchInternetWindows::get(const String& url)
+{
+ WinINetRequest request(url);
+ return request.send();
+}
+
+String
+ArchInternetWindows::urlEncode(const String& url)
+{
+ TCHAR buffer[1024];
+ DWORD bufferSize = sizeof(buffer);
+
+ if (UrlEscape(url.c_str(), buffer, &bufferSize, URL_ESCAPE_UNSAFE) != S_OK) {
+ throw XArch(new XArchEvalWindows());
+ }
+
+ String result(buffer);
+
+ // the win32 url encoding funcitons are pretty useless (to us) and only
+ // escape "unsafe" chars, but not + or =, so we need to replace these
+ // manually (and probably many other chars).
+ barrier::string::findReplaceAll(result, "+", "%2B");
+ barrier::string::findReplaceAll(result, "=", "%3D");
+
+ return result;
+}
+
+//
+// WinINetRequest
+//
+
+static WinINetUrl parseUrl(const String& url);
+
+WinINetRequest::WinINetRequest(const String& url) :
+ m_session(NULL),
+ m_connect(NULL),
+ m_request(NULL),
+ m_used(false),
+ m_url(parseUrl(url))
+{
+}
+
+WinINetRequest::~WinINetRequest()
+{
+ if (m_request != NULL) {
+ InternetCloseHandle(m_request);
+ }
+
+ if (m_connect != NULL) {
+ InternetCloseHandle(m_connect);
+ }
+
+ if (m_session != NULL) {
+ InternetCloseHandle(m_session);
+ }
+}
+
+String
+WinINetRequest::send()
+{
+ if (m_used) {
+ throw XArch("class is one time use.");
+ }
+ m_used = true;
+
+ openSession();
+ connect();
+ openRequest();
+
+ String headers("Content-Type: text/html");
+ if (!HttpSendRequest(m_request, headers.c_str(), (DWORD)headers.length(), NULL, NULL)) {
+ throw XArch(new XArchEvalWindows());
+ }
+
+ std::stringstream result;
+ CHAR buffer[1025];
+ DWORD read = 0;
+
+ while (InternetReadFile(m_request, buffer, sizeof(buffer) - 1, &read) && (read != 0)) {
+ buffer[read] = 0;
+ result << buffer;
+ read = 0;
+ }
+
+ return result.str();
+}
+
+void
+WinINetRequest::openSession()
+{
+ std::stringstream userAgent;
+ userAgent << "Barrier ";
+ userAgent << kVersion;
+
+ m_session = InternetOpen(
+ userAgent.str().c_str(),
+ INTERNET_OPEN_TYPE_PRECONFIG,
+ NULL,
+ NULL,
+ NULL);
+
+ if (m_session == NULL) {
+ throw XArch(new XArchEvalWindows());
+ }
+}
+
+void
+WinINetRequest::connect()
+{
+ m_connect = InternetConnect(
+ m_session,
+ m_url.m_host.c_str(),
+ m_url.m_port,
+ NULL,
+ NULL,
+ INTERNET_SERVICE_HTTP,
+ NULL,
+ NULL);
+
+ if (m_connect == NULL) {
+ throw XArch(new XArchEvalWindows());
+ }
+}
+
+void
+WinINetRequest::openRequest()
+{
+ m_request = HttpOpenRequest(
+ m_connect,
+ "GET",
+ m_url.m_path.c_str(),
+ HTTP_VERSION,
+ NULL,
+ NULL,
+ m_url.m_flags,
+ NULL);
+
+ if (m_request == NULL) {
+ throw XArch(new XArchEvalWindows());
+ }
+}
+
+// nb: i tried to use InternetCrackUrl here, but couldn't quite get that to
+// work. here's some (less robust) code to split the url into components.
+// this works fine with simple urls, but doesn't consider the full url spec.
+static WinINetUrl
+parseUrl(const String& url)
+{
+ WinINetUrl parsed;
+
+ size_t schemeEnd = url.find("://");
+ size_t hostEnd = url.find('/', schemeEnd + 3);
+
+ parsed.m_scheme = url.substr(0, schemeEnd);
+ parsed.m_host = url.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
+ parsed.m_path = url.substr(hostEnd);
+
+ parsed.m_port = INTERNET_DEFAULT_HTTP_PORT;
+ parsed.m_flags = 0;
+
+ if (parsed.m_scheme.find("https") != String::npos) {
+ parsed.m_port = INTERNET_DEFAULT_HTTPS_PORT;
+ parsed.m_flags = INTERNET_FLAG_SECURE;
+ }
+
+ return parsed;
+}
diff --git a/src/lib/arch/win32/ArchInternetWindows.h b/src/lib/arch/win32/ArchInternetWindows.h
new file mode 100644
index 0000000..bab8d3c
--- /dev/null
+++ b/src/lib/arch/win32/ArchInternetWindows.h
@@ -0,0 +1,28 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#define ARCH_INTERNET ArchInternetWindows
+
+#include "base/String.h"
+
+class ArchInternetWindows {
+public:
+ String get(const String& url);
+ String urlEncode(const String& url);
+};
diff --git a/src/lib/arch/win32/ArchLogWindows.cpp b/src/lib/arch/win32/ArchLogWindows.cpp
new file mode 100644
index 0000000..bc17abf
--- /dev/null
+++ b/src/lib/arch/win32/ArchLogWindows.cpp
@@ -0,0 +1,95 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchLogWindows.h"
+#include "arch/win32/ArchMiscWindows.h"
+
+#include <string.h>
+
+//
+// ArchLogWindows
+//
+
+ArchLogWindows::ArchLogWindows() : m_eventLog(NULL)
+{
+ // do nothing
+}
+
+ArchLogWindows::~ArchLogWindows()
+{
+ // do nothing
+}
+
+void
+ArchLogWindows::openLog(const char* name)
+{
+ if (m_eventLog == NULL) {
+ m_eventLog = RegisterEventSource(NULL, name);
+ }
+}
+
+void
+ArchLogWindows::closeLog()
+{
+ if (m_eventLog != NULL) {
+ DeregisterEventSource(m_eventLog);
+ m_eventLog = NULL;
+ }
+}
+
+void
+ArchLogWindows::showLog(bool)
+{
+ // do nothing
+}
+
+void
+ArchLogWindows::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<WORD>(level),
+ 0, // event ID
+ NULL,
+ 0,
+ (DWORD)strlen(msg) + 1, // raw data size
+ NULL,
+ const_cast<char*>(msg));// raw data
+ }
+}
diff --git a/src/lib/arch/win32/ArchLogWindows.h b/src/lib/arch/win32/ArchLogWindows.h
new file mode 100644
index 0000000..3a997f1
--- /dev/null
+++ b/src/lib/arch/win32/ArchLogWindows.h
@@ -0,0 +1,42 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchLog.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+#define ARCH_LOG ArchLogWindows
+
+//! Win32 implementation of IArchLog
+class ArchLogWindows : public IArchLog {
+public:
+ ArchLogWindows();
+ virtual ~ArchLogWindows();
+
+ // 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;
+};
diff --git a/src/lib/arch/win32/ArchMiscWindows.cpp b/src/lib/arch/win32/ArchMiscWindows.cpp
new file mode 100644
index 0000000..924afa2
--- /dev/null
+++ b/src/lib/arch/win32/ArchMiscWindows.cpp
@@ -0,0 +1,524 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchMiscWindows.h"
+#include "arch/win32/ArchDaemonWindows.h"
+#include "base/Log.h"
+#include "common/Version.h"
+
+#include <Wtsapi32.h>
+#pragma warning(disable: 4099)
+#include <Userenv.h>
+#pragma warning(default: 4099)
+
+// parent process name for services in Vista
+#define SERVICE_LAUNCHER "services.exe"
+
+#ifndef ES_SYSTEM_REQUIRED
+#define ES_SYSTEM_REQUIRED ((DWORD)0x00000001)
+#endif
+#ifndef ES_DISPLAY_REQUIRED
+#define ES_DISPLAY_REQUIRED ((DWORD)0x00000002)
+#endif
+#ifndef ES_CONTINUOUS
+#define ES_CONTINUOUS ((DWORD)0x80000000)
+#endif
+typedef DWORD EXECUTION_STATE;
+
+//
+// ArchMiscWindows
+//
+
+ArchMiscWindows::Dialogs* ArchMiscWindows::s_dialogs = NULL;
+DWORD ArchMiscWindows::s_busyState = 0;
+ArchMiscWindows::STES_t ArchMiscWindows::s_stes = NULL;
+HICON ArchMiscWindows::s_largeIcon = NULL;
+HICON ArchMiscWindows::s_smallIcon = NULL;
+HINSTANCE ArchMiscWindows::s_instanceWin32 = NULL;
+
+void
+ArchMiscWindows::cleanup()
+{
+ delete s_dialogs;
+}
+
+void
+ArchMiscWindows::init()
+{
+ // stop windows system error dialogs from showing.
+ SetErrorMode(SEM_FAILCRITICALERRORS);
+
+ s_dialogs = new Dialogs;
+}
+
+void
+ArchMiscWindows::setIcons(HICON largeIcon, HICON smallIcon)
+{
+ s_largeIcon = largeIcon;
+ s_smallIcon = smallIcon;
+}
+
+void
+ArchMiscWindows::getIcons(HICON& largeIcon, HICON& smallIcon)
+{
+ largeIcon = s_largeIcon;
+ smallIcon = s_smallIcon;
+}
+
+int
+ArchMiscWindows::runDaemon(RunFunc runFunc)
+{
+ return ArchDaemonWindows::runDaemon(runFunc);
+}
+
+void
+ArchMiscWindows::daemonRunning(bool running)
+{
+ ArchDaemonWindows::daemonRunning(running);
+}
+
+void
+ArchMiscWindows::daemonFailed(int result)
+{
+ ArchDaemonWindows::daemonFailed(result);
+}
+
+UINT
+ArchMiscWindows::getDaemonQuitMessage()
+{
+ return ArchDaemonWindows::getDaemonQuitMessage();
+}
+
+HKEY
+ArchMiscWindows::openKey(HKEY key, const TCHAR* keyName)
+{
+ return openKey(key, keyName, false);
+}
+
+HKEY
+ArchMiscWindows::openKey(HKEY key, const TCHAR* const* keyNames)
+{
+ return openKey(key, keyNames, false);
+}
+
+HKEY
+ArchMiscWindows::addKey(HKEY key, const TCHAR* keyName)
+{
+ return openKey(key, keyName, true);
+}
+
+HKEY
+ArchMiscWindows::addKey(HKEY key, const TCHAR* const* keyNames)
+{
+ return openKey(key, keyNames, true);
+}
+
+HKEY
+ArchMiscWindows::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
+ArchMiscWindows::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
+ArchMiscWindows::closeKey(HKEY key)
+{
+ assert(key != NULL);
+ if (key==NULL) return;
+ RegCloseKey(key);
+}
+
+void
+ArchMiscWindows::deleteKey(HKEY key, const TCHAR* name)
+{
+ assert(key != NULL);
+ assert(name != NULL);
+ if (key==NULL || name==NULL) return;
+ RegDeleteKey(key, name);
+}
+
+void
+ArchMiscWindows::deleteValue(HKEY key, const TCHAR* name)
+{
+ assert(key != NULL);
+ assert(name != NULL);
+ if (key==NULL || name==NULL) return;
+ RegDeleteValue(key, name);
+}
+
+bool
+ArchMiscWindows::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));
+}
+
+ArchMiscWindows::EValueType
+ArchMiscWindows::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
+ArchMiscWindows::setValue(HKEY key,
+ const TCHAR* name, const std::string& value)
+{
+ assert(key != NULL);
+ if (key == NULL) {
+ // TODO: throw exception
+ return;
+ }
+ RegSetValueEx(key, name, 0, REG_SZ,
+ reinterpret_cast<const BYTE*>(value.c_str()),
+ (DWORD)value.size() + 1);
+}
+
+void
+ArchMiscWindows::setValue(HKEY key, const TCHAR* name, DWORD value)
+{
+ assert(key != NULL);
+ if (key == NULL) {
+ // TODO: throw exception
+ return;
+ }
+ RegSetValueEx(key, name, 0, REG_DWORD,
+ reinterpret_cast<CONST BYTE*>(&value),
+ sizeof(DWORD));
+}
+
+void
+ArchMiscWindows::setValueBinary(HKEY key,
+ const TCHAR* name, const std::string& value)
+{
+ assert(key != NULL);
+ assert(name != NULL);
+ if (key == NULL || name == NULL) {
+ // TODO: throw exception
+ return;
+ }
+ RegSetValueEx(key, name, 0, REG_BINARY,
+ reinterpret_cast<const BYTE*>(value.data()),
+ (DWORD)value.size());
+}
+
+std::string
+ArchMiscWindows::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<BYTE*>(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
+ArchMiscWindows::readValueString(HKEY key, const TCHAR* name)
+{
+ return readBinaryOrString(key, name, REG_SZ);
+}
+
+std::string
+ArchMiscWindows::readValueBinary(HKEY key, const TCHAR* name)
+{
+ return readBinaryOrString(key, name, REG_BINARY);
+}
+
+DWORD
+ArchMiscWindows::readValueInt(HKEY key, const TCHAR* name)
+{
+ DWORD type;
+ DWORD value;
+ DWORD size = sizeof(value);
+ LONG result = RegQueryValueEx(key, name, 0, &type,
+ reinterpret_cast<BYTE*>(&value), &size);
+ if (result != ERROR_SUCCESS || type != REG_DWORD) {
+ return 0;
+ }
+ return value;
+}
+
+void
+ArchMiscWindows::addDialog(HWND hwnd)
+{
+ s_dialogs->insert(hwnd);
+}
+
+void
+ArchMiscWindows::removeDialog(HWND hwnd)
+{
+ s_dialogs->erase(hwnd);
+}
+
+bool
+ArchMiscWindows::processDialog(MSG* msg)
+{
+ for (Dialogs::const_iterator index = s_dialogs->begin();
+ index != s_dialogs->end(); ++index) {
+ if (IsDialogMessage(*index, msg)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+ArchMiscWindows::addBusyState(DWORD busyModes)
+{
+ s_busyState |= busyModes;
+ setThreadExecutionState(s_busyState);
+}
+
+void
+ArchMiscWindows::removeBusyState(DWORD busyModes)
+{
+ s_busyState &= ~busyModes;
+ setThreadExecutionState(s_busyState);
+}
+
+void
+ArchMiscWindows::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<STES_t>(GetProcAddress(kernel,
+ "SetThreadExecutionState"));
+ }
+ if (s_stes == NULL) {
+ s_stes = &ArchMiscWindows::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
+ArchMiscWindows::dummySetThreadExecutionState(DWORD)
+{
+ // do nothing
+ return 0;
+}
+
+void
+ArchMiscWindows::wakeupDisplay()
+{
+ // We can't use ::setThreadExecutionState here because it sets
+ // ES_CONTINUOUS, which we don't want.
+
+ if (s_stes == NULL) {
+ HINSTANCE kernel = LoadLibrary("kernel32.dll");
+ if (kernel != NULL) {
+ s_stes = reinterpret_cast<STES_t>(GetProcAddress(kernel,
+ "SetThreadExecutionState"));
+ }
+ if (s_stes == NULL) {
+ s_stes = &ArchMiscWindows::dummySetThreadExecutionState;
+ }
+ }
+
+ s_stes(ES_DISPLAY_REQUIRED);
+
+ // restore the original execution states
+ setThreadExecutionState(s_busyState);
+}
+
+bool
+ArchMiscWindows::wasLaunchedAsService()
+{
+ String name;
+ if (!getParentProcessName(name)) {
+ LOG((CLOG_ERR "cannot determine if process was launched as service"));
+ return false;
+ }
+
+ return (name == SERVICE_LAUNCHER);
+}
+
+bool
+ArchMiscWindows::getParentProcessName(String &name)
+{
+ PROCESSENTRY32 parentEntry;
+ if (!getParentProcessEntry(parentEntry)){
+ LOG((CLOG_ERR "could not get entry for parent process"));
+ return false;
+ }
+
+ name = parentEntry.szExeFile;
+ return true;
+}
+
+BOOL WINAPI
+ArchMiscWindows::getSelfProcessEntry(PROCESSENTRY32& entry)
+{
+ // get entry from current PID
+ return getProcessEntry(entry, GetCurrentProcessId());
+}
+
+BOOL WINAPI
+ArchMiscWindows::getParentProcessEntry(PROCESSENTRY32& entry)
+{
+ // get the current process, so we can get parent PID
+ PROCESSENTRY32 selfEntry;
+ if (!getSelfProcessEntry(selfEntry)) {
+ return FALSE;
+ }
+
+ // get entry from parent PID
+ return getProcessEntry(entry, selfEntry.th32ParentProcessID);
+}
+
+BOOL WINAPI
+ArchMiscWindows::getProcessEntry(PROCESSENTRY32& entry, DWORD processID)
+{
+ // first we need to take a snapshot of the running processes
+ HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (snapshot == INVALID_HANDLE_VALUE) {
+ LOG((CLOG_ERR "could not get process snapshot (error: %i)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ entry.dwSize = sizeof(PROCESSENTRY32);
+
+ // get the first process, and if we can't do that then it's
+ // unlikely we can go any further
+ BOOL gotEntry = Process32First(snapshot, &entry);
+ if (!gotEntry) {
+ LOG((CLOG_ERR "could not get first process entry (error: %i)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ while(gotEntry) {
+
+ if (entry.th32ProcessID == processID) {
+ // found current process
+ return TRUE;
+ }
+
+ // now move on to the next entry (when we reach end, loop will stop)
+ gotEntry = Process32Next(snapshot, &entry);
+ }
+
+ return FALSE;
+}
+
+HINSTANCE
+ArchMiscWindows::instanceWin32()
+{
+ assert(s_instanceWin32 != NULL);
+ return s_instanceWin32;
+}
+
+void
+ArchMiscWindows::setInstanceWin32(HINSTANCE instance)
+{
+ assert(instance != NULL);
+ s_instanceWin32 = instance;
+} \ No newline at end of file
diff --git a/src/lib/arch/win32/ArchMiscWindows.h b/src/lib/arch/win32/ArchMiscWindows.h
new file mode 100644
index 0000000..0ecd79d
--- /dev/null
+++ b/src/lib/arch/win32/ArchMiscWindows.h
@@ -0,0 +1,202 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/common.h"
+#include "common/stdstring.h"
+#include "common/stdset.h"
+#include "base/String.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <Tlhelp32.h>
+
+//! Miscellaneous win32 functions.
+class ArchMiscWindows {
+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();
+
+ //! Delete memory
+ static void cleanup();
+
+ //! 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 ArchDaemonWindows.
+ */
+ static int runDaemon(RunFunc runFunc);
+
+ //! Indicate daemon is in main loop
+ /*!
+ Delegates to ArchDaemonWindows.
+ */
+ static void daemonRunning(bool running);
+
+ //! Indicate failure of running daemon
+ /*!
+ Delegates to ArchDaemonWindows.
+ */
+ static void daemonFailed(int result);
+
+ //! Get daemon quit message
+ /*!
+ Delegates to ArchDaemonWindows.
+ */
+ 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);
+
+ //! Briefly interrupt power saving
+ static void wakeupDisplay();
+
+ //! Returns true if this process was launched via NT service host.
+ static bool wasLaunchedAsService();
+
+ //! Returns true if we got the parent process name.
+ static bool getParentProcessName(String &name);
+
+ static HINSTANCE instanceWin32();
+
+ static void setInstanceWin32(HINSTANCE instance);
+
+ static BOOL WINAPI getProcessEntry(PROCESSENTRY32& entry, DWORD processID);
+ static BOOL WINAPI getSelfProcessEntry(PROCESSENTRY32& entry);
+ static BOOL WINAPI getParentProcessEntry(PROCESSENTRY32& entry);
+
+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<HWND> Dialogs;
+ typedef DWORD (WINAPI *STES_t)(DWORD);
+
+ static Dialogs* s_dialogs;
+ static DWORD s_busyState;
+ static STES_t s_stes;
+ static HICON s_largeIcon;
+ static HICON s_smallIcon;
+ static HINSTANCE s_instanceWin32;
+};
diff --git a/src/lib/arch/win32/ArchMultithreadWindows.cpp b/src/lib/arch/win32/ArchMultithreadWindows.cpp
new file mode 100644
index 0000000..d3fd059
--- /dev/null
+++ b/src/lib/arch/win32/ArchMultithreadWindows.cpp
@@ -0,0 +1,705 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if defined(_MSC_VER) && !defined(_MT)
+# error multithreading compile option is required
+#endif
+
+#include "arch/win32/ArchMultithreadWindows.h"
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+
+#include <process.h>
+
+//
+// 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.
+//
+
+//
+// ArchThreadImpl
+//
+
+class ArchThreadImpl {
+public:
+ ArchThreadImpl();
+ ~ArchThreadImpl();
+
+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;
+};
+
+ArchThreadImpl::ArchThreadImpl() :
+ 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);
+}
+
+ArchThreadImpl::~ArchThreadImpl()
+{
+ CloseHandle(m_exit);
+ CloseHandle(m_cancel);
+}
+
+
+//
+// ArchMultithreadWindows
+//
+
+ArchMultithreadWindows* ArchMultithreadWindows::s_instance = NULL;
+
+ArchMultithreadWindows::ArchMultithreadWindows()
+{
+ 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 ArchThreadImpl;
+ m_mainThread->m_thread = NULL;
+ m_mainThread->m_id = GetCurrentThreadId();
+ insert(m_mainThread);
+}
+
+ArchMultithreadWindows::~ArchMultithreadWindows()
+{
+ s_instance = NULL;
+
+ // clean up thread list
+ for (ThreadList::iterator index = m_threadList.begin();
+ index != m_threadList.end(); ++index) {
+ delete *index;
+ }
+
+ // done with mutex
+ delete m_threadMutex;
+}
+
+void
+ArchMultithreadWindows::setNetworkDataForCurrentThread(void* data)
+{
+ lockMutex(m_threadMutex);
+ ArchThreadImpl* thread = findNoRef(GetCurrentThreadId());
+ thread->m_networkData = data;
+ unlockMutex(m_threadMutex);
+}
+
+void*
+ArchMultithreadWindows::getNetworkDataForThread(ArchThread thread)
+{
+ lockMutex(m_threadMutex);
+ void* data = thread->m_networkData;
+ unlockMutex(m_threadMutex);
+ return data;
+}
+
+HANDLE
+ArchMultithreadWindows::getCancelEventForCurrentThread()
+{
+ lockMutex(m_threadMutex);
+ ArchThreadImpl* thread = findNoRef(GetCurrentThreadId());
+ unlockMutex(m_threadMutex);
+ return thread->m_cancel;
+}
+
+ArchMultithreadWindows*
+ArchMultithreadWindows::getInstance()
+{
+ return s_instance;
+}
+
+ArchCond
+ArchMultithreadWindows::newCondVar()
+{
+ ArchCondImpl* cond = new ArchCondImpl;
+ cond->m_events[ArchCondImpl::kSignal] = CreateEvent(NULL,
+ FALSE, FALSE, NULL);
+ cond->m_events[ArchCondImpl::kBroadcast] = CreateEvent(NULL,
+ TRUE, FALSE, NULL);
+ cond->m_waitCountMutex = newMutex();
+ cond->m_waitCount = 0;
+ return cond;
+}
+
+void
+ArchMultithreadWindows::closeCondVar(ArchCond cond)
+{
+ CloseHandle(cond->m_events[ArchCondImpl::kSignal]);
+ CloseHandle(cond->m_events[ArchCondImpl::kBroadcast]);
+ closeMutex(cond->m_waitCountMutex);
+ delete cond;
+}
+
+void
+ArchMultithreadWindows::signalCondVar(ArchCond 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[ArchCondImpl::kSignal]);
+ }
+}
+
+void
+ArchMultithreadWindows::broadcastCondVar(ArchCond 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[ArchCondImpl::kBroadcast]);
+ }
+}
+
+bool
+ArchMultithreadWindows::waitCondVar(ArchCond cond,
+ ArchMutex mutex, double timeout)
+{
+ // prepare to wait
+ const DWORD winTimeout = (timeout < 0.0) ? INFINITE :
+ static_cast<DWORD>(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[ArchCondImpl::kSignal];
+ handles[1] = cond->m_events[ArchCondImpl::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
+ // TODO: this doesn't always signal when kill signals are sent
+ 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[ArchCondImpl::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);
+}
+
+ArchMutex
+ArchMultithreadWindows::newMutex()
+{
+ ArchMutexImpl* mutex = new ArchMutexImpl;
+ InitializeCriticalSection(&mutex->m_mutex);
+ return mutex;
+}
+
+void
+ArchMultithreadWindows::closeMutex(ArchMutex mutex)
+{
+ DeleteCriticalSection(&mutex->m_mutex);
+ delete mutex;
+}
+
+void
+ArchMultithreadWindows::lockMutex(ArchMutex mutex)
+{
+ EnterCriticalSection(&mutex->m_mutex);
+}
+
+void
+ArchMultithreadWindows::unlockMutex(ArchMutex mutex)
+{
+ LeaveCriticalSection(&mutex->m_mutex);
+}
+
+ArchThread
+ArchMultithreadWindows::newThread(ThreadFunc func, void* data)
+{
+ lockMutex(m_threadMutex);
+
+ // create thread impl for new thread
+ ArchThreadImpl* thread = new ArchThreadImpl;
+ thread->m_func = func;
+ thread->m_userData = data;
+
+ // create thread
+ unsigned int id = 0;
+ thread->m_thread = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0,
+ threadFunc, (void*)thread, 0, &id));
+ thread->m_id = static_cast<DWORD>(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;
+}
+
+ArchThread
+ArchMultithreadWindows::newCurrentThread()
+{
+ lockMutex(m_threadMutex);
+ ArchThreadImpl* thread = find(GetCurrentThreadId());
+ unlockMutex(m_threadMutex);
+ assert(thread != NULL);
+ return thread;
+}
+
+void
+ArchMultithreadWindows::closeThread(ArchThread 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;
+ }
+}
+
+ArchThread
+ArchMultithreadWindows::copyThread(ArchThread thread)
+{
+ refThread(thread);
+ return thread;
+}
+
+void
+ArchMultithreadWindows::cancelThread(ArchThread thread)
+{
+ assert(thread != NULL);
+
+ // set cancel flag
+ SetEvent(thread->m_cancel);
+}
+
+void
+ArchMultithreadWindows::setPriorityOfThread(ArchThread thread, int n)
+{
+ struct PriorityInfo {
+ public:
+ DWORD m_class;
+ int m_level;
+ };
+ static const PriorityInfo 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
+ArchMultithreadWindows::testCancelThread()
+{
+ // find current thread
+ lockMutex(m_threadMutex);
+ ArchThreadImpl* thread = findNoRef(GetCurrentThreadId());
+ unlockMutex(m_threadMutex);
+
+ // test cancel on thread
+ testCancelThreadImpl(thread);
+}
+
+bool
+ArchMultithreadWindows::wait(ArchThread target, double timeout)
+{
+ assert(target != NULL);
+
+ lockMutex(m_threadMutex);
+
+ // find current thread
+ ArchThreadImpl* 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
+ArchMultithreadWindows::isSameThread(ArchThread thread1, ArchThread thread2)
+{
+ return (thread1 == thread2);
+}
+
+bool
+ArchMultithreadWindows::isExitedThread(ArchThread thread)
+{
+ // poll exit event
+ return (WaitForSingleObject(thread->m_exit, 0) == WAIT_OBJECT_0);
+}
+
+void*
+ArchMultithreadWindows::getResultOfThread(ArchThread thread)
+{
+ lockMutex(m_threadMutex);
+ void* result = thread->m_result;
+ unlockMutex(m_threadMutex);
+ return result;
+}
+
+IArchMultithread::ThreadID
+ArchMultithreadWindows::getIDOfThread(ArchThread thread)
+{
+ return static_cast<ThreadID>(thread->m_id);
+}
+
+void
+ArchMultithreadWindows::setSignalHandler(
+ ESignal signal, SignalFunc func, void* userData)
+{
+ lockMutex(m_threadMutex);
+ m_signalFunc[signal] = func;
+ m_signalUserData[signal] = userData;
+ unlockMutex(m_threadMutex);
+}
+
+void
+ArchMultithreadWindows::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);
+}
+
+ArchThreadImpl*
+ArchMultithreadWindows::find(DWORD id)
+{
+ ArchThreadImpl* impl = findNoRef(id);
+ if (impl != NULL) {
+ refThread(impl);
+ }
+ return impl;
+}
+
+ArchThreadImpl*
+ArchMultithreadWindows::findNoRef(DWORD id)
+{
+ ArchThreadImpl* 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 ArchThreadImpl;
+ impl->m_thread = NULL;
+ impl->m_id = GetCurrentThreadId();
+ insert(impl);
+ }
+ return impl;
+}
+
+ArchThreadImpl*
+ArchMultithreadWindows::findNoRefOrCreate(DWORD id)
+{
+ // linear search
+ for (ThreadList::const_iterator index = m_threadList.begin();
+ index != m_threadList.end(); ++index) {
+ if ((*index)->m_id == id) {
+ return *index;
+ }
+ }
+ return NULL;
+}
+
+void
+ArchMultithreadWindows::insert(ArchThreadImpl* 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
+ArchMultithreadWindows::erase(ArchThreadImpl* thread)
+{
+ for (ThreadList::iterator index = m_threadList.begin();
+ index != m_threadList.end(); ++index) {
+ if (*index == thread) {
+ m_threadList.erase(index);
+ break;
+ }
+ }
+}
+
+void
+ArchMultithreadWindows::refThread(ArchThreadImpl* thread)
+{
+ assert(thread != NULL);
+ assert(findNoRefOrCreate(thread->m_id) != NULL);
+ ++thread->m_refCount;
+}
+
+void
+ArchMultithreadWindows::testCancelThreadImpl(ArchThreadImpl* 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
+ArchMultithreadWindows::threadFunc(void* vrep)
+{
+ // get the thread
+ ArchThreadImpl* thread = static_cast<ArchThreadImpl*>(vrep);
+
+ // run thread
+ s_instance->doThreadFunc(thread);
+
+ // terminate the thread
+ return 0;
+}
+
+void
+ArchMultithreadWindows::doThreadFunc(ArchThread 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/src/lib/arch/win32/ArchMultithreadWindows.h b/src/lib/arch/win32/ArchMultithreadWindows.h
new file mode 100644
index 0000000..99aa640
--- /dev/null
+++ b/src/lib/arch/win32/ArchMultithreadWindows.h
@@ -0,0 +1,116 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchMultithread.h"
+#include "common/stdlist.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+#define ARCH_MULTITHREAD ArchMultithreadWindows
+
+class ArchCondImpl {
+public:
+ enum { kSignal = 0, kBroadcast };
+
+ HANDLE m_events[2];
+ mutable int m_waitCount;
+ ArchMutex m_waitCountMutex;
+};
+
+class ArchMutexImpl {
+public:
+ CRITICAL_SECTION m_mutex;
+};
+
+//! Win32 implementation of IArchMultithread
+class ArchMultithreadWindows : public IArchMultithread {
+public:
+ ArchMultithreadWindows();
+ virtual ~ArchMultithreadWindows();
+
+ //! @name manipulators
+ //@{
+
+ void setNetworkDataForCurrentThread(void*);
+
+ //@}
+ //! @name accessors
+ //@{
+
+ HANDLE getCancelEventForCurrentThread();
+
+ void* getNetworkDataForThread(ArchThread);
+
+ static ArchMultithreadWindows* getInstance();
+
+ //@}
+
+ // IArchMultithread overrides
+ virtual ArchCond newCondVar();
+ virtual void closeCondVar(ArchCond);
+ virtual void signalCondVar(ArchCond);
+ virtual void broadcastCondVar(ArchCond);
+ virtual bool waitCondVar(ArchCond, ArchMutex, double timeout);
+ virtual ArchMutex newMutex();
+ virtual void closeMutex(ArchMutex);
+ virtual void lockMutex(ArchMutex);
+ virtual void unlockMutex(ArchMutex);
+ virtual ArchThread newThread(ThreadFunc, void*);
+ virtual ArchThread newCurrentThread();
+ virtual ArchThread copyThread(ArchThread);
+ virtual void closeThread(ArchThread);
+ virtual void cancelThread(ArchThread);
+ virtual void setPriorityOfThread(ArchThread, int n);
+ virtual void testCancelThread();
+ virtual bool wait(ArchThread, double timeout);
+ virtual bool isSameThread(ArchThread, ArchThread);
+ virtual bool isExitedThread(ArchThread);
+ virtual void* getResultOfThread(ArchThread);
+ virtual ThreadID getIDOfThread(ArchThread);
+ virtual void setSignalHandler(ESignal, SignalFunc, void*);
+ virtual void raiseSignal(ESignal);
+
+private:
+ ArchThreadImpl* find(DWORD id);
+ ArchThreadImpl* findNoRef(DWORD id);
+ ArchThreadImpl* findNoRefOrCreate(DWORD id);
+ void insert(ArchThreadImpl* thread);
+ void erase(ArchThreadImpl* thread);
+
+ void refThread(ArchThreadImpl* rep);
+ void testCancelThreadImpl(ArchThreadImpl* rep);
+
+ void doThreadFunc(ArchThread thread);
+ static unsigned int __stdcall threadFunc(void* vrep);
+
+private:
+ typedef std::list<ArchThread> ThreadList;
+
+ static ArchMultithreadWindows* s_instance;
+
+ ArchMutex m_threadMutex;
+
+ ThreadList m_threadList;
+ ArchThread m_mainThread;
+
+ SignalFunc m_signalFunc[kNUM_SIGNALS];
+ void* m_signalUserData[kNUM_SIGNALS];
+};
diff --git a/src/lib/arch/win32/ArchNetworkWinsock.cpp b/src/lib/arch/win32/ArchNetworkWinsock.cpp
new file mode 100644
index 0000000..722c4c5
--- /dev/null
+++ b/src/lib/arch/win32/ArchNetworkWinsock.cpp
@@ -0,0 +1,985 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchNetworkWinsock.h"
+#include "arch/win32/ArchMultithreadWindows.h"
+#include "arch/win32/XArchWindows.h"
+#include "arch/IArchMultithread.h"
+#include "arch/Arch.h"
+
+#include <malloc.h>
+
+static const int s_family[] = {
+ PF_UNSPEC,
+ PF_INET,
+ PF_INET6,
+};
+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;
+}
+
+ArchNetAddressImpl*
+ArchNetAddressImpl::alloc(size_t size)
+{
+ size_t totalSize = size + ADDR_HDR_SIZE;
+ ArchNetAddressImpl* addr = (ArchNetAddressImpl*)malloc(totalSize);
+ addr->m_len = (int)size;
+ return addr;
+}
+
+
+//
+// ArchNetworkWinsock
+//
+
+ArchNetworkWinsock::ArchNetworkWinsock() :
+ m_mutex(NULL)
+{
+}
+
+ArchNetworkWinsock::~ArchNetworkWinsock()
+{
+ if (s_networkModule != NULL) {
+ WSACleanup_winsock();
+ ::FreeLibrary(s_networkModule);
+
+ WSACleanup_winsock = NULL;
+ s_networkModule = NULL;
+ }
+ if (m_mutex != NULL) {
+ ARCH->closeMutex(m_mutex);
+ }
+
+ EventList::iterator it;
+ for (it = m_unblockEvents.begin(); it != m_unblockEvents.end(); it++) {
+ delete *it;
+ }
+}
+
+void
+ArchNetworkWinsock::init()
+{
+ 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 {
+ initModule((HMODULE)::LoadLibrary(s_library[i]));
+ m_mutex = ARCH->newMutex();
+ return;
+ }
+ catch (XArchNetwork&) {
+ // ignore
+ }
+ }
+
+ // can't initialize any library
+ throw XArchNetworkSupport("Cannot load winsock library");
+}
+
+void
+ArchNetworkWinsock::initModule(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*/, 2 /*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;
+}
+
+ArchSocket
+ArchNetworkWinsock::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);
+ BOOL flag = 0;
+ int size = sizeof(flag);
+ if (setsockopt_winsock(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, size) == SOCKET_ERROR) {
+ throwError(getsockerror_winsock());
+ }
+ }
+ catch (...) {
+ close_winsock(fd);
+ throw;
+ }
+
+ // allocate socket object
+ ArchSocketImpl* socket = new ArchSocketImpl;
+ socket->m_socket = fd;
+ socket->m_refCount = 1;
+ socket->m_event = WSACreateEvent_winsock();
+ socket->m_pollWrite = true;
+ return socket;
+}
+
+ArchSocket
+ArchNetworkWinsock::copySocket(ArchSocket s)
+{
+ assert(s != NULL);
+
+ // ref the socket and return it
+ ARCH->lockMutex(m_mutex);
+ ++s->m_refCount;
+ ARCH->unlockMutex(m_mutex);
+ return s;
+}
+
+void
+ArchNetworkWinsock::closeSocket(ArchSocket 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
+ArchNetworkWinsock::closeSocketForRead(ArchSocket s)
+{
+ assert(s != NULL);
+
+ if (shutdown_winsock(s->m_socket, SD_RECEIVE) == SOCKET_ERROR) {
+ if (getsockerror_winsock() != WSAENOTCONN) {
+ throwError(getsockerror_winsock());
+ }
+ }
+}
+
+void
+ArchNetworkWinsock::closeSocketForWrite(ArchSocket s)
+{
+ assert(s != NULL);
+
+ if (shutdown_winsock(s->m_socket, SD_SEND) == SOCKET_ERROR) {
+ if (getsockerror_winsock() != WSAENOTCONN) {
+ throwError(getsockerror_winsock());
+ }
+ }
+}
+
+void
+ArchNetworkWinsock::bindSocket(ArchSocket s, ArchNetAddress addr)
+{
+ assert(s != NULL);
+ assert(addr != NULL);
+
+ if (bind_winsock(s->m_socket, TYPED_ADDR(struct sockaddr, addr), addr->m_len) == SOCKET_ERROR) {
+ throwError(getsockerror_winsock());
+ }
+}
+
+void
+ArchNetworkWinsock::listenOnSocket(ArchSocket s)
+{
+ assert(s != NULL);
+
+ // hardcoding backlog
+ if (listen_winsock(s->m_socket, 3) == SOCKET_ERROR) {
+ throwError(getsockerror_winsock());
+ }
+}
+
+ArchSocket
+ArchNetworkWinsock::acceptSocket(ArchSocket s, ArchNetAddress* const addr)
+{
+ assert(s != NULL);
+
+ // create new socket and temporary address
+ ArchSocketImpl* socket = new ArchSocketImpl;
+ ArchNetAddress tmp = ArchNetAddressImpl::alloc(sizeof(struct sockaddr_in6));
+
+ // accept on socket
+ SOCKET fd = accept_winsock(s->m_socket, TYPED_ADDR(struct sockaddr, tmp), &tmp->m_len);
+ if (fd == INVALID_SOCKET) {
+ int err = getsockerror_winsock();
+ delete socket;
+ free(tmp);
+ if (addr) {
+ *addr = NULL;
+ }
+ if (err == WSAEWOULDBLOCK) {
+ return NULL;
+ }
+ throwError(err);
+ }
+
+ try {
+ setBlockingOnSocket(fd, false);
+ }
+ catch (...) {
+ close_winsock(fd);
+ delete socket;
+ free(tmp);
+ if (addr) {
+ *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
+ArchNetworkWinsock::connectSocket(ArchSocket s, ArchNetAddress addr)
+{
+ assert(s != NULL);
+ assert(addr != NULL);
+
+ if (connect_winsock(s->m_socket, TYPED_ADDR(struct sockaddr, addr),
+ addr->m_len) == SOCKET_ERROR) {
+ if (getsockerror_winsock() == WSAEISCONN) {
+ return true;
+ }
+ if (getsockerror_winsock() == WSAEWOULDBLOCK) {
+ return false;
+ }
+ throwError(getsockerror_winsock());
+ }
+ return true;
+}
+
+int
+ArchNetworkWinsock::pollSocket(PollEntry 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
+ ArchMultithreadWindows* mt = ArchMultithreadWindows::getInstance();
+ ArchThread thread = mt->newCurrentThread();
+ WSAEVENT* unblockEvent = (WSAEVENT*)mt->getNetworkDataForThread(thread);
+ ARCH->closeThread(thread);
+ if (unblockEvent == NULL) {
+ unblockEvent = new WSAEVENT;
+ m_unblockEvents.push_back(unblockEvent);
+ *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
+ArchNetworkWinsock::unblockPollSocket(ArchThread thread)
+{
+ // set the unblock event
+ ArchMultithreadWindows* mt = ArchMultithreadWindows::getInstance();
+ WSAEVENT* unblockEvent = (WSAEVENT*)mt->getNetworkDataForThread(thread);
+ if (unblockEvent != NULL) {
+ WSASetEvent_winsock(*unblockEvent);
+ }
+}
+
+size_t
+ArchNetworkWinsock::readSocket(ArchSocket s, void* buf, size_t len)
+{
+ assert(s != NULL);
+
+ int n = recv_winsock(s->m_socket, buf, (int)len, 0);
+ if (n == SOCKET_ERROR) {
+ int err = getsockerror_winsock();
+ if (err == WSAEINTR || err == WSAEWOULDBLOCK) {
+ return 0;
+ }
+ throwError(err);
+ }
+ return static_cast<size_t>(n);
+}
+
+size_t
+ArchNetworkWinsock::writeSocket(ArchSocket s, const void* buf, size_t len)
+{
+ assert(s != NULL);
+
+ int n = send_winsock(s->m_socket, buf, (int)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<size_t>(n);
+}
+
+void
+ArchNetworkWinsock::throwErrorOnSocket(ArchSocket 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
+ArchNetworkWinsock::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
+ArchNetworkWinsock::setNoDelayOnSocket(ArchSocket 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
+ArchNetworkWinsock::setReuseAddrOnSocket(ArchSocket 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
+ArchNetworkWinsock::getHostName()
+{
+ char name[256];
+ if (gethostname_winsock(name, sizeof(name)) == -1) {
+ name[0] = '\0';
+ }
+ else {
+ name[sizeof(name) - 1] = '\0';
+ }
+ return name;
+}
+
+ArchNetAddress
+ArchNetworkWinsock::newAnyAddr(EAddressFamily family)
+{
+ ArchNetAddressImpl* addr = NULL;
+ switch (family) {
+ case kINET: {
+ addr = ArchNetAddressImpl::alloc(sizeof(struct sockaddr_in));
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
+ ipAddr->sin_family = AF_INET;
+ ipAddr->sin_port = 0;
+ ipAddr->sin_addr.s_addr = INADDR_ANY;
+ break;
+ }
+
+ case kINET6: {
+ addr = ArchNetAddressImpl::alloc(sizeof(struct sockaddr_in6));
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
+ ipAddr->sin6_family = AF_INET6;
+ ipAddr->sin6_port = 0;
+ memcpy(&ipAddr->sin6_addr, &in6addr_any, sizeof(in6addr_any));
+ break;
+ }
+
+ default:
+ assert(0 && "invalid family");
+ }
+ return addr;
+}
+
+ArchNetAddress
+ArchNetworkWinsock::copyAddr(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ ArchNetAddressImpl* copy = ArchNetAddressImpl::alloc(addr->m_len);
+ memcpy(TYPED_ADDR(void, copy), TYPED_ADDR(void, addr), addr->m_len);
+ return copy;
+}
+
+ArchNetAddress
+ArchNetworkWinsock::nameToAddr(const std::string& name)
+{
+ // allocate address
+
+ ArchNetAddressImpl* addr = new ArchNetAddressImpl;
+
+ struct addrinfo hints;
+ struct addrinfo *p;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ int ret = -1;
+
+ ARCH->lockMutex(m_mutex);
+ if ((ret = getaddrinfo(name.c_str(), NULL, &hints, &p)) != 0) {
+ ARCH->unlockMutex(m_mutex);
+ delete addr;
+ throwNameError(ret);
+ }
+
+ if (p->ai_family == AF_INET) {
+ addr->m_len = (socklen_t)sizeof(struct sockaddr_in);
+ } else {
+ addr->m_len = (socklen_t)sizeof(struct sockaddr_in6);
+ }
+
+ memcpy(&addr->m_addr, p->ai_addr, addr->m_len);
+ freeaddrinfo(p);
+ ARCH->unlockMutex(m_mutex);
+ return addr;
+}
+
+void
+ArchNetworkWinsock::closeAddr(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ free(addr);
+}
+
+std::string
+ArchNetworkWinsock::addrToName(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ char host[1024];
+ char service[20];
+ int ret = getnameinfo(TYPED_ADDR(struct sockaddr, addr), addr->m_len, host, sizeof(host), service, sizeof(service), 0);
+
+ if (ret != NULL) {
+ throwNameError(ret);
+ }
+
+ // return (primary) name
+ std::string name = host;
+ return name;
+}
+
+std::string
+ArchNetworkWinsock::addrToString(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ switch (getAddrFamily(addr)) {
+ case kINET: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
+ return inet_ntoa_winsock(ipAddr->sin_addr);
+ }
+
+ case kINET6: {
+ char strAddr[INET6_ADDRSTRLEN];
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
+ inet_ntop(AF_INET6, &ipAddr->sin6_addr, strAddr, INET6_ADDRSTRLEN);
+ return strAddr;
+ }
+
+ default:
+ assert(0 && "unknown address family");
+ return "";
+ }
+}
+
+IArchNetwork::EAddressFamily
+ArchNetworkWinsock::getAddrFamily(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ switch (addr->m_addr.ss_family) {
+ case AF_INET:
+ return kINET;
+
+ case AF_INET6:
+ return kINET6;
+
+ default:
+ return kUNKNOWN;
+ }
+}
+
+void
+ArchNetworkWinsock::setAddrPort(ArchNetAddress addr, int port)
+{
+ assert(addr != NULL);
+
+ switch (getAddrFamily(addr)) {
+ case kINET: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
+ ipAddr->sin_port = htons_winsock(static_cast<u_short>(port));
+ break;
+ }
+
+ case kINET6: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
+ ipAddr->sin6_port = htons_winsock(static_cast<u_short>(port));
+ break;
+ }
+
+ default:
+ assert(0 && "unknown address family");
+ break;
+ }
+}
+
+int
+ArchNetworkWinsock::getAddrPort(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ switch (getAddrFamily(addr)) {
+ case kINET: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
+ return ntohs_winsock(ipAddr->sin_port);
+ }
+
+ case kINET6: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
+ return ntohs_winsock(ipAddr->sin6_port);
+ }
+
+ default:
+ assert(0 && "unknown address family");
+ return 0;
+ }
+}
+
+bool
+ArchNetworkWinsock::isAnyAddr(ArchNetAddress addr)
+{
+ assert(addr != NULL);
+
+ switch (getAddrFamily(addr)) {
+ case kINET: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
+ return (addr->m_len == sizeof(struct sockaddr_in) &&
+ ipAddr->sin_addr.s_addr == INADDR_ANY);
+ }
+
+ case kINET6: {
+ auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
+ return (addr->m_len == sizeof(struct sockaddr_in) &&
+ memcmp(&ipAddr->sin6_addr, &in6addr_any, sizeof(in6addr_any))== 0);
+ }
+
+ default:
+ assert(0 && "unknown address family");
+ return true;
+ }
+}
+
+bool
+ArchNetworkWinsock::isEqualAddr(ArchNetAddress a, ArchNetAddress b)
+{
+ return (a == b || (a->m_len == b->m_len &&
+ memcmp(&a->m_addr, &b->m_addr, a->m_len) == 0));
+}
+
+void
+ArchNetworkWinsock::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
+ArchNetworkWinsock::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/src/lib/arch/win32/ArchNetworkWinsock.h b/src/lib/arch/win32/ArchNetworkWinsock.h
new file mode 100644
index 0000000..0b01671
--- /dev/null
+++ b/src/lib/arch/win32/ArchNetworkWinsock.h
@@ -0,0 +1,111 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <ws2tcpip.h>
+// declare no functions in winsock2
+#ifndef INCL_WINSOCK_API_PROTOTYPES
+#define INCL_WINSOCK_API_PROTOTYPES 0
+#endif
+#define INCL_WINSOCK_API_TYPEDEFS 0
+
+#include "arch/IArchNetwork.h"
+#include "arch/IArchMultithread.h"
+
+#include <WinSock2.h>
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <list>
+
+#pragma comment(lib, "ws2_32.lib")
+
+#define ARCH_NETWORK ArchNetworkWinsock
+
+class ArchSocketImpl {
+public:
+ SOCKET m_socket;
+ int m_refCount;
+ WSAEVENT m_event;
+ bool m_pollWrite;
+};
+
+class ArchNetAddressImpl {
+public:
+ static ArchNetAddressImpl* alloc(size_t);
+
+public:
+ int m_len;
+ struct sockaddr_storage m_addr;
+};
+#define ADDR_HDR_SIZE offsetof(ArchNetAddressImpl, m_addr)
+#define TYPED_ADDR(type_, addr_) (reinterpret_cast<type_*>(&addr_->m_addr))
+
+//! Win32 implementation of IArchNetwork
+class ArchNetworkWinsock : public IArchNetwork {
+public:
+ ArchNetworkWinsock();
+ virtual ~ArchNetworkWinsock();
+
+ virtual void init();
+
+ // IArchNetwork overrides
+ virtual ArchSocket newSocket(EAddressFamily, ESocketType);
+ virtual ArchSocket copySocket(ArchSocket s);
+ virtual void closeSocket(ArchSocket s);
+ virtual void closeSocketForRead(ArchSocket s);
+ virtual void closeSocketForWrite(ArchSocket s);
+ virtual void bindSocket(ArchSocket s, ArchNetAddress addr);
+ virtual void listenOnSocket(ArchSocket s);
+ virtual ArchSocket acceptSocket(ArchSocket s, ArchNetAddress* addr);
+ virtual bool connectSocket(ArchSocket s, ArchNetAddress name);
+ virtual int pollSocket(PollEntry[], int num, double timeout);
+ virtual void unblockPollSocket(ArchThread thread);
+ virtual size_t readSocket(ArchSocket s, void* buf, size_t len);
+ virtual size_t writeSocket(ArchSocket s,
+ const void* buf, size_t len);
+ virtual void throwErrorOnSocket(ArchSocket);
+ virtual bool setNoDelayOnSocket(ArchSocket, bool noDelay);
+ virtual bool setReuseAddrOnSocket(ArchSocket, bool reuse);
+ virtual std::string getHostName();
+ virtual ArchNetAddress newAnyAddr(EAddressFamily);
+ virtual ArchNetAddress copyAddr(ArchNetAddress);
+ virtual ArchNetAddress nameToAddr(const std::string&);
+ virtual void closeAddr(ArchNetAddress);
+ virtual std::string addrToName(ArchNetAddress);
+ virtual std::string addrToString(ArchNetAddress);
+ virtual EAddressFamily getAddrFamily(ArchNetAddress);
+ virtual void setAddrPort(ArchNetAddress, int port);
+ virtual int getAddrPort(ArchNetAddress);
+ virtual bool isAnyAddr(ArchNetAddress);
+ virtual bool isEqualAddr(ArchNetAddress, ArchNetAddress);
+
+private:
+ void initModule(HMODULE);
+
+ void setBlockingOnSocket(SOCKET, bool blocking);
+
+ void throwError(int);
+ void throwNameError(int);
+
+private:
+ typedef std::list<WSAEVENT> EventList;
+
+ ArchMutex m_mutex;
+ EventList m_unblockEvents;
+};
diff --git a/src/lib/arch/win32/ArchSleepWindows.cpp b/src/lib/arch/win32/ArchSleepWindows.cpp
new file mode 100644
index 0000000..69648a7
--- /dev/null
+++ b/src/lib/arch/win32/ArchSleepWindows.cpp
@@ -0,0 +1,61 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchSleepWindows.h"
+#include "arch/Arch.h"
+#include "arch/win32/ArchMultithreadWindows.h"
+
+//
+// ArchSleepWindows
+//
+
+ArchSleepWindows::ArchSleepWindows()
+{
+ // do nothing
+}
+
+ArchSleepWindows::~ArchSleepWindows()
+{
+ // do nothing
+}
+
+void
+ArchSleepWindows::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.
+ ArchMultithreadWindows* mt = ArchMultithreadWindows::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/src/lib/arch/win32/ArchSleepWindows.h b/src/lib/arch/win32/ArchSleepWindows.h
new file mode 100644
index 0000000..d673caf
--- /dev/null
+++ b/src/lib/arch/win32/ArchSleepWindows.h
@@ -0,0 +1,33 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchSleep.h"
+
+#define ARCH_SLEEP ArchSleepWindows
+
+//! Win32 implementation of IArchSleep
+class ArchSleepWindows : public IArchSleep {
+public:
+ ArchSleepWindows();
+ virtual ~ArchSleepWindows();
+
+ // IArchSleep overrides
+ virtual void sleep(double timeout);
+};
diff --git a/src/lib/arch/win32/ArchStringWindows.cpp b/src/lib/arch/win32/ArchStringWindows.cpp
new file mode 100644
index 0000000..deaf536
--- /dev/null
+++ b/src/lib/arch/win32/ArchStringWindows.cpp
@@ -0,0 +1,46 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchStringWindows.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <stdio.h>
+
+//
+// ArchStringWindows
+//
+
+#include "arch/multibyte.h"
+#define HAVE_VSNPRINTF 1
+#define ARCH_VSNPRINTF _vsnprintf
+#include "arch/vsnprintf.h"
+
+ArchStringWindows::ArchStringWindows()
+{
+}
+
+ArchStringWindows::~ArchStringWindows()
+{
+}
+
+IArchString::EWideCharEncoding
+ArchStringWindows::getWideCharEncoding()
+{
+ return kUTF16;
+}
diff --git a/src/lib/arch/win32/ArchStringWindows.h b/src/lib/arch/win32/ArchStringWindows.h
new file mode 100644
index 0000000..23812dc
--- /dev/null
+++ b/src/lib/arch/win32/ArchStringWindows.h
@@ -0,0 +1,34 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchString.h"
+
+#define ARCH_STRING ArchStringWindows
+
+//! Win32 implementation of IArchString
+class ArchStringWindows : public IArchString {
+public:
+ ArchStringWindows();
+ virtual ~ArchStringWindows();
+
+ // IArchString overrides
+ virtual EWideCharEncoding
+ getWideCharEncoding();
+};
diff --git a/src/lib/arch/win32/ArchSystemWindows.cpp b/src/lib/arch/win32/ArchSystemWindows.cpp
new file mode 100644
index 0000000..cf3b066
--- /dev/null
+++ b/src/lib/arch/win32/ArchSystemWindows.cpp
@@ -0,0 +1,166 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchSystemWindows.h"
+#include "arch/win32/ArchMiscWindows.h"
+#include "arch/win32/XArchWindows.h"
+
+#include "tchar.h"
+#include <string>
+
+#include <windows.h>
+#include <psapi.h>
+
+static const char* s_settingsKeyNames[] = {
+ _T("SOFTWARE"),
+ _T("Barrier"),
+ NULL
+};
+
+//
+// ArchSystemWindows
+//
+
+ArchSystemWindows::ArchSystemWindows()
+{
+ // do nothing
+}
+
+ArchSystemWindows::~ArchSystemWindows()
+{
+ // do nothing
+}
+
+std::string
+ArchSystemWindows::getOSName() const
+{
+ std::string osName ("Microsoft Windows <unknown>");
+ static const TCHAR* const windowsVersionKeyNames[] = {
+ _T("SOFTWARE"),
+ _T("Microsoft"),
+ _T("Windows NT"),
+ _T("CurrentVersion"),
+ NULL
+ };
+
+ HKEY key = ArchMiscWindows::openKey(HKEY_LOCAL_MACHINE, windowsVersionKeyNames);
+ if (key == NULL) {
+ return osName;
+ }
+
+ std::string productName = ArchMiscWindows::readValueString(key, "ProductName");
+ if (osName.empty()) {
+ return osName;
+ }
+
+ return "Microsoft " + productName;
+}
+
+std::string
+ArchSystemWindows::getPlatformName() const
+{
+#ifdef _X86_
+ if (isWOW64())
+ return "x86 (WOW64)";
+ else
+ return "x86";
+#else
+#ifdef _AMD64_
+ return "x64";
+#else
+ return "Unknown";
+#endif
+#endif
+}
+
+std::string
+ArchSystemWindows::setting(const std::string& valueName) const
+{
+ HKEY key = ArchMiscWindows::openKey(HKEY_LOCAL_MACHINE, s_settingsKeyNames);
+ if (key == NULL)
+ return "";
+
+ return ArchMiscWindows::readValueString(key, valueName.c_str());
+}
+
+void
+ArchSystemWindows::setting(const std::string& valueName, const std::string& valueString) const
+{
+ HKEY key = ArchMiscWindows::addKey(HKEY_LOCAL_MACHINE, s_settingsKeyNames);
+ if (key == NULL)
+ throw XArch(std::string("could not access registry key: ") + valueName);
+ ArchMiscWindows::setValue(key, valueName.c_str(), valueString.c_str());
+}
+
+bool
+ArchSystemWindows::isWOW64() const
+{
+#if WINVER >= _WIN32_WINNT_WINXP
+ typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
+ HMODULE hModule = GetModuleHandle(TEXT("kernel32"));
+ if (!hModule) return FALSE;
+
+ LPFN_ISWOW64PROCESS fnIsWow64Process =
+ (LPFN_ISWOW64PROCESS) GetProcAddress(hModule, "IsWow64Process");
+
+ BOOL bIsWow64 = FALSE;
+ if (NULL != fnIsWow64Process &&
+ fnIsWow64Process(GetCurrentProcess(), &bIsWow64) &&
+ bIsWow64)
+ {
+ return true;
+ }
+#endif
+ return false;
+}
+#pragma comment(lib, "psapi")
+
+std::string
+ArchSystemWindows::getLibsUsed(void) const
+{
+ HMODULE hMods[1024];
+ HANDLE hProcess;
+ DWORD cbNeeded;
+ unsigned int i;
+ char hex[16];
+
+ DWORD pid = GetCurrentProcessId();
+
+ std::string msg = "pid:" + std::to_string((unsigned long long)pid) + "\n";
+
+ hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
+
+ if (NULL == hProcess) {
+ return msg;
+ }
+
+ if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
+ for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
+ TCHAR szModName[MAX_PATH];
+ if (GetModuleFileNameEx(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(TCHAR))) {
+ sprintf(hex, "(0x%08llX)", reinterpret_cast<long long>(hMods[i]));
+ msg += szModName;
+ msg.append(hex);
+ msg.append("\n");
+ }
+ }
+ }
+
+ CloseHandle(hProcess);
+ return msg;
+}
diff --git a/src/lib/arch/win32/ArchSystemWindows.h b/src/lib/arch/win32/ArchSystemWindows.h
new file mode 100644
index 0000000..3d45ee6
--- /dev/null
+++ b/src/lib/arch/win32/ArchSystemWindows.h
@@ -0,0 +1,39 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchSystem.h"
+
+#define ARCH_SYSTEM ArchSystemWindows
+
+//! Win32 implementation of IArchString
+class ArchSystemWindows : public IArchSystem {
+public:
+ ArchSystemWindows();
+ virtual ~ArchSystemWindows();
+
+ // IArchSystem overrides
+ virtual std::string getOSName() const;
+ virtual std::string getPlatformName() const;
+ virtual std::string setting(const std::string& valueName) const;
+ virtual void setting(const std::string& valueName, const std::string& valueString) const;
+ virtual std::string getLibsUsed(void) const;
+
+ bool isWOW64() const;
+};
diff --git a/src/lib/arch/win32/ArchTaskBarWindows.cpp b/src/lib/arch/win32/ArchTaskBarWindows.cpp
new file mode 100644
index 0000000..731dc59
--- /dev/null
+++ b/src/lib/arch/win32/ArchTaskBarWindows.cpp
@@ -0,0 +1,514 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchTaskBarWindows.h"
+#include "arch/win32/ArchMiscWindows.h"
+#include "arch/IArchTaskBarReceiver.h"
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+#include "barrier/win32/AppUtilWindows.h"
+
+#include <string.h>
+#include <shellapi.h>
+
+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;
+
+//
+// ArchTaskBarWindows
+//
+
+ArchTaskBarWindows* ArchTaskBarWindows::s_instance = NULL;
+
+ArchTaskBarWindows::ArchTaskBarWindows() :
+ m_mutex(NULL),
+ m_condVar(NULL),
+ m_ready(false),
+ m_result(0),
+ m_thread(NULL),
+ m_hwnd(NULL),
+ m_taskBarRestart(0),
+ m_nextID(kFirstReceiverID)
+{
+ // save the singleton instance
+ s_instance = this;
+}
+
+ArchTaskBarWindows::~ArchTaskBarWindows()
+{
+ if (m_thread != NULL) {
+ PostMessage(m_hwnd, WM_QUIT, 0, 0);
+ ARCH->wait(m_thread, -1.0);
+ ARCH->closeThread(m_thread);
+ }
+ if (m_condVar != NULL) {
+ ARCH->closeCondVar(m_condVar);
+ }
+ if (m_mutex != NULL) {
+ ARCH->closeMutex(m_mutex);
+ }
+ s_instance = NULL;
+}
+
+void
+ArchTaskBarWindows::init()
+{
+ // 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(&ArchTaskBarWindows::threadEntry, this);
+
+ // wait for child thread
+ while (!m_ready) {
+ ARCH->waitCondVar(m_condVar, m_mutex, -1.0);
+ }
+
+ // ready
+ ARCH->unlockMutex(m_mutex);
+}
+
+void
+ArchTaskBarWindows::addDialog(HWND hwnd)
+{
+ ArchMiscWindows::addDialog(hwnd);
+}
+
+void
+ArchTaskBarWindows::removeDialog(HWND hwnd)
+{
+ ArchMiscWindows::removeDialog(hwnd);
+}
+
+void
+ArchTaskBarWindows::addReceiver(IArchTaskBarReceiver* receiver)
+{
+ // ignore bogus receiver
+ if (receiver == NULL) {
+ return;
+ }
+
+ // add receiver if necessary
+ ReceiverToInfoMap::iterator index = m_receivers.find(receiver);
+ if (index == m_receivers.end()) {
+ // add it, creating a new message ID for it
+ ReceiverInfo 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
+ArchTaskBarWindows::removeReceiver(IArchTaskBarReceiver* receiver)
+{
+ // find receiver
+ ReceiverToInfoMap::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
+ArchTaskBarWindows::updateReceiver(IArchTaskBarReceiver* receiver)
+{
+ // find receiver
+ ReceiverToInfoMap::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
+ArchTaskBarWindows::getNextID()
+{
+ if (m_oldIDs.empty()) {
+ return m_nextID++;
+ }
+ UINT id = m_oldIDs.back();
+ m_oldIDs.pop_back();
+ return id;
+}
+
+void
+ArchTaskBarWindows::recycleID(UINT id)
+{
+ m_oldIDs.push_back(id);
+}
+
+void
+ArchTaskBarWindows::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
+ArchTaskBarWindows::removeIcon(UINT id)
+{
+ ARCH->lockMutex(m_mutex);
+ removeIconNoLock(id);
+ ARCH->unlockMutex(m_mutex);
+}
+
+void
+ArchTaskBarWindows::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
+ArchTaskBarWindows::addAllIcons()
+{
+ ARCH->lockMutex(m_mutex);
+ for (ReceiverToInfoMap::const_iterator index = m_receivers.begin();
+ index != m_receivers.end(); ++index) {
+ modifyIconNoLock(index, NIM_ADD);
+ }
+ ARCH->unlockMutex(m_mutex);
+}
+
+void
+ArchTaskBarWindows::removeAllIcons()
+{
+ ARCH->lockMutex(m_mutex);
+ for (ReceiverToInfoMap::const_iterator index = m_receivers.begin();
+ index != m_receivers.end(); ++index) {
+ removeIconNoLock(index->second.m_id);
+ }
+ ARCH->unlockMutex(m_mutex);
+}
+
+void
+ArchTaskBarWindows::modifyIconNoLock(
+ ReceiverToInfoMap::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 = static_cast<HICON>(
+ const_cast<IArchTaskBarReceiver::Icon>(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
+ArchTaskBarWindows::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
+ArchTaskBarWindows::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
+ArchTaskBarWindows::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 (Dialogs::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 (Dialogs::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
+ArchTaskBarWindows::wndProc(HWND hwnd,
+ UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case kNotifyReceiver: {
+ // lookup receiver
+ CIDToReceiverMap::const_iterator index = m_idTable.find((UINT)wParam);
+ if (index != m_idTable.end()) {
+ IArchTaskBarReceiver* receiver = index->second->first;
+ handleIconMessage(receiver, lParam);
+ return 0;
+ }
+ break;
+ }
+
+ case kAddReceiver:
+ addIcon((UINT)wParam);
+ break;
+
+ case kRemoveReceiver:
+ removeIcon((UINT)wParam);
+ break;
+
+ case kUpdateReceiver:
+ updateIcon((UINT)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
+ArchTaskBarWindows::staticWndProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ // if msg is WM_NCCREATE, extract the ArchTaskBarWindows* and put
+ // it in the extra window data then forward the call.
+ ArchTaskBarWindows* self = NULL;
+ if (msg == WM_NCCREATE) {
+ CREATESTRUCT* createInfo;
+ createInfo = reinterpret_cast<CREATESTRUCT*>(lParam);
+ self = static_cast<ArchTaskBarWindows*>(
+ createInfo->lpCreateParams);
+ SetWindowLongPtr(hwnd, 0, reinterpret_cast<LONG_PTR>(createInfo->lpCreateParams));
+ }
+ else {
+ // get the extra window data and forward the call
+ LONG_PTR data = GetWindowLongPtr(hwnd, 0);
+ if (data != 0) {
+ self = static_cast<ArchTaskBarWindows*>(reinterpret_cast<void*>(data));
+ }
+ }
+
+ // forward the message
+ if (self != NULL) {
+ return self->wndProc(hwnd, msg, wParam, lParam);
+ }
+ else {
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+ }
+}
+
+void
+ArchTaskBarWindows::threadMainLoop()
+{
+ // register the task bar restart message
+ m_taskBarRestart = RegisterWindowMessage(TEXT("TaskbarCreated"));
+
+ // register a window class
+ LPCTSTR className = TEXT("BarrierTaskBar");
+ WNDCLASSEX classInfo;
+ classInfo.cbSize = sizeof(classInfo);
+ classInfo.style = CS_NOCLOSE;
+ classInfo.lpfnWndProc = &ArchTaskBarWindows::staticWndProc;
+ classInfo.cbClsExtra = 0;
+ classInfo.cbWndExtra = sizeof(ArchTaskBarWindows*);
+ classInfo.hInstance = instanceWin32();
+ classInfo.hIcon = NULL;
+ classInfo.hCursor = NULL;
+ classInfo.hbrBackground = NULL;
+ classInfo.lpszMenuName = NULL;
+ classInfo.lpszClassName = className;
+ classInfo.hIconSm = NULL;
+ ATOM windowClass = RegisterClassEx(&classInfo);
+
+ // create window
+ m_hwnd = CreateWindowEx(WS_EX_TOOLWINDOW,
+ className,
+ TEXT("Barrier Task Bar"),
+ WS_POPUP,
+ 0, 0, 1, 1,
+ NULL,
+ NULL,
+ instanceWin32(),
+ static_cast<void*>(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(className, instanceWin32());
+ 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(className, instanceWin32());
+}
+
+void*
+ArchTaskBarWindows::threadEntry(void* self)
+{
+ static_cast<ArchTaskBarWindows*>(self)->threadMainLoop();
+ return NULL;
+}
+
+HINSTANCE ArchTaskBarWindows::instanceWin32()
+{
+ return ArchMiscWindows::instanceWin32();
+} \ No newline at end of file
diff --git a/src/lib/arch/win32/ArchTaskBarWindows.h b/src/lib/arch/win32/ArchTaskBarWindows.h
new file mode 100644
index 0000000..0edddf8
--- /dev/null
+++ b/src/lib/arch/win32/ArchTaskBarWindows.h
@@ -0,0 +1,114 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchTaskBar.h"
+#include "arch/IArchMultithread.h"
+#include "common/stdmap.h"
+#include "common/stdvector.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+#define ARCH_TASKBAR ArchTaskBarWindows
+
+//! Win32 implementation of IArchTaskBar
+class ArchTaskBarWindows : public IArchTaskBar {
+public:
+ ArchTaskBarWindows();
+ virtual ~ArchTaskBarWindows();
+
+ virtual void init();
+
+ //! 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 ReceiverInfo {
+ public:
+ UINT m_id;
+ };
+
+ typedef std::map<IArchTaskBarReceiver*, ReceiverInfo> ReceiverToInfoMap;
+ typedef std::map<UINT, ReceiverToInfoMap::iterator> CIDToReceiverMap;
+ typedef std::vector<UINT> CIDStack;
+ typedef std::map<HWND, bool> Dialogs;
+
+ UINT getNextID();
+ void recycleID(UINT);
+
+ void addIcon(UINT);
+ void removeIcon(UINT);
+ void updateIcon(UINT);
+ void addAllIcons();
+ void removeAllIcons();
+ void modifyIconNoLock(ReceiverToInfoMap::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*);
+
+ HINSTANCE instanceWin32();
+
+private:
+ static ArchTaskBarWindows* s_instance;
+
+ // multithread data
+ ArchMutex m_mutex;
+ ArchCond m_condVar;
+ bool m_ready;
+ int m_result;
+ ArchThread m_thread;
+
+ // child thread data
+ HWND m_hwnd;
+ UINT m_taskBarRestart;
+
+ // shared data
+ ReceiverToInfoMap m_receivers;
+ CIDToReceiverMap m_idTable;
+ CIDStack m_oldIDs;
+ UINT m_nextID;
+
+ // dialogs
+ Dialogs m_dialogs;
+ Dialogs m_addedDialogs;
+};
diff --git a/src/lib/arch/win32/ArchTimeWindows.cpp b/src/lib/arch/win32/ArchTimeWindows.cpp
new file mode 100644
index 0000000..568a483
--- /dev/null
+++ b/src/lib/arch/win32/ArchTimeWindows.cpp
@@ -0,0 +1,89 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/ArchTimeWindows.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+#define MMNODRV // Disable: Installable driver support
+#define MMNOSOUND // Disable: Sound support
+#define MMNOWAVE // Disable: Waveform support
+#define MMNOMIDI // Disable: MIDI support
+#define MMNOAUX // Disable: Auxiliary audio support
+#define MMNOMIXER // Disable: Mixer support
+#define MMNOJOY // Disable: Joystick support
+#define MMNOMCI // Disable: MCI support
+#define MMNOMMIO // Disable: Multimedia file I/O support
+#define MMNOMMSYSTEM // Disable: General MMSYSTEM functions
+#include <MMSystem.h>
+
+typedef WINMMAPI DWORD (WINAPI *PTimeGetTime)(void);
+
+static double s_freq = 0.0;
+static HINSTANCE s_mmInstance = NULL;
+static PTimeGetTime s_tgt = NULL;
+
+
+//
+// ArchTimeWindows
+//
+
+ArchTimeWindows::ArchTimeWindows()
+{
+ assert(s_freq == 0.0 || s_mmInstance == NULL);
+
+ LARGE_INTEGER freq;
+ if (QueryPerformanceFrequency(&freq) && freq.QuadPart != 0) {
+ s_freq = 1.0 / static_cast<double>(freq.QuadPart);
+ }
+ else {
+ // load winmm.dll and get timeGetTime
+ s_mmInstance = LoadLibrary("winmm");
+ if (s_mmInstance != NULL) {
+ s_tgt = (PTimeGetTime)GetProcAddress(s_mmInstance, "timeGetTime");
+ }
+ }
+}
+
+ArchTimeWindows::~ArchTimeWindows()
+{
+ s_freq = 0.0;
+ if (s_mmInstance == NULL) {
+ FreeLibrary(static_cast<HMODULE>(s_mmInstance));
+ s_tgt = NULL;
+ s_mmInstance = NULL;
+ }
+}
+
+double
+ArchTimeWindows::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<double>(c.QuadPart);
+ }
+ else if (s_tgt != NULL) {
+ return 0.001 * static_cast<double>(s_tgt());
+ }
+ else {
+ return 0.001 * static_cast<double>(GetTickCount());
+ }
+}
diff --git a/src/lib/arch/win32/ArchTimeWindows.h b/src/lib/arch/win32/ArchTimeWindows.h
new file mode 100644
index 0000000..42351a1
--- /dev/null
+++ b/src/lib/arch/win32/ArchTimeWindows.h
@@ -0,0 +1,33 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchTime.h"
+
+#define ARCH_TIME ArchTimeWindows
+
+//! Win32 implementation of IArchTime
+class ArchTimeWindows : public IArchTime {
+public:
+ ArchTimeWindows();
+ virtual ~ArchTimeWindows();
+
+ // IArchTime overrides
+ virtual double time();
+};
diff --git a/src/lib/arch/win32/XArchWindows.cpp b/src/lib/arch/win32/XArchWindows.cpp
new file mode 100644
index 0000000..eeec0e1
--- /dev/null
+++ b/src/lib/arch/win32/XArchWindows.cpp
@@ -0,0 +1,120 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/win32/XArchWindows.h"
+#include "arch/win32/ArchNetworkWinsock.h"
+#include "base/String.h"
+
+//
+// XArchEvalWindows
+//
+
+std::string
+XArchEvalWindows::eval() const throw()
+{
+ char* cmsg;
+ if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_IGNORE_INSERTS |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ 0,
+ m_error,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR)&cmsg,
+ 0,
+ NULL) == 0) {
+ cmsg = NULL;
+ return barrier::string::sprintf("Unknown error, code %d", m_error);
+ }
+ std::string smsg(cmsg);
+ LocalFree(cmsg);
+ return smsg;
+}
+
+
+//
+// XArchEvalWinsock
+//
+
+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 host 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, "Connection was refused"},
+ /* 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_error) {
+ return s_netErrorCodes[i].m_msg;
+ }
+ }
+ return "Unknown error";
+}
diff --git a/src/lib/arch/win32/XArchWindows.h b/src/lib/arch/win32/XArchWindows.h
new file mode 100644
index 0000000..10106b1
--- /dev/null
+++ b/src/lib/arch/win32/XArchWindows.h
@@ -0,0 +1,49 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/XArch.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+//! Lazy error message string evaluation for windows
+class XArchEvalWindows : public XArchEval {
+public:
+ XArchEvalWindows() : m_error(GetLastError()) { }
+ XArchEvalWindows(DWORD error) : m_error(error) { }
+ virtual ~XArchEvalWindows() { }
+
+ virtual std::string eval() const;
+
+private:
+ DWORD m_error;
+};
+
+//! Lazy error message string evaluation for winsock
+class XArchEvalWinsock : public XArchEval {
+public:
+ XArchEvalWinsock(int error) : m_error(error) { }
+ virtual ~XArchEvalWinsock() { }
+
+ virtual std::string eval() const;
+
+private:
+ int m_error;
+};
diff --git a/src/lib/barrier/App.cpp b/src/lib/barrier/App.cpp
new file mode 100644
index 0000000..1f4eda3
--- /dev/null
+++ b/src/lib/barrier/App.cpp
@@ -0,0 +1,329 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/App.h"
+
+#include "base/Log.h"
+#include "common/Version.h"
+#include "barrier/protocol_types.h"
+#include "arch/Arch.h"
+#include "base/XBase.h"
+#include "arch/XArch.h"
+#include "base/log_outputters.h"
+#include "barrier/XBarrier.h"
+#include "barrier/ArgsBase.h"
+#include "ipc/IpcServerProxy.h"
+#include "base/TMethodEventJob.h"
+#include "ipc/IpcMessage.h"
+#include "ipc/Ipc.h"
+#include "base/EventQueue.h"
+
+#if SYSAPI_WIN32
+#include "arch/win32/ArchMiscWindows.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodJob.h"
+#endif
+
+#include <iostream>
+#include <stdio.h>
+
+#if WINAPI_CARBON
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
+#if defined(__APPLE__)
+#include "platform/OSXDragSimulator.h"
+#endif
+
+App* App::s_instance = nullptr;
+
+//
+// App
+//
+
+App::App(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver, ArgsBase* args) :
+ m_bye(&exit),
+ m_taskBarReceiver(NULL),
+ m_suspended(false),
+ m_events(events),
+ m_args(args),
+ m_fileLog(nullptr),
+ m_createTaskBarReceiver(createTaskBarReceiver),
+ m_appUtil(events),
+ m_ipcClient(nullptr),
+ m_socketMultiplexer(nullptr)
+{
+ assert(s_instance == nullptr);
+ s_instance = this;
+}
+
+App::~App()
+{
+ s_instance = nullptr;
+ delete m_args;
+}
+
+void
+App::version()
+{
+ char buffer[500];
+ sprintf(
+ buffer,
+ "%s %s, protocol version %d.%d\n%s",
+ argsBase().m_pname,
+ kVersion,
+ kProtocolMajorVersion,
+ kProtocolMinorVersion,
+ kCopyright
+ );
+
+ std::cout << buffer << std::endl;
+}
+
+int
+App::run(int argc, char** argv)
+{
+#if MAC_OS_X_VERSION_10_7
+ // dock hide only supported on lion :(
+ ProcessSerialNumber psn = { 0, kCurrentProcess };
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ GetCurrentProcess(&psn);
+#pragma GCC diagnostic pop
+
+ TransformProcessType(&psn, kProcessTransformToBackgroundApplication);
+#endif
+
+ // install application in to arch
+ appUtil().adoptApp(this);
+
+ // HACK: fail by default (saves us setting result in each catch)
+ int result = kExitFailed;
+
+ try {
+ result = appUtil().run(argc, argv);
+ }
+ catch (XExitApp& e) {
+ // instead of showing a nasty error, just exit with the error code.
+ // not sure if i like this behaviour, but it's probably better than
+ // using the exit(int) function!
+ result = e.getCode();
+ }
+ catch (std::exception& e) {
+ LOG((CLOG_CRIT "An error occurred: %s\n", e.what()));
+ }
+ catch (...) {
+ LOG((CLOG_CRIT "An unknown error occurred.\n"));
+ }
+
+ appUtil().beforeAppExit();
+
+ return result;
+}
+
+int
+App::daemonMainLoop(int, const char**)
+{
+#if SYSAPI_WIN32
+ SystemLogger sysLogger(daemonName(), false);
+#else
+ SystemLogger sysLogger(daemonName(), true);
+#endif
+ return mainLoop();
+}
+
+void
+App::setupFileLogging()
+{
+ if (argsBase().m_logFile != NULL) {
+ m_fileLog = new FileLogOutputter(argsBase().m_logFile);
+ CLOG->insert(m_fileLog);
+ LOG((CLOG_DEBUG1 "logging to file (%s) enabled", argsBase().m_logFile));
+ }
+}
+
+void
+App::loggingFilterWarning()
+{
+ if (CLOG->getFilter() > CLOG->getConsoleMaxLevel()) {
+ if (argsBase().m_logFile == NULL) {
+ LOG((CLOG_WARN "log messages above %s are NOT sent to console (use file logging)",
+ CLOG->getFilterName(CLOG->getConsoleMaxLevel())));
+ }
+ }
+}
+
+void
+App::initApp(int argc, const char** argv)
+{
+ // parse command line
+ parseArgs(argc, argv);
+
+ ARCH->setProfileDirectory(argsBase().m_profileDirectory);
+ ARCH->setPluginDirectory(argsBase().m_pluginDirectory);
+
+ // set log filter
+ if (!CLOG->setFilter(argsBase().m_logFilter)) {
+ LOG((CLOG_PRINT "%s: unrecognized log level `%s'" BYE,
+ argsBase().m_pname, argsBase().m_logFilter, argsBase().m_pname));
+ m_bye(kExitArgs);
+ }
+ loggingFilterWarning();
+
+ if (argsBase().m_enableDragDrop) {
+ LOG((CLOG_INFO "drag and drop enabled"));
+ }
+
+ // setup file logging after parsing args
+ setupFileLogging();
+
+ // load configuration
+ loadConfig();
+
+ if (!argsBase().m_disableTray) {
+
+ // create a log buffer so we can show the latest message
+ // as a tray icon tooltip
+ BufferedLogOutputter* logBuffer = new BufferedLogOutputter(1000);
+ CLOG->insert(logBuffer, true);
+
+ // make the task bar receiver. the user can control this app
+ // through the task bar.
+ m_taskBarReceiver = m_createTaskBarReceiver(logBuffer, m_events);
+ }
+}
+
+void
+App::initIpcClient()
+{
+ m_ipcClient = new IpcClient(m_events, m_socketMultiplexer);
+ m_ipcClient->connect();
+
+ m_events->adoptHandler(
+ m_events->forIpcClient().messageReceived(), m_ipcClient,
+ new TMethodEventJob<App>(this, &App::handleIpcMessage));
+}
+
+void
+App::cleanupIpcClient()
+{
+ m_ipcClient->disconnect();
+ m_events->removeHandler(m_events->forIpcClient().messageReceived(), m_ipcClient);
+ delete m_ipcClient;
+}
+
+void
+App::handleIpcMessage(const Event& e, void*)
+{
+ IpcMessage* m = static_cast<IpcMessage*>(e.getDataObject());
+ if (m->type() == kIpcShutdown) {
+ LOG((CLOG_INFO "got ipc shutdown message"));
+ m_events->addEvent(Event(Event::kQuit));
+ }
+}
+
+void
+App::runEventsLoop(void*)
+{
+ m_events->loop();
+
+#if defined(MAC_OS_X_VERSION_10_7)
+
+ stopCocoaLoop();
+
+#endif
+}
+
+//
+// MinimalApp
+//
+
+MinimalApp::MinimalApp() :
+ App(NULL, NULL, new ArgsBase())
+{
+ m_arch.init();
+ setEvents(m_events);
+}
+
+MinimalApp::~MinimalApp()
+{
+}
+
+int
+MinimalApp::standardStartup(int argc, char** argv)
+{
+ return 0;
+}
+
+int
+MinimalApp::runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup)
+{
+ return 0;
+}
+
+void
+MinimalApp::startNode()
+{
+}
+
+int
+MinimalApp::mainLoop()
+{
+ return 0;
+}
+
+int
+MinimalApp::foregroundStartup(int argc, char** argv)
+{
+ return 0;
+}
+
+barrier::Screen*
+MinimalApp::createScreen()
+{
+ return NULL;
+}
+
+void
+MinimalApp::loadConfig()
+{
+}
+
+bool
+MinimalApp::loadConfig(const String& pathname)
+{
+ return false;
+}
+
+const char*
+MinimalApp::daemonInfo() const
+{
+ return "";
+}
+
+const char*
+MinimalApp::daemonName() const
+{
+ return "";
+}
+
+void
+MinimalApp::parseArgs(int argc, const char* const* argv)
+{
+}
diff --git a/src/lib/barrier/App.h b/src/lib/barrier/App.h
new file mode 100644
index 0000000..b7c77a0
--- /dev/null
+++ b/src/lib/barrier/App.h
@@ -0,0 +1,200 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ipc/IpcClient.h"
+#include "barrier/IApp.h"
+#include "base/String.h"
+#include "base/Log.h"
+#include "base/EventQueue.h"
+#include "common/common.h"
+
+#if SYSAPI_WIN32
+#include "barrier/win32/AppUtilWindows.h"
+#elif SYSAPI_UNIX
+#include "barrier/unix/AppUtilUnix.h"
+#endif
+
+class IArchTaskBarReceiver;
+class BufferedLogOutputter;
+class ILogOutputter;
+class FileLogOutputter;
+namespace barrier { class Screen; }
+class IEventQueue;
+class SocketMultiplexer;
+
+typedef IArchTaskBarReceiver* (*CreateTaskBarReceiverFunc)(const BufferedLogOutputter*, IEventQueue* events);
+
+class App : public IApp {
+public:
+ App(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver, ArgsBase* args);
+ virtual ~App();
+
+ // Returns args that are common between server and client.
+ ArgsBase& argsBase() const { return *m_args; }
+
+ // Prints the current compiled version.
+ virtual void version();
+
+ // Prints help specific to client or server.
+ virtual void help() = 0;
+
+ // Parse command line arguments.
+ virtual void parseArgs(int argc, const char* const* argv) = 0;
+
+ int run(int argc, char** argv);
+
+ int daemonMainLoop(int, const char**);
+
+ virtual void loadConfig() = 0;
+ virtual bool loadConfig(const String& pathname) = 0;
+
+ // A description of the daemon (used only on Windows).
+ virtual const char* daemonInfo() const = 0;
+
+ // Function pointer for function to exit immediately.
+ // TODO: this is old C code - use inheritance to normalize
+ void (*m_bye)(int);
+
+ static App& instance() { assert(s_instance != nullptr); return *s_instance; }
+
+ // If --log was specified in args, then add a file logger.
+ void setupFileLogging();
+
+ // If messages will be hidden (to improve performance), warn user.
+ void loggingFilterWarning();
+
+ // Parses args, sets up file logging, and loads the config.
+ void initApp(int argc, const char** argv);
+
+ // HACK: accept non-const, but make it const anyway
+ void initApp(int argc, char** argv) { initApp(argc, (const char**)argv); }
+
+ ARCH_APP_UTIL& appUtil() { return m_appUtil; }
+
+ virtual IArchTaskBarReceiver* taskBarReceiver() const { return m_taskBarReceiver; }
+
+ virtual void setByeFunc(void(*bye)(int)) { m_bye = bye; }
+ virtual void bye(int error) { m_bye(error); }
+
+ virtual IEventQueue* getEvents() const { return m_events; }
+
+ void setSocketMultiplexer(SocketMultiplexer* sm) { m_socketMultiplexer = sm; }
+ SocketMultiplexer* getSocketMultiplexer() const { return m_socketMultiplexer; }
+
+ void setEvents(EventQueue& events) { m_events = &events; }
+
+private:
+ void handleIpcMessage(const Event&, void*);
+
+protected:
+ void initIpcClient();
+ void cleanupIpcClient();
+ void runEventsLoop(void*);
+
+ IArchTaskBarReceiver* m_taskBarReceiver;
+ bool m_suspended;
+ IEventQueue* m_events;
+
+private:
+ ArgsBase* m_args;
+ static App* s_instance;
+ FileLogOutputter* m_fileLog;
+ CreateTaskBarReceiverFunc m_createTaskBarReceiver;
+ ARCH_APP_UTIL m_appUtil;
+ IpcClient* m_ipcClient;
+ SocketMultiplexer* m_socketMultiplexer;
+};
+
+class MinimalApp : public App {
+public:
+ MinimalApp();
+ virtual ~MinimalApp();
+
+ // IApp overrides
+ virtual int standardStartup(int argc, char** argv);
+ virtual int runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup);
+ virtual void startNode();
+ virtual int mainLoop();
+ virtual int foregroundStartup(int argc, char** argv);
+ virtual barrier::Screen*
+ createScreen();
+ virtual void loadConfig();
+ virtual bool loadConfig(const String& pathname);
+ virtual const char* daemonInfo() const;
+ virtual const char* daemonName() const;
+ virtual void parseArgs(int argc, const char* const* argv);
+
+private:
+ Arch m_arch;
+ Log m_log;
+ EventQueue m_events;
+};
+
+#if WINAPI_MSWINDOWS
+#define DAEMON_RUNNING(running_) ArchMiscWindows::daemonRunning(running_)
+#else
+#define DAEMON_RUNNING(running_)
+#endif
+
+#define HELP_COMMON_INFO_1 \
+ " -d, --debug <level> filter out log messages with priority below level.\n" \
+ " level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n" \
+ " DEBUG, DEBUG1, DEBUG2.\n" \
+ " -n, --name <screen-name> use screen-name instead the hostname to identify\n" \
+ " this screen in the configuration.\n" \
+ " -1, --no-restart do not try to restart on failure.\n" \
+ " --restart restart the server automatically if it fails. (*)\n" \
+ " -l --log <file> write log messages to file.\n" \
+ " --no-tray disable the system tray icon.\n" \
+ " --enable-drag-drop enable file drag & drop.\n" \
+ " --enable-crypto enable the crypto (ssl) plugin.\n"
+
+#define HELP_COMMON_INFO_2 \
+ " -h, --help display this help and exit.\n" \
+ " --version display version information and exit.\n"
+
+#define HELP_COMMON_ARGS \
+ " [--name <screen-name>]" \
+ " [--restart|--no-restart]" \
+ " [--debug <level>]"
+
+// system args (windows/unix)
+#if SYSAPI_UNIX
+
+// unix daemon mode args
+# define HELP_SYS_ARGS \
+ " [--daemon|--no-daemon]"
+# define HELP_SYS_INFO \
+ " -f, --no-daemon run in the foreground.\n" \
+ " --daemon run as a daemon. (*)\n"
+
+#elif SYSAPI_WIN32
+
+// windows args
+# define HELP_SYS_ARGS \
+ " [--service <action>] [--relaunch] [--exit-pause]"
+# define HELP_SYS_INFO \
+ " --service <action> manage the windows service, valid options are:\n" \
+ " install/uninstall/start/stop\n" \
+ " --relaunch persistently relaunches process in current user \n" \
+ " session (useful for vista and upward).\n" \
+ " --exit-pause wait for key press on exit, can be useful for\n" \
+ " reading error messages that occur on exit.\n"
+#endif
diff --git a/src/lib/barrier/AppUtil.cpp b/src/lib/barrier/AppUtil.cpp
new file mode 100644
index 0000000..3298d7b
--- /dev/null
+++ b/src/lib/barrier/AppUtil.cpp
@@ -0,0 +1,52 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/AppUtil.h"
+
+AppUtil* AppUtil::s_instance = nullptr;
+
+AppUtil::AppUtil() :
+m_app(nullptr)
+{
+ s_instance = this;
+}
+
+AppUtil::~AppUtil()
+{
+}
+
+void
+AppUtil::adoptApp(IApp* app)
+{
+ app->setByeFunc(&exitAppStatic);
+ m_app = app;
+}
+
+IApp&
+AppUtil::app() const
+{
+ assert(m_app != nullptr);
+ return *m_app;
+}
+
+AppUtil&
+AppUtil::instance()
+{
+ assert(s_instance != nullptr);
+ return *s_instance;
+}
diff --git a/src/lib/barrier/AppUtil.h b/src/lib/barrier/AppUtil.h
new file mode 100644
index 0000000..6f5f073
--- /dev/null
+++ b/src/lib/barrier/AppUtil.h
@@ -0,0 +1,40 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/IAppUtil.h"
+#include "barrier/XBarrier.h"
+
+class AppUtil : public IAppUtil {
+public:
+ AppUtil();
+ virtual ~AppUtil();
+
+ virtual void adoptApp(IApp* app);
+ IApp& app() const;
+ virtual void exitApp(int code) { throw XExitApp(code); }
+
+ static AppUtil& instance();
+ static void exitAppStatic(int code) { instance().exitApp(code); }
+ virtual void beforeAppExit() {}
+
+private:
+ IApp* m_app;
+ static AppUtil* s_instance;
+};
diff --git a/src/lib/barrier/ArgParser.cpp b/src/lib/barrier/ArgParser.cpp
new file mode 100644
index 0000000..20e849c
--- /dev/null
+++ b/src/lib/barrier/ArgParser.cpp
@@ -0,0 +1,519 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ArgParser.h"
+
+#include "barrier/StreamChunker.h"
+#include "barrier/App.h"
+#include "barrier/ServerArgs.h"
+#include "barrier/ClientArgs.h"
+#include "barrier/ToolArgs.h"
+#include "barrier/ArgsBase.h"
+#include "base/Log.h"
+#include "base/String.h"
+
+#ifdef WINAPI_MSWINDOWS
+#include <VersionHelpers.h>
+#endif
+
+ArgsBase* ArgParser::m_argsBase = NULL;
+
+ArgParser::ArgParser(App* app) :
+ m_app(app)
+{
+}
+
+bool
+ArgParser::parseServerArgs(ServerArgs& args, int argc, const char* const* argv)
+{
+ setArgsBase(args);
+ updateCommonArgs(argv);
+
+ for (int i = 1; i < argc; ++i) {
+ if (parsePlatformArg(args, argc, argv, i)) {
+ continue;
+ }
+ else if (parseGenericArgs(argc, argv, i)) {
+ continue;
+ }
+ else if (parseDeprecatedArgs(argc, argv, i)) {
+ continue;
+ }
+ else if (isArg(i, argc, argv, "-a", "--address", 1)) {
+ // save listen address
+ args.m_barrierAddress = argv[++i];
+ }
+ else if (isArg(i, argc, argv, "-c", "--config", 1)) {
+ // save configuration file path
+ args.m_configFile = argv[++i];
+ }
+ else {
+ LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, args.m_pname, argv[i], args.m_pname));
+ return false;
+ }
+ }
+
+ if (checkUnexpectedArgs()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ArgParser::parseClientArgs(ClientArgs& args, int argc, const char* const* argv)
+{
+ setArgsBase(args);
+ updateCommonArgs(argv);
+
+ int i;
+ for (i = 1; i < argc; ++i) {
+ if (parsePlatformArg(args, argc, argv, i)) {
+ continue;
+ }
+ else if (parseGenericArgs(argc, argv, i)) {
+ continue;
+ }
+ else if (parseDeprecatedArgs(argc, argv, i)) {
+ continue;
+ }
+ else if (isArg(i, argc, argv, NULL, "--camp")) {
+ // ignore -- included for backwards compatibility
+ }
+ else if (isArg(i, argc, argv, NULL, "--no-camp")) {
+ // ignore -- included for backwards compatibility
+ }
+ else if (isArg(i, argc, argv, NULL, "--yscroll", 1)) {
+ // define scroll
+ args.m_yscroll = atoi(argv[++i]);
+ }
+ else {
+ if (i + 1 == argc) {
+ args.m_barrierAddress = argv[i];
+ return true;
+ }
+
+ LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, args.m_pname, argv[i], args.m_pname));
+ return false;
+ }
+ }
+
+ if (args.m_shouldExit)
+ return true;
+
+ // exactly one non-option argument (server-address)
+ if (i == argc) {
+ LOG((CLOG_PRINT "%s: a server address or name is required" BYE,
+ args.m_pname, args.m_pname));
+ return false;
+ }
+
+ if (checkUnexpectedArgs()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ArgParser::parsePlatformArg(ArgsBase& argsBase, const int& argc, const char* const* argv, int& i)
+{
+#if WINAPI_MSWINDOWS
+ if (isArg(i, argc, argv, NULL, "--service")) {
+ LOG((CLOG_WARN "obsolete argument --service, use barrierd instead."));
+ argsBase.m_shouldExit = true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--exit-pause")) {
+ argsBase.m_pauseOnExit = true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--stop-on-desk-switch")) {
+ argsBase.m_stopOnDeskSwitch = true;
+ }
+ else {
+ // option not supported here
+ return false;
+ }
+
+ return true;
+#elif WINAPI_XWINDOWS
+ if (isArg(i, argc, argv, "-display", "--display", 1)) {
+ // use alternative display
+ argsBase.m_display = argv[++i];
+ }
+
+ else if (isArg(i, argc, argv, NULL, "--no-xinitthreads")) {
+ argsBase.m_disableXInitThreads = true;
+ }
+
+ else {
+ // option not supported here
+ return false;
+ }
+
+ return true;
+#elif WINAPI_CARBON
+ // no options for carbon
+ return false;
+#endif
+}
+
+bool
+ArgParser::parseToolArgs(ToolArgs& args, int argc, const char* const* argv)
+{
+ for (int i = 1; i < argc; ++i) {
+ if (isArg(i, argc, argv, NULL, "--get-active-desktop", 0)) {
+ args.m_printActiveDesktopName = true;
+ return true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--login-auth", 0)) {
+ args.m_loginAuthenticate = true;
+ return true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--get-installed-dir", 0)) {
+ args.m_getInstalledDir = true;
+ return true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--get-profile-dir", 0)) {
+ args.m_getProfileDir = true;
+ return true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--get-arch", 0)) {
+ args.m_getArch = true;
+ return true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--notify-activation", 0)) {
+ args.m_notifyActivation = true;
+ return true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--notify-update", 0)) {
+ args.m_notifyUpdate = true;
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ return false;
+}
+
+bool
+ArgParser::parseGenericArgs(int argc, const char* const* argv, int& i)
+{
+ if (isArg(i, argc, argv, "-d", "--debug", 1)) {
+ // change logging level
+ argsBase().m_logFilter = argv[++i];
+ }
+ else if (isArg(i, argc, argv, "-l", "--log", 1)) {
+ argsBase().m_logFile = argv[++i];
+ }
+ else if (isArg(i, argc, argv, "-f", "--no-daemon")) {
+ // not a daemon
+ argsBase().m_daemon = false;
+ }
+ else if (isArg(i, argc, argv, NULL, "--daemon")) {
+ // daemonize
+ argsBase().m_daemon = true;
+ }
+ else if (isArg(i, argc, argv, "-n", "--name", 1)) {
+ // save screen name
+ argsBase().m_name = argv[++i];
+ }
+ else if (isArg(i, argc, argv, "-1", "--no-restart")) {
+ // don't try to restart
+ argsBase().m_restartable = false;
+ }
+ else if (isArg(i, argc, argv, NULL, "--restart")) {
+ // try to restart
+ argsBase().m_restartable = true;
+ }
+ else if (isArg(i, argc, argv, "-z", NULL)) {
+ argsBase().m_backend = true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--no-hooks")) {
+ argsBase().m_noHooks = true;
+ }
+ else if (isArg(i, argc, argv, "-h", "--help")) {
+ if (m_app) {
+ m_app->help();
+ }
+ argsBase().m_shouldExit = true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--version")) {
+ if (m_app) {
+ m_app->version();
+ }
+ argsBase().m_shouldExit = true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--no-tray")) {
+ argsBase().m_disableTray = true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--ipc")) {
+ argsBase().m_enableIpc = true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--server")) {
+ // HACK: stop error happening when using portable (barrierp)
+ }
+ else if (isArg(i, argc, argv, NULL, "--client")) {
+ // HACK: stop error happening when using portable (barrierp)
+ }
+ else if (isArg(i, argc, argv, NULL, "--enable-drag-drop")) {
+ bool useDragDrop = true;
+
+#ifdef WINAPI_XWINDOWS
+
+ useDragDrop = false;
+ LOG((CLOG_INFO "ignoring --enable-drag-drop, not supported on linux."));
+
+#endif
+
+#ifdef WINAPI_MSWINDOWS
+
+ if (!IsWindowsVistaOrGreater()) {
+ useDragDrop = false;
+ LOG((CLOG_INFO "ignoring --enable-drag-drop, not supported below vista."));
+ }
+#endif
+
+ if (useDragDrop) {
+ argsBase().m_enableDragDrop = true;
+ }
+ }
+ else if (isArg(i, argc, argv, NULL, "--enable-crypto")) {
+ argsBase().m_enableCrypto = true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--profile-dir", 1)) {
+ argsBase().m_profileDirectory = argv[++i];
+ }
+ else if (isArg(i, argc, argv, NULL, "--plugin-dir", 1)) {
+ argsBase().m_pluginDirectory = argv[++i];
+ }
+ else {
+ // option not supported here
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ArgParser::parseDeprecatedArgs(int argc, const char* const* argv, int& i)
+{
+ if (isArg(i, argc, argv, NULL, "--crypto-pass")) {
+ LOG((CLOG_NOTE "--crypto-pass is deprecated"));
+ i++;
+ return true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--res-w")) {
+ LOG((CLOG_NOTE "--res-w is deprecated"));
+ i++;
+ return true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--res-h")) {
+ LOG((CLOG_NOTE "--res-h is deprecated"));
+ i++;
+ return true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--prm-wc")) {
+ LOG((CLOG_NOTE "--prm-wc is deprecated"));
+ i++;
+ return true;
+ }
+ else if (isArg(i, argc, argv, NULL, "--prm-hc")) {
+ LOG((CLOG_NOTE "--prm-hc is deprecated"));
+ i++;
+ return true;
+ }
+
+ return false;
+}
+
+bool
+ArgParser::isArg(
+ int argi, int argc, const char* const* argv,
+ const char* name1, const char* name2,
+ int minRequiredParameters)
+{
+ if ((name1 != NULL && strcmp(argv[argi], name1) == 0) ||
+ (name2 != NULL && strcmp(argv[argi], name2) == 0)) {
+ // match. check args left.
+ if (argi + minRequiredParameters >= argc) {
+ LOG((CLOG_PRINT "%s: missing arguments for `%s'" BYE,
+ argsBase().m_pname, argv[argi], argsBase().m_pname));
+ argsBase().m_shouldExit = true;
+ return false;
+ }
+ return true;
+ }
+
+ // no match
+ return false;
+}
+
+void
+ArgParser::splitCommandString(String& command, std::vector<String>& argv)
+{
+ if (command.empty()) {
+ return ;
+ }
+
+ size_t leftDoubleQuote = 0;
+ size_t rightDoubleQuote = 0;
+ searchDoubleQuotes(command, leftDoubleQuote, rightDoubleQuote);
+
+ size_t startPos = 0;
+ size_t space = command.find(" ", startPos);
+
+ while (space != String::npos) {
+ bool ignoreThisSpace = false;
+
+ // check if the space is between two double quotes
+ if (space > leftDoubleQuote && space < rightDoubleQuote) {
+ ignoreThisSpace = true;
+ }
+ else if (space > rightDoubleQuote){
+ searchDoubleQuotes(command, leftDoubleQuote, rightDoubleQuote, rightDoubleQuote + 1);
+ }
+
+ if (!ignoreThisSpace) {
+ String subString = command.substr(startPos, space - startPos);
+
+ removeDoubleQuotes(subString);
+ argv.push_back(subString);
+ }
+
+ // find next space
+ if (ignoreThisSpace) {
+ space = command.find(" ", rightDoubleQuote + 1);
+ }
+ else {
+ startPos = space + 1;
+ space = command.find(" ", startPos);
+ }
+ }
+
+ String subString = command.substr(startPos, command.size());
+ removeDoubleQuotes(subString);
+ argv.push_back(subString);
+}
+
+bool
+ArgParser::searchDoubleQuotes(String& command, size_t& left, size_t& right, size_t startPos)
+{
+ bool result = false;
+ left = String::npos;
+ right = String::npos;
+
+ left = command.find("\"", startPos);
+ if (left != String::npos) {
+ right = command.find("\"", left + 1);
+ if (right != String::npos) {
+ result = true;
+ }
+ }
+
+ if (!result) {
+ left = 0;
+ right = 0;
+ }
+
+ return result;
+}
+
+void
+ArgParser::removeDoubleQuotes(String& arg)
+{
+ // if string is surrounded by double quotes, remove them
+ if (arg[0] == '\"' &&
+ arg[arg.size() - 1] == '\"') {
+ arg = arg.substr(1, arg.size() - 2);
+ }
+}
+
+const char**
+ArgParser::getArgv(std::vector<String>& argsArray)
+{
+ size_t argc = argsArray.size();
+
+ // caller is responsible for deleting the outer array only
+ // we use the c string pointers from argsArray and assign
+ // them to the inner array. So caller only need to use
+ // delete[] to delete the outer array
+ const char** argv = new const char*[argc];
+
+ for (size_t i = 0; i < argc; i++) {
+ argv[i] = argsArray[i].c_str();
+ }
+
+ return argv;
+}
+
+String
+ArgParser::assembleCommand(std::vector<String>& argsArray, String ignoreArg, int parametersRequired)
+{
+ String result;
+
+ for (std::vector<String>::iterator it = argsArray.begin(); it != argsArray.end(); ++it) {
+ if (it->compare(ignoreArg) == 0) {
+ it = it + parametersRequired;
+ continue;
+ }
+
+ // if there is a space in this arg, use double quotes surround it
+ if ((*it).find(" ") != String::npos) {
+ (*it).insert(0, "\"");
+ (*it).push_back('\"');
+ }
+
+ result.append(*it);
+ // add space to saperate args
+ result.append(" ");
+ }
+
+ if (!result.empty()) {
+ // remove the tail space
+ result = result.substr(0, result.size() - 1);
+ }
+
+ return result;
+}
+
+void
+ArgParser::updateCommonArgs(const char* const* argv)
+{
+ argsBase().m_name = ARCH->getHostName();
+ argsBase().m_pname = ARCH->getBasename(argv[0]);
+}
+
+bool
+ArgParser::checkUnexpectedArgs()
+{
+#if SYSAPI_WIN32
+ // suggest that user installs as a windows service. when launched as
+ // service, process should automatically detect that it should run in
+ // daemon mode.
+ if (argsBase().m_daemon) {
+ LOG((CLOG_ERR
+ "the --daemon argument is not supported on windows. "
+ "instead, install %s as a service (--service install)",
+ argsBase().m_pname));
+ return true;
+ }
+#endif
+
+ return false;
+}
diff --git a/src/lib/barrier/ArgParser.h b/src/lib/barrier/ArgParser.h
new file mode 100644
index 0000000..5fc2649
--- /dev/null
+++ b/src/lib/barrier/ArgParser.h
@@ -0,0 +1,63 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+#include "common/stdvector.h"
+
+class ServerArgs;
+class ClientArgs;
+class ToolArgs;
+class ArgsBase;
+class App;
+
+class ArgParser {
+
+public:
+ ArgParser(App* app);
+
+ bool parseServerArgs(ServerArgs& args, int argc, const char* const* argv);
+ bool parseClientArgs(ClientArgs& args, int argc, const char* const* argv);
+ bool parsePlatformArg(ArgsBase& argsBase, const int& argc, const char* const* argv, int& i);
+ bool parseToolArgs(ToolArgs& args, int argc, const char* const* argv);
+ bool parseGenericArgs(int argc, const char* const* argv, int& i);
+ bool parseDeprecatedArgs(int argc, const char* const* argv, int& i);
+ void setArgsBase(ArgsBase& argsBase) { m_argsBase = &argsBase; }
+
+ static bool isArg(int argi, int argc, const char* const* argv,
+ const char* name1, const char* name2,
+ int minRequiredParameters = 0);
+ static void splitCommandString(String& command, std::vector<String>& argv);
+ static bool searchDoubleQuotes(String& command, size_t& left,
+ size_t& right, size_t startPos = 0);
+ static void removeDoubleQuotes(String& arg);
+ static const char** getArgv(std::vector<String>& argsArray);
+ static String assembleCommand(std::vector<String>& argsArray,
+ String ignoreArg = "", int parametersRequired = 0);
+
+private:
+ void updateCommonArgs(const char* const* argv);
+ bool checkUnexpectedArgs();
+
+ static ArgsBase& argsBase() { return *m_argsBase; }
+
+private:
+ App* m_app;
+
+ static ArgsBase* m_argsBase;
+};
diff --git a/src/lib/barrier/ArgsBase.cpp b/src/lib/barrier/ArgsBase.cpp
new file mode 100644
index 0000000..eedb312
--- /dev/null
+++ b/src/lib/barrier/ArgsBase.cpp
@@ -0,0 +1,53 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ArgsBase.h"
+
+ArgsBase::ArgsBase() :
+#if SYSAPI_WIN32
+m_daemon(false), // daemon mode not supported on windows (use --service)
+m_debugServiceWait(false),
+m_pauseOnExit(false),
+m_stopOnDeskSwitch(false),
+#else
+m_daemon(true), // backward compatibility for unix (daemon by default)
+#endif
+#if WINAPI_XWINDOWS
+m_disableXInitThreads(false),
+#endif
+m_backend(false),
+m_restartable(true),
+m_noHooks(false),
+m_pname(NULL),
+m_logFilter(NULL),
+m_logFile(NULL),
+m_display(NULL),
+m_disableTray(false),
+m_enableIpc(false),
+m_enableDragDrop(false),
+m_shouldExit(false),
+m_barrierAddress(),
+m_enableCrypto(false),
+m_profileDirectory(""),
+m_pluginDirectory("")
+{
+}
+
+ArgsBase::~ArgsBase()
+{
+}
diff --git a/src/lib/barrier/ArgsBase.h b/src/lib/barrier/ArgsBase.h
new file mode 100644
index 0000000..1f49984
--- /dev/null
+++ b/src/lib/barrier/ArgsBase.h
@@ -0,0 +1,54 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+
+class ArgsBase {
+public:
+ ArgsBase();
+ virtual ~ArgsBase();
+
+public:
+ bool m_daemon;
+ bool m_backend;
+ bool m_restartable;
+ bool m_noHooks;
+ const char* m_pname;
+ const char* m_logFilter;
+ const char* m_logFile;
+ const char* m_display;
+ String m_name;
+ bool m_disableTray;
+ bool m_enableIpc;
+ bool m_enableDragDrop;
+#if SYSAPI_WIN32
+ bool m_debugServiceWait;
+ bool m_pauseOnExit;
+ bool m_stopOnDeskSwitch;
+#endif
+#if WINAPI_XWINDOWS
+ bool m_disableXInitThreads;
+#endif
+ bool m_shouldExit;
+ String m_barrierAddress;
+ bool m_enableCrypto;
+ String m_profileDirectory;
+ String m_pluginDirectory;
+};
diff --git a/src/lib/barrier/CMakeLists.txt b/src/lib/barrier/CMakeLists.txt
new file mode 100644
index 0000000..6978aef
--- /dev/null
+++ b/src/lib/barrier/CMakeLists.txt
@@ -0,0 +1,40 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+file(GLOB headers "*.h")
+file(GLOB sources "*.cpp")
+
+# arch
+if (WIN32)
+ file(GLOB arch_headers "win32/*.h")
+ file(GLOB arch_sources "win32/*.cpp")
+elseif (UNIX)
+ file(GLOB arch_headers "unix/*.h")
+ file(GLOB arch_sources "unix/*.cpp")
+endif()
+
+list(APPEND sources ${arch_sources})
+list(APPEND headers ${arch_headers})
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+add_library(synlib STATIC ${sources})
+
+if (UNIX)
+ target_link_libraries(synlib arch client ipc net base platform mt server)
+endif()
diff --git a/src/lib/barrier/Chunk.cpp b/src/lib/barrier/Chunk.cpp
new file mode 100644
index 0000000..f11bff5
--- /dev/null
+++ b/src/lib/barrier/Chunk.cpp
@@ -0,0 +1,30 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/Chunk.h"
+#include "base/String.h"
+
+Chunk::Chunk(size_t size): m_dataSize(0)
+{
+ m_chunk = new char[size];
+ memset(m_chunk, 0, size);
+}
+
+Chunk::~Chunk()
+{
+ delete[] m_chunk;
+}
diff --git a/src/lib/barrier/Chunk.h b/src/lib/barrier/Chunk.h
new file mode 100644
index 0000000..42b85bf
--- /dev/null
+++ b/src/lib/barrier/Chunk.h
@@ -0,0 +1,30 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/basic_types.h"
+
+class Chunk {
+public:
+ Chunk(size_t size);
+ ~Chunk();
+
+public:
+ size_t m_dataSize;
+ char* m_chunk;
+};
diff --git a/src/lib/barrier/ClientApp.cpp b/src/lib/barrier/ClientApp.cpp
new file mode 100644
index 0000000..87686f2
--- /dev/null
+++ b/src/lib/barrier/ClientApp.cpp
@@ -0,0 +1,560 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ClientApp.h"
+
+#include "client/Client.h"
+#include "barrier/ArgParser.h"
+#include "barrier/protocol_types.h"
+#include "barrier/Screen.h"
+#include "barrier/XScreen.h"
+#include "barrier/ClientArgs.h"
+#include "net/NetworkAddress.h"
+#include "net/TCPSocketFactory.h"
+#include "net/SocketMultiplexer.h"
+#include "net/XSocket.h"
+#include "mt/Thread.h"
+#include "arch/IArchTaskBarReceiver.h"
+#include "arch/Arch.h"
+#include "base/String.h"
+#include "base/Event.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+#include "base/log_outputters.h"
+#include "base/EventQueue.h"
+#include "base/TMethodJob.h"
+#include "base/Log.h"
+#include "common/Version.h"
+
+#if SYSAPI_WIN32
+#include "arch/win32/ArchMiscWindows.h"
+#endif
+
+#if WINAPI_MSWINDOWS
+#include "platform/MSWindowsScreen.h"
+#elif WINAPI_XWINDOWS
+#include "platform/XWindowsScreen.h"
+#elif WINAPI_CARBON
+#include "platform/OSXScreen.h"
+#endif
+
+#if defined(__APPLE__)
+#include "platform/OSXDragSimulator.h"
+#endif
+
+#include <iostream>
+#include <stdio.h>
+#include <sstream>
+
+#define RETRY_TIME 1.0
+
+ClientApp::ClientApp(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver) :
+ App(events, createTaskBarReceiver, new ClientArgs()),
+ m_client(NULL),
+ m_clientScreen(NULL),
+ m_serverAddress(NULL)
+{
+}
+
+ClientApp::~ClientApp()
+{
+}
+
+void
+ClientApp::parseArgs(int argc, const char* const* argv)
+{
+ ArgParser argParser(this);
+ bool result = argParser.parseClientArgs(args(), argc, argv);
+
+ if (!result || args().m_shouldExit) {
+ m_bye(kExitArgs);
+ }
+ else {
+ // save server address
+ if (!args().m_barrierAddress.empty()) {
+ try {
+ *m_serverAddress = NetworkAddress(args().m_barrierAddress, kDefaultPort);
+ m_serverAddress->resolve();
+ }
+ catch (XSocketAddress& e) {
+ // allow an address that we can't look up if we're restartable.
+ // we'll try to resolve the address each time we connect to the
+ // server. a bad port will never get better. patch by Brent
+ // Priddy.
+ if (!args().m_restartable || e.getError() == XSocketAddress::kBadPort) {
+ LOG((CLOG_PRINT "%s: %s" BYE,
+ args().m_pname, e.what(), args().m_pname));
+ m_bye(kExitFailed);
+ }
+ }
+ }
+ }
+}
+
+void
+ClientApp::help()
+{
+#if WINAPI_XWINDOWS
+# define WINAPI_ARG \
+ " [--display <display>] [--no-xinitthreads]"
+# define WINAPI_INFO \
+ " --display <display> connect to the X server at <display>\n" \
+ " --no-xinitthreads do not call XInitThreads()\n"
+#else
+# define WINAPI_ARG ""
+# define WINAPI_INFO ""
+#endif
+
+ std::ostringstream buffer;
+ buffer << "Start the barrier client and connect to a remote server component." << std::endl
+ << std::endl
+ << "Usage: " << args().m_pname << " [--yscroll <delta>]" << WINAPI_ARG << HELP_SYS_ARGS
+ << HELP_COMMON_ARGS << " <server-address>" << std::endl
+ << std::endl
+ << "Options:" << std::endl
+ << HELP_COMMON_INFO_1 << WINAPI_INFO << HELP_SYS_INFO
+ << " --yscroll <delta> defines the vertical scrolling delta, which is" << std::endl
+ << " 120 by default." << std::endl
+ << HELP_COMMON_INFO_2
+ << std::endl
+ << "Default options are marked with a *" << std::endl
+ << std::endl
+ << "The server address is of the form: [<hostname>][:<port>]. The hostname" << std::endl
+ << "must be the address or hostname of the server. The port overrides the" << std::endl
+ << "default port, " << kDefaultPort << "." << std::endl;
+
+ LOG((CLOG_PRINT "%s", buffer.str().c_str()));
+}
+
+const char*
+ClientApp::daemonName() const
+{
+#if SYSAPI_WIN32
+ return "Barrier Client";
+#elif SYSAPI_UNIX
+ return "barrierc";
+#endif
+}
+
+const char*
+ClientApp::daemonInfo() const
+{
+#if SYSAPI_WIN32
+ return "Allows another computer to share it's keyboard and mouse with this computer.";
+#elif SYSAPI_UNIX
+ return "";
+#endif
+}
+
+barrier::Screen*
+ClientApp::createScreen()
+{
+#if WINAPI_MSWINDOWS
+ return new barrier::Screen(new MSWindowsScreen(
+ false, args().m_noHooks, args().m_stopOnDeskSwitch, m_events), m_events);
+#elif WINAPI_XWINDOWS
+ return new barrier::Screen(new XWindowsScreen(
+ args().m_display, false, args().m_disableXInitThreads,
+ args().m_yscroll, m_events), m_events);
+#elif WINAPI_CARBON
+ return new barrier::Screen(new OSXScreen(m_events, false), m_events);
+#endif
+}
+
+void
+ClientApp::updateStatus()
+{
+ updateStatus("");
+}
+
+
+void
+ClientApp::updateStatus(const String& msg)
+{
+ if (m_taskBarReceiver)
+ {
+ m_taskBarReceiver->updateStatus(m_client, msg);
+ }
+}
+
+
+void
+ClientApp::resetRestartTimeout()
+{
+ // retry time can nolonger be changed
+ //s_retryTime = 0.0;
+}
+
+
+double
+ClientApp::nextRestartTimeout()
+{
+ // retry at a constant rate (Issue 52)
+ return RETRY_TIME;
+
+ /*
+ // choose next restart timeout. we start with rapid retries
+ // then slow down.
+ if (s_retryTime < 1.0) {
+ s_retryTime = 1.0;
+ }
+ else if (s_retryTime < 3.0) {
+ s_retryTime = 3.0;
+ }
+ else {
+ s_retryTime = 5.0;
+ }
+ return s_retryTime;
+ */
+}
+
+
+void
+ClientApp::handleScreenError(const Event&, void*)
+{
+ LOG((CLOG_CRIT "error on screen"));
+ m_events->addEvent(Event(Event::kQuit));
+}
+
+
+barrier::Screen*
+ClientApp::openClientScreen()
+{
+ barrier::Screen* screen = createScreen();
+ screen->setEnableDragDrop(argsBase().m_enableDragDrop);
+ m_events->adoptHandler(m_events->forIScreen().error(),
+ screen->getEventTarget(),
+ new TMethodEventJob<ClientApp>(
+ this, &ClientApp::handleScreenError));
+ return screen;
+}
+
+
+void
+ClientApp::closeClientScreen(barrier::Screen* screen)
+{
+ if (screen != NULL) {
+ m_events->removeHandler(m_events->forIScreen().error(),
+ screen->getEventTarget());
+ delete screen;
+ }
+}
+
+
+void
+ClientApp::handleClientRestart(const Event&, void* vtimer)
+{
+ // discard old timer
+ EventQueueTimer* timer = static_cast<EventQueueTimer*>(vtimer);
+ m_events->deleteTimer(timer);
+ m_events->removeHandler(Event::kTimer, timer);
+
+ // reconnect
+ startClient();
+}
+
+
+void
+ClientApp::scheduleClientRestart(double retryTime)
+{
+ // install a timer and handler to retry later
+ LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime));
+ EventQueueTimer* timer = m_events->newOneShotTimer(retryTime, NULL);
+ m_events->adoptHandler(Event::kTimer, timer,
+ new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientRestart, timer));
+}
+
+
+void
+ClientApp::handleClientConnected(const Event&, void*)
+{
+ LOG((CLOG_NOTE "connected to server"));
+ resetRestartTimeout();
+ updateStatus();
+}
+
+
+void
+ClientApp::handleClientFailed(const Event& e, void*)
+{
+ Client::FailInfo* info =
+ static_cast<Client::FailInfo*>(e.getData());
+
+ updateStatus(String("Failed to connect to server: ") + info->m_what);
+ if (!args().m_restartable || !info->m_retry) {
+ LOG((CLOG_ERR "failed to connect to server: %s", info->m_what.c_str()));
+ m_events->addEvent(Event(Event::kQuit));
+ }
+ else {
+ LOG((CLOG_WARN "failed to connect to server: %s", info->m_what.c_str()));
+ if (!m_suspended) {
+ scheduleClientRestart(nextRestartTimeout());
+ }
+ }
+ delete info;
+}
+
+
+void
+ClientApp::handleClientDisconnected(const Event&, void*)
+{
+ LOG((CLOG_NOTE "disconnected from server"));
+ if (!args().m_restartable) {
+ m_events->addEvent(Event(Event::kQuit));
+ }
+ else if (!m_suspended) {
+ scheduleClientRestart(nextRestartTimeout());
+ }
+ updateStatus();
+}
+
+Client*
+ClientApp::openClient(const String& name, const NetworkAddress& address,
+ barrier::Screen* screen)
+{
+ Client* client = new Client(
+ m_events,
+ name,
+ address,
+ new TCPSocketFactory(m_events, getSocketMultiplexer()),
+ screen,
+ args());
+
+ try {
+ m_events->adoptHandler(
+ m_events->forClient().connected(),
+ client->getEventTarget(),
+ new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientConnected));
+
+ m_events->adoptHandler(
+ m_events->forClient().connectionFailed(),
+ client->getEventTarget(),
+ new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientFailed));
+
+ m_events->adoptHandler(
+ m_events->forClient().disconnected(),
+ client->getEventTarget(),
+ new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientDisconnected));
+
+ } catch (std::bad_alloc &ba) {
+ delete client;
+ throw ba;
+ }
+
+ return client;
+}
+
+
+void
+ClientApp::closeClient(Client* client)
+{
+ if (client == NULL) {
+ return;
+ }
+
+ m_events->removeHandler(m_events->forClient().connected(), client);
+ m_events->removeHandler(m_events->forClient().connectionFailed(), client);
+ m_events->removeHandler(m_events->forClient().disconnected(), client);
+ delete client;
+}
+
+int
+ClientApp::foregroundStartup(int argc, char** argv)
+{
+ initApp(argc, argv);
+
+ // never daemonize
+ return mainLoop();
+}
+
+bool
+ClientApp::startClient()
+{
+ double retryTime;
+ barrier::Screen* clientScreen = NULL;
+ try {
+ if (m_clientScreen == NULL) {
+ clientScreen = openClientScreen();
+ m_client = openClient(args().m_name,
+ *m_serverAddress, clientScreen);
+ m_clientScreen = clientScreen;
+ LOG((CLOG_NOTE "started client"));
+ }
+
+ m_client->connect();
+
+ updateStatus();
+ return true;
+ }
+ catch (XScreenUnavailable& e) {
+ LOG((CLOG_WARN "secondary screen unavailable: %s", e.what()));
+ closeClientScreen(clientScreen);
+ updateStatus(String("secondary screen unavailable: ") + e.what());
+ retryTime = e.getRetryTime();
+ }
+ catch (XScreenOpenFailure& e) {
+ LOG((CLOG_CRIT "failed to start client: %s", e.what()));
+ closeClientScreen(clientScreen);
+ return false;
+ }
+ catch (XBase& e) {
+ LOG((CLOG_CRIT "failed to start client: %s", e.what()));
+ closeClientScreen(clientScreen);
+ return false;
+ }
+
+ if (args().m_restartable) {
+ scheduleClientRestart(retryTime);
+ return true;
+ }
+ else {
+ // don't try again
+ return false;
+ }
+}
+
+
+void
+ClientApp::stopClient()
+{
+ closeClient(m_client);
+ closeClientScreen(m_clientScreen);
+ m_client = NULL;
+ m_clientScreen = NULL;
+}
+
+
+int
+ClientApp::mainLoop()
+{
+ // create socket multiplexer. this must happen after daemonization
+ // on unix because threads evaporate across a fork().
+ SocketMultiplexer multiplexer;
+ setSocketMultiplexer(&multiplexer);
+
+ // start client, etc
+ appUtil().startNode();
+
+ // init ipc client after node start, since create a new screen wipes out
+ // the event queue (the screen ctors call adoptBuffer).
+ if (argsBase().m_enableIpc) {
+ initIpcClient();
+ }
+
+ // run event loop. if startClient() failed we're supposed to retry
+ // later. the timer installed by startClient() will take care of
+ // that.
+ DAEMON_RUNNING(true);
+
+#if defined(MAC_OS_X_VERSION_10_7)
+
+ Thread thread(
+ new TMethodJob<ClientApp>(
+ this, &ClientApp::runEventsLoop,
+ NULL));
+
+ // wait until carbon loop is ready
+ OSXScreen* screen = dynamic_cast<OSXScreen*>(
+ m_clientScreen->getPlatformScreen());
+ screen->waitForCarbonLoop();
+
+ runCocoaApp();
+#else
+ m_events->loop();
+#endif
+
+ DAEMON_RUNNING(false);
+
+ // close down
+ LOG((CLOG_DEBUG1 "stopping client"));
+ stopClient();
+ updateStatus();
+ LOG((CLOG_NOTE "stopped client"));
+
+ if (argsBase().m_enableIpc) {
+ cleanupIpcClient();
+ }
+
+ return kExitSuccess;
+}
+
+static
+int
+daemonMainLoopStatic(int argc, const char** argv)
+{
+ return ClientApp::instance().daemonMainLoop(argc, argv);
+}
+
+int
+ClientApp::standardStartup(int argc, char** argv)
+{
+ initApp(argc, argv);
+
+ // daemonize if requested
+ if (args().m_daemon) {
+ return ARCH->daemonize(daemonName(), &daemonMainLoopStatic);
+ }
+ else {
+ return mainLoop();
+ }
+}
+
+int
+ClientApp::runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup)
+{
+ // general initialization
+ m_serverAddress = new NetworkAddress;
+ args().m_pname = ARCH->getBasename(argv[0]);
+
+ // install caller's output filter
+ if (outputter != NULL) {
+ CLOG->insert(outputter);
+ }
+
+ int result;
+ try
+ {
+ // run
+ result = startup(argc, argv);
+ }
+ catch (...)
+ {
+ if (m_taskBarReceiver)
+ {
+ // done with task bar receiver
+ delete m_taskBarReceiver;
+ }
+
+ delete m_serverAddress;
+
+ throw;
+ }
+
+ return result;
+}
+
+void
+ClientApp::startNode()
+{
+ // start the client. if this return false then we've failed and
+ // we shouldn't retry.
+ LOG((CLOG_DEBUG1 "starting client"));
+ if (!startClient()) {
+ m_bye(kExitFailed);
+ }
+}
diff --git a/src/lib/barrier/ClientApp.h b/src/lib/barrier/ClientApp.h
new file mode 100644
index 0000000..777f3d3
--- /dev/null
+++ b/src/lib/barrier/ClientApp.h
@@ -0,0 +1,83 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/App.h"
+
+namespace barrier { class Screen; }
+class Event;
+class Client;
+class NetworkAddress;
+class Thread;
+class ClientArgs;
+
+class ClientApp : public App {
+public:
+ ClientApp(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver);
+ virtual ~ClientApp();
+
+ // Parse client specific command line arguments.
+ void parseArgs(int argc, const char* const* argv);
+
+ // Prints help specific to client.
+ void help();
+
+ // Returns arguments that are common and for client.
+ ClientArgs& args() const { return (ClientArgs&)argsBase(); }
+
+ const char* daemonName() const;
+ const char* daemonInfo() const;
+
+ // TODO: move to server only (not supported on client)
+ void loadConfig() { }
+ bool loadConfig(const String& pathname) { return false; }
+
+ int foregroundStartup(int argc, char** argv);
+ int standardStartup(int argc, char** argv);
+ int runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup);
+ barrier::Screen* createScreen();
+ void updateStatus();
+ void updateStatus(const String& msg);
+ void resetRestartTimeout();
+ double nextRestartTimeout();
+ void handleScreenError(const Event&, void*);
+ barrier::Screen* openClientScreen();
+ void closeClientScreen(barrier::Screen* screen);
+ void handleClientRestart(const Event&, void* vtimer);
+ void scheduleClientRestart(double retryTime);
+ void handleClientConnected(const Event&, void*);
+ void handleClientFailed(const Event& e, void*);
+ void handleClientDisconnected(const Event&, void*);
+ Client* openClient(const String& name, const NetworkAddress& address,
+ barrier::Screen* screen);
+ void closeClient(Client* client);
+ bool startClient();
+ void stopClient();
+ int mainLoop();
+ void startNode();
+
+ static ClientApp& instance() { return (ClientApp&)App::instance(); }
+
+ Client* getClientPtr() { return m_client; }
+
+private:
+ Client* m_client;
+ barrier::Screen*m_clientScreen;
+ NetworkAddress* m_serverAddress;
+};
diff --git a/src/lib/barrier/ClientArgs.cpp b/src/lib/barrier/ClientArgs.cpp
new file mode 100644
index 0000000..5c9ed88
--- /dev/null
+++ b/src/lib/barrier/ClientArgs.cpp
@@ -0,0 +1,23 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ClientArgs.h"
+
+ClientArgs::ClientArgs() :
+ m_yscroll(0)
+{
+}
diff --git a/src/lib/barrier/ClientArgs.h b/src/lib/barrier/ClientArgs.h
new file mode 100644
index 0000000..70285fa
--- /dev/null
+++ b/src/lib/barrier/ClientArgs.h
@@ -0,0 +1,30 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/ArgsBase.h"
+
+class NetworkAddress;
+
+class ClientArgs : public ArgsBase {
+public:
+ ClientArgs();
+
+public:
+ int m_yscroll;
+};
diff --git a/src/lib/barrier/ClientTaskBarReceiver.cpp b/src/lib/barrier/ClientTaskBarReceiver.cpp
new file mode 100644
index 0000000..2ea6566
--- /dev/null
+++ b/src/lib/barrier/ClientTaskBarReceiver.cpp
@@ -0,0 +1,141 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ClientTaskBarReceiver.h"
+#include "client/Client.h"
+#include "mt/Lock.h"
+#include "base/String.h"
+#include "base/IEventQueue.h"
+#include "arch/Arch.h"
+#include "common/Version.h"
+
+//
+// ClientTaskBarReceiver
+//
+
+ClientTaskBarReceiver::ClientTaskBarReceiver(IEventQueue* events) :
+ m_state(kNotRunning),
+ m_events(events)
+{
+ // do nothing
+}
+
+ClientTaskBarReceiver::~ClientTaskBarReceiver()
+{
+ // do nothing
+}
+
+void
+ClientTaskBarReceiver::updateStatus(Client* client, const String& 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);
+}
+
+ClientTaskBarReceiver::EState
+ClientTaskBarReceiver::getStatus() const
+{
+ return m_state;
+}
+
+const String&
+ClientTaskBarReceiver::getErrorMessage() const
+{
+ return m_errorMessage;
+}
+
+void
+ClientTaskBarReceiver::quit()
+{
+ m_events->addEvent(Event(Event::kQuit));
+}
+
+void
+ClientTaskBarReceiver::onStatusChanged(Client*)
+{
+ // do nothing
+}
+
+void
+ClientTaskBarReceiver::lock() const
+{
+ // do nothing
+}
+
+void
+ClientTaskBarReceiver::unlock() const
+{
+ // do nothing
+}
+
+std::string
+ClientTaskBarReceiver::getToolTip() const
+{
+ switch (m_state) {
+ case kNotRunning:
+ return barrier::string::sprintf("%s: Not running", kAppVersion);
+
+ case kNotWorking:
+ return barrier::string::sprintf("%s: %s",
+ kAppVersion, m_errorMessage.c_str());
+
+ case kNotConnected:
+ return barrier::string::sprintf("%s: Not connected: %s",
+ kAppVersion, m_errorMessage.c_str());
+
+ case kConnecting:
+ return barrier::string::sprintf("%s: Connecting to %s...",
+ kAppVersion, m_server.c_str());
+
+ case kConnected:
+ return barrier::string::sprintf("%s: Connected to %s",
+ kAppVersion, m_server.c_str());
+
+ default:
+ return "";
+ }
+}
diff --git a/src/lib/barrier/ClientTaskBarReceiver.h b/src/lib/barrier/ClientTaskBarReceiver.h
new file mode 100644
index 0000000..da15154
--- /dev/null
+++ b/src/lib/barrier/ClientTaskBarReceiver.h
@@ -0,0 +1,95 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CCLIENTTASKBARRECEIVER_H
+#define CCLIENTTASKBARRECEIVER_H
+
+#include "base/String.h"
+#include "arch/IArchTaskBarReceiver.h"
+#include "base/log_outputters.h"
+#include "client/Client.h"
+
+class IEventQueue;
+
+//! Implementation of IArchTaskBarReceiver for the barrier server
+class ClientTaskBarReceiver : public IArchTaskBarReceiver {
+public:
+ ClientTaskBarReceiver(IEventQueue* events);
+ virtual ~ClientTaskBarReceiver();
+
+ //! @name manipulators
+ //@{
+
+ //! Update status
+ /*!
+ Determine the status and query required information from the client.
+ */
+ void updateStatus(Client*, const String& errorMsg);
+
+ void updateStatus(INode* n, const String& errorMsg) { updateStatus((Client*)n, 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;
+ virtual void cleanup() {}
+
+protected:
+ enum EState {
+ kNotRunning,
+ kNotWorking,
+ kNotConnected,
+ kConnecting,
+ kConnected,
+ kMaxState
+ };
+
+ //! Get status
+ EState getStatus() const;
+
+ //! Get error message
+ const String& 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(Client* client);
+
+private:
+ EState m_state;
+ String m_errorMessage;
+ String m_server;
+ IEventQueue* m_events;
+};
+
+IArchTaskBarReceiver* createTaskBarReceiver(const BufferedLogOutputter* logBuffer, IEventQueue* events);
+
+#endif
diff --git a/src/lib/barrier/Clipboard.cpp b/src/lib/barrier/Clipboard.cpp
new file mode 100644
index 0000000..a6a166d
--- /dev/null
+++ b/src/lib/barrier/Clipboard.cpp
@@ -0,0 +1,118 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/Clipboard.h"
+
+//
+// Clipboard
+//
+
+Clipboard::Clipboard() :
+ m_open(false),
+ m_owner(false)
+{
+ open(0);
+ empty();
+ close();
+}
+
+Clipboard::~Clipboard()
+{
+ // do nothing
+}
+
+bool
+Clipboard::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
+Clipboard::add(EFormat format, const String& data)
+{
+ assert(m_open);
+ assert(m_owner);
+
+ m_data[format] = data;
+ m_added[format] = true;
+}
+
+bool
+Clipboard::open(Time time) const
+{
+ assert(!m_open);
+
+ m_open = true;
+ m_time = time;
+
+ return true;
+}
+
+void
+Clipboard::close() const
+{
+ assert(m_open);
+
+ m_open = false;
+}
+
+Clipboard::Time
+Clipboard::getTime() const
+{
+ return m_timeOwned;
+}
+
+bool
+Clipboard::has(EFormat format) const
+{
+ assert(m_open);
+ return m_added[format];
+}
+
+String
+Clipboard::get(EFormat format) const
+{
+ assert(m_open);
+ return m_data[format];
+}
+
+void
+Clipboard::unmarshall(const String& data, Time time)
+{
+ IClipboard::unmarshall(this, data, time);
+}
+
+String
+Clipboard::marshall() const
+{
+ return IClipboard::marshall(this);
+}
diff --git a/src/lib/barrier/Clipboard.h b/src/lib/barrier/Clipboard.h
new file mode 100644
index 0000000..23bea75
--- /dev/null
+++ b/src/lib/barrier/Clipboard.h
@@ -0,0 +1,71 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/IClipboard.h"
+
+//! Memory buffer clipboard
+/*!
+This class implements a clipboard that stores data in memory.
+*/
+class Clipboard : public IClipboard {
+public:
+ Clipboard();
+ virtual ~Clipboard();
+
+ //! @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 String& 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.
+ */
+ String marshall() const;
+
+ //@}
+
+ // IClipboard overrides
+ virtual bool empty();
+ virtual void add(EFormat, const String& data);
+ virtual bool open(Time) const;
+ virtual void close() const;
+ virtual Time getTime() const;
+ virtual bool has(EFormat) const;
+ virtual String get(EFormat) const;
+
+private:
+ mutable bool m_open;
+ mutable Time m_time;
+ bool m_owner;
+ Time m_timeOwned;
+ bool m_added[kNumFormats];
+ String m_data[kNumFormats];
+};
diff --git a/src/lib/barrier/ClipboardChunk.cpp b/src/lib/barrier/ClipboardChunk.cpp
new file mode 100644
index 0000000..bc71471
--- /dev/null
+++ b/src/lib/barrier/ClipboardChunk.cpp
@@ -0,0 +1,154 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ClipboardChunk.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "barrier/protocol_types.h"
+#include "io/IStream.h"
+#include "base/Log.h"
+#include <cstring>
+
+size_t ClipboardChunk::s_expectedSize = 0;
+
+ClipboardChunk::ClipboardChunk(size_t size) :
+ Chunk(size)
+{
+ m_dataSize = size - CLIPBOARD_CHUNK_META_SIZE;
+}
+
+ClipboardChunk*
+ClipboardChunk::start(
+ ClipboardID id,
+ UInt32 sequence,
+ const String& size)
+{
+ size_t sizeLength = size.size();
+ ClipboardChunk* start = new ClipboardChunk(sizeLength + CLIPBOARD_CHUNK_META_SIZE);
+ char* chunk = start->m_chunk;
+
+ chunk[0] = id;
+ std::memcpy (&chunk[1], &sequence, 4);
+ chunk[5] = kDataStart;
+ memcpy(&chunk[6], size.c_str(), sizeLength);
+ chunk[sizeLength + CLIPBOARD_CHUNK_META_SIZE - 1] = '\0';
+
+ return start;
+}
+
+ClipboardChunk*
+ClipboardChunk::data(
+ ClipboardID id,
+ UInt32 sequence,
+ const String& data)
+{
+ size_t dataSize = data.size();
+ ClipboardChunk* chunk = new ClipboardChunk(dataSize + CLIPBOARD_CHUNK_META_SIZE);
+ char* chunkData = chunk->m_chunk;
+
+ chunkData[0] = id;
+ std::memcpy (&chunkData[1], &sequence, 4);
+ chunkData[5] = kDataChunk;
+ memcpy(&chunkData[6], data.c_str(), dataSize);
+ chunkData[dataSize + CLIPBOARD_CHUNK_META_SIZE - 1] = '\0';
+
+ return chunk;
+}
+
+ClipboardChunk*
+ClipboardChunk::end(ClipboardID id, UInt32 sequence)
+{
+ ClipboardChunk* end = new ClipboardChunk(CLIPBOARD_CHUNK_META_SIZE);
+ char* chunk = end->m_chunk;
+
+ chunk[0] = id;
+ std::memcpy (&chunk[1], &sequence, 4);
+ chunk[5] = kDataEnd;
+ chunk[CLIPBOARD_CHUNK_META_SIZE - 1] = '\0';
+
+ return end;
+}
+
+int
+ClipboardChunk::assemble(barrier::IStream* stream,
+ String& dataCached,
+ ClipboardID& id,
+ UInt32& sequence)
+{
+ UInt8 mark;
+ String data;
+
+ if (!ProtocolUtil::readf(stream, kMsgDClipboard + 4, &id, &sequence, &mark, &data)) {
+ return kError;
+ }
+
+ if (mark == kDataStart) {
+ s_expectedSize = barrier::string::stringToSizeType(data);
+ LOG((CLOG_DEBUG "start receiving clipboard data"));
+ dataCached.clear();
+ return kStart;
+ }
+ else if (mark == kDataChunk) {
+ dataCached.append(data);
+ return kNotFinish;
+ }
+ else if (mark == kDataEnd) {
+ // validate
+ if (id >= kClipboardEnd) {
+ return kError;
+ }
+ else if (s_expectedSize != dataCached.size()) {
+ LOG((CLOG_ERR "corrupted clipboard data, expected size=%d actual size=%d", s_expectedSize, dataCached.size()));
+ return kError;
+ }
+ return kFinish;
+ }
+
+ LOG((CLOG_ERR "clipboard transmission failed: unknown error"));
+ return kError;
+}
+
+void
+ClipboardChunk::send(barrier::IStream* stream, void* data)
+{
+ ClipboardChunk* clipboardData = static_cast<ClipboardChunk*>(data);
+
+ LOG((CLOG_DEBUG1 "sending clipboard chunk"));
+
+ char* chunk = clipboardData->m_chunk;
+ ClipboardID id = chunk[0];
+ UInt32 sequence;
+ std::memcpy (&sequence, &chunk[1], 4);
+ UInt8 mark = chunk[5];
+ String dataChunk(&chunk[6], clipboardData->m_dataSize);
+
+ switch (mark) {
+ case kDataStart:
+ LOG((CLOG_DEBUG2 "sending clipboard chunk start: size=%s", dataChunk.c_str()));
+ break;
+
+ case kDataChunk:
+ LOG((CLOG_DEBUG2 "sending clipboard chunk data: size=%i", dataChunk.size()));
+ break;
+
+ case kDataEnd:
+ LOG((CLOG_DEBUG2 "sending clipboard finished"));
+ break;
+ }
+
+ ProtocolUtil::writef(stream, kMsgDClipboard, id, sequence, mark, &dataChunk);
+}
diff --git a/src/lib/barrier/ClipboardChunk.h b/src/lib/barrier/ClipboardChunk.h
new file mode 100644
index 0000000..6402aca
--- /dev/null
+++ b/src/lib/barrier/ClipboardChunk.h
@@ -0,0 +1,60 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/Chunk.h"
+#include "barrier/clipboard_types.h"
+#include "base/String.h"
+#include "common/basic_types.h"
+
+#define CLIPBOARD_CHUNK_META_SIZE 7
+
+namespace barrier {
+class IStream;
+};
+
+class ClipboardChunk : public Chunk {
+public:
+ ClipboardChunk(size_t size);
+
+ static ClipboardChunk*
+ start(
+ ClipboardID id,
+ UInt32 sequence,
+ const String& size);
+ static ClipboardChunk*
+ data(
+ ClipboardID id,
+ UInt32 sequence,
+ const String& data);
+ static ClipboardChunk*
+ end(ClipboardID id, UInt32 sequence);
+
+ static int assemble(
+ barrier::IStream* stream,
+ String& dataCached,
+ ClipboardID& id,
+ UInt32& sequence);
+
+ static void send(barrier::IStream* stream, void* data);
+
+ static size_t getExpectedSize() { return s_expectedSize; }
+
+private:
+ static size_t s_expectedSize;
+};
diff --git a/src/lib/barrier/DragInformation.cpp b/src/lib/barrier/DragInformation.cpp
new file mode 100644
index 0000000..db28f3d
--- /dev/null
+++ b/src/lib/barrier/DragInformation.cpp
@@ -0,0 +1,158 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/DragInformation.h"
+#include "base/Log.h"
+
+#include <fstream>
+#include <sstream>
+#include <stdexcept>
+
+using namespace std;
+
+DragInformation::DragInformation() :
+ m_filename(),
+ m_filesize(0)
+{
+}
+
+void
+DragInformation::parseDragInfo(DragFileList& dragFileList, UInt32 fileNum, String data)
+{
+ size_t startPos = 0;
+ size_t findResult1 = 0;
+ size_t findResult2 = 0;
+ dragFileList.clear();
+ String slash("\\");
+ if (data.find("/", startPos) != string::npos) {
+ slash = "/";
+ }
+
+ UInt32 index = 0;
+ while (index < fileNum) {
+ findResult1 = data.find(',', startPos);
+ findResult2 = data.find_last_of(slash, findResult1);
+
+ if (findResult1 == startPos) {
+ //TODO: file number does not match, something goes wrong
+ break;
+ }
+
+ // set filename
+ if (findResult1 - findResult2 > 1) {
+ String filename = data.substr(findResult2 + 1,
+ findResult1 - findResult2 - 1);
+ DragInformation di;
+ di.setFilename(filename);
+ dragFileList.push_back(di);
+ }
+ startPos = findResult1 + 1;
+
+ //set filesize
+ findResult2 = data.find(',', startPos);
+ if (findResult2 - findResult1 > 1) {
+ String filesize = data.substr(findResult1 + 1,
+ findResult2 - findResult1 - 1);
+ size_t size = stringToNum(filesize);
+ dragFileList.at(index).setFilesize(size);
+ }
+ startPos = findResult1 + 1;
+
+ ++index;
+ }
+
+ LOG((CLOG_DEBUG "drag info received, total drag file number: %i",
+ dragFileList.size()));
+
+ for (size_t i = 0; i < dragFileList.size(); ++i) {
+ LOG((CLOG_DEBUG "dragging file %i name: %s",
+ i + 1,
+ dragFileList.at(i).getFilename().c_str()));
+ }
+}
+
+String
+DragInformation::getDragFileExtension(String filename)
+{
+ size_t findResult = string::npos;
+ findResult = filename.find_last_of(".", filename.size());
+ if (findResult != string::npos) {
+ return filename.substr(findResult + 1, filename.size() - findResult - 1);
+ }
+ else {
+ return "";
+ }
+}
+
+int
+DragInformation::setupDragInfo(DragFileList& fileList, String& output)
+{
+ int size = static_cast<int>(fileList.size());
+ for (int i = 0; i < size; ++i) {
+ output.append(fileList.at(i).getFilename());
+ output.append(",");
+ String filesize = getFileSize(fileList.at(i).getFilename());
+ output.append(filesize);
+ output.append(",");
+ }
+ return size;
+}
+
+bool
+DragInformation::isFileValid(String filename)
+{
+ bool result = false;
+ std::fstream file(filename.c_str(), ios::in|ios::binary);
+
+ if (file.is_open()) {
+ result = true;
+ }
+
+ file. close();
+
+ return result;
+}
+
+size_t
+DragInformation::stringToNum(String& str)
+{
+ istringstream iss(str.c_str());
+ size_t size;
+ iss >> size;
+ return size;
+}
+
+String
+DragInformation::getFileSize(String& filename)
+{
+ std::fstream file(filename.c_str(), ios::in|ios::binary);
+
+ if (!file.is_open()) {
+ throw std::runtime_error("failed to get file size");
+ }
+
+ // check file size
+ file.seekg (0, std::ios::end);
+ size_t size = (size_t)file.tellg();
+
+ stringstream ss;
+ ss << size;
+
+ file. close();
+
+ return ss.str();
+}
diff --git a/src/lib/barrier/DragInformation.h b/src/lib/barrier/DragInformation.h
new file mode 100644
index 0000000..b985bd1
--- /dev/null
+++ b/src/lib/barrier/DragInformation.h
@@ -0,0 +1,53 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/stdvector.h"
+#include "base/String.h"
+#include "base/EventTypes.h"
+
+class DragInformation;
+typedef std::vector<DragInformation> DragFileList;
+
+class DragInformation {
+public:
+ DragInformation();
+ ~DragInformation() { }
+
+ String& getFilename() { return m_filename; }
+ void setFilename(String& name) { m_filename = name; }
+ size_t getFilesize() { return m_filesize; }
+ void setFilesize(size_t size) { m_filesize = size; }
+
+ static void parseDragInfo(DragFileList& dragFileList, UInt32 fileNum, String data);
+ static String getDragFileExtension(String filename);
+ // helper function to setup drag info
+ // example: filename1,filesize1,filename2,filesize2,
+ // return file count
+ static int setupDragInfo(DragFileList& fileList, String& output);
+
+ static bool isFileValid(String filename);
+
+private:
+ static size_t stringToNum(String& str);
+ static String getFileSize(String& filename);
+
+private:
+ String m_filename;
+ size_t m_filesize;
+};
diff --git a/src/lib/barrier/DropHelper.cpp b/src/lib/barrier/DropHelper.cpp
new file mode 100644
index 0000000..ee5e5ee
--- /dev/null
+++ b/src/lib/barrier/DropHelper.cpp
@@ -0,0 +1,53 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/DropHelper.h"
+
+#include "base/Log.h"
+
+#include <fstream>
+
+void
+DropHelper::writeToDir(const String& destination, DragFileList& fileList, String& data)
+{
+ LOG((CLOG_DEBUG "dropping file, files=%i target=%s", fileList.size(), destination.c_str()));
+
+ if (!destination.empty() && fileList.size() > 0) {
+ std::fstream file;
+ String dropTarget = destination;
+#ifdef SYSAPI_WIN32
+ dropTarget.append("\\");
+#else
+ dropTarget.append("/");
+#endif
+ dropTarget.append(fileList.at(0).getFilename());
+ file.open(dropTarget.c_str(), std::ios::out | std::ios::binary);
+ if (!file.is_open()) {
+ LOG((CLOG_ERR "drop file failed: can not open %s", dropTarget.c_str()));
+ }
+
+ file.write(data.c_str(), data.size());
+ file.close();
+
+ LOG((CLOG_DEBUG "%s is saved to %s", fileList.at(0).getFilename().c_str(), destination.c_str()));
+
+ fileList.clear();
+ }
+ else {
+ LOG((CLOG_ERR "drop file failed: drop target is empty"));
+ }
+}
diff --git a/src/lib/barrier/DropHelper.h b/src/lib/barrier/DropHelper.h
new file mode 100644
index 0000000..67facbb
--- /dev/null
+++ b/src/lib/barrier/DropHelper.h
@@ -0,0 +1,27 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/DragInformation.h"
+#include "base/String.h"
+
+class DropHelper {
+public:
+ static void writeToDir(const String& destination,
+ DragFileList& fileList, String& data);
+};
diff --git a/src/lib/barrier/FileChunk.cpp b/src/lib/barrier/FileChunk.cpp
new file mode 100644
index 0000000..3a98568
--- /dev/null
+++ b/src/lib/barrier/FileChunk.cpp
@@ -0,0 +1,156 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/FileChunk.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "barrier/protocol_types.h"
+#include "io/IStream.h"
+#include "base/Stopwatch.h"
+#include "base/Log.h"
+
+static const UInt16 kIntervalThreshold = 1;
+
+FileChunk::FileChunk(size_t size) :
+ Chunk(size)
+{
+ m_dataSize = size - FILE_CHUNK_META_SIZE;
+}
+
+FileChunk*
+FileChunk::start(const String& size)
+{
+ size_t sizeLength = size.size();
+ FileChunk* start = new FileChunk(sizeLength + FILE_CHUNK_META_SIZE);
+ char* chunk = start->m_chunk;
+ chunk[0] = kDataStart;
+ memcpy(&chunk[1], size.c_str(), sizeLength);
+ chunk[sizeLength + 1] = '\0';
+
+ return start;
+}
+
+FileChunk*
+FileChunk::data(UInt8* data, size_t dataSize)
+{
+ FileChunk* chunk = new FileChunk(dataSize + FILE_CHUNK_META_SIZE);
+ char* chunkData = chunk->m_chunk;
+ chunkData[0] = kDataChunk;
+ memcpy(&chunkData[1], data, dataSize);
+ chunkData[dataSize + 1] = '\0';
+
+ return chunk;
+}
+
+FileChunk*
+FileChunk::end()
+{
+ FileChunk* end = new FileChunk(FILE_CHUNK_META_SIZE);
+ char* chunk = end->m_chunk;
+ chunk[0] = kDataEnd;
+ chunk[1] = '\0';
+
+ return end;
+}
+
+int
+FileChunk::assemble(barrier::IStream* stream, String& dataReceived, size_t& expectedSize)
+{
+ // parse
+ UInt8 mark = 0;
+ String content;
+ static size_t receivedDataSize;
+ static double elapsedTime;
+ static Stopwatch stopwatch;
+
+ if (!ProtocolUtil::readf(stream, kMsgDFileTransfer + 4, &mark, &content)) {
+ return kError;
+ }
+
+ switch (mark) {
+ case kDataStart:
+ dataReceived.clear();
+ expectedSize = barrier::string::stringToSizeType(content);
+ receivedDataSize = 0;
+ elapsedTime = 0;
+ stopwatch.reset();
+
+ if (CLOG->getFilter() >= kDEBUG2) {
+ LOG((CLOG_DEBUG2 "recv file size=%s", content.c_str()));
+ stopwatch.start();
+ }
+ return kStart;
+
+ case kDataChunk:
+ dataReceived.append(content);
+ if (CLOG->getFilter() >= kDEBUG2) {
+ LOG((CLOG_DEBUG2 "recv file chunck size=%i", content.size()));
+ double interval = stopwatch.getTime();
+ receivedDataSize += content.size();
+ LOG((CLOG_DEBUG2 "recv file interval=%f s", interval));
+ if (interval >= kIntervalThreshold) {
+ double averageSpeed = receivedDataSize / interval / 1000;
+ LOG((CLOG_DEBUG2 "recv file average speed=%f kb/s", averageSpeed));
+
+ receivedDataSize = 0;
+ elapsedTime += interval;
+ stopwatch.reset();
+ }
+ }
+ return kNotFinish;
+
+ case kDataEnd:
+ if (expectedSize != dataReceived.size()) {
+ LOG((CLOG_ERR "corrupted clipboard data, expected size=%d actual size=%d", expectedSize, dataReceived.size()));
+ return kError;
+ }
+
+ if (CLOG->getFilter() >= kDEBUG2) {
+ LOG((CLOG_DEBUG2 "file transfer finished"));
+ elapsedTime += stopwatch.getTime();
+ double averageSpeed = expectedSize / elapsedTime / 1000;
+ LOG((CLOG_DEBUG2 "file transfer finished: total time consumed=%f s", elapsedTime));
+ LOG((CLOG_DEBUG2 "file transfer finished: total data received=%i kb", expectedSize / 1000));
+ LOG((CLOG_DEBUG2 "file transfer finished: total average speed=%f kb/s", averageSpeed));
+ }
+ return kFinish;
+ }
+
+ return kError;
+}
+
+void
+FileChunk::send(barrier::IStream* stream, UInt8 mark, char* data, size_t dataSize)
+{
+ String chunk(data, dataSize);
+
+ switch (mark) {
+ case kDataStart:
+ LOG((CLOG_DEBUG2 "sending file chunk start: size=%s", data));
+ break;
+
+ case kDataChunk:
+ LOG((CLOG_DEBUG2 "sending file chunk: size=%i", chunk.size()));
+ break;
+
+ case kDataEnd:
+ LOG((CLOG_DEBUG2 "sending file finished"));
+ break;
+ }
+
+ ProtocolUtil::writef(stream, kMsgDFileTransfer, mark, &chunk);
+}
diff --git a/src/lib/barrier/FileChunk.h b/src/lib/barrier/FileChunk.h
new file mode 100644
index 0000000..bdc2f64
--- /dev/null
+++ b/src/lib/barrier/FileChunk.h
@@ -0,0 +1,46 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/Chunk.h"
+#include "base/String.h"
+#include "common/basic_types.h"
+
+#define FILE_CHUNK_META_SIZE 2
+
+namespace barrier {
+class IStream;
+};
+
+class FileChunk : public Chunk {
+public:
+ FileChunk(size_t size);
+
+ static FileChunk* start(const String& size);
+ static FileChunk* data(UInt8* data, size_t dataSize);
+ static FileChunk* end();
+ static int assemble(
+ barrier::IStream* stream,
+ String& dataCached,
+ size_t& expectedSize);
+ static void send(
+ barrier::IStream* stream,
+ UInt8 mark,
+ char* data,
+ size_t dataSize);
+};
diff --git a/src/lib/barrier/IApp.h b/src/lib/barrier/IApp.h
new file mode 100644
index 0000000..3a8cd56
--- /dev/null
+++ b/src/lib/barrier/IApp.h
@@ -0,0 +1,47 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+
+typedef int (*StartupFunc)(int, char**);
+
+class ILogOutputter;
+class ArgsBase;
+class IArchTaskBarReceiver;
+namespace barrier { class Screen; }
+class IEventQueue;
+
+class IApp : public IInterface
+{
+public:
+ virtual void setByeFunc(void(*bye)(int)) = 0;
+ virtual ArgsBase& argsBase() const = 0;
+ virtual int standardStartup(int argc, char** argv) = 0;
+ virtual int runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup) = 0;
+ virtual void startNode() = 0;
+ virtual IArchTaskBarReceiver* taskBarReceiver() const = 0;
+ virtual void bye(int error) = 0;
+ virtual int mainLoop() = 0;
+ virtual void initApp(int argc, const char** argv) = 0;
+ virtual const char* daemonName() const = 0;
+ virtual int foregroundStartup(int argc, char** argv) = 0;
+ virtual barrier::Screen* createScreen() = 0;
+ virtual IEventQueue* getEvents() const = 0;
+};
diff --git a/src/lib/barrier/IAppUtil.h b/src/lib/barrier/IAppUtil.h
new file mode 100644
index 0000000..39df65d
--- /dev/null
+++ b/src/lib/barrier/IAppUtil.h
@@ -0,0 +1,31 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "barrier/IApp.h"
+
+class IAppUtil : public IInterface {
+public:
+ virtual void adoptApp(IApp* app) = 0;
+ virtual IApp& app() const = 0;
+ virtual int run(int argc, char** argv) = 0;
+ virtual void beforeAppExit() = 0;
+ virtual void startNode() = 0;
+};
diff --git a/src/lib/barrier/IClient.h b/src/lib/barrier/IClient.h
new file mode 100644
index 0000000..d9b2194
--- /dev/null
+++ b/src/lib/barrier/IClient.h
@@ -0,0 +1,176 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/clipboard_types.h"
+#include "barrier/IScreen.h"
+#include "barrier/key_types.h"
+#include "barrier/mouse_types.h"
+#include "barrier/option_types.h"
+#include "base/String.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 OptionsList& options) = 0;
+
+ //@}
+ //! @name accessors
+ //@{
+
+ //! Get client name
+ /*!
+ Return the client's name.
+ */
+ virtual String 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;
+};
diff --git a/src/lib/barrier/IClipboard.cpp b/src/lib/barrier/IClipboard.cpp
new file mode 100644
index 0000000..19b4b56
--- /dev/null
+++ b/src/lib/barrier/IClipboard.cpp
@@ -0,0 +1,168 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/IClipboard.h"
+#include "common/stdvector.h"
+
+//
+// IClipboard
+//
+
+void
+IClipboard::unmarshall(IClipboard* clipboard, const String& data, Time time)
+{
+ assert(clipboard != NULL);
+
+ const char* index = data.data();
+
+ if (clipboard->open(time)) {
+ // clear existing data
+ 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<IClipboard::EFormat>(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 <IClipboard::kNumFormats) {
+ clipboard->add(format, String(index, size));
+ }
+ index += size;
+ }
+
+ // done
+ clipboard->close();
+ }
+}
+
+String
+IClipboard::marshall(const IClipboard* clipboard)
+{
+ // return data format:
+ // 4 bytes => number of formats included
+ // 4 bytes => format enum
+ // 4 bytes => clipboard data size n
+ // n bytes => clipboard data
+ // back to the second 4 bytes if there is another format
+
+ assert(clipboard != NULL);
+
+ String data;
+
+ std::vector<String> formatData;
+ formatData.resize(IClipboard::kNumFormats);
+ // FIXME -- use current time
+ if (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<IClipboard::EFormat>(format))) {
+ ++numFormats;
+ formatData[format] =
+ clipboard->get(static_cast<IClipboard::EFormat>(format));
+ size += 4 + 4 + (UInt32)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<IClipboard::EFormat>(format))) {
+ writeUInt32(&data, format);
+ writeUInt32(&data, (UInt32)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<const unsigned char*>(buf);
+ return (static_cast<UInt32>(ubuf[0]) << 24) |
+ (static_cast<UInt32>(ubuf[1]) << 16) |
+ (static_cast<UInt32>(ubuf[2]) << 8) |
+ static_cast<UInt32>(ubuf[3]);
+}
+
+void
+IClipboard::writeUInt32(String* buf, UInt32 v)
+{
+ *buf += static_cast<UInt8>((v >> 24) & 0xff);
+ *buf += static_cast<UInt8>((v >> 16) & 0xff);
+ *buf += static_cast<UInt8>((v >> 8) & 0xff);
+ *buf += static_cast<UInt8>( v & 0xff);
+}
diff --git a/src/lib/barrier/IClipboard.h b/src/lib/barrier/IClipboard.h
new file mode 100644
index 0000000..e11b264
--- /dev/null
+++ b/src/lib/barrier/IClipboard.h
@@ -0,0 +1,169 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+#include "base/EventTypes.h"
+#include "common/IInterface.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).
+
+ \c 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.
+
+ \c 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
+ kHTML, //!< HTML format, HTML fragment, UTF-8, newline is LF
+ kBitmap, //!< Bitmap format, BMP 24/32bpp, BI_RGB
+ 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 String& 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 String 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 String 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 String& 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(String*, UInt32);
+};
diff --git a/src/lib/barrier/IKeyState.cpp b/src/lib/barrier/IKeyState.cpp
new file mode 100644
index 0000000..5d1114c
--- /dev/null
+++ b/src/lib/barrier/IKeyState.cpp
@@ -0,0 +1,161 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/IKeyState.h"
+#include "base/EventQueue.h"
+
+#include <cstring>
+#include <cstdlib>
+
+//
+// IKeyState
+//
+
+IKeyState::IKeyState(IEventQueue* events)
+{
+}
+
+//
+// IKeyState::KeyInfo
+//
+
+IKeyState::KeyInfo*
+IKeyState::KeyInfo::alloc(KeyID id,
+ KeyModifierMask mask, KeyButton button, SInt32 count)
+{
+ KeyInfo* info = (KeyInfo*)malloc(sizeof(KeyInfo));
+ 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::KeyInfo*
+IKeyState::KeyInfo::alloc(KeyID id,
+ KeyModifierMask mask, KeyButton button, SInt32 count,
+ const std::set<String>& destinations)
+{
+ String screens = join(destinations);
+
+ // build structure
+ KeyInfo* info = (KeyInfo*)malloc(sizeof(KeyInfo) + 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::KeyInfo*
+IKeyState::KeyInfo::alloc(const KeyInfo& x)
+{
+ KeyInfo* info = (KeyInfo*)malloc(sizeof(KeyInfo) +
+ 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::KeyInfo::isDefault(const char* screens)
+{
+ return (screens == NULL || screens[0] == '\0');
+}
+
+bool
+IKeyState::KeyInfo::contains(const char* screens, const String& name)
+{
+ // special cases
+ if (isDefault(screens)) {
+ return false;
+ }
+ if (screens[0] == '*') {
+ return true;
+ }
+
+ // search
+ String match;
+ match.reserve(name.size() + 2);
+ match += ":";
+ match += name;
+ match += ":";
+ return (strstr(screens, match.c_str()) != NULL);
+}
+
+bool
+IKeyState::KeyInfo::equal(const KeyInfo* a, const KeyInfo* 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);
+}
+
+String
+IKeyState::KeyInfo::join(const std::set<String>& 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.
+ String screens;
+ for (std::set<String>::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::KeyInfo::split(const char* screens, std::set<String>& 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(String(i, j - i));
+ i = j + 1;
+ }
+}
diff --git a/src/lib/barrier/IKeyState.h b/src/lib/barrier/IKeyState.h
new file mode 100644
index 0000000..b9d4706
--- /dev/null
+++ b/src/lib/barrier/IKeyState.h
@@ -0,0 +1,174 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/key_types.h"
+#include "base/Event.h"
+#include "base/String.h"
+#include "base/IEventQueue.h"
+#include "base/EventTypes.h"
+#include "common/stdset.h"
+#include "common/IInterface.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:
+ IKeyState(IEventQueue* events);
+
+ enum {
+ kNumButtons = 0x200
+ };
+
+ //! Key event data
+ class KeyInfo {
+ public:
+ static KeyInfo* alloc(KeyID, KeyModifierMask, KeyButton, SInt32 count);
+ static KeyInfo* alloc(KeyID, KeyModifierMask, KeyButton, SInt32 count,
+ const std::set<String>& destinations);
+ static KeyInfo* alloc(const KeyInfo&);
+
+ static bool isDefault(const char* screens);
+ static bool contains(const char* screens, const String& name);
+ static bool equal(const KeyInfo*, const KeyInfo*);
+ static String join(const std::set<String>& destinations);
+ static void split(const char* screens, std::set<String>&);
+
+ public:
+ KeyID m_key;
+ KeyModifierMask m_mask;
+ KeyButton m_button;
+ SInt32 m_count;
+ char* m_screens;
+ char m_screensBuffer[1];
+ };
+
+ typedef std::set<KeyButton> 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 bool 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 bool 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;
+
+ //! Fake a media key
+ /*!
+ Synthesizes a media key down and up. Only Mac would implement this by
+ use cocoa appkit framework.
+ */
+ virtual bool fakeMediaKey(KeyID id) = 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;
+
+ //@}
+};
diff --git a/src/lib/barrier/INode.h b/src/lib/barrier/INode.h
new file mode 100644
index 0000000..2e78f7c
--- /dev/null
+++ b/src/lib/barrier/INode.h
@@ -0,0 +1,25 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+
+class INode : IInterface {
+
+};
diff --git a/src/lib/barrier/IPlatformScreen.cpp b/src/lib/barrier/IPlatformScreen.cpp
new file mode 100644
index 0000000..d1d9f78
--- /dev/null
+++ b/src/lib/barrier/IPlatformScreen.cpp
@@ -0,0 +1,24 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2016 Symless.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file COPYING that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/IPlatformScreen.h"
+
+bool
+IPlatformScreen::fakeMediaKey(KeyID id)
+{
+ return false;
+}
diff --git a/src/lib/barrier/IPlatformScreen.h b/src/lib/barrier/IPlatformScreen.h
new file mode 100644
index 0000000..440e218
--- /dev/null
+++ b/src/lib/barrier/IPlatformScreen.h
@@ -0,0 +1,227 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/DragInformation.h"
+#include "barrier/clipboard_types.h"
+#include "barrier/IScreen.h"
+#include "barrier/IPrimaryScreen.h"
+#include "barrier/ISecondaryScreen.h"
+#include "barrier/IKeyState.h"
+#include "barrier/option_types.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
+ //@{
+
+ IPlatformScreen(IEventQueue* events) : IKeyState(events) { }
+
+ //! 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 OptionsList& options) = 0;
+
+ //! Set clipboard sequence number
+ /*!
+ Sets the sequence number to use in subsequent clipboard events.
+ */
+ virtual void setSequenceNumber(UInt32) = 0;
+
+ //! Change dragging status
+ virtual void setDraggingStarted(bool started) = 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(UInt32& buttonID) const = 0;
+ virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0;
+
+ // ISecondaryScreen overrides
+ virtual void fakeMouseButton(ButtonID id, bool press) = 0;
+ virtual void fakeMouseMove(SInt32 x, SInt32 y) = 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 bool fakeKeyRepeat(KeyID id, KeyModifierMask mask,
+ SInt32 count, KeyButton button) = 0;
+ virtual bool fakeKeyUp(KeyButton button) = 0;
+ virtual void fakeAllKeysUp() = 0;
+ virtual bool fakeCtrlAltDel() = 0;
+ virtual bool fakeMediaKey(KeyID id);
+ 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;
+
+ virtual String& getDraggingFilename() = 0;
+ virtual void clearDraggingFilename() = 0;
+ virtual bool isDraggingStarted() = 0;
+ virtual bool isFakeDraggingStarted() = 0;
+
+ virtual void fakeDraggingFiles(DragFileList fileList) = 0;
+ virtual const String&
+ getDropTarget() 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
+ m_events->adoptHandler(Event::kSystem,
+ m_events->getSystemTarget(),
+ new TMethodEventJob<CXXXPlatformScreen>(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 KeyState whenever a key is pressed
+ or released (but not for key repeats). And it should call
+ \c updateKeyMap() on its \c KeyState if necessary when the keyboard
+ mapping changes.
+
+ The target of all events should be the value returned by
+ \c getEventTarget().
+ */
+ virtual void handleSystemEvent(const Event& event, void*) = 0;
+};
diff --git a/src/lib/barrier/IPrimaryScreen.cpp b/src/lib/barrier/IPrimaryScreen.cpp
new file mode 100644
index 0000000..4954e4f
--- /dev/null
+++ b/src/lib/barrier/IPrimaryScreen.cpp
@@ -0,0 +1,91 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/IPrimaryScreen.h"
+#include "base/EventQueue.h"
+
+#include <cstdlib>
+
+//
+// IPrimaryScreen::ButtonInfo
+//
+
+IPrimaryScreen::ButtonInfo*
+IPrimaryScreen::ButtonInfo::alloc(ButtonID id, KeyModifierMask mask)
+{
+ ButtonInfo* info = (ButtonInfo*)malloc(sizeof(ButtonInfo));
+ info->m_button = id;
+ info->m_mask = mask;
+ return info;
+}
+
+IPrimaryScreen::ButtonInfo*
+IPrimaryScreen::ButtonInfo::alloc(const ButtonInfo& x)
+{
+ ButtonInfo* info = (ButtonInfo*)malloc(sizeof(ButtonInfo));
+ info->m_button = x.m_button;
+ info->m_mask = x.m_mask;
+ return info;
+}
+
+bool
+IPrimaryScreen::ButtonInfo::equal(const ButtonInfo* a, const ButtonInfo* b)
+{
+ return (a->m_button == b->m_button && a->m_mask == b->m_mask);
+}
+
+
+//
+// IPrimaryScreen::MotionInfo
+//
+
+IPrimaryScreen::MotionInfo*
+IPrimaryScreen::MotionInfo::alloc(SInt32 x, SInt32 y)
+{
+ MotionInfo* info = (MotionInfo*)malloc(sizeof(MotionInfo));
+ info->m_x = x;
+ info->m_y = y;
+ return info;
+}
+
+
+//
+// IPrimaryScreen::WheelInfo
+//
+
+IPrimaryScreen::WheelInfo*
+IPrimaryScreen::WheelInfo::alloc(SInt32 xDelta, SInt32 yDelta)
+{
+ WheelInfo* info = (WheelInfo*)malloc(sizeof(WheelInfo));
+ info->m_xDelta = xDelta;
+ info->m_yDelta = yDelta;
+ return info;
+}
+
+
+//
+// IPrimaryScreen::HotKeyInfo
+//
+
+IPrimaryScreen::HotKeyInfo*
+IPrimaryScreen::HotKeyInfo::alloc(UInt32 id)
+{
+ HotKeyInfo* info = (HotKeyInfo*)malloc(sizeof(HotKeyInfo));
+ info->m_id = id;
+ return info;
+}
diff --git a/src/lib/barrier/IPrimaryScreen.h b/src/lib/barrier/IPrimaryScreen.h
new file mode 100644
index 0000000..7f3fa9c
--- /dev/null
+++ b/src/lib/barrier/IPrimaryScreen.h
@@ -0,0 +1,165 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/key_types.h"
+#include "barrier/mouse_types.h"
+#include "base/Event.h"
+#include "base/EventTypes.h"
+#include "common/IInterface.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 ButtonInfo {
+ public:
+ static ButtonInfo* alloc(ButtonID, KeyModifierMask);
+ static ButtonInfo* alloc(const ButtonInfo&);
+
+ static bool equal(const ButtonInfo*, const ButtonInfo*);
+
+ public:
+ ButtonID m_button;
+ KeyModifierMask m_mask;
+ };
+ //! Motion event data
+ class MotionInfo {
+ public:
+ static MotionInfo* alloc(SInt32 x, SInt32 y);
+
+ public:
+ SInt32 m_x;
+ SInt32 m_y;
+ };
+ //! Wheel motion event data
+ class WheelInfo {
+ public:
+ static WheelInfo* alloc(SInt32 xDelta, SInt32 yDelta);
+
+ public:
+ SInt32 m_xDelta;
+ SInt32 m_yDelta;
+ };
+ //! Hot key event data
+ class HotKeyInfo {
+ public:
+ static HotKeyInfo* 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(UInt32& buttonID) 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;
+
+ //@}
+};
diff --git a/src/lib/barrier/IScreen.h b/src/lib/barrier/IScreen.h
new file mode 100644
index 0000000..47d6578
--- /dev/null
+++ b/src/lib/barrier/IScreen.h
@@ -0,0 +1,71 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/clipboard_types.h"
+#include "base/Event.h"
+#include "base/EventTypes.h"
+#include "common/IInterface.h"
+
+class IClipboard;
+
+//! Screen interface
+/*!
+This interface defines the methods common to all screens.
+*/
+class IScreen : public IInterface {
+public:
+ struct ClipboardInfo {
+ 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;
+
+ //@}
+};
diff --git a/src/lib/barrier/IScreenSaver.h b/src/lib/barrier/IScreenSaver.h
new file mode 100644
index 0000000..fc21ac5
--- /dev/null
+++ b/src/lib/barrier/IScreenSaver.h
@@ -0,0 +1,75 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/Event.h"
+#include "common/IInterface.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;
+
+ //@}
+};
diff --git a/src/lib/barrier/ISecondaryScreen.h b/src/lib/barrier/ISecondaryScreen.h
new file mode 100644
index 0000000..527ca2e
--- /dev/null
+++ b/src/lib/barrier/ISecondaryScreen.h
@@ -0,0 +1,61 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/mouse_types.h"
+#include "base/Event.h"
+#include "base/EventTypes.h"
+#include "common/IInterface.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) = 0;
+
+ //! Fake mouse move
+ /*!
+ Synthesize a mouse move to the absolute coordinates \c x,y.
+ */
+ virtual void fakeMouseMove(SInt32 x, SInt32 y) = 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;
+
+ //@}
+};
diff --git a/src/lib/barrier/KeyMap.cpp b/src/lib/barrier/KeyMap.cpp
new file mode 100644
index 0000000..fd68204
--- /dev/null
+++ b/src/lib/barrier/KeyMap.cpp
@@ -0,0 +1,1344 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/KeyMap.h"
+#include "barrier/key_types.h"
+#include "base/Log.h"
+
+#include <assert.h>
+#include <cctype>
+#include <cstdlib>
+
+namespace barrier {
+
+KeyMap::NameToKeyMap* KeyMap::s_nameToKeyMap = NULL;
+KeyMap::NameToModifierMap* KeyMap::s_nameToModifierMap = NULL;
+KeyMap::KeyToNameMap* KeyMap::s_keyToNameMap = NULL;
+KeyMap::ModifierToNameMap* KeyMap::s_modifierToNameMap = NULL;
+
+KeyMap::KeyMap() :
+ 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;
+}
+
+KeyMap::~KeyMap()
+{
+ // do nothing
+}
+
+void
+KeyMap::swap(KeyMap& 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
+KeyMap::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<size_t>(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_DEBUG5 "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
+KeyMap::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) {
+ KeyMap::KeyItem targetItem = sourceEntry->back();
+ targetItem.m_id = targetID;
+ targetItem.m_group = eg;
+ addKeyEntry(targetItem);
+ break;
+ }
+ }
+}
+
+bool
+KeyMap::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<size_t>(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
+KeyMap::allowGroupSwitchDuringCompose()
+{
+ m_composeAcrossGroups = true;
+}
+
+void
+KeyMap::addHalfDuplexButton(KeyButton button)
+{
+ m_halfDuplex.insert(button);
+}
+
+void
+KeyMap::clearHalfDuplexModifiers()
+{
+ m_halfDuplexMods.clear();
+}
+
+void
+KeyMap::addHalfDuplexModifier(KeyID key)
+{
+ m_halfDuplexMods.insert(key);
+}
+
+void
+KeyMap::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
+KeyMap::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<SInt32>(group),
+ itemList[k], userData);
+ }
+ }
+ }
+ }
+}
+
+const KeyMap::KeyItem*
+KeyMap::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
+KeyMap::getNumGroups() const
+{
+ return m_numGroups;
+}
+
+SInt32
+KeyMap::getEffectiveGroup(SInt32 group, SInt32 offset) const
+{
+ return (group + offset + getNumGroups()) % getNumGroups();
+}
+
+const KeyMap::KeyItemList*
+KeyMap::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
+KeyMap::isHalfDuplex(KeyID key, KeyButton button) const
+{
+ return (m_halfDuplex.count(button) + m_halfDuplexMods.count(key) > 0);
+}
+
+bool
+KeyMap::isCommand(KeyModifierMask mask) const
+{
+ return ((mask & getCommandModifiers()) != 0);
+}
+
+KeyModifierMask
+KeyMap::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 |
+ KeyModifierAltGr |
+ KeyModifierMeta |
+ KeyModifierSuper;
+}
+
+void
+KeyMap::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
+KeyMap::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
+KeyMap::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<SInt32>(max);
+}
+
+void
+KeyMap::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 = (SInt32)g * kKeyModifierNumBits + b;
+ m_modifierKeys[mIndex].push_back(&item);
+ }
+ }
+ }
+ }
+ }
+}
+
+const KeyMap::KeyItem*
+KeyMap::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;
+ }
+
+ // match based on shift and make sure all required modifiers,
+ // except shift, are already in the desired mask; 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();
+ KeyModifierMask desiredShiftMask = KeyModifierShift & desiredMask;
+ KeyModifierMask requiredIgnoreShiftMask = item.m_required & ~KeyModifierShift;
+ if ((item.m_required & desiredShiftMask) == (item.m_sensitive & desiredShiftMask) &&
+ ((requiredIgnoreShiftMask & desiredMask) == requiredIgnoreShiftMask)) {
+ LOG((CLOG_INFO "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 KeyMap::KeyItem*
+KeyMap::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 KeyMap::KeyItem*
+KeyMap::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
+KeyMap::findBestKey(const KeyEntryList& entryList,
+ KeyModifierMask /*currentState*/,
+ KeyModifierMask desiredState) const
+{
+ // check for an item that can accommodate the desiredState exactly
+ for (SInt32 i = 0; i < (SInt32)entryList.size(); ++i) {
+ const KeyItem& item = entryList[i].back();
+ if ((item.m_required & desiredState) == item.m_required &&
+ (item.m_required & desiredState) == (item.m_sensitive & desiredState)) {
+ LOG((CLOG_DEBUG1 "best key index %d of %d (exact)", i + 1, entryList.size()));
+ return i;
+ }
+ }
+
+ // choose the item that requires the fewest modifier changes
+ SInt32 bestCount = 32;
+ SInt32 bestIndex = -1;
+ for (SInt32 i = 0; i < (SInt32)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 + 1, entryList.size(), bestCount));
+ }
+
+ return bestIndex;
+}
+
+
+const KeyMap::KeyItem*
+KeyMap::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
+KeyMap::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
+KeyMap::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
+KeyMap::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
+KeyMap::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<ModifierToKeys::iterator,
+ ModifierToKeys::iterator> 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<ModifierToKeys::const_iterator,
+ ModifierToKeys::const_iterator> 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
+KeyMap::getNumModifiers(KeyModifierMask state)
+{
+ SInt32 n = 0;
+ for (; state != 0; state >>= 1) {
+ if ((state & 1) != 0) {
+ ++n;
+ }
+ }
+ return n;
+}
+
+bool
+KeyMap::isDeadKey(KeyID key)
+{
+ return (key == kKeyCompose || (key >= 0x0300 && key <= 0x036f));
+}
+
+KeyID
+KeyMap::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;
+ }
+}
+
+String
+KeyMap::formatKey(KeyID key, KeyModifierMask mask)
+{
+ // initialize tables
+ initKeyNameMaps();
+
+ String 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 += barrier::string::sprintf("\\u%04x", key);
+ }
+ }
+ else if (!x.empty()) {
+ // remove trailing '+'
+ x.erase(x.size() - 1);
+ }
+ return x;
+}
+
+bool
+KeyMap::parseKey(const String& 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
+KeyMap::parseModifiers(String& x, KeyModifierMask& mask)
+{
+ // initialize tables
+ initKeyNameMaps();
+
+ mask = 0;
+ String::size_type tb = x.find_first_not_of(" \t", 0);
+ while (tb != String::npos) {
+ // get next component
+ String::size_type te = x.find_first_of(" \t+)", tb);
+ if (te == String::npos) {
+ te = x.size();
+ }
+ String 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);
+ String::size_type tb = x.find_first_not_of(" \t");
+ String::size_type te = x.find_last_not_of(" \t");
+ if (tb == String::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 != String::npos) {
+ if (x[tb] != '+') {
+ // expected '+'
+ return false;
+ }
+ tb = x.find_first_not_of(" \t", tb + 1);
+ }
+ }
+
+ // parsed the whole thing
+ x = "";
+ return true;
+}
+
+void
+KeyMap::initKeyNameMaps()
+{
+ // initialize tables
+ if (s_nameToKeyMap == NULL) {
+ s_nameToKeyMap = new NameToKeyMap;
+ s_keyToNameMap = new KeyToNameMap;
+ 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 NameToModifierMap;
+ s_modifierToNameMap = new ModifierToNameMap;
+ 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;
+ }
+ }
+}
+
+
+//
+// KeyMap::KeyItem
+//
+
+bool
+KeyMap::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);
+}
+
+
+//
+// KeyMap::Keystroke
+//
+
+KeyMap::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;
+}
+
+KeyMap::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/src/lib/barrier/KeyMap.h b/src/lib/barrier/KeyMap.h
new file mode 100644
index 0000000..b6eb865
--- /dev/null
+++ b/src/lib/barrier/KeyMap.h
@@ -0,0 +1,512 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/key_types.h"
+#include "base/String.h"
+#include "common/stdmap.h"
+#include "common/stdset.h"
+#include "common/stdvector.h"
+
+#include <gtest/gtest_prod.h>
+
+namespace barrier {
+
+//! Key map
+/*!
+This class provides a keyboard mapping.
+*/
+class KeyMap {
+public:
+ KeyMap();
+ virtual ~KeyMap();
+
+ //! 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<KeyItem> 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 Button {
+ 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 Group {
+ 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 Data {
+ public:
+ Button m_button;
+ Group m_group;
+ };
+
+ EType m_type;
+ Data m_data;
+ };
+
+ //! A sequence of keystrokes
+ typedef std::vector<Keystroke> Keystrokes;
+
+ //! A mapping of a modifier to keys for that modifier
+ typedef std::multimap<KeyModifierMask, KeyItem> ModifierToKeys;
+
+ //! A set of buttons
+ typedef std::map<KeyButton, const KeyItem*> ButtonToKeyMap;
+
+ //! Callback type for \c foreachKey
+ typedef void (*ForeachKeyCallback)(KeyID, SInt32 group,
+ KeyItem&, void* userData);
+
+ //! @name manipulators
+ //@{
+
+ //! Swap with another \c KeyMap
+ virtual void swap(KeyMap&);
+
+ //! 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.
+ */
+ virtual void addHalfDuplexModifier(KeyID key);
+
+ //! Finish adding entries
+ /*!
+ Called after adding entries, this does some internal housekeeping.
+ */
+ virtual void finish();
+
+ //! Iterate over all added keys items
+ /*!
+ Calls \p cb for every key item.
+ */
+ virtual 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.
+ */
+ virtual 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.
+ */
+ virtual 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 String 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 String&, 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(String&, KeyModifierMask&);
+
+ //@}
+
+private:
+ FRIEND_TEST(KeyMapTests,
+ findBestKey_requiredDown_matchExactFirstItem);
+ FRIEND_TEST(KeyMapTests,
+ findBestKey_requiredAndExtraSensitiveDown_matchExactFirstItem);
+ FRIEND_TEST(KeyMapTests,
+ findBestKey_requiredAndExtraSensitiveDown_matchExactSecondItem);
+ FRIEND_TEST(KeyMapTests,
+ findBestKey_extraSensitiveDown_matchExactSecondItem);
+ FRIEND_TEST(KeyMapTests,
+ findBestKey_noRequiredDown_matchOneRequiredChangeItem);
+ FRIEND_TEST(KeyMapTests,
+ findBestKey_onlyOneRequiredDown_matchTwoRequiredChangesItem);
+ FRIEND_TEST(KeyMapTests, findBestKey_noRequiredDown_cannotMatch);
+
+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<KeyItemList> 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
+ KeyMap(const KeyMap&);
+ KeyMap& operator=(const KeyMap&);
+
+private:
+ // Ways to synthesize a KeyID over multiple keyboard groups
+ typedef std::vector<KeyEntryList> KeyGroupTable;
+
+ // Table of KeyID to ways to synthesize that KeyID
+ typedef std::map<KeyID, KeyGroupTable> KeyIDMap;
+
+ // List of KeyItems that generate a particular modifier
+ typedef std::vector<const KeyItem*> ModifierKeyItemList;
+
+ // Map a modifier to the KeyItems that synthesize that modifier
+ typedef std::vector<ModifierKeyItemList> ModifierToKeyTable;
+
+ // A set of keys
+ typedef std::set<KeyID> KeySet;
+
+ // A set of buttons
+ typedef std::set<KeyButton> KeyButtonSet;
+
+ // Key maps for parsing/formatting
+ typedef std::map<String, KeyID,
+ barrier::string::CaselessCmp> NameToKeyMap;
+ typedef std::map<String, KeyModifierMask,
+ barrier::string::CaselessCmp> NameToModifierMap;
+ typedef std::map<KeyID, String> KeyToNameMap;
+ typedef std::map<KeyModifierMask, String> ModifierToNameMap;
+
+ // 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 barrier
+ KeySet m_halfDuplexMods; // half-duplex set by user
+
+ // dummy KeyItem for changing modifiers
+ KeyItem m_modifierKeyItem;
+
+ // parsing/formatting tables
+ static NameToKeyMap* s_nameToKeyMap;
+ static NameToModifierMap* s_nameToModifierMap;
+ static KeyToNameMap* s_keyToNameMap;
+ static ModifierToNameMap* s_modifierToNameMap;
+};
+
+}
diff --git a/src/lib/barrier/KeyState.cpp b/src/lib/barrier/KeyState.cpp
new file mode 100644
index 0000000..fc5579d
--- /dev/null
+++ b/src/lib/barrier/KeyState.cpp
@@ -0,0 +1,936 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/KeyState.h"
+#include "base/Log.h"
+
+#include <cstring>
+#include <algorithm>
+#include <iterator>
+#include <list>
+
+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
+};
+
+//
+// KeyState
+//
+
+KeyState::KeyState(IEventQueue* events) :
+ IKeyState(events),
+ m_keyMapPtr(new barrier::KeyMap()),
+ m_keyMap(*m_keyMapPtr),
+ m_mask(0),
+ m_events(events)
+{
+ init();
+}
+
+KeyState::KeyState(IEventQueue* events, barrier::KeyMap& keyMap) :
+ IKeyState(events),
+ m_keyMapPtr(0),
+ m_keyMap(keyMap),
+ m_mask(0),
+ m_events(events)
+{
+ init();
+}
+
+KeyState::~KeyState()
+{
+ if (m_keyMapPtr)
+ delete m_keyMapPtr;
+}
+
+void
+KeyState::init()
+{
+ 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));
+}
+
+void
+KeyState::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
+KeyState::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 {
+ m_events->addEvent(Event(m_events->forIKeyState().keyDown(), target,
+ KeyInfo::alloc(key, mask, button, 1)));
+ m_events->addEvent(Event(m_events->forIKeyState().keyUp(), target,
+ KeyInfo::alloc(key, mask, button, 1)));
+ }
+ }
+ else {
+ if (isAutoRepeat) {
+ m_events->addEvent(Event(m_events->forIKeyState().keyRepeat(), target,
+ KeyInfo::alloc(key, mask, button, count)));
+ }
+ else if (press) {
+ m_events->addEvent(Event(m_events->forIKeyState().keyDown(), target,
+ KeyInfo::alloc(key, mask, button, 1)));
+ }
+ else {
+ m_events->addEvent(Event(m_events->forIKeyState().keyUp(), target,
+ KeyInfo::alloc(key, mask, button, 1)));
+ }
+ }
+}
+
+void
+KeyState::updateKeyMap()
+{
+ // get the current keyboard map
+ barrier::KeyMap keyMap;
+ getKeyMap(keyMap);
+ m_keyMap.swap(keyMap);
+ m_keyMap.finish();
+
+ // add special keys
+ addCombinationEntries();
+ addKeypadEntries();
+ addAliasEntries();
+}
+
+void
+KeyState::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
+ AddActiveModifierContext addModifierContext(pollActiveGroup(), m_mask,
+ m_activeModifiers);
+ m_keyMap.foreachKey(&KeyState::addActiveModifierCB, &addModifierContext);
+
+ LOG((CLOG_DEBUG1 "modifiers on update: 0x%04x", m_mask));
+}
+
+void
+KeyState::addActiveModifierCB(KeyID, SInt32 group,
+ barrier::KeyMap::KeyItem& keyItem, void* vcontext)
+{
+ AddActiveModifierContext* context =
+ static_cast<AddActiveModifierContext*>(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
+KeyState::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
+KeyState::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 barrier::KeyMap::KeyItem* keyItem =
+ m_keyMap.mapKey(keys, id, pollActiveGroup(), m_activeModifiers,
+ getActiveModifiersRValue(), mask, false);
+ if (keyItem == NULL) {
+ // a media key won't be mapped on mac, so we need to fake it in a
+ // special way
+ if (id == kKeyAudioDown || id == kKeyAudioUp ||
+ id == kKeyAudioMute || id == kKeyAudioPlay ||
+ id == kKeyAudioPrev || id == kKeyAudioNext ||
+ id == kKeyBrightnessDown || id == kKeyBrightnessUp
+ ) {
+ LOG((CLOG_DEBUG1 "emulating media key"));
+ fakeMediaKey(id);
+ }
+
+ 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);
+}
+
+bool
+KeyState::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 false;
+ }
+
+ // get keys for key repeat
+ Keystrokes keys;
+ ModifierToKeys oldActiveModifiers = m_activeModifiers;
+ const barrier::KeyMap::KeyItem* keyItem =
+ m_keyMap.mapKey(keys, id, pollActiveGroup(), m_activeModifiers,
+ getActiveModifiersRValue(), mask, true);
+ if (keyItem == NULL) {
+ return false;
+ }
+ KeyButton localID = (KeyButton)(keyItem->m_button & kButtonMask);
+ if (localID == 0) {
+ return false;
+ }
+
+ // 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);
+ return true;
+}
+
+bool
+KeyState::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 false;
+ }
+
+ // 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
+ ModifierToKeys::iterator i = m_activeModifiers.begin();
+ while (i != m_activeModifiers.end()) {
+ if (i->second.m_button == localID && !i->second.m_lock) {
+ // modifier is no longer down
+ KeyModifierMask mask = i->first;
+
+ ModifierToKeys::iterator tmp = i;
+ ++i;
+ m_activeModifiers.erase(tmp);
+
+ 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));
+ }
+ }
+ else {
+ ++i;
+ }
+ }
+
+ // generate key events
+ fakeKeys(keys, 1);
+ return true;
+}
+
+void
+KeyState::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
+KeyState::fakeMediaKey(KeyID id)
+{
+ return false;
+}
+
+bool
+KeyState::isKeyDown(KeyButton button) const
+{
+ return (m_keys[button & kButtonMask] > 0);
+}
+
+KeyModifierMask
+KeyState::getActiveModifiers() const
+{
+ return m_mask;
+}
+
+KeyModifierMask&
+KeyState::getActiveModifiersRValue()
+{
+ return m_mask;
+}
+
+SInt32
+KeyState::getEffectiveGroup(SInt32 group, SInt32 offset) const
+{
+ return m_keyMap.getEffectiveGroup(group, offset);
+}
+
+bool
+KeyState::isIgnoredKey(KeyID key, KeyModifierMask) const
+{
+ switch (key) {
+ case kKeyCapsLock:
+ case kKeyNumLock:
+ case kKeyScrollLock:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+KeyButton
+KeyState::getButton(KeyID id, SInt32 group) const
+{
+ const barrier::KeyMap::KeyItemList* items =
+ m_keyMap.findCompatibleKey(id, group, 0, 0);
+ if (items == NULL) {
+ return 0;
+ }
+ else {
+ return items->back().m_button;
+ }
+}
+
+void
+KeyState::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
+KeyState::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
+KeyState::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
+KeyState::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
+KeyState::updateModifierKeyState(KeyButton button,
+ const ModifierToKeys& oldModifiers,
+ const ModifierToKeys& newModifiers)
+{
+ // get the pressed modifier buttons before and after
+ barrier::KeyMap::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
+ barrier::KeyMap::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 (barrier::KeyMap::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 (barrier::KeyMap::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;
+ }
+ }
+}
+
+//
+// KeyState::AddActiveModifierContext
+//
+
+KeyState::AddActiveModifierContext::AddActiveModifierContext(
+ SInt32 group, KeyModifierMask mask,
+ ModifierToKeys& activeModifiers) :
+ m_activeGroup(group),
+ m_mask(mask),
+ m_activeModifiers(activeModifiers)
+{
+ // do nothing
+}
diff --git a/src/lib/barrier/KeyState.h b/src/lib/barrier/KeyState.h
new file mode 100644
index 0000000..737d515
--- /dev/null
+++ b/src/lib/barrier/KeyState.h
@@ -0,0 +1,232 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/IKeyState.h"
+#include "barrier/KeyMap.h"
+
+//! Core key state
+/*!
+This class provides key state services. Subclasses must implement a few
+platform specific methods.
+*/
+class KeyState : public IKeyState {
+public:
+ KeyState(IEventQueue* events);
+ KeyState(IEventQueue* events, barrier::KeyMap& keyMap);
+ virtual ~KeyState();
+
+ //! @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 bool fakeKeyRepeat(KeyID id, KeyModifierMask mask,
+ SInt32 count, KeyButton button);
+ virtual bool fakeKeyUp(KeyButton button);
+ virtual void fakeAllKeysUp();
+ virtual bool fakeCtrlAltDel() = 0;
+ virtual bool fakeMediaKey(KeyID id);
+
+ 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;
+
+ SInt32 getKeyState(KeyButton keyButton) { return m_keys[keyButton]; }
+
+protected:
+ typedef barrier::KeyMap::Keystroke Keystroke;
+
+ //! @name protected manipulators
+ //@{
+
+ //! Get the keyboard map
+ /*!
+ Fills \p keyMap with the current keyboard map.
+ */
+ virtual void getKeyMap(barrier::KeyMap& 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 barrier::KeyMap::Keystrokes Keystrokes;
+ typedef barrier::KeyMap::ModifierToKeys ModifierToKeys;
+public:
+ struct AddActiveModifierContext {
+ public:
+ AddActiveModifierContext(SInt32 group, KeyModifierMask mask,
+ ModifierToKeys& activeModifiers);
+
+ public:
+ SInt32 m_activeGroup;
+ KeyModifierMask m_mask;
+ ModifierToKeys& m_activeModifiers;
+
+ private:
+ // not implemented
+ AddActiveModifierContext(const AddActiveModifierContext&);
+ AddActiveModifierContext& operator=(const AddActiveModifierContext&);
+ };
+private:
+
+ class ButtonToKeyLess {
+ public:
+ bool operator()(const barrier::KeyMap::ButtonToKeyMap::value_type& a,
+ const barrier::KeyMap::ButtonToKeyMap::value_type b) const
+ {
+ return (a.first < b.first);
+ }
+ };
+
+ // not implemented
+ KeyState(const KeyState&);
+ KeyState& operator=(const KeyState&);
+
+ // called by all ctors.
+ void init();
+
+ // 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,
+ barrier::KeyMap::KeyItem& keyItem, void* vcontext);
+
+private:
+ // must be declared before m_keyMap. used when this class owns the key map.
+ barrier::KeyMap* m_keyMapPtr;
+
+ // the keyboard map
+ barrier::KeyMap& 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];
+
+ IEventQueue* m_events;
+};
diff --git a/src/lib/barrier/PacketStreamFilter.cpp b/src/lib/barrier/PacketStreamFilter.cpp
new file mode 100644
index 0000000..16f0fe7
--- /dev/null
+++ b/src/lib/barrier/PacketStreamFilter.cpp
@@ -0,0 +1,198 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/PacketStreamFilter.h"
+#include "base/IEventQueue.h"
+#include "mt/Lock.h"
+#include "base/TMethodEventJob.h"
+
+#include <cstring>
+#include <memory>
+
+//
+// PacketStreamFilter
+//
+
+PacketStreamFilter::PacketStreamFilter(IEventQueue* events, barrier::IStream* stream, bool adoptStream) :
+ StreamFilter(events, stream, adoptStream),
+ m_size(0),
+ m_inputShutdown(false),
+ m_events(events)
+{
+ // do nothing
+}
+
+PacketStreamFilter::~PacketStreamFilter()
+{
+ // do nothing
+}
+
+void
+PacketStreamFilter::close()
+{
+ Lock lock(&m_mutex);
+ m_size = 0;
+ m_buffer.pop(m_buffer.getSize());
+ StreamFilter::close();
+}
+
+UInt32
+PacketStreamFilter::read(void* buffer, UInt32 n)
+{
+ if (n == 0) {
+ return 0;
+ }
+
+ Lock 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) {
+ m_events->addEvent(Event(m_events->forIStream().inputShutdown(),
+ getEventTarget(), NULL));
+ }
+
+ return n;
+}
+
+void
+PacketStreamFilter::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
+PacketStreamFilter::shutdownInput()
+{
+ Lock lock(&m_mutex);
+ m_size = 0;
+ m_buffer.pop(m_buffer.getSize());
+ StreamFilter::shutdownInput();
+}
+
+bool
+PacketStreamFilter::isReady() const
+{
+ Lock lock(&m_mutex);
+ return isReadyNoLock();
+}
+
+UInt32
+PacketStreamFilter::getSize() const
+{
+ Lock lock(&m_mutex);
+ return isReadyNoLock() ? m_size : 0;
+}
+
+bool
+PacketStreamFilter::isReadyNoLock() const
+{
+ return (m_size != 0 && m_buffer.getSize() >= m_size);
+}
+
+void
+PacketStreamFilter::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
+PacketStreamFilter::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
+PacketStreamFilter::filterEvent(const Event& event)
+{
+ if (event.getType() == m_events->forIStream().inputReady()) {
+ Lock lock(&m_mutex);
+ if (!readMore()) {
+ return;
+ }
+ }
+ else if (event.getType() == m_events->forIStream().inputShutdown()) {
+ // discard this if we have buffered data
+ Lock lock(&m_mutex);
+ m_inputShutdown = true;
+ if (m_size != 0) {
+ return;
+ }
+ }
+
+ // pass event
+ StreamFilter::filterEvent(event);
+}
diff --git a/src/lib/barrier/PacketStreamFilter.h b/src/lib/barrier/PacketStreamFilter.h
new file mode 100644
index 0000000..bcbd604
--- /dev/null
+++ b/src/lib/barrier/PacketStreamFilter.h
@@ -0,0 +1,59 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "io/StreamFilter.h"
+#include "io/StreamBuffer.h"
+#include "mt/Mutex.h"
+
+class IEventQueue;
+
+//! Packetizing stream filter
+/*!
+Filters a stream to read and write packets.
+*/
+class PacketStreamFilter : public StreamFilter {
+public:
+ PacketStreamFilter(IEventQueue* events, barrier::IStream* stream, bool adoptStream = true);
+ ~PacketStreamFilter();
+
+ // 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:
+ // StreamFilter overrides
+ virtual void filterEvent(const Event&);
+
+private:
+ bool isReadyNoLock() const;
+ void readPacketSize();
+ bool readMore();
+
+private:
+ Mutex m_mutex;
+ UInt32 m_size;
+ StreamBuffer m_buffer;
+ bool m_inputShutdown;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/barrier/PlatformScreen.cpp b/src/lib/barrier/PlatformScreen.cpp
new file mode 100644
index 0000000..b0fdc75
--- /dev/null
+++ b/src/lib/barrier/PlatformScreen.cpp
@@ -0,0 +1,123 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/PlatformScreen.h"
+#include "barrier/App.h"
+#include "barrier/ArgsBase.h"
+
+PlatformScreen::PlatformScreen(IEventQueue* events) :
+ IPlatformScreen(events),
+ m_draggingStarted(false),
+ m_fakeDraggingStarted(false)
+{
+}
+
+PlatformScreen::~PlatformScreen()
+{
+ // do nothing
+}
+
+void
+PlatformScreen::updateKeyMap()
+{
+ getKeyState()->updateKeyMap();
+}
+
+void
+PlatformScreen::updateKeyState()
+{
+ getKeyState()->updateKeyState();
+ updateButtons();
+}
+
+void
+PlatformScreen::setHalfDuplexMask(KeyModifierMask mask)
+{
+ getKeyState()->setHalfDuplexMask(mask);
+}
+
+void
+PlatformScreen::fakeKeyDown(KeyID id, KeyModifierMask mask,
+ KeyButton button)
+{
+ getKeyState()->fakeKeyDown(id, mask, button);
+}
+
+bool
+PlatformScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask,
+ SInt32 count, KeyButton button)
+{
+ return getKeyState()->fakeKeyRepeat(id, mask, count, button);
+}
+
+bool
+PlatformScreen::fakeKeyUp(KeyButton button)
+{
+ return getKeyState()->fakeKeyUp(button);
+}
+
+void
+PlatformScreen::fakeAllKeysUp()
+{
+ getKeyState()->fakeAllKeysUp();
+}
+
+bool
+PlatformScreen::fakeCtrlAltDel()
+{
+ return getKeyState()->fakeCtrlAltDel();
+}
+
+bool
+PlatformScreen::isKeyDown(KeyButton button) const
+{
+ return getKeyState()->isKeyDown(button);
+}
+
+KeyModifierMask
+PlatformScreen::getActiveModifiers() const
+{
+ return getKeyState()->getActiveModifiers();
+}
+
+KeyModifierMask
+PlatformScreen::pollActiveModifiers() const
+{
+ return getKeyState()->pollActiveModifiers();
+}
+
+SInt32
+PlatformScreen::pollActiveGroup() const
+{
+ return getKeyState()->pollActiveGroup();
+}
+
+void
+PlatformScreen::pollPressedKeys(KeyButtonSet& pressedKeys) const
+{
+ getKeyState()->pollPressedKeys(pressedKeys);
+}
+
+bool
+PlatformScreen::isDraggingStarted()
+{
+ if (App::instance().argsBase().m_enableDragDrop) {
+ return m_draggingStarted;
+ }
+ return false;
+}
diff --git a/src/lib/barrier/PlatformScreen.h b/src/lib/barrier/PlatformScreen.h
new file mode 100644
index 0000000..82cbfaa
--- /dev/null
+++ b/src/lib/barrier/PlatformScreen.h
@@ -0,0 +1,127 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/IPlatformScreen.h"
+#include "barrier/DragInformation.h"
+#include "common/stdexcept.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 PlatformScreen : public IPlatformScreen {
+public:
+ PlatformScreen(IEventQueue* events);
+ virtual ~PlatformScreen();
+
+ // 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(UInt32& buttonID) const = 0;
+ virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0;
+
+ // ISecondaryScreen overrides
+ virtual void fakeMouseButton(ButtonID id, bool press) = 0;
+ virtual void fakeMouseMove(SInt32 x, SInt32 y) = 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 bool fakeKeyRepeat(KeyID id, KeyModifierMask mask,
+ SInt32 count, KeyButton button);
+ virtual bool 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;
+
+ virtual void setDraggingStarted(bool started) { m_draggingStarted = started; }
+ virtual bool isDraggingStarted();
+ virtual bool isFakeDraggingStarted() { return m_fakeDraggingStarted; }
+ virtual String& getDraggingFilename() { return m_draggingFilename; }
+ virtual void clearDraggingFilename() { }
+
+ // 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 OptionsList& options) = 0;
+ virtual void setSequenceNumber(UInt32) = 0;
+ virtual bool isPrimary() const = 0;
+
+ virtual void fakeDraggingFiles(DragFileList fileList) { throw std::runtime_error("fakeDraggingFiles not implemented"); }
+ virtual const String&
+ getDropTarget() const { throw std::runtime_error("getDropTarget not implemented"); }
+
+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 Event& event, void*) = 0;
+
+protected:
+ String m_draggingFilename;
+ bool m_draggingStarted;
+ bool m_fakeDraggingStarted;
+};
diff --git a/src/lib/barrier/PortableTaskBarReceiver.cpp b/src/lib/barrier/PortableTaskBarReceiver.cpp
new file mode 100644
index 0000000..384cacd
--- /dev/null
+++ b/src/lib/barrier/PortableTaskBarReceiver.cpp
@@ -0,0 +1,121 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/PortableTaskBarReceiver.h"
+#include "mt/Lock.h"
+#include "base/String.h"
+#include "base/IEventQueue.h"
+#include "arch/Arch.h"
+#include "common/Version.h"
+
+//
+// PortableTaskBarReceiver
+//
+
+PortableTaskBarReceiver::PortableTaskBarReceiver(IEventQueue* events) :
+ m_state(kNotRunning),
+ m_events(events)
+{
+ // do nothing
+}
+
+PortableTaskBarReceiver::~PortableTaskBarReceiver()
+{
+ // do nothing
+}
+
+void
+PortableTaskBarReceiver::updateStatus(INode* node, const String& errorMsg)
+{
+ {
+ // update our status
+ m_errorMessage = errorMsg;
+ if (node == NULL) {
+ if (m_errorMessage.empty()) {
+ m_state = kNotRunning;
+ }
+ else {
+ m_state = kNotWorking;
+ }
+ }
+ else {
+ m_state = kNotConnected;
+ }
+
+ // let subclasses have a go
+ onStatusChanged(node);
+ }
+
+ // tell task bar
+ ARCH->updateReceiver(this);
+}
+
+PortableTaskBarReceiver::EState
+PortableTaskBarReceiver::getStatus() const
+{
+ return m_state;
+}
+
+const String&
+PortableTaskBarReceiver::getErrorMessage() const
+{
+ return m_errorMessage;
+}
+
+void
+PortableTaskBarReceiver::quit()
+{
+ m_events->addEvent(Event(Event::kQuit));
+}
+
+void
+PortableTaskBarReceiver::onStatusChanged(INode*)
+{
+ // do nothing
+}
+
+void
+PortableTaskBarReceiver::lock() const
+{
+ // do nothing
+}
+
+void
+PortableTaskBarReceiver::unlock() const
+{
+ // do nothing
+}
+
+std::string
+PortableTaskBarReceiver::getToolTip() const
+{
+ switch (m_state) {
+ case kNotRunning:
+ return barrier::string::sprintf("%s: Not running", kAppVersion);
+
+ case kNotWorking:
+ return barrier::string::sprintf("%s: %s",
+ kAppVersion, m_errorMessage.c_str());
+
+ case kNotConnected:
+ return barrier::string::sprintf("%s: Unknown", kAppVersion);
+
+ default:
+ return "";
+ }
+}
diff --git a/src/lib/barrier/PortableTaskBarReceiver.h b/src/lib/barrier/PortableTaskBarReceiver.h
new file mode 100644
index 0000000..d335e44
--- /dev/null
+++ b/src/lib/barrier/PortableTaskBarReceiver.h
@@ -0,0 +1,96 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/INode.h"
+#include "arch/IArchTaskBarReceiver.h"
+#include "base/log_outputters.h"
+#include "base/EventTypes.h"
+#include "base/Event.h"
+#include "base/String.h"
+#include "common/stdvector.h"
+
+class IEventQueue;
+
+//! Implementation of IArchTaskBarReceiver for the barrier server
+class PortableTaskBarReceiver : public IArchTaskBarReceiver {
+public:
+ PortableTaskBarReceiver(IEventQueue* events);
+ virtual ~PortableTaskBarReceiver();
+
+ //! @name manipulators
+ //@{
+
+ //! Update status
+ /*!
+ Determine the status and query required information from the server.
+ */
+ void updateStatus(INode*, const String& 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<String> Clients;
+ enum EState {
+ kNotRunning,
+ kNotWorking,
+ kNotConnected,
+ kConnected,
+ kMaxState
+ };
+
+ //! Get status
+ EState getStatus() const;
+
+ //! Get error message
+ const String& 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(INode* node);
+
+private:
+ EState m_state;
+ String m_errorMessage;
+
+ String m_server;
+ Clients m_clients;
+
+ IEventQueue* m_events;
+};
+
+IArchTaskBarReceiver* createTaskBarReceiver(const BufferedLogOutputter* logBuffer, IEventQueue* events);
diff --git a/src/lib/barrier/ProtocolUtil.cpp b/src/lib/barrier/ProtocolUtil.cpp
new file mode 100644
index 0000000..42fe69c
--- /dev/null
+++ b/src/lib/barrier/ProtocolUtil.cpp
@@ -0,0 +1,544 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ProtocolUtil.h"
+#include "io/IStream.h"
+#include "base/Log.h"
+#include "common/stdvector.h"
+
+#include <cctype>
+#include <cstring>
+
+//
+// ProtocolUtil
+//
+
+void
+ProtocolUtil::writef(barrier::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
+ProtocolUtil::readf(barrier::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
+ProtocolUtil::vwritef(barrier::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
+ProtocolUtil::vreadf(barrier::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
+ *static_cast<UInt8*>(v) = buffer[0];
+ LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *static_cast<UInt8*>(v), *static_cast<UInt8*>(v)));
+ break;
+
+ case 2:
+ // 2 byte integer
+ *static_cast<UInt16*>(v) =
+ static_cast<UInt16>(
+ (static_cast<UInt16>(buffer[0]) << 8) |
+ static_cast<UInt16>(buffer[1]));
+ LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *static_cast<UInt16*>(v), *static_cast<UInt16*>(v)));
+ break;
+
+ case 4:
+ // 4 byte integer
+ *static_cast<UInt32*>(v) =
+ (static_cast<UInt32>(buffer[0]) << 24) |
+ (static_cast<UInt32>(buffer[1]) << 16) |
+ (static_cast<UInt32>(buffer[2]) << 8) |
+ static_cast<UInt32>(buffer[3]);
+ LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *static_cast<UInt32*>(v), *static_cast<UInt32*>(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<UInt32>(buffer[0]) << 24) |
+ (static_cast<UInt32>(buffer[1]) << 16) |
+ (static_cast<UInt32>(buffer[2]) << 8) |
+ static_cast<UInt32>(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);
+ static_cast<std::vector<UInt8>*>(v)->push_back(
+ buffer[0]);
+ LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, static_cast<std::vector<UInt8>*>(v)->back(), static_cast<std::vector<UInt8>*>(v)->back()));
+ }
+ break;
+
+ case 2:
+ // 2 byte integer
+ for (UInt32 i = 0; i < n; ++i) {
+ read(stream, buffer, 2);
+ static_cast<std::vector<UInt16>*>(v)->push_back(
+ static_cast<UInt16>(
+ (static_cast<UInt16>(buffer[0]) << 8) |
+ static_cast<UInt16>(buffer[1])));
+ LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, static_cast<std::vector<UInt16>*>(v)->back(), static_cast<std::vector<UInt16>*>(v)->back()));
+ }
+ break;
+
+ case 4:
+ // 4 byte integer
+ for (UInt32 i = 0; i < n; ++i) {
+ read(stream, buffer, 4);
+ static_cast<std::vector<UInt32>*>(v)->push_back(
+ (static_cast<UInt32>(buffer[0]) << 24) |
+ (static_cast<UInt32>(buffer[1]) << 16) |
+ (static_cast<UInt32>(buffer[2]) << 8) |
+ static_cast<UInt32>(buffer[3]));
+ LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, static_cast<std::vector<UInt32>*>(v)->back(), static_cast<std::vector<UInt32>*>(v)->back()));
+ }
+ break;
+ }
+ break;
+ }
+
+ case 's': {
+ assert(len == 0);
+
+ // read the string length
+ UInt8 buffer[128];
+ read(stream, buffer, 4);
+ UInt32 len = (static_cast<UInt32>(buffer[0]) << 24) |
+ (static_cast<UInt32>(buffer[1]) << 16) |
+ (static_cast<UInt32>(buffer[2]) << 8) |
+ static_cast<UInt32>(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", len));
+
+ // save the data
+ String* dst = va_arg(args, String*);
+ 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
+ProtocolUtil::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 = (UInt32)(va_arg(args, std::vector<UInt8>*))->size() + 4;
+ break;
+
+ case 2:
+ len = 2 * (UInt32)(va_arg(args, std::vector<UInt16>*))->size() + 4;
+ break;
+
+ case 4:
+ len = 4 * (UInt32)(va_arg(args, std::vector<UInt32>*))->size() + 4;
+ break;
+ }
+ break;
+
+ case 's':
+ assert(len == 0);
+ len = (UInt32)(va_arg(args, String*))->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
+ProtocolUtil::writef(void* buffer, const char* fmt, va_list args)
+{
+ UInt8* dst = static_cast<UInt8*>(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<UInt8>(v & 0xff);
+ break;
+
+ case 2:
+ // 2 byte integer
+ *dst++ = static_cast<UInt8>((v >> 8) & 0xff);
+ *dst++ = static_cast<UInt8>( v & 0xff);
+ break;
+
+ case 4:
+ // 4 byte integer
+ *dst++ = static_cast<UInt8>((v >> 24) & 0xff);
+ *dst++ = static_cast<UInt8>((v >> 16) & 0xff);
+ *dst++ = static_cast<UInt8>((v >> 8) & 0xff);
+ *dst++ = static_cast<UInt8>( v & 0xff);
+ break;
+
+ default:
+ assert(0 && "invalid integer format length");
+ return;
+ }
+ break;
+ }
+
+ case 'I': {
+ switch (len) {
+ case 1: {
+ // 1 byte integers
+ const std::vector<UInt8>* list =
+ va_arg(args, const std::vector<UInt8>*);
+ const UInt32 n = (UInt32)list->size();
+ *dst++ = static_cast<UInt8>((n >> 24) & 0xff);
+ *dst++ = static_cast<UInt8>((n >> 16) & 0xff);
+ *dst++ = static_cast<UInt8>((n >> 8) & 0xff);
+ *dst++ = static_cast<UInt8>( n & 0xff);
+ for (UInt32 i = 0; i < n; ++i) {
+ *dst++ = (*list)[i];
+ }
+ break;
+ }
+
+ case 2: {
+ // 2 byte integers
+ const std::vector<UInt16>* list =
+ va_arg(args, const std::vector<UInt16>*);
+ const UInt32 n = (UInt32)list->size();
+ *dst++ = static_cast<UInt8>((n >> 24) & 0xff);
+ *dst++ = static_cast<UInt8>((n >> 16) & 0xff);
+ *dst++ = static_cast<UInt8>((n >> 8) & 0xff);
+ *dst++ = static_cast<UInt8>( n & 0xff);
+ for (UInt32 i = 0; i < n; ++i) {
+ const UInt16 v = (*list)[i];
+ *dst++ = static_cast<UInt8>((v >> 8) & 0xff);
+ *dst++ = static_cast<UInt8>( v & 0xff);
+ }
+ break;
+ }
+
+ case 4: {
+ // 4 byte integers
+ const std::vector<UInt32>* list =
+ va_arg(args, const std::vector<UInt32>*);
+ const UInt32 n = (UInt32)list->size();
+ *dst++ = static_cast<UInt8>((n >> 24) & 0xff);
+ *dst++ = static_cast<UInt8>((n >> 16) & 0xff);
+ *dst++ = static_cast<UInt8>((n >> 8) & 0xff);
+ *dst++ = static_cast<UInt8>( n & 0xff);
+ for (UInt32 i = 0; i < n; ++i) {
+ const UInt32 v = (*list)[i];
+ *dst++ = static_cast<UInt8>((v >> 24) & 0xff);
+ *dst++ = static_cast<UInt8>((v >> 16) & 0xff);
+ *dst++ = static_cast<UInt8>((v >> 8) & 0xff);
+ *dst++ = static_cast<UInt8>( v & 0xff);
+ }
+ break;
+ }
+
+ default:
+ assert(0 && "invalid integer vector format length");
+ return;
+ }
+ break;
+ }
+
+ case 's': {
+ assert(len == 0);
+ const String* src = va_arg(args, String*);
+ const UInt32 len = (src != NULL) ? (UInt32)src->size() : 0;
+ *dst++ = static_cast<UInt8>((len >> 24) & 0xff);
+ *dst++ = static_cast<UInt8>((len >> 16) & 0xff);
+ *dst++ = static_cast<UInt8>((len >> 8) & 0xff);
+ *dst++ = static_cast<UInt8>( 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<UInt8>((len >> 24) & 0xff);
+ *dst++ = static_cast<UInt8>((len >> 16) & 0xff);
+ *dst++ = static_cast<UInt8>((len >> 8) & 0xff);
+ *dst++ = static_cast<UInt8>( 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
+ProtocolUtil::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
+ProtocolUtil::read(barrier::IStream* stream, void* vbuffer, UInt32 count)
+{
+ assert(stream != NULL);
+ assert(vbuffer != NULL);
+
+ UInt8* buffer = static_cast<UInt8*>(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
+//
+
+String
+XIOReadMismatch::getWhat() const throw()
+{
+ return format("XIOReadMismatch", "ProtocolUtil::readf() mismatch");
+}
diff --git a/src/lib/barrier/ProtocolUtil.h b/src/lib/barrier/ProtocolUtil.h
new file mode 100644
index 0000000..78bb5ca
--- /dev/null
+++ b/src/lib/barrier/ProtocolUtil.h
@@ -0,0 +1,96 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "io/XIO.h"
+#include "base/EventTypes.h"
+
+#include <stdarg.h>
+
+namespace barrier { class IStream; }
+
+//! Barrier protocol utilities
+/*!
+This class provides various functions for implementing the barrier
+protocol.
+*/
+class ProtocolUtil {
+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<UInt8>* to 1 byte integers
+ - \%2I -- converts std::vector<UInt16>* to 2 byte integers in NBO
+ - \%4I -- converts std::vector<UInt32>* to 4 byte integers in NBO
+ - \%s -- converts String* to stream of bytes
+ - \%S -- converts integer N and const UInt8* to stream of N bytes
+ */
+ static void writef(barrier::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<UInt8>*
+ - \%2I -- reads NBO 2 byte integers; arg is std::vector<UInt16>*
+ - \%4I -- reads NBO 4 byte integers; arg is std::vector<UInt32>*
+ - \%s -- reads bytes; argument must be a String*, \b not a char*
+ */
+ static bool readf(barrier::IStream*,
+ const char* fmt, ...);
+
+private:
+ static void vwritef(barrier::IStream*,
+ const char* fmt, UInt32 size, va_list);
+ static void vreadf(barrier::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(barrier::IStream*, void*, UInt32);
+};
+
+//! Mismatched read exception
+/*!
+Thrown by ProtocolUtil::readf() when the data being read does not
+match the format.
+*/
+class XIOReadMismatch : public XIO {
+public:
+ // XBase overrides
+ virtual String getWhat() const throw();
+};
diff --git a/src/lib/barrier/Screen.cpp b/src/lib/barrier/Screen.cpp
new file mode 100644
index 0000000..32442f6
--- /dev/null
+++ b/src/lib/barrier/Screen.cpp
@@ -0,0 +1,559 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/Screen.h"
+#include "barrier/IPlatformScreen.h"
+#include "barrier/protocol_types.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "server/ClientProxy.h"
+#include "base/TMethodEventJob.h"
+
+namespace barrier {
+
+//
+// Screen
+//
+
+Screen::Screen(IPlatformScreen* platformScreen, IEventQueue* events) :
+ m_screen(platformScreen),
+ m_isPrimary(platformScreen->isPrimary()),
+ m_enabled(false),
+ m_entered(m_isPrimary),
+ m_screenSaverSync(true),
+ m_fakeInput(false),
+ m_events(events),
+ m_mock(false),
+ m_enableDragDrop(false)
+{
+ assert(m_screen != NULL);
+
+ // reset options
+ resetOptions();
+
+ LOG((CLOG_DEBUG "opened display"));
+}
+
+Screen::~Screen()
+{
+ if (m_mock) {
+ return;
+ }
+
+ if (m_enabled) {
+ disable();
+ }
+ assert(!m_enabled);
+ assert(m_entered == m_isPrimary);
+ delete m_screen;
+ LOG((CLOG_DEBUG "closed display"));
+}
+
+void
+Screen::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
+Screen::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
+Screen::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
+Screen::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
+Screen::reconfigure(UInt32 activeSides)
+{
+ assert(m_isPrimary);
+ m_screen->reconfigure(activeSides);
+}
+
+void
+Screen::warpCursor(SInt32 x, SInt32 y)
+{
+ assert(m_isPrimary);
+ m_screen->warpCursor(x, y);
+}
+
+void
+Screen::setClipboard(ClipboardID id, const IClipboard* clipboard)
+{
+ m_screen->setClipboard(id, clipboard);
+}
+
+void
+Screen::grabClipboard(ClipboardID id)
+{
+ m_screen->setClipboard(id, NULL);
+}
+
+void
+Screen::screensaver(bool activate)
+{
+ if (!m_isPrimary) {
+ // activate/deactivation screen saver iff synchronization enabled
+ if (m_screenSaverSync) {
+ m_screen->screensaver(activate);
+ }
+ }
+}
+
+void
+Screen::keyDown(KeyID id, KeyModifierMask mask, KeyButton button)
+{
+ // 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
+Screen::keyRepeat(KeyID id,
+ KeyModifierMask mask, SInt32 count, KeyButton button)
+{
+ assert(!m_isPrimary);
+ m_screen->fakeKeyRepeat(id, mask, count, button);
+}
+
+void
+Screen::keyUp(KeyID, KeyModifierMask, KeyButton button)
+{
+ m_screen->fakeKeyUp(button);
+}
+
+void
+Screen::mouseDown(ButtonID button)
+{
+ m_screen->fakeMouseButton(button, true);
+}
+
+void
+Screen::mouseUp(ButtonID button)
+{
+ m_screen->fakeMouseButton(button, false);
+}
+
+void
+Screen::mouseMove(SInt32 x, SInt32 y)
+{
+ assert(!m_isPrimary);
+ m_screen->fakeMouseMove(x, y);
+}
+
+void
+Screen::mouseRelativeMove(SInt32 dx, SInt32 dy)
+{
+ assert(!m_isPrimary);
+ m_screen->fakeMouseRelativeMove(dx, dy);
+}
+
+void
+Screen::mouseWheel(SInt32 xDelta, SInt32 yDelta)
+{
+ assert(!m_isPrimary);
+ m_screen->fakeMouseWheel(xDelta, yDelta);
+}
+
+void
+Screen::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
+Screen::setOptions(const OptionsList& options)
+{
+ // update options
+ bool oldScreenSaverSync = m_screenSaverSync;
+ for (UInt32 i = 0, n = (UInt32)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
+Screen::setSequenceNumber(UInt32 seqNum)
+{
+ m_screen->setSequenceNumber(seqNum);
+}
+
+UInt32
+Screen::registerHotKey(KeyID key, KeyModifierMask mask)
+{
+ return m_screen->registerHotKey(key, mask);
+}
+
+void
+Screen::unregisterHotKey(UInt32 id)
+{
+ m_screen->unregisterHotKey(id);
+}
+
+void
+Screen::fakeInputBegin()
+{
+ assert(!m_fakeInput);
+
+ m_fakeInput = true;
+ m_screen->fakeInputBegin();
+}
+
+void
+Screen::fakeInputEnd()
+{
+ assert(m_fakeInput);
+
+ m_fakeInput = false;
+ m_screen->fakeInputEnd();
+}
+
+bool
+Screen::isOnScreen() const
+{
+ return m_entered;
+}
+
+bool
+Screen::isLockedToScreen() const
+{
+ // check for pressed mouse buttons
+ // HACK: commented out as it breaks new drag drop feature
+ UInt32 buttonID = 0;
+
+ if (m_screen->isAnyMouseButtonDown(buttonID)) {
+ if (buttonID != kButtonLeft) {
+ LOG((CLOG_DEBUG "locked by mouse buttonID: %d", buttonID));
+ }
+
+ if (m_enableDragDrop) {
+ return (buttonID == kButtonLeft) ? false : true;
+ }
+ else {
+ return true;
+ }
+ }
+
+ // not locked
+ return false;
+}
+
+SInt32
+Screen::getJumpZoneSize() const
+{
+ if (!m_isPrimary) {
+ return 0;
+ }
+ else {
+ return m_screen->getJumpZoneSize();
+ }
+}
+
+void
+Screen::getCursorCenter(SInt32& x, SInt32& y) const
+{
+ m_screen->getCursorCenter(x, y);
+}
+
+KeyModifierMask
+Screen::getActiveModifiers() const
+{
+ return m_screen->getActiveModifiers();
+}
+
+KeyModifierMask
+Screen::pollActiveModifiers() const
+{
+ return m_screen->pollActiveModifiers();
+}
+
+bool
+Screen::isDraggingStarted() const
+{
+ return m_screen->isDraggingStarted();
+}
+
+bool
+Screen::isFakeDraggingStarted() const
+{
+ return m_screen->isFakeDraggingStarted();
+}
+
+void
+Screen::setDraggingStarted(bool started)
+{
+ m_screen->setDraggingStarted(started);
+}
+
+void
+Screen::startDraggingFiles(DragFileList& fileList)
+{
+ m_screen->fakeDraggingFiles(fileList);
+}
+
+void
+Screen::setEnableDragDrop(bool enabled)
+{
+ m_enableDragDrop = enabled;
+}
+
+String&
+Screen::getDraggingFilename() const
+{
+ return m_screen->getDraggingFilename();
+}
+
+void
+Screen::clearDraggingFilename()
+{
+ m_screen->clearDraggingFilename();
+}
+
+const String&
+Screen::getDropTarget() const
+{
+ return m_screen->getDropTarget();
+}
+
+void*
+Screen::getEventTarget() const
+{
+ return m_screen;
+}
+
+bool
+Screen::getClipboard(ClipboardID id, IClipboard* clipboard) const
+{
+ return m_screen->getClipboard(id, clipboard);
+}
+
+void
+Screen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
+{
+ m_screen->getShape(x, y, w, h);
+}
+
+void
+Screen::getCursorPos(SInt32& x, SInt32& y) const
+{
+ m_screen->getCursorPos(x, y);
+}
+
+void
+Screen::enablePrimary()
+{
+ // get notified of screen saver activation/deactivation
+ m_screen->openScreensaver(true);
+
+ // claim screen changed size
+ m_events->addEvent(Event(m_events->forIScreen().shapeChanged(), getEventTarget()));
+}
+
+void
+Screen::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
+Screen::disablePrimary()
+{
+ // done with screen saver
+ m_screen->closeScreensaver();
+}
+
+void
+Screen::disableSecondary()
+{
+ // done with screen saver
+ m_screen->closeScreensaver();
+}
+
+void
+Screen::enterPrimary()
+{
+ // do nothing
+}
+
+void
+Screen::enterSecondary(KeyModifierMask)
+{
+ // do nothing
+}
+
+void
+Screen::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
+Screen::leaveSecondary()
+{
+ // release any keys we think are still down
+ m_screen->fakeAllKeysUp();
+}
+
+}
diff --git a/src/lib/barrier/Screen.h b/src/lib/barrier/Screen.h
new file mode 100644
index 0000000..b16feff
--- /dev/null
+++ b/src/lib/barrier/Screen.h
@@ -0,0 +1,345 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/DragInformation.h"
+#include "barrier/clipboard_types.h"
+#include "barrier/IScreen.h"
+#include "barrier/key_types.h"
+#include "barrier/mouse_types.h"
+#include "barrier/option_types.h"
+#include "base/String.h"
+
+class IClipboard;
+class IPlatformScreen;
+class IEventQueue;
+
+namespace barrier {
+
+//! Platform independent screen
+/*!
+This is a platform independent screen. It can work as either a
+primary or secondary screen.
+*/
+class Screen : public IScreen {
+public:
+ Screen(IPlatformScreen* platformScreen, IEventQueue* events);
+ virtual ~Screen();
+
+#ifdef TEST_ENV
+ Screen() : m_mock(true) { }
+#endif
+
+ //! @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.
+ */
+ virtual void enable();
+
+ //! Deactivate screen
+ /*!
+ Undoes the operations in activate() and events are no longer
+ reported. It also releases keys that are logically pressed.
+ */
+ virtual 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.
+ */
+ virtual 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.
+ */
+ virtual void setOptions(const OptionsList& 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();
+
+ //! Change dragging status
+ void setDraggingStarted(bool started);
+
+ //! Fake a files dragging operation
+ void startDraggingFiles(DragFileList& fileList);
+
+ void setEnableDragDrop(bool enabled);
+ //@}
+ //! @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;
+
+ //! Test if file is dragged on primary screen
+ bool isDraggingStarted() const;
+
+ //! Test if file is dragged on secondary screen
+ bool isFakeDraggingStarted() const;
+
+ //! Get the filename of the file being dragged
+ String& getDraggingFilename() const;
+
+ //! Clear the filename of the file that was dragged
+ void clearDraggingFilename();
+
+ //! Get the drop target directory
+ const String& getDropTarget() 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;
+
+ IPlatformScreen* getPlatformScreen() { return m_screen; }
+
+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;
+
+ IEventQueue* m_events;
+
+ bool m_mock;
+ bool m_enableDragDrop;
+};
+
+}
diff --git a/src/lib/barrier/ServerApp.cpp b/src/lib/barrier/ServerApp.cpp
new file mode 100644
index 0000000..112f290
--- /dev/null
+++ b/src/lib/barrier/ServerApp.cpp
@@ -0,0 +1,859 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ServerApp.h"
+
+#include "server/Server.h"
+#include "server/ClientListener.h"
+#include "server/ClientProxy.h"
+#include "server/PrimaryClient.h"
+#include "barrier/ArgParser.h"
+#include "barrier/Screen.h"
+#include "barrier/XScreen.h"
+#include "barrier/ServerTaskBarReceiver.h"
+#include "barrier/ServerArgs.h"
+#include "net/SocketMultiplexer.h"
+#include "net/TCPSocketFactory.h"
+#include "net/XSocket.h"
+#include "arch/Arch.h"
+#include "base/EventQueue.h"
+#include "base/log_outputters.h"
+#include "base/FunctionEventJob.h"
+#include "base/TMethodJob.h"
+#include "base/IEventQueue.h"
+#include "base/Log.h"
+#include "base/TMethodEventJob.h"
+#include "common/Version.h"
+
+#if SYSAPI_WIN32
+#include "arch/win32/ArchMiscWindows.h"
+#endif
+
+#if WINAPI_MSWINDOWS
+#include "platform/MSWindowsScreen.h"
+#elif WINAPI_XWINDOWS
+#include "platform/XWindowsScreen.h"
+#elif WINAPI_CARBON
+#include "platform/OSXScreen.h"
+#endif
+
+#if defined(__APPLE__)
+#include "platform/OSXDragSimulator.h"
+#endif
+
+#include <iostream>
+#include <stdio.h>
+#include <fstream>
+#include <sstream>
+
+//
+// ServerApp
+//
+
+ServerApp::ServerApp(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver) :
+ App(events, createTaskBarReceiver, new ServerArgs()),
+ m_server(NULL),
+ m_serverState(kUninitialized),
+ m_serverScreen(NULL),
+ m_primaryClient(NULL),
+ m_listener(NULL),
+ m_timer(NULL),
+ m_barrierAddress(NULL)
+{
+}
+
+ServerApp::~ServerApp()
+{
+}
+
+void
+ServerApp::parseArgs(int argc, const char* const* argv)
+{
+ ArgParser argParser(this);
+ bool result = argParser.parseServerArgs(args(), argc, argv);
+
+ if (!result || args().m_shouldExit) {
+ m_bye(kExitArgs);
+ }
+ else {
+ if (!args().m_barrierAddress.empty()) {
+ try {
+ *m_barrierAddress = NetworkAddress(args().m_barrierAddress,
+ kDefaultPort);
+ m_barrierAddress->resolve();
+ }
+ catch (XSocketAddress& e) {
+ LOG((CLOG_PRINT "%s: %s" BYE,
+ args().m_pname, e.what(), args().m_pname));
+ m_bye(kExitArgs);
+ }
+ }
+ }
+}
+
+void
+ServerApp::help()
+{
+ // window api args (windows/x-windows/carbon)
+#if WINAPI_XWINDOWS
+# define WINAPI_ARGS \
+ " [--display <display>] [--no-xinitthreads]"
+# define WINAPI_INFO \
+ " --display <display> connect to the X server at <display>\n" \
+ " --no-xinitthreads do not call XInitThreads()\n"
+#else
+# define WINAPI_ARGS ""
+# define WINAPI_INFO ""
+#endif
+
+ std::ostringstream buffer;
+ buffer << "Start the barrier server component." << std::endl
+ << std::endl
+ << "Usage: " << args().m_pname
+ << " [--address <address>]"
+ << " [--config <pathname>]"
+ << WINAPI_ARGS << HELP_SYS_ARGS << HELP_COMMON_ARGS << std::endl
+ << std::endl
+ << "Options:" << std::endl
+ << " -a, --address <address> listen for clients on the given address." << std::endl
+ << " -c, --config <pathname> use the named configuration file instead." << std::endl
+ << HELP_COMMON_INFO_1 << WINAPI_INFO << HELP_SYS_INFO << HELP_COMMON_INFO_2 << std::endl
+ << "Default options are marked with a *" << std::endl
+ << std::endl
+ << "The argument for --address is of the form: [<hostname>][:<port>]. The" << std::endl
+ << "hostname must be the address or hostname of an interface on the system." << std::endl
+ << "The default is to listen on all interfaces. The port overrides the" << std::endl
+ << "default port, " << kDefaultPort << "." << std::endl
+ << std::endl
+ << "If no configuration file pathname is provided then the first of the" << std::endl
+ << "following to load successfully sets the configuration:" << std::endl
+ << " $HOME/" << USR_CONFIG_NAME << std::endl
+ << " " << ARCH->concatPath(ARCH->getSystemDirectory(), SYS_CONFIG_NAME) << std::endl;
+
+ LOG((CLOG_PRINT "%s", buffer.str().c_str()));
+}
+
+void
+ServerApp::reloadSignalHandler(Arch::ESignal, void*)
+{
+ IEventQueue* events = App::instance().getEvents();
+ events->addEvent(Event(events->forServerApp().reloadConfig(),
+ events->getSystemTarget()));
+}
+
+void
+ServerApp::reloadConfig(const Event&, void*)
+{
+ LOG((CLOG_DEBUG "reload configuration"));
+ if (loadConfig(args().m_configFile)) {
+ if (m_server != NULL) {
+ m_server->setConfig(*args().m_config);
+ }
+ LOG((CLOG_NOTE "reloaded configuration"));
+ }
+}
+
+void
+ServerApp::loadConfig()
+{
+ bool loaded = false;
+
+ // load the config file, if specified
+ if (!args().m_configFile.empty()) {
+ loaded = loadConfig(args().m_configFile);
+ }
+
+ // load the default configuration if no explicit file given
+ else {
+ // get the user's home directory
+ String path = ARCH->getUserDirectory();
+ if (!path.empty()) {
+ // complete path
+ path = ARCH->concatPath(path, USR_CONFIG_NAME);
+
+ // now try loading the user's configuration
+ if (loadConfig(path)) {
+ loaded = true;
+ args().m_configFile = path;
+ }
+ }
+ if (!loaded) {
+ // try the system-wide config file
+ path = ARCH->getSystemDirectory();
+ if (!path.empty()) {
+ path = ARCH->concatPath(path, SYS_CONFIG_NAME);
+ if (loadConfig(path)) {
+ loaded = true;
+ args().m_configFile = path;
+ }
+ }
+ }
+ }
+
+ if (!loaded) {
+ LOG((CLOG_PRINT "%s: no configuration available", args().m_pname));
+ m_bye(kExitConfig);
+ }
+}
+
+bool
+ServerApp::loadConfig(const String& pathname)
+{
+ try {
+ // load configuration
+ LOG((CLOG_DEBUG "opening configuration \"%s\"", pathname.c_str()));
+ std::ifstream configStream(pathname.c_str());
+ if (!configStream.is_open()) {
+ // report failure to open configuration as a debug message
+ // since we try several paths and we expect some to be
+ // missing.
+ LOG((CLOG_DEBUG "cannot open configuration \"%s\"",
+ pathname.c_str()));
+ return false;
+ }
+ configStream >> *args().m_config;
+ LOG((CLOG_DEBUG "configuration read successfully"));
+ return true;
+ }
+ catch (XConfigRead& e) {
+ // report error in configuration file
+ LOG((CLOG_ERR "cannot read configuration \"%s\": %s",
+ pathname.c_str(), e.what()));
+ }
+ return false;
+}
+
+void
+ServerApp::forceReconnect(const Event&, void*)
+{
+ if (m_server != NULL) {
+ m_server->disconnect();
+ }
+}
+
+void
+ServerApp::handleClientConnected(const Event&, void* vlistener)
+{
+ ClientListener* listener = static_cast<ClientListener*>(vlistener);
+ ClientProxy* client = listener->getNextClient();
+ if (client != NULL) {
+ m_server->adoptClient(client);
+ updateStatus();
+ }
+}
+
+void
+ServerApp::handleClientsDisconnected(const Event&, void*)
+{
+ m_events->addEvent(Event(Event::kQuit));
+}
+
+void
+ServerApp::closeServer(Server* 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;
+ EventQueueTimer* timer = m_events->newOneShotTimer(timeout, NULL);
+ m_events->adoptHandler(Event::kTimer, timer,
+ new TMethodEventJob<ServerApp>(this, &ServerApp::handleClientsDisconnected));
+ m_events->adoptHandler(m_events->forServer().disconnected(), server,
+ new TMethodEventJob<ServerApp>(this, &ServerApp::handleClientsDisconnected));
+
+ m_events->loop();
+
+ m_events->removeHandler(Event::kTimer, timer);
+ m_events->deleteTimer(timer);
+ m_events->removeHandler(m_events->forServer().disconnected(), server);
+
+ // done with server
+ delete server;
+}
+
+void
+ServerApp::stopRetryTimer()
+{
+ if (m_timer != NULL) {
+ m_events->deleteTimer(m_timer);
+ m_events->removeHandler(Event::kTimer, NULL);
+ m_timer = NULL;
+ }
+}
+
+void
+ServerApp::updateStatus()
+{
+ updateStatus("");
+}
+
+void ServerApp::updateStatus(const String& msg)
+{
+ if (m_taskBarReceiver)
+ {
+ m_taskBarReceiver->updateStatus(m_server, msg);
+ }
+}
+
+void
+ServerApp::closeClientListener(ClientListener* listen)
+{
+ if (listen != NULL) {
+ m_events->removeHandler(m_events->forClientListener().connected(), listen);
+ delete listen;
+ }
+}
+
+void
+ServerApp::stopServer()
+{
+ if (m_serverState == kStarted) {
+ closeServer(m_server);
+ closeClientListener(m_listener);
+ m_server = NULL;
+ m_listener = NULL;
+ m_serverState = kInitialized;
+ }
+ else if (m_serverState == kStarting) {
+ stopRetryTimer();
+ m_serverState = kInitialized;
+ }
+ assert(m_server == NULL);
+ assert(m_listener == NULL);
+}
+
+void
+ServerApp::closePrimaryClient(PrimaryClient* primaryClient)
+{
+ delete primaryClient;
+}
+
+void
+ServerApp::closeServerScreen(barrier::Screen* screen)
+{
+ if (screen != NULL) {
+ m_events->removeHandler(m_events->forIScreen().error(),
+ screen->getEventTarget());
+ m_events->removeHandler(m_events->forIScreen().suspend(),
+ screen->getEventTarget());
+ m_events->removeHandler(m_events->forIScreen().resume(),
+ screen->getEventTarget());
+ delete screen;
+ }
+}
+
+void ServerApp::cleanupServer()
+{
+ stopServer();
+ if (m_serverState == kInitialized) {
+ closePrimaryClient(m_primaryClient);
+ closeServerScreen(m_serverScreen);
+ m_primaryClient = NULL;
+ m_serverScreen = NULL;
+ m_serverState = kUninitialized;
+ }
+ else if (m_serverState == kInitializing ||
+ m_serverState == kInitializingToStart) {
+ stopRetryTimer();
+ m_serverState = kUninitialized;
+ }
+ assert(m_primaryClient == NULL);
+ assert(m_serverScreen == NULL);
+ assert(m_serverState == kUninitialized);
+}
+
+void
+ServerApp::retryHandler(const Event&, void*)
+{
+ // discard old timer
+ assert(m_timer != NULL);
+ stopRetryTimer();
+
+ // try initializing/starting the server again
+ switch (m_serverState) {
+ case kUninitialized:
+ case kInitialized:
+ case kStarted:
+ assert(0 && "bad internal server state");
+ break;
+
+ case kInitializing:
+ LOG((CLOG_DEBUG1 "retry server initialization"));
+ m_serverState = kUninitialized;
+ if (!initServer()) {
+ m_events->addEvent(Event(Event::kQuit));
+ }
+ break;
+
+ case kInitializingToStart:
+ LOG((CLOG_DEBUG1 "retry server initialization"));
+ m_serverState = kUninitialized;
+ if (!initServer()) {
+ m_events->addEvent(Event(Event::kQuit));
+ }
+ else if (m_serverState == kInitialized) {
+ LOG((CLOG_DEBUG1 "starting server"));
+ if (!startServer()) {
+ m_events->addEvent(Event(Event::kQuit));
+ }
+ }
+ break;
+
+ case kStarting:
+ LOG((CLOG_DEBUG1 "retry starting server"));
+ m_serverState = kInitialized;
+ if (!startServer()) {
+ m_events->addEvent(Event(Event::kQuit));
+ }
+ break;
+ }
+}
+
+bool ServerApp::initServer()
+{
+ // skip if already initialized or initializing
+ if (m_serverState != kUninitialized) {
+ return true;
+ }
+
+ double retryTime;
+ barrier::Screen* serverScreen = NULL;
+ PrimaryClient* primaryClient = NULL;
+ try {
+ String name = args().m_config->getCanonicalName(args().m_name);
+ serverScreen = openServerScreen();
+ primaryClient = openPrimaryClient(name, serverScreen);
+ m_serverScreen = serverScreen;
+ m_primaryClient = primaryClient;
+ m_serverState = kInitialized;
+ updateStatus();
+ return true;
+ }
+ catch (XScreenUnavailable& e) {
+ LOG((CLOG_WARN "primary screen unavailable: %s", e.what()));
+ closePrimaryClient(primaryClient);
+ closeServerScreen(serverScreen);
+ updateStatus(String("primary screen unavailable: ") + e.what());
+ retryTime = e.getRetryTime();
+ }
+ catch (XScreenOpenFailure& e) {
+ LOG((CLOG_CRIT "failed to start server: %s", e.what()));
+ closePrimaryClient(primaryClient);
+ closeServerScreen(serverScreen);
+ return false;
+ }
+ catch (XBase& e) {
+ LOG((CLOG_CRIT "failed to start server: %s", e.what()));
+ closePrimaryClient(primaryClient);
+ closeServerScreen(serverScreen);
+ return false;
+ }
+
+ if (args().m_restartable) {
+ // install a timer and handler to retry later
+ assert(m_timer == NULL);
+ LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime));
+ m_timer = m_events->newOneShotTimer(retryTime, NULL);
+ m_events->adoptHandler(Event::kTimer, m_timer,
+ new TMethodEventJob<ServerApp>(this, &ServerApp::retryHandler));
+ m_serverState = kInitializing;
+ return true;
+ }
+ else {
+ // don't try again
+ return false;
+ }
+}
+
+barrier::Screen*
+ServerApp::openServerScreen()
+{
+ barrier::Screen* screen = createScreen();
+ screen->setEnableDragDrop(argsBase().m_enableDragDrop);
+ m_events->adoptHandler(m_events->forIScreen().error(),
+ screen->getEventTarget(),
+ new TMethodEventJob<ServerApp>(
+ this, &ServerApp::handleScreenError));
+ m_events->adoptHandler(m_events->forIScreen().suspend(),
+ screen->getEventTarget(),
+ new TMethodEventJob<ServerApp>(
+ this, &ServerApp::handleSuspend));
+ m_events->adoptHandler(m_events->forIScreen().resume(),
+ screen->getEventTarget(),
+ new TMethodEventJob<ServerApp>(
+ this, &ServerApp::handleResume));
+ return screen;
+}
+
+bool
+ServerApp::startServer()
+{
+ // skip if already started or starting
+ if (m_serverState == kStarting || m_serverState == kStarted) {
+ return true;
+ }
+
+ // initialize if necessary
+ if (m_serverState != kInitialized) {
+ if (!initServer()) {
+ // hard initialization failure
+ return false;
+ }
+ if (m_serverState == kInitializing) {
+ // not ready to start
+ m_serverState = kInitializingToStart;
+ return true;
+ }
+ assert(m_serverState == kInitialized);
+ }
+
+ double retryTime;
+ ClientListener* listener = NULL;
+ try {
+ listener = openClientListener(args().m_config->getBarrierAddress());
+ m_server = openServer(*args().m_config, m_primaryClient);
+ listener->setServer(m_server);
+ m_server->setListener(listener);
+ m_listener = listener;
+ updateStatus();
+ LOG((CLOG_NOTE "started server, waiting for clients"));
+ m_serverState = kStarted;
+ return true;
+ }
+ catch (XSocketAddressInUse& e) {
+ LOG((CLOG_WARN "cannot listen for clients: %s", e.what()));
+ closeClientListener(listener);
+ updateStatus(String("cannot listen for clients: ") + e.what());
+ retryTime = 10.0;
+ }
+ catch (XBase& e) {
+ LOG((CLOG_CRIT "failed to start server: %s", e.what()));
+ closeClientListener(listener);
+ return false;
+ }
+
+ if (args().m_restartable) {
+ // install a timer and handler to retry later
+ assert(m_timer == NULL);
+ LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime));
+ m_timer = m_events->newOneShotTimer(retryTime, NULL);
+ m_events->adoptHandler(Event::kTimer, m_timer,
+ new TMethodEventJob<ServerApp>(this, &ServerApp::retryHandler));
+ m_serverState = kStarting;
+ return true;
+ }
+ else {
+ // don't try again
+ return false;
+ }
+}
+
+barrier::Screen*
+ServerApp::createScreen()
+{
+#if WINAPI_MSWINDOWS
+ return new barrier::Screen(new MSWindowsScreen(
+ true, args().m_noHooks, args().m_stopOnDeskSwitch, m_events), m_events);
+#elif WINAPI_XWINDOWS
+ return new barrier::Screen(new XWindowsScreen(
+ args().m_display, true, args().m_disableXInitThreads, 0, m_events), m_events);
+#elif WINAPI_CARBON
+ return new barrier::Screen(new OSXScreen(m_events, true), m_events);
+#endif
+}
+
+PrimaryClient*
+ServerApp::openPrimaryClient(const String& name, barrier::Screen* screen)
+{
+ LOG((CLOG_DEBUG1 "creating primary screen"));
+ return new PrimaryClient(name, screen);
+
+}
+
+void
+ServerApp::handleScreenError(const Event&, void*)
+{
+ LOG((CLOG_CRIT "error on screen"));
+ m_events->addEvent(Event(Event::kQuit));
+}
+
+void
+ServerApp::handleSuspend(const Event&, void*)
+{
+ if (!m_suspended) {
+ LOG((CLOG_INFO "suspend"));
+ stopServer();
+ m_suspended = true;
+ }
+}
+
+void
+ServerApp::handleResume(const Event&, void*)
+{
+ if (m_suspended) {
+ LOG((CLOG_INFO "resume"));
+ startServer();
+ m_suspended = false;
+ }
+}
+
+ClientListener*
+ServerApp::openClientListener(const NetworkAddress& address)
+{
+ ClientListener* listen = new ClientListener(
+ address,
+ new TCPSocketFactory(m_events, getSocketMultiplexer()),
+ m_events,
+ args().m_enableCrypto);
+
+ m_events->adoptHandler(
+ m_events->forClientListener().connected(), listen,
+ new TMethodEventJob<ServerApp>(
+ this, &ServerApp::handleClientConnected, listen));
+
+ return listen;
+}
+
+Server*
+ServerApp::openServer(Config& config, PrimaryClient* primaryClient)
+{
+ Server* server = new Server(config, primaryClient, m_serverScreen, m_events, args());
+ try {
+ m_events->adoptHandler(
+ m_events->forServer().disconnected(), server,
+ new TMethodEventJob<ServerApp>(this, &ServerApp::handleNoClients));
+
+ m_events->adoptHandler(
+ m_events->forServer().screenSwitched(), server,
+ new TMethodEventJob<ServerApp>(this, &ServerApp::handleScreenSwitched));
+
+ } catch (std::bad_alloc &ba) {
+ delete server;
+ throw ba;
+ }
+
+ return server;
+}
+
+void
+ServerApp::handleNoClients(const Event&, void*)
+{
+ updateStatus();
+}
+
+void
+ServerApp::handleScreenSwitched(const Event& e, void*)
+{
+}
+
+int
+ServerApp::mainLoop()
+{
+ // create socket multiplexer. this must happen after daemonization
+ // on unix because threads evaporate across a fork().
+ SocketMultiplexer multiplexer;
+ setSocketMultiplexer(&multiplexer);
+
+ // if configuration has no screens then add this system
+ // as the default
+ if (args().m_config->begin() == args().m_config->end()) {
+ args().m_config->addScreen(args().m_name);
+ }
+
+ // set the contact address, if provided, in the config.
+ // otherwise, if the config doesn't have an address, use
+ // the default.
+ if (m_barrierAddress->isValid()) {
+ args().m_config->setBarrierAddress(*m_barrierAddress);
+ }
+ else if (!args().m_config->getBarrierAddress().isValid()) {
+ args().m_config->setBarrierAddress(NetworkAddress(kDefaultPort));
+ }
+
+ // canonicalize the primary screen name
+ String primaryName = args().m_config->getCanonicalName(args().m_name);
+ if (primaryName.empty()) {
+ LOG((CLOG_CRIT "unknown screen name `%s'", args().m_name.c_str()));
+ return kExitFailed;
+ }
+
+ // start server, etc
+ appUtil().startNode();
+
+ // init ipc client after node start, since create a new screen wipes out
+ // the event queue (the screen ctors call adoptBuffer).
+ if (argsBase().m_enableIpc) {
+ initIpcClient();
+ }
+
+ // handle hangup signal by reloading the server's configuration
+ ARCH->setSignalHandler(Arch::kHANGUP, &reloadSignalHandler, NULL);
+ m_events->adoptHandler(m_events->forServerApp().reloadConfig(),
+ m_events->getSystemTarget(),
+ new TMethodEventJob<ServerApp>(this, &ServerApp::reloadConfig));
+
+ // handle force reconnect event by disconnecting clients. they'll
+ // reconnect automatically.
+ m_events->adoptHandler(m_events->forServerApp().forceReconnect(),
+ m_events->getSystemTarget(),
+ new TMethodEventJob<ServerApp>(this, &ServerApp::forceReconnect));
+
+ // to work around the sticky meta keys problem, we'll give users
+ // the option to reset the state of barriers
+ m_events->adoptHandler(m_events->forServerApp().resetServer(),
+ m_events->getSystemTarget(),
+ new TMethodEventJob<ServerApp>(this, &ServerApp::resetServer));
+
+ // run event loop. if startServer() failed we're supposed to retry
+ // later. the timer installed by startServer() will take care of
+ // that.
+ DAEMON_RUNNING(true);
+
+#if defined(MAC_OS_X_VERSION_10_7)
+
+ Thread thread(
+ new TMethodJob<ServerApp>(
+ this, &ServerApp::runEventsLoop,
+ NULL));
+
+ // wait until carbon loop is ready
+ OSXScreen* screen = dynamic_cast<OSXScreen*>(
+ m_serverScreen->getPlatformScreen());
+ screen->waitForCarbonLoop();
+
+ runCocoaApp();
+#else
+ m_events->loop();
+#endif
+
+ DAEMON_RUNNING(false);
+
+ // close down
+ LOG((CLOG_DEBUG1 "stopping server"));
+ m_events->removeHandler(m_events->forServerApp().forceReconnect(),
+ m_events->getSystemTarget());
+ m_events->removeHandler(m_events->forServerApp().reloadConfig(),
+ m_events->getSystemTarget());
+ cleanupServer();
+ updateStatus();
+ LOG((CLOG_NOTE "stopped server"));
+
+ if (argsBase().m_enableIpc) {
+ cleanupIpcClient();
+ }
+
+ return kExitSuccess;
+}
+
+void ServerApp::resetServer(const Event&, void*)
+{
+ LOG((CLOG_DEBUG1 "resetting server"));
+ stopServer();
+ cleanupServer();
+ startServer();
+}
+
+int
+ServerApp::runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup)
+{
+ // general initialization
+ m_barrierAddress = new NetworkAddress;
+ args().m_config = new Config(m_events);
+ args().m_pname = ARCH->getBasename(argv[0]);
+
+ // install caller's output filter
+ if (outputter != NULL) {
+ CLOG->insert(outputter);
+ }
+
+ // run
+ int result = startup(argc, argv);
+
+ if (m_taskBarReceiver)
+ {
+ // done with task bar receiver
+ delete m_taskBarReceiver;
+ }
+
+ delete args().m_config;
+ delete m_barrierAddress;
+ return result;
+}
+
+int daemonMainLoopStatic(int argc, const char** argv) {
+ return ServerApp::instance().daemonMainLoop(argc, argv);
+}
+
+int
+ServerApp::standardStartup(int argc, char** argv)
+{
+ initApp(argc, argv);
+
+ // daemonize if requested
+ if (args().m_daemon) {
+ return ARCH->daemonize(daemonName(), daemonMainLoopStatic);
+ }
+ else {
+ return mainLoop();
+ }
+}
+
+int
+ServerApp::foregroundStartup(int argc, char** argv)
+{
+ initApp(argc, argv);
+
+ // never daemonize
+ return mainLoop();
+}
+
+const char*
+ServerApp::daemonName() const
+{
+#if SYSAPI_WIN32
+ return "Barrier Server";
+#elif SYSAPI_UNIX
+ return "barriers";
+#endif
+}
+
+const char*
+ServerApp::daemonInfo() const
+{
+#if SYSAPI_WIN32
+ return "Shares this computers mouse and keyboard with other computers.";
+#elif SYSAPI_UNIX
+ return "";
+#endif
+}
+
+void
+ServerApp::startNode()
+{
+ // start the server. if this return false then we've failed and
+ // we shouldn't retry.
+ LOG((CLOG_DEBUG1 "starting server"));
+ if (!startServer()) {
+ m_bye(kExitFailed);
+ }
+}
diff --git a/src/lib/barrier/ServerApp.h b/src/lib/barrier/ServerApp.h
new file mode 100644
index 0000000..528aa24
--- /dev/null
+++ b/src/lib/barrier/ServerApp.h
@@ -0,0 +1,127 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/ArgsBase.h"
+#include "barrier/App.h"
+#include "base/String.h"
+#include "server/Config.h"
+#include "net/NetworkAddress.h"
+#include "arch/Arch.h"
+#include "arch/IArchMultithread.h"
+#include "barrier/ArgsBase.h"
+#include "base/EventTypes.h"
+
+#include <map>
+
+enum EServerState {
+ kUninitialized,
+ kInitializing,
+ kInitializingToStart,
+ kInitialized,
+ kStarting,
+ kStarted
+};
+
+class Server;
+namespace barrier { class Screen; }
+class ClientListener;
+class EventQueueTimer;
+class ILogOutputter;
+class IEventQueue;
+class ServerArgs;
+
+class ServerApp : public App {
+public:
+ ServerApp(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver);
+ virtual ~ServerApp();
+
+ // Parse server specific command line arguments.
+ void parseArgs(int argc, const char* const* argv);
+
+ // Prints help specific to server.
+ void help();
+
+ // Returns arguments that are common and for server.
+ ServerArgs& args() const { return (ServerArgs&)argsBase(); }
+
+ const char* daemonName() const;
+ const char* daemonInfo() const;
+
+ // TODO: Document these functions.
+ static void reloadSignalHandler(Arch::ESignal, void*);
+
+ void reloadConfig(const Event&, void*);
+ void loadConfig();
+ bool loadConfig(const String& pathname);
+ void forceReconnect(const Event&, void*);
+ void resetServer(const Event&, void*);
+ void handleClientConnected(const Event&, void* vlistener);
+ void handleClientsDisconnected(const Event&, void*);
+ void closeServer(Server* server);
+ void stopRetryTimer();
+ void updateStatus();
+ void updateStatus(const String& msg);
+ void closeClientListener(ClientListener* listen);
+ void stopServer();
+ void closePrimaryClient(PrimaryClient* primaryClient);
+ void closeServerScreen(barrier::Screen* screen);
+ void cleanupServer();
+ bool initServer();
+ void retryHandler(const Event&, void*);
+ barrier::Screen* openServerScreen();
+ barrier::Screen* createScreen();
+ PrimaryClient* openPrimaryClient(const String& name, barrier::Screen* screen);
+ void handleScreenError(const Event&, void*);
+ void handleSuspend(const Event&, void*);
+ void handleResume(const Event&, void*);
+ ClientListener* openClientListener(const NetworkAddress& address);
+ Server* openServer(Config& config, PrimaryClient* primaryClient);
+ void handleNoClients(const Event&, void*);
+ bool startServer();
+ int mainLoop();
+ int runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup);
+ int standardStartup(int argc, char** argv);
+ int foregroundStartup(int argc, char** argv);
+ void startNode();
+
+ static ServerApp& instance() { return (ServerApp&)App::instance(); }
+
+ Server* getServerPtr() { return m_server; }
+
+ Server* m_server;
+ EServerState m_serverState;
+ barrier::Screen* m_serverScreen;
+ PrimaryClient* m_primaryClient;
+ ClientListener* m_listener;
+ EventQueueTimer* m_timer;
+ NetworkAddress* m_barrierAddress;
+
+private:
+ void handleScreenSwitched(const Event&, void* data);
+};
+
+// configuration file name
+#if SYSAPI_WIN32
+#define USR_CONFIG_NAME "barrier.sgc"
+#define SYS_CONFIG_NAME "barrier.sgc"
+#elif SYSAPI_UNIX
+#define USR_CONFIG_NAME ".barrier.conf"
+#define SYS_CONFIG_NAME "barrier.conf"
+#endif
diff --git a/src/lib/barrier/ServerArgs.cpp b/src/lib/barrier/ServerArgs.cpp
new file mode 100644
index 0000000..49832f2
--- /dev/null
+++ b/src/lib/barrier/ServerArgs.cpp
@@ -0,0 +1,25 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ServerArgs.h"
+
+ServerArgs::ServerArgs() :
+ m_configFile(),
+ m_config(NULL)
+{
+}
+
diff --git a/src/lib/barrier/ServerArgs.h b/src/lib/barrier/ServerArgs.h
new file mode 100644
index 0000000..9c6e568
--- /dev/null
+++ b/src/lib/barrier/ServerArgs.h
@@ -0,0 +1,32 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/ArgsBase.h"
+
+class NetworkAddress;
+class Config;
+
+class ServerArgs : public ArgsBase {
+public:
+ ServerArgs();
+
+public:
+ String m_configFile;
+ Config* m_config;
+};
diff --git a/src/lib/barrier/ServerTaskBarReceiver.cpp b/src/lib/barrier/ServerTaskBarReceiver.cpp
new file mode 100644
index 0000000..b427cd1
--- /dev/null
+++ b/src/lib/barrier/ServerTaskBarReceiver.cpp
@@ -0,0 +1,138 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ServerTaskBarReceiver.h"
+#include "server/Server.h"
+#include "mt/Lock.h"
+#include "base/String.h"
+#include "base/IEventQueue.h"
+#include "arch/Arch.h"
+#include "common/Version.h"
+
+//
+// ServerTaskBarReceiver
+//
+
+ServerTaskBarReceiver::ServerTaskBarReceiver(IEventQueue* events) :
+ m_state(kNotRunning),
+ m_events(events)
+{
+ // do nothing
+}
+
+ServerTaskBarReceiver::~ServerTaskBarReceiver()
+{
+ // do nothing
+}
+
+void
+ServerTaskBarReceiver::updateStatus(Server* server, const String& 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);
+}
+
+ServerTaskBarReceiver::EState
+ServerTaskBarReceiver::getStatus() const
+{
+ return m_state;
+}
+
+const String&
+ServerTaskBarReceiver::getErrorMessage() const
+{
+ return m_errorMessage;
+}
+
+const ServerTaskBarReceiver::Clients&
+ServerTaskBarReceiver::getClients() const
+{
+ return m_clients;
+}
+
+void
+ServerTaskBarReceiver::quit()
+{
+ m_events->addEvent(Event(Event::kQuit));
+}
+
+void
+ServerTaskBarReceiver::onStatusChanged(Server*)
+{
+ // do nothing
+}
+
+void
+ServerTaskBarReceiver::lock() const
+{
+ // do nothing
+}
+
+void
+ServerTaskBarReceiver::unlock() const
+{
+ // do nothing
+}
+
+std::string
+ServerTaskBarReceiver::getToolTip() const
+{
+ switch (m_state) {
+ case kNotRunning:
+ return barrier::string::sprintf("%s: Not running", kAppVersion);
+
+ case kNotWorking:
+ return barrier::string::sprintf("%s: %s",
+ kAppVersion, m_errorMessage.c_str());
+
+ case kNotConnected:
+ return barrier::string::sprintf("%s: Waiting for clients", kAppVersion);
+
+ case kConnected:
+ return barrier::string::sprintf("%s: Connected", kAppVersion);
+
+ default:
+ return "";
+ }
+}
diff --git a/src/lib/barrier/ServerTaskBarReceiver.h b/src/lib/barrier/ServerTaskBarReceiver.h
new file mode 100644
index 0000000..3cef9c0
--- /dev/null
+++ b/src/lib/barrier/ServerTaskBarReceiver.h
@@ -0,0 +1,98 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/Server.h"
+#include "barrier/ServerApp.h"
+#include "arch/IArchTaskBarReceiver.h"
+#include "base/EventTypes.h"
+#include "base/String.h"
+#include "base/Event.h"
+#include "common/stdvector.h"
+
+class IEventQueue;
+
+//! Implementation of IArchTaskBarReceiver for the barrier server
+class ServerTaskBarReceiver : public IArchTaskBarReceiver {
+public:
+ ServerTaskBarReceiver(IEventQueue* events);
+ virtual ~ServerTaskBarReceiver();
+
+ //! @name manipulators
+ //@{
+
+ //! Update status
+ /*!
+ Determine the status and query required information from the server.
+ */
+ void updateStatus(Server*, const String& errorMsg);
+
+ void updateStatus(INode* n, const String& errorMsg) { updateStatus((Server*)n, 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<String> Clients;
+ enum EState {
+ kNotRunning,
+ kNotWorking,
+ kNotConnected,
+ kConnected,
+ kMaxState
+ };
+
+ //! Get status
+ EState getStatus() const;
+
+ //! Get error message
+ const String& getErrorMessage() const;
+
+ //! Get connected clients
+ const Clients& 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(Server* server);
+
+private:
+ EState m_state;
+ String m_errorMessage;
+ Clients m_clients;
+ IEventQueue* m_events;
+};
+
+IArchTaskBarReceiver* createTaskBarReceiver(const BufferedLogOutputter* logBuffer, IEventQueue* events);
diff --git a/src/lib/barrier/StreamChunker.cpp b/src/lib/barrier/StreamChunker.cpp
new file mode 100644
index 0000000..8b8971c
--- /dev/null
+++ b/src/lib/barrier/StreamChunker.cpp
@@ -0,0 +1,166 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/StreamChunker.h"
+
+#include "mt/Lock.h"
+#include "mt/Mutex.h"
+#include "barrier/FileChunk.h"
+#include "barrier/ClipboardChunk.h"
+#include "barrier/protocol_types.h"
+#include "base/EventTypes.h"
+#include "base/Event.h"
+#include "base/IEventQueue.h"
+#include "base/EventTypes.h"
+#include "base/Log.h"
+#include "base/Stopwatch.h"
+#include "base/String.h"
+#include "common/stdexcept.h"
+
+#include <fstream>
+
+using namespace std;
+
+static const size_t g_chunkSize = 32 * 1024; //32kb
+
+bool StreamChunker::s_isChunkingFile = false;
+bool StreamChunker::s_interruptFile = false;
+Mutex* StreamChunker::s_interruptMutex = NULL;
+
+void
+StreamChunker::sendFile(
+ char* filename,
+ IEventQueue* events,
+ void* eventTarget)
+{
+ s_isChunkingFile = true;
+
+ std::fstream file(static_cast<char*>(filename), std::ios::in | std::ios::binary);
+
+ if (!file.is_open()) {
+ throw runtime_error("failed to open file");
+ }
+
+ // check file size
+ file.seekg (0, std::ios::end);
+ size_t size = (size_t)file.tellg();
+
+ // send first message (file size)
+ String fileSize = barrier::string::sizeTypeToString(size);
+ FileChunk* sizeMessage = FileChunk::start(fileSize);
+
+ events->addEvent(Event(events->forFile().fileChunkSending(), eventTarget, sizeMessage));
+
+ // send chunk messages with a fixed chunk size
+ size_t sentLength = 0;
+ size_t chunkSize = g_chunkSize;
+ file.seekg (0, std::ios::beg);
+
+ while (true) {
+ if (s_interruptFile) {
+ s_interruptFile = false;
+ LOG((CLOG_DEBUG "file transmission interrupted"));
+ break;
+ }
+
+ events->addEvent(Event(events->forFile().keepAlive(), eventTarget));
+
+ // make sure we don't read too much from the mock data.
+ if (sentLength + chunkSize > size) {
+ chunkSize = size - sentLength;
+ }
+
+ char* chunkData = new char[chunkSize];
+ file.read(chunkData, chunkSize);
+ UInt8* data = reinterpret_cast<UInt8*>(chunkData);
+ FileChunk* fileChunk = FileChunk::data(data, chunkSize);
+ delete[] chunkData;
+
+ events->addEvent(Event(events->forFile().fileChunkSending(), eventTarget, fileChunk));
+
+ sentLength += chunkSize;
+ file.seekg (sentLength, std::ios::beg);
+
+ if (sentLength == size) {
+ break;
+ }
+ }
+
+ // send last message
+ FileChunk* end = FileChunk::end();
+
+ events->addEvent(Event(events->forFile().fileChunkSending(), eventTarget, end));
+
+ file.close();
+
+ s_isChunkingFile = false;
+}
+
+void
+StreamChunker::sendClipboard(
+ String& data,
+ size_t size,
+ ClipboardID id,
+ UInt32 sequence,
+ IEventQueue* events,
+ void* eventTarget)
+{
+ // send first message (data size)
+ String dataSize = barrier::string::sizeTypeToString(size);
+ ClipboardChunk* sizeMessage = ClipboardChunk::start(id, sequence, dataSize);
+
+ events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, sizeMessage));
+
+ // send clipboard chunk with a fixed size
+ size_t sentLength = 0;
+ size_t chunkSize = g_chunkSize;
+
+ while (true) {
+ events->addEvent(Event(events->forFile().keepAlive(), eventTarget));
+
+ // make sure we don't read too much from the mock data.
+ if (sentLength + chunkSize > size) {
+ chunkSize = size - sentLength;
+ }
+
+ String chunk(data.substr(sentLength, chunkSize).c_str(), chunkSize);
+ ClipboardChunk* dataChunk = ClipboardChunk::data(id, sequence, chunk);
+
+ events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, dataChunk));
+
+ sentLength += chunkSize;
+ if (sentLength == size) {
+ break;
+ }
+ }
+
+ // send last message
+ ClipboardChunk* end = ClipboardChunk::end(id, sequence);
+
+ events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, end));
+
+ LOG((CLOG_DEBUG "sent clipboard size=%d", sentLength));
+}
+
+void
+StreamChunker::interruptFile()
+{
+ if (s_isChunkingFile) {
+ s_interruptFile = true;
+ LOG((CLOG_INFO "previous dragged file has become invalid"));
+ }
+}
diff --git a/src/lib/barrier/StreamChunker.h b/src/lib/barrier/StreamChunker.h
new file mode 100644
index 0000000..ab57c7e
--- /dev/null
+++ b/src/lib/barrier/StreamChunker.h
@@ -0,0 +1,45 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/clipboard_types.h"
+#include "base/String.h"
+
+class IEventQueue;
+class Mutex;
+
+class StreamChunker {
+public:
+ static void sendFile(
+ char* filename,
+ IEventQueue* events,
+ void* eventTarget);
+ static void sendClipboard(
+ String& data,
+ size_t size,
+ ClipboardID id,
+ UInt32 sequence,
+ IEventQueue* events,
+ void* eventTarget);
+ static void interruptFile();
+
+private:
+ static bool s_isChunkingFile;
+ static bool s_interruptFile;
+ static Mutex* s_interruptMutex;
+};
diff --git a/src/lib/barrier/ToolApp.cpp b/src/lib/barrier/ToolApp.cpp
new file mode 100644
index 0000000..ae85e6d
--- /dev/null
+++ b/src/lib/barrier/ToolApp.cpp
@@ -0,0 +1,205 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ToolApp.h"
+
+#include "barrier/ArgParser.h"
+#include "arch/Arch.h"
+#include "base/Log.h"
+#include "base/String.h"
+
+#include <iostream>
+#include <sstream>
+
+#if SYSAPI_WIN32
+#include "platform/MSWindowsSession.h"
+#endif
+
+#define JSON_URL "https://symless.com/account/json/"
+
+enum {
+ kErrorOk,
+ kErrorArgs,
+ kErrorException,
+ kErrorUnknown
+};
+
+UInt32
+ToolApp::run(int argc, char** argv)
+{
+ if (argc <= 1) {
+ std::cerr << "no args" << std::endl;
+ return kErrorArgs;
+ }
+
+ try {
+ ArgParser argParser(this);
+ bool result = argParser.parseToolArgs(m_args, argc, argv);
+
+ if (!result) {
+ m_bye(kExitArgs);
+ }
+
+ if (m_args.m_printActiveDesktopName) {
+#if SYSAPI_WIN32
+ MSWindowsSession session;
+ String name = session.getActiveDesktopName();
+ if (name.empty()) {
+ LOG((CLOG_CRIT "failed to get active desktop name"));
+ return kExitFailed;
+ }
+ else {
+ String output = barrier::string::sprintf("activeDesktop:%s", name.c_str());
+ LOG((CLOG_INFO "%s", output.c_str()));
+ }
+#endif
+ }
+ else if (m_args.m_loginAuthenticate) {
+ loginAuth();
+ }
+ else if (m_args.m_getInstalledDir) {
+ std::cout << ARCH->getInstalledDirectory() << std::endl;
+ }
+ else if (m_args.m_getProfileDir) {
+ std::cout << ARCH->getProfileDirectory() << std::endl;
+ }
+ else if (m_args.m_getArch) {
+ std::cout << ARCH->getPlatformName() << std::endl;
+ }
+ else if (m_args.m_notifyUpdate) {
+ notifyUpdate();
+ }
+ else if (m_args.m_notifyActivation) {
+ notifyActivation();
+ }
+ else {
+ throw XBarrier("Nothing to do");
+ }
+ }
+ catch (std::exception& e) {
+ LOG((CLOG_CRIT "An error occurred: %s\n", e.what()));
+ return kExitFailed;
+ }
+ catch (...) {
+ LOG((CLOG_CRIT "An unknown error occurred.\n"));
+ return kExitFailed;
+ }
+
+#if WINAPI_XWINDOWS
+ // HACK: avoid sigsegv on linux
+ m_bye(kErrorOk);
+#endif
+
+ return kErrorOk;
+}
+
+void
+ToolApp::help()
+{
+}
+
+void
+ToolApp::loginAuth()
+{
+ String credentials;
+ std::cin >> credentials;
+
+ std::vector<String> parts = barrier::string::splitString(credentials, ':');
+ size_t count = parts.size();
+
+ if (count == 2 ) {
+ String email = parts[0];
+ String password = parts[1];
+
+ std::stringstream ss;
+ ss << JSON_URL << "auth/";
+ ss << "?email=" << ARCH->internet().urlEncode(email);
+ ss << "&password=" << password;
+
+ std::cout << ARCH->internet().get(ss.str()) << std::endl;
+ }
+ else {
+ throw XBarrier("Invalid credentials.");
+ }
+}
+
+void
+ToolApp::notifyUpdate()
+{
+ String data;
+ std::cin >> data;
+
+ std::vector<String> parts = barrier::string::splitString(data, ':');
+ size_t count = parts.size();
+
+ if (count == 3) {
+ std::stringstream ss;
+ ss << JSON_URL << "notify/update";
+ ss << "?from=" << parts[0];
+ ss << "&to=" << parts[1];
+
+ std::cout << ARCH->internet().get(ss.str()) << std::endl;
+ }
+ else {
+ throw XBarrier("Invalid update data.");
+ }
+}
+
+void
+ToolApp::notifyActivation()
+{
+ String info;
+ std::cin >> info;
+
+ std::vector<String> parts = barrier::string::splitString(info, ':');
+ size_t count = parts.size();
+
+ if (count == 3 || count == 4) {
+ String action = parts[0];
+ String identity = parts[1];
+ String macHash = parts[2];
+ String os;
+
+ if (count == 4) {
+ os = parts[3];
+ }
+ else {
+ os = ARCH->getOSName();
+ }
+
+ std::stringstream ss;
+ ss << JSON_URL << "notify/";
+ ss << "?action=" << action;
+ ss << "&identity=" << ARCH->internet().urlEncode(identity);
+ ss << "&mac=" << ARCH->internet().urlEncode(macHash);
+ ss << "&os=" << ARCH->internet().urlEncode(ARCH->getOSName());
+ ss << "&arch=" << ARCH->internet().urlEncode(ARCH->getPlatformName());
+
+ try {
+ std::cout << ARCH->internet().get(ss.str()) << std::endl;
+ }
+ catch (std::exception& e) {
+ LOG((CLOG_NOTE "An error occurred during notification: %s\n", e.what()));
+ }
+ catch (...) {
+ LOG((CLOG_NOTE "An unknown error occurred during notification.\n"));
+ }
+ }
+ else {
+ LOG((CLOG_NOTE "notification failed"));
+ }
+}
diff --git a/src/lib/barrier/ToolApp.h b/src/lib/barrier/ToolApp.h
new file mode 100644
index 0000000..5cb9a7c
--- /dev/null
+++ b/src/lib/barrier/ToolApp.h
@@ -0,0 +1,37 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/App.h"
+#include "barrier/ToolArgs.h"
+#include "common/basic_types.h"
+
+class ToolApp : public MinimalApp
+{
+public:
+ UInt32 run(int argc, char** argv);
+ void help();
+
+private:
+ void loginAuth();
+ void notifyActivation();
+ void notifyUpdate();
+
+private:
+ ToolArgs m_args;
+};
diff --git a/src/lib/barrier/ToolArgs.cpp b/src/lib/barrier/ToolArgs.cpp
new file mode 100644
index 0000000..634a784
--- /dev/null
+++ b/src/lib/barrier/ToolArgs.cpp
@@ -0,0 +1,29 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/ToolArgs.h"
+
+ToolArgs::ToolArgs() :
+ m_printActiveDesktopName(false),
+ m_loginAuthenticate(false),
+ m_getInstalledDir(false),
+ m_getProfileDir(false),
+ m_getArch(false),
+ m_notifyActivation(false),
+ m_notifyUpdate(false)
+{
+}
diff --git a/src/lib/barrier/ToolArgs.h b/src/lib/barrier/ToolArgs.h
new file mode 100644
index 0000000..36b4be3
--- /dev/null
+++ b/src/lib/barrier/ToolArgs.h
@@ -0,0 +1,34 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+
+class ToolArgs {
+public:
+ ToolArgs();
+
+public:
+ bool m_printActiveDesktopName;
+ bool m_loginAuthenticate;
+ bool m_getInstalledDir;
+ bool m_getProfileDir;
+ bool m_getArch;
+ bool m_notifyActivation;
+ bool m_notifyUpdate;
+};
diff --git a/src/lib/barrier/XBarrier.cpp b/src/lib/barrier/XBarrier.cpp
new file mode 100644
index 0000000..49a015e
--- /dev/null
+++ b/src/lib/barrier/XBarrier.cpp
@@ -0,0 +1,133 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/XBarrier.h"
+#include "base/String.h"
+
+//
+// XBadClient
+//
+
+String
+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;
+}
+
+String
+XIncompatibleClient::getWhat() const throw()
+{
+ return format("XIncompatibleClient", "incompatible client %{1}.%{2}",
+ barrier::string::sprintf("%d", m_major).c_str(),
+ barrier::string::sprintf("%d", m_minor).c_str());
+}
+
+
+//
+// XDuplicateClient
+//
+
+XDuplicateClient::XDuplicateClient(const String& name) :
+ m_name(name)
+{
+ // do nothing
+}
+
+const String&
+XDuplicateClient::getName() const throw()
+{
+ return m_name;
+}
+
+String
+XDuplicateClient::getWhat() const throw()
+{
+ return format("XDuplicateClient", "duplicate client %{1}", m_name.c_str());
+}
+
+
+//
+// XUnknownClient
+//
+
+XUnknownClient::XUnknownClient(const String& name) :
+ m_name(name)
+{
+ // do nothing
+}
+
+const String&
+XUnknownClient::getName() const throw()
+{
+ return m_name;
+}
+
+String
+XUnknownClient::getWhat() const throw()
+{
+ return format("XUnknownClient", "unknown client %{1}", m_name.c_str());
+}
+
+
+//
+// XExitApp
+//
+
+XExitApp::XExitApp(int code) :
+ m_code(code)
+{
+ // do nothing
+}
+
+int
+XExitApp::getCode() const throw()
+{
+ return m_code;
+}
+
+String
+XExitApp::getWhat() const throw()
+{
+ return format(
+ "XExitApp", "exiting with code %{1}",
+ barrier::string::sprintf("%d", m_code).c_str());
+}
diff --git a/src/lib/barrier/XBarrier.h b/src/lib/barrier/XBarrier.h
new file mode 100644
index 0000000..fdf5213
--- /dev/null
+++ b/src/lib/barrier/XBarrier.h
@@ -0,0 +1,135 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/XBase.h"
+
+//! Generic barrier exception
+XBASE_SUBCLASS(XBarrier, XBase);
+
+//! Subscription error
+/*!
+Thrown when there is a problem with the subscription.
+*/
+XBASE_SUBCLASS(XSubscription, XBarrier);
+
+//! Client error exception
+/*!
+Thrown when the client fails to follow the protocol.
+*/
+XBASE_SUBCLASS_WHAT(XBadClient, XBarrier);
+
+//! Incompatible client exception
+/*!
+Thrown when a client attempting to connect has an incompatible version.
+*/
+class XIncompatibleClient : public XBarrier {
+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 String 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 XBarrier {
+public:
+ XDuplicateClient(const String& name);
+ virtual ~XDuplicateClient() _NOEXCEPT { }
+
+ //! @name accessors
+ //@{
+
+ //! Get client's name
+ virtual const String&
+ getName() const throw();
+
+ //@}
+
+protected:
+ virtual String getWhat() const throw();
+
+private:
+ String 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 XBarrier {
+public:
+ XUnknownClient(const String& name);
+ virtual ~XUnknownClient() _NOEXCEPT { }
+
+ //! @name accessors
+ //@{
+
+ //! Get the client's name
+ virtual const String&
+ getName() const throw();
+
+ //@}
+
+protected:
+ virtual String getWhat() const throw();
+
+private:
+ String m_name;
+};
+
+//! Generic exit eception
+/*!
+Thrown when we want to abort, with the opportunity to clean up. This is a
+little bit of a hack, but it's a better way of exiting, than just calling
+exit(int).
+*/
+class XExitApp : public XBarrier {
+public:
+ XExitApp(int code);
+ virtual ~XExitApp() _NOEXCEPT { }
+
+ //! Get the exit code
+ int getCode() const throw();
+
+protected:
+ virtual String getWhat() const throw();
+
+private:
+ int m_code;
+};
diff --git a/src/lib/barrier/XScreen.cpp b/src/lib/barrier/XScreen.cpp
new file mode 100644
index 0000000..a202240
--- /dev/null
+++ b/src/lib/barrier/XScreen.cpp
@@ -0,0 +1,68 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/XScreen.h"
+
+//
+// XScreenOpenFailure
+//
+
+String
+XScreenOpenFailure::getWhat() const throw()
+{
+ return format("XScreenOpenFailure", "unable to open screen");
+}
+
+
+//
+// XScreenXInputFailure
+//
+
+String
+XScreenXInputFailure::getWhat() const throw()
+{
+ return "";
+}
+
+
+//
+// XScreenUnavailable
+//
+
+XScreenUnavailable::XScreenUnavailable(double timeUntilRetry) :
+ m_timeUntilRetry(timeUntilRetry)
+{
+ // do nothing
+}
+
+XScreenUnavailable::~XScreenUnavailable() _NOEXCEPT
+{
+ // do nothing
+}
+
+double
+XScreenUnavailable::getRetryTime() const
+{
+ return m_timeUntilRetry;
+}
+
+String
+XScreenUnavailable::getWhat() const throw()
+{
+ return format("XScreenUnavailable", "unable to open screen");
+}
diff --git a/src/lib/barrier/XScreen.h b/src/lib/barrier/XScreen.h
new file mode 100644
index 0000000..0cb511e
--- /dev/null
+++ b/src/lib/barrier/XScreen.h
@@ -0,0 +1,68 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/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);
+
+//! XInput exception
+/*!
+Thrown when an XInput error occurs
+*/
+XBASE_SUBCLASS_WHAT(XScreenXInputFailure, 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() _NOEXCEPT;
+
+ //! @name manipulators
+ //@{
+
+ //! Get retry time
+ /*!
+ Returns the suggested time to wait until retrying to open the screen.
+ */
+ double getRetryTime() const;
+
+ //@}
+
+protected:
+ virtual String getWhat() const throw();
+
+private:
+ double m_timeUntilRetry;
+};
diff --git a/src/lib/barrier/clipboard_types.h b/src/lib/barrier/clipboard_types.h
new file mode 100644
index 0000000..54f2732
--- /dev/null
+++ b/src/lib/barrier/clipboard_types.h
@@ -0,0 +1,42 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/basic_types.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;
+//@}
diff --git a/src/lib/barrier/key_types.cpp b/src/lib/barrier/key_types.cpp
new file mode 100644
index 0000000..902670d
--- /dev/null
+++ b/src/lib/barrier/key_types.cpp
@@ -0,0 +1,208 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/key_types.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/src/lib/barrier/key_types.h b/src/lib/barrier/key_types.h
new file mode 100644
index 0000000..7a8ea53
--- /dev/null
+++ b/src/lib/barrier/key_types.h
@@ -0,0 +1,314 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/basic_types.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 KeyModifierLevel5Lock = 0x0040;
+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 kKeyModifierBitLevel5Lock = 6;
+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 kKeyModifierIDAltGr = 6;
+static const KeyModifierID kKeyModifierIDLast = 7;
+//@}
+
+//! @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 kKeyKana = 0xEF26; /* Kana */
+static const KeyID kKeyHiraganaKatakana = 0xEF27; /* Hiragana/Katakana toggle */
+static const KeyID kKeyZenkaku = 0xEF2A; /* Zenkaku/Hankaku */
+static const KeyID kKeyKanzi = 0xEF2A; /* Kanzi */
+static const KeyID kKeyHangul = 0xEF31; /* Hangul */
+static const KeyID kKeyHanja = 0xEF34; /* Hanja */
+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;
+static const KeyID kKeyBrightnessDown = 0xE0B8;
+static const KeyID kKeyBrightnessUp = 0xE0B9;
+static const KeyID kKeyMissionControl = 0xE0C0;
+static const KeyID kKeyLaunchpad = 0xE0C1;
+
+//@}
+
+struct KeyNameMapEntry {
+ const char* m_name;
+ KeyID m_id;
+};
+struct KeyModifierNameMapEntry {
+ 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 struct 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 struct KeyModifierNameMapEntry kModifierNameMap[];
diff --git a/src/lib/barrier/mouse_types.h b/src/lib/barrier/mouse_types.h
new file mode 100644
index 0000000..cf860c0
--- /dev/null
+++ b/src/lib/barrier/mouse_types.h
@@ -0,0 +1,41 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/EventTypes.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;
+
+static const ButtonID kMacButtonRight = 2;
+static const ButtonID kMacButtonMiddle = 3;
+//@}
+
+static const UInt8 NumButtonIDs = 5;
diff --git a/src/lib/barrier/option_types.h b/src/lib/barrier/option_types.h
new file mode 100644
index 0000000..6323e37
--- /dev/null
+++ b/src/lib/barrier/option_types.h
@@ -0,0 +1,99 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/EventTypes.h"
+#include "common/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<UInt32> OptionsList;
+
+// macro for packing 4 character strings into 4 byte integers
+#define OPTION_CODE(_s) \
+ (static_cast<UInt32>(static_cast<unsigned char>(_s[0]) << 24) | \
+ static_cast<UInt32>(static_cast<unsigned char>(_s[1]) << 16) | \
+ static_cast<UInt32>(static_cast<unsigned char>(_s[2]) << 8) | \
+ static_cast<UInt32>(static_cast<unsigned char>(_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 kOptionModifierMapForAltGr = OPTION_CODE("MMFG");
+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 kOptionScreenSwitchNeedsShift = OPTION_CODE("SSNS");
+static const OptionID kOptionScreenSwitchNeedsControl = OPTION_CODE("SSNC");
+static const OptionID kOptionScreenSwitchNeedsAlt = OPTION_CODE("SSNA");
+static const OptionID kOptionScreenSaverSync = OPTION_CODE("SSVR");
+static const OptionID kOptionXTestXineramaUnaware = OPTION_CODE("XTXU");
+static const OptionID kOptionScreenPreserveFocus = OPTION_CODE("SFOC");
+static const OptionID kOptionRelativeMouseMoves = OPTION_CODE("MDLT");
+static const OptionID kOptionWin32KeepForeground = OPTION_CODE("_KFW");
+static const OptionID kOptionClipboardSharing = OPTION_CODE("CLPS");
+//@}
+
+//! @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
diff --git a/src/lib/barrier/protocol_types.cpp b/src/lib/barrier/protocol_types.cpp
new file mode 100644
index 0000000..07acf61
--- /dev/null
+++ b/src/lib/barrier/protocol_types.cpp
@@ -0,0 +1,53 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/protocol_types.h"
+
+const char* kMsgHello = "Barrier%2i%2i";
+const char* kMsgHelloBack = "Barrier%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%1i%s";
+const char* kMsgDInfo = "DINF%2i%2i%2i%2i%2i%2i%2i";
+const char* kMsgDSetOptions = "DSOP%4I";
+const char* kMsgDFileTransfer = "DFTR%1i%s";
+const char* kMsgDDragInfo = "DDRG%2i%s";
+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/src/lib/barrier/protocol_types.h b/src/lib/barrier/protocol_types.h
new file mode 100644
index 0000000..bc5e037
--- /dev/null
+++ b/src/lib/barrier/protocol_types.h
@@ -0,0 +1,337 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/EventTypes.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
+// 1.4: adds crypto support
+// 1.5: adds file transfer and removes home brew crypto
+// 1.6: adds clipboard streaming
+// NOTE: with new version, barrier minor version should increment
+static const SInt16 kProtocolMajorVersion = 1;
+static const SInt16 kProtocolMinorVersion = 6;
+
+// 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
+};
+
+// Data transfer constants
+enum EDataTransfer {
+ kDataStart = 1,
+ kDataChunk = 2,
+ kDataEnd = 3
+};
+
+// Data received constants
+enum EDataReceived {
+ kStart,
+ kNotFinish,
+ kFinish,
+ kError
+};
+
+//
+// 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 = mark $4 = 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;
+
+// file data: primary <-> secondary
+// transfer file data. A mark is used in the first byte.
+// 0 means the content followed is the file size.
+// 1 means the content followed is the chunk data.
+// 2 means the file transfer is finished.
+extern const char* kMsgDFileTransfer;
+
+// drag infomation: primary <-> secondary
+// transfer drag infomation. The first 2 bytes are used for storing
+// the number of dragging objects. Then the following string consists
+// of each object's directory.
+extern const char* kMsgDDragInfo;
+
+//
+// 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 ClientInfo {
+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;
+};
diff --git a/src/lib/barrier/unix/AppUtilUnix.cpp b/src/lib/barrier/unix/AppUtilUnix.cpp
new file mode 100644
index 0000000..a1548d8
--- /dev/null
+++ b/src/lib/barrier/unix/AppUtilUnix.cpp
@@ -0,0 +1,46 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/unix/AppUtilUnix.h"
+#include "barrier/ArgsBase.h"
+
+AppUtilUnix::AppUtilUnix(IEventQueue* events)
+{
+}
+
+AppUtilUnix::~AppUtilUnix()
+{
+}
+
+int
+standardStartupStatic(int argc, char** argv)
+{
+ return AppUtil::instance().app().standardStartup(argc, argv);
+}
+
+int
+AppUtilUnix::run(int argc, char** argv)
+{
+ return app().runInner(argc, argv, NULL, &standardStartupStatic);
+}
+
+void
+AppUtilUnix::startNode()
+{
+ app().startNode();
+}
diff --git a/src/lib/barrier/unix/AppUtilUnix.h b/src/lib/barrier/unix/AppUtilUnix.h
new file mode 100644
index 0000000..fefcfea
--- /dev/null
+++ b/src/lib/barrier/unix/AppUtilUnix.h
@@ -0,0 +1,34 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/AppUtil.h"
+
+#define ARCH_APP_UTIL AppUtilUnix
+
+class IEventQueue;
+
+class AppUtilUnix : public AppUtil {
+public:
+ AppUtilUnix(IEventQueue* events);
+ virtual ~AppUtilUnix();
+
+ int run(int argc, char** argv);
+ void startNode();
+};
diff --git a/src/lib/barrier/win32/AppUtilWindows.cpp b/src/lib/barrier/win32/AppUtilWindows.cpp
new file mode 100644
index 0000000..560b029
--- /dev/null
+++ b/src/lib/barrier/win32/AppUtilWindows.cpp
@@ -0,0 +1,185 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/win32/AppUtilWindows.h"
+#include "barrier/Screen.h"
+#include "barrier/ArgsBase.h"
+#include "barrier/App.h"
+#include "barrier/XBarrier.h"
+#include "platform/MSWindowsScreen.h"
+#include "arch/win32/XArchWindows.h"
+#include "arch/win32/ArchMiscWindows.h"
+#include "arch/IArchTaskBarReceiver.h"
+#include "base/Log.h"
+#include "base/log_outputters.h"
+#include "base/IEventQueue.h"
+#include "base/Event.h"
+#include "base/EventQueue.h"
+#include "common/Version.h"
+
+#include <sstream>
+#include <iostream>
+#include <conio.h>
+#include <VersionHelpers.h>
+
+AppUtilWindows::AppUtilWindows(IEventQueue* events) :
+ m_events(events),
+ m_exitMode(kExitModeNormal)
+{
+ if (SetConsoleCtrlHandler((PHANDLER_ROUTINE)consoleHandler, TRUE) == FALSE)
+ {
+ throw XArch(new XArchEvalWindows());
+ }
+}
+
+AppUtilWindows::~AppUtilWindows()
+{
+}
+
+BOOL WINAPI AppUtilWindows::consoleHandler(DWORD)
+{
+ LOG((CLOG_INFO "got shutdown signal"));
+ IEventQueue* events = AppUtil::instance().app().getEvents();
+ events->addEvent(Event(Event::kQuit));
+ return TRUE;
+}
+
+static
+int
+mainLoopStatic()
+{
+ return AppUtil::instance().app().mainLoop();
+}
+
+int
+AppUtilWindows::daemonNTMainLoop(int argc, const char** argv)
+{
+ app().initApp(argc, argv);
+ debugServiceWait();
+
+ // NB: what the hell does this do?!
+ app().argsBase().m_backend = false;
+
+ return ArchMiscWindows::runDaemon(mainLoopStatic);
+}
+
+void
+AppUtilWindows::exitApp(int code)
+{
+ switch (m_exitMode) {
+
+ case kExitModeDaemon:
+ ArchMiscWindows::daemonFailed(code);
+ break;
+
+ default:
+ throw XExitApp(code);
+ }
+}
+
+int daemonNTMainLoopStatic(int argc, const char** argv)
+{
+ return AppUtilWindows::instance().daemonNTMainLoop(argc, argv);
+}
+
+int
+AppUtilWindows::daemonNTStartup(int, char**)
+{
+ SystemLogger sysLogger(app().daemonName(), false);
+ m_exitMode = kExitModeDaemon;
+ return ARCH->daemonize(app().daemonName(), daemonNTMainLoopStatic);
+}
+
+static
+int
+daemonNTStartupStatic(int argc, char** argv)
+{
+ return AppUtilWindows::instance().daemonNTStartup(argc, argv);
+}
+
+static
+int
+foregroundStartupStatic(int argc, char** argv)
+{
+ return AppUtil::instance().app().foregroundStartup(argc, argv);
+}
+
+void
+AppUtilWindows::beforeAppExit()
+{
+ // this can be handy for debugging, since the application is launched in
+ // a new console window, and will normally close on exit (making it so
+ // that we can't see error messages).
+ if (app().argsBase().m_pauseOnExit) {
+ std::cout << std::endl << "press any key to exit..." << std::endl;
+ int c = _getch();
+ }
+}
+
+int
+AppUtilWindows::run(int argc, char** argv)
+{
+ if (!IsWindowsXPSP3OrGreater()) {
+ throw std::runtime_error("Barrier only supports Windows XP SP3 and above.");
+ }
+
+ // record window instance for tray icon, etc
+ ArchMiscWindows::setInstanceWin32(GetModuleHandle(NULL));
+
+ MSWindowsScreen::init(ArchMiscWindows::instanceWin32());
+ Thread::getCurrentThread().setPriority(-14);
+
+ StartupFunc startup;
+ if (ArchMiscWindows::wasLaunchedAsService()) {
+ startup = &daemonNTStartupStatic;
+ } else {
+ startup = &foregroundStartupStatic;
+ app().argsBase().m_daemon = false;
+ }
+
+ return app().runInner(argc, argv, NULL, startup);
+}
+
+AppUtilWindows&
+AppUtilWindows::instance()
+{
+ return (AppUtilWindows&)AppUtil::instance();
+}
+
+void
+AppUtilWindows::debugServiceWait()
+{
+ if (app().argsBase().m_debugServiceWait)
+ {
+ while(true)
+ {
+ // this code is only executed when the process is launched via the
+ // windows service controller (and --debug-service-wait arg is
+ // used). to debug, set a breakpoint on this line so that
+ // execution is delayed until the debugger is attached.
+ ARCH->sleep(1);
+ LOG((CLOG_INFO "waiting for debugger to attach"));
+ }
+ }
+}
+
+void
+AppUtilWindows::startNode()
+{
+ app().startNode();
+}
diff --git a/src/lib/barrier/win32/AppUtilWindows.h b/src/lib/barrier/win32/AppUtilWindows.h
new file mode 100644
index 0000000..c5da228
--- /dev/null
+++ b/src/lib/barrier/win32/AppUtilWindows.h
@@ -0,0 +1,62 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/AppUtil.h"
+#include "base/String.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include "Windows.h"
+
+#define ARCH_APP_UTIL AppUtilWindows
+
+class IEventQueue;
+
+enum AppExitMode {
+ kExitModeNormal,
+ kExitModeDaemon
+};
+
+class AppUtilWindows : public AppUtil {
+public:
+ AppUtilWindows(IEventQueue* events);
+ virtual ~AppUtilWindows();
+
+ int daemonNTStartup(int, char**);
+
+ int daemonNTMainLoop(int argc, const char** argv);
+
+ void debugServiceWait();
+
+ int run(int argc, char** argv);
+
+ void exitApp(int code);
+
+ void beforeAppExit();
+
+ static AppUtilWindows& instance();
+
+ void startNode();
+
+private:
+ AppExitMode m_exitMode;
+ IEventQueue* m_events;
+
+ static BOOL WINAPI consoleHandler(DWORD Event);
+};
diff --git a/src/lib/barrier/win32/DaemonApp.cpp b/src/lib/barrier/win32/DaemonApp.cpp
new file mode 100644
index 0000000..62fecf8
--- /dev/null
+++ b/src/lib/barrier/win32/DaemonApp.cpp
@@ -0,0 +1,361 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "barrier/win32/DaemonApp.h"
+
+#include "barrier/App.h"
+#include "barrier/ArgParser.h"
+#include "barrier/ServerArgs.h"
+#include "barrier/ClientArgs.h"
+#include "ipc/IpcClientProxy.h"
+#include "ipc/IpcMessage.h"
+#include "ipc/IpcLogOutputter.h"
+#include "net/SocketMultiplexer.h"
+#include "arch/XArch.h"
+#include "base/Log.h"
+#include "base/TMethodJob.h"
+#include "base/TMethodEventJob.h"
+#include "base/EventQueue.h"
+#include "base/log_outputters.h"
+#include "base/Log.h"
+
+#include "arch/win32/ArchMiscWindows.h"
+#include "arch/win32/XArchWindows.h"
+#include "barrier/Screen.h"
+#include "platform/MSWindowsScreen.h"
+#include "platform/MSWindowsDebugOutputter.h"
+#include "platform/MSWindowsWatchdog.h"
+#include "platform/MSWindowsEventQueueBuffer.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+#include <string>
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+
+DaemonApp* DaemonApp::s_instance = NULL;
+
+int
+mainLoopStatic()
+{
+ DaemonApp::s_instance->mainLoop(true);
+ return kExitSuccess;
+}
+
+int
+mainLoopStatic(int, const char**)
+{
+ return ArchMiscWindows::runDaemon(mainLoopStatic);
+}
+
+DaemonApp::DaemonApp() :
+ m_ipcServer(nullptr),
+ m_ipcLogOutputter(nullptr),
+ m_watchdog(nullptr),
+ m_events(nullptr),
+ m_fileLogOutputter(nullptr)
+{
+ s_instance = this;
+}
+
+DaemonApp::~DaemonApp()
+{
+}
+
+int
+DaemonApp::run(int argc, char** argv)
+{
+ // win32 instance needed for threading, etc.
+ ArchMiscWindows::setInstanceWin32(GetModuleHandle(NULL));
+
+ Arch arch;
+ arch.init();
+
+ Log log;
+ EventQueue events;
+ m_events = &events;
+
+ bool uninstall = false;
+ try
+ {
+ // sends debug messages to visual studio console window.
+ log.insert(new MSWindowsDebugOutputter());
+
+ // default log level to system setting.
+ string logLevel = arch.setting("LogLevel");
+ if (logLevel != "")
+ log.setFilter(logLevel.c_str());
+
+ bool foreground = false;
+
+ for (int i = 1; i < argc; ++i) {
+ string arg(argv[i]);
+
+ if (arg == "/f" || arg == "-f") {
+ foreground = true;
+ }
+ else if (arg == "/install") {
+ uninstall = true;
+ arch.installDaemon();
+ return kExitSuccess;
+ }
+ else if (arg == "/uninstall") {
+ arch.uninstallDaemon();
+ return kExitSuccess;
+ }
+ else {
+ stringstream ss;
+ ss << "Unrecognized argument: " << arg;
+ foregroundError(ss.str().c_str());
+ return kExitArgs;
+ }
+ }
+
+ if (foreground) {
+ // run process in foreground instead of daemonizing.
+ // useful for debugging.
+ mainLoop(false);
+ }
+ else {
+ arch.daemonize("Barrier", mainLoopStatic);
+ }
+
+ return kExitSuccess;
+ }
+ catch (XArch& e) {
+ String message = e.what();
+ if (uninstall && (message.find("The service has not been started") != String::npos)) {
+ // TODO: if we're keeping this use error code instead (what is it?!).
+ // HACK: this message happens intermittently, not sure where from but
+ // it's quite misleading for the user. they thing something has gone
+ // horribly wrong, but it's just the service manager reporting a false
+ // positive (the service has actually shut down in most cases).
+ }
+ else {
+ foregroundError(message.c_str());
+ }
+ return kExitFailed;
+ }
+ catch (std::exception& e) {
+ foregroundError(e.what());
+ return kExitFailed;
+ }
+ catch (...) {
+ foregroundError("Unrecognized error.");
+ return kExitFailed;
+ }
+}
+
+void
+DaemonApp::mainLoop(bool daemonized)
+{
+ try
+ {
+ DAEMON_RUNNING(true);
+
+ if (daemonized) {
+ m_fileLogOutputter = new FileLogOutputter(logFilename().c_str());
+ CLOG->insert(m_fileLogOutputter);
+ }
+
+ // create socket multiplexer. this must happen after daemonization
+ // on unix because threads evaporate across a fork().
+ SocketMultiplexer multiplexer;
+
+ // uses event queue, must be created here.
+ m_ipcServer = new IpcServer(m_events, &multiplexer);
+
+ // send logging to gui via ipc, log system adopts outputter.
+ m_ipcLogOutputter = new IpcLogOutputter(*m_ipcServer, kIpcClientGui, true);
+ CLOG->insert(m_ipcLogOutputter);
+
+ m_watchdog = new MSWindowsWatchdog(daemonized, false, *m_ipcServer, *m_ipcLogOutputter);
+ m_watchdog->setFileLogOutputter(m_fileLogOutputter);
+
+ m_events->adoptHandler(
+ m_events->forIpcServer().messageReceived(), m_ipcServer,
+ new TMethodEventJob<DaemonApp>(this, &DaemonApp::handleIpcMessage));
+
+ m_ipcServer->listen();
+
+ // install the platform event queue to handle service stop events.
+ m_events->adoptBuffer(new MSWindowsEventQueueBuffer(m_events));
+
+ String command = ARCH->setting("Command");
+ bool elevate = ARCH->setting("Elevate") == "1";
+ if (command != "") {
+ LOG((CLOG_INFO "using last known command: %s", command.c_str()));
+ m_watchdog->setCommand(command, elevate);
+ }
+
+ m_watchdog->startAsync();
+
+ m_events->loop();
+
+ m_watchdog->stop();
+ delete m_watchdog;
+
+ m_events->removeHandler(
+ m_events->forIpcServer().messageReceived(), m_ipcServer);
+
+ CLOG->remove(m_ipcLogOutputter);
+ delete m_ipcLogOutputter;
+ delete m_ipcServer;
+
+ DAEMON_RUNNING(false);
+ }
+ catch (std::exception& e) {
+ LOG((CLOG_CRIT "An error occurred: %s", e.what()));
+ }
+ catch (...) {
+ LOG((CLOG_CRIT "An unknown error occurred.\n"));
+ }
+}
+
+void
+DaemonApp::foregroundError(const char* message)
+{
+ MessageBox(NULL, message, "Barrier Service", MB_OK | MB_ICONERROR);
+}
+
+std::string
+DaemonApp::logFilename()
+{
+ string logFilename;
+ logFilename = ARCH->setting("LogFilename");
+ if (logFilename.empty()) {
+ logFilename = ARCH->getLogDirectory();
+ logFilename.append("/");
+ logFilename.append(LOG_FILENAME);
+ }
+
+ return logFilename;
+}
+
+void
+DaemonApp::handleIpcMessage(const Event& e, void*)
+{
+ IpcMessage* m = static_cast<IpcMessage*>(e.getDataObject());
+ switch (m->type()) {
+ case kIpcCommand: {
+ IpcCommandMessage* cm = static_cast<IpcCommandMessage*>(m);
+ String command = cm->command();
+
+ // if empty quotes, clear.
+ if (command == "\"\"") {
+ command.clear();
+ }
+
+ if (!command.empty()) {
+ LOG((CLOG_DEBUG "new command, elevate=%d command=%s", cm->elevate(), command.c_str()));
+
+ std::vector<String> argsArray;
+ ArgParser::splitCommandString(command, argsArray);
+ ArgParser argParser(NULL);
+ const char** argv = argParser.getArgv(argsArray);
+ ServerArgs serverArgs;
+ ClientArgs clientArgs;
+ int argc = static_cast<int>(argsArray.size());
+ bool server = argsArray[0].find("barriers") != String::npos ? true : false;
+ ArgsBase* argBase = NULL;
+
+ if (server) {
+ argParser.parseServerArgs(serverArgs, argc, argv);
+ argBase = &serverArgs;
+ }
+ else {
+ argParser.parseClientArgs(clientArgs, argc, argv);
+ argBase = &clientArgs;
+ }
+
+ delete[] argv;
+
+ String logLevel(argBase->m_logFilter);
+ if (!logLevel.empty()) {
+ try {
+ // change log level based on that in the command string
+ // and change to that log level now.
+ ARCH->setting("LogLevel", logLevel);
+ CLOG->setFilter(logLevel.c_str());
+ }
+ catch (XArch& e) {
+ LOG((CLOG_ERR "failed to save LogLevel setting, %s", e.what()));
+ }
+ }
+
+ // eg. no log-to-file while running in foreground
+ if (m_fileLogOutputter != nullptr) {
+ String logFilename;
+ if (argBase->m_logFile != NULL) {
+ logFilename = String(argBase->m_logFile);
+ ARCH->setting("LogFilename", logFilename);
+ m_watchdog->setFileLogOutputter(m_fileLogOutputter);
+ command = ArgParser::assembleCommand(argsArray, "--log", 1);
+ LOG((CLOG_DEBUG "removed log file argument and filename %s from command ", logFilename.c_str()));
+ LOG((CLOG_DEBUG "new command, elevate=%d command=%s", cm->elevate(), command.c_str()));
+ } else {
+ m_watchdog->setFileLogOutputter(NULL);
+ }
+ m_fileLogOutputter->setLogFilename(logFilename.c_str());
+ }
+ }
+ else {
+ LOG((CLOG_DEBUG "empty command, elevate=%d", cm->elevate()));
+ }
+
+ try {
+ // store command in system settings. this is used when the daemon
+ // next starts.
+ ARCH->setting("Command", command);
+
+ // TODO: it would be nice to store bools/ints...
+ ARCH->setting("Elevate", String(cm->elevate() ? "1" : "0"));
+ }
+ catch (XArch& e) {
+ LOG((CLOG_ERR "failed to save settings, %s", e.what()));
+ }
+
+ // tell the relauncher about the new command. this causes the
+ // relauncher to stop the existing command and start the new
+ // command.
+ m_watchdog->setCommand(command, cm->elevate());
+
+ break;
+ }
+
+ case kIpcHello:
+ IpcHelloMessage* hm = static_cast<IpcHelloMessage*>(m);
+ String type;
+ switch (hm->clientType()) {
+ case kIpcClientGui: type = "gui"; break;
+ case kIpcClientNode: type = "node"; break;
+ default: type = "unknown"; break;
+ }
+
+ LOG((CLOG_DEBUG "ipc hello, type=%s", type.c_str()));
+
+ const char * serverstatus = m_watchdog->isProcessActive() ? "active" : "not active";
+ LOG((CLOG_INFO "server status: %s", serverstatus));
+
+ m_ipcLogOutputter->notifyBuffer();
+ break;
+ }
+}
diff --git a/src/lib/barrier/win32/DaemonApp.h b/src/lib/barrier/win32/DaemonApp.h
new file mode 100644
index 0000000..2a8484b
--- /dev/null
+++ b/src/lib/barrier/win32/DaemonApp.h
@@ -0,0 +1,58 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/Arch.h"
+#include "ipc/IpcServer.h"
+
+#include <string>
+
+class Event;
+class IpcLogOutputter;
+class FileLogOutputter;
+
+class MSWindowsWatchdog;
+
+class DaemonApp {
+
+public:
+ DaemonApp();
+ virtual ~DaemonApp();
+ int run(int argc, char** argv);
+ void mainLoop(bool daemonized);
+
+private:
+ void daemonize();
+ void foregroundError(const char* message);
+ std::string logFilename();
+ void handleIpcMessage(const Event&, void*);
+
+public:
+ static DaemonApp* s_instance;
+
+ MSWindowsWatchdog* m_watchdog;
+
+private:
+ IpcServer* m_ipcServer;
+ IpcLogOutputter* m_ipcLogOutputter;
+ IEventQueue* m_events;
+ FileLogOutputter* m_fileLogOutputter;
+};
+
+#define LOG_FILENAME "barrierd.log"
diff --git a/src/lib/base/CMakeLists.txt b/src/lib/base/CMakeLists.txt
new file mode 100644
index 0000000..66ba5a6
--- /dev/null
+++ b/src/lib/base/CMakeLists.txt
@@ -0,0 +1,28 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+file(GLOB headers "*.h")
+file(GLOB sources "*.cpp")
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+add_library(base STATIC ${sources})
+
+if (UNIX)
+ target_link_libraries(base common)
+endif()
diff --git a/src/lib/base/ELevel.h b/src/lib/base/ELevel.h
new file mode 100644
index 0000000..ec0f94f
--- /dev/null
+++ b/src/lib/base/ELevel.h
@@ -0,0 +1,38 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2011 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+//! Log levels
+/*!
+The logging priority levels in order of highest to lowest priority.
+*/
+enum ELevel {
+ kPRINT = -1, //!< For print only (no file or time)
+ 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 verbosity +1 debugging messages
+ kDEBUG2, //!< For verbosity +2 debugging messages
+ kDEBUG3, //!< For verbosity +3 debugging messages
+ kDEBUG4, //!< For verbosity +4 debugging messages
+ kDEBUG5 //!< For verbosity +5 debugging messages
+};
diff --git a/src/lib/base/Event.cpp b/src/lib/base/Event.cpp
new file mode 100644
index 0000000..f2c1a12
--- /dev/null
+++ b/src/lib/base/Event.cpp
@@ -0,0 +1,100 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/Event.h"
+#include "base/EventQueue.h"
+
+//
+// Event
+//
+
+Event::Event() :
+ m_type(kUnknown),
+ m_target(NULL),
+ m_data(NULL),
+ m_flags(0),
+ m_dataObject(nullptr)
+{
+ // do nothing
+}
+
+Event::Event(Type type, void* target, void* data, Flags flags) :
+ m_type(type),
+ m_target(target),
+ m_data(data),
+ m_flags(flags),
+ m_dataObject(nullptr)
+{
+ // do nothing
+}
+
+Event::Type
+Event::getType() const
+{
+ return m_type;
+}
+
+void*
+Event::getTarget() const
+{
+ return m_target;
+}
+
+void*
+Event::getData() const
+{
+ return m_data;
+}
+
+EventData*
+Event::getDataObject() const
+{
+ return m_dataObject;
+}
+
+Event::Flags
+Event::getFlags() const
+{
+ return m_flags;
+}
+
+void
+Event::deleteData(const Event& event)
+{
+ switch (event.getType()) {
+ case kUnknown:
+ case kQuit:
+ case kSystem:
+ case kTimer:
+ break;
+
+ default:
+ if ((event.getFlags() & kDontFreeData) == 0) {
+ free(event.getData());
+ delete event.getDataObject();
+ }
+ break;
+ }
+}
+
+void
+Event::setDataObject(EventData* dataObject)
+{
+ assert(m_dataObject == nullptr);
+ m_dataObject = dataObject;
+}
diff --git a/src/lib/base/Event.h b/src/lib/base/Event.h
new file mode 100644
index 0000000..2741813
--- /dev/null
+++ b/src/lib/base/Event.h
@@ -0,0 +1,126 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/basic_types.h"
+#include "common/stdmap.h"
+
+class EventData {
+public:
+ EventData() { }
+ virtual ~EventData() { }
+};
+
+//! Event
+/*!
+A \c Event holds an event type and a pointer to event data.
+*/
+class Event {
+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
+ };
+
+ Event();
+
+ //! Create \c Event with data (POD)
+ /*!
+ 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. For non-POD (normal C++ objects
+ use \c setDataObject().
+ \p target is the intended recipient of the event.
+ \p flags is any combination of \c Flags.
+ */
+ Event(Type type, void* target = NULL, void* data = NULL,
+ Flags flags = kNone);
+
+ //! @name manipulators
+ //@{
+
+ //! Release event data
+ /*!
+ Deletes event data for the given event (using free()).
+ */
+ static void deleteData(const Event&);
+
+ //! Set data (non-POD)
+ /*!
+ Set non-POD (non plain old data), where delete is called when the event
+ is deleted, and the destructor is called.
+ */
+ void setDataObject(EventData* dataObject);
+
+ //@}
+ //! @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 (POD).
+ /*!
+ Returns the event data (POD).
+ */
+ void* getData() const;
+
+ //! Get the event data (non-POD)
+ /*!
+ Returns the event data (non-POD). The difference between this and
+ \c getData() is that when delete is called on this data, so non-POD
+ (non plain old data) dtor is called.
+ */
+ EventData* getDataObject() const;
+
+ //! Get event flags
+ /*!
+ Returns the event flags.
+ */
+ Flags getFlags() const;
+
+ //@}
+
+private:
+ Type m_type;
+ void* m_target;
+ void* m_data;
+ Flags m_flags;
+ EventData* m_dataObject;
+};
diff --git a/src/lib/base/EventQueue.cpp b/src/lib/base/EventQueue.cpp
new file mode 100644
index 0000000..b17e35b
--- /dev/null
+++ b/src/lib/base/EventQueue.cpp
@@ -0,0 +1,658 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/EventQueue.h"
+
+#include "mt/Mutex.h"
+#include "mt/Lock.h"
+#include "arch/Arch.h"
+#include "base/SimpleEventQueueBuffer.h"
+#include "base/Stopwatch.h"
+#include "base/IEventJob.h"
+#include "base/EventTypes.h"
+#include "base/Log.h"
+#include "base/XBase.h"
+#include "../gui/src/ShutdownCh.h"
+
+EVENT_TYPE_ACCESSOR(Client)
+EVENT_TYPE_ACCESSOR(IStream)
+EVENT_TYPE_ACCESSOR(IpcClient)
+EVENT_TYPE_ACCESSOR(IpcClientProxy)
+EVENT_TYPE_ACCESSOR(IpcServer)
+EVENT_TYPE_ACCESSOR(IpcServerProxy)
+EVENT_TYPE_ACCESSOR(IDataSocket)
+EVENT_TYPE_ACCESSOR(IListenSocket)
+EVENT_TYPE_ACCESSOR(ISocket)
+EVENT_TYPE_ACCESSOR(OSXScreen)
+EVENT_TYPE_ACCESSOR(ClientListener)
+EVENT_TYPE_ACCESSOR(ClientProxy)
+EVENT_TYPE_ACCESSOR(ClientProxyUnknown)
+EVENT_TYPE_ACCESSOR(Server)
+EVENT_TYPE_ACCESSOR(ServerApp)
+EVENT_TYPE_ACCESSOR(IKeyState)
+EVENT_TYPE_ACCESSOR(IPrimaryScreen)
+EVENT_TYPE_ACCESSOR(IScreen)
+EVENT_TYPE_ACCESSOR(Clipboard)
+EVENT_TYPE_ACCESSOR(File)
+
+// interrupt handler. this just adds a quit event to the queue.
+static
+void
+interrupt(Arch::ESignal, void* data)
+{
+ EventQueue* events = static_cast<EventQueue*>(data);
+ events->addEvent(Event(Event::kQuit));
+}
+
+
+//
+// EventQueue
+//
+
+EventQueue::EventQueue() :
+ m_systemTarget(0),
+ m_nextType(Event::kLast),
+ m_typesForClient(NULL),
+ m_typesForIStream(NULL),
+ m_typesForIpcClient(NULL),
+ m_typesForIpcClientProxy(NULL),
+ m_typesForIpcServer(NULL),
+ m_typesForIpcServerProxy(NULL),
+ m_typesForIDataSocket(NULL),
+ m_typesForIListenSocket(NULL),
+ m_typesForISocket(NULL),
+ m_typesForOSXScreen(NULL),
+ m_typesForClientListener(NULL),
+ m_typesForClientProxy(NULL),
+ m_typesForClientProxyUnknown(NULL),
+ m_typesForServer(NULL),
+ m_typesForServerApp(NULL),
+ m_typesForIKeyState(NULL),
+ m_typesForIPrimaryScreen(NULL),
+ m_typesForIScreen(NULL),
+ m_typesForClipboard(NULL),
+ m_typesForFile(NULL),
+ m_readyMutex(new Mutex),
+ m_readyCondVar(new CondVar<bool>(m_readyMutex, false))
+{
+ m_mutex = ARCH->newMutex();
+ ARCH->setSignalHandler(Arch::kINTERRUPT, &interrupt, this);
+ ARCH->setSignalHandler(Arch::kTERMINATE, &interrupt, this);
+ m_buffer = new SimpleEventQueueBuffer;
+}
+
+EventQueue::~EventQueue()
+{
+ delete m_buffer;
+ delete m_readyCondVar;
+ delete m_readyMutex;
+
+ ARCH->setSignalHandler(Arch::kINTERRUPT, NULL, NULL);
+ ARCH->setSignalHandler(Arch::kTERMINATE, NULL, NULL);
+ ARCH->closeMutex(m_mutex);
+}
+
+void
+EventQueue::loop()
+{
+ m_buffer->init();
+ {
+ Lock lock(m_readyMutex);
+ *m_readyCondVar = true;
+ m_readyCondVar->signal();
+ }
+ LOG((CLOG_DEBUG "event queue is ready"));
+ while (!m_pending.empty()) {
+ LOG((CLOG_DEBUG "add pending events to buffer"));
+ Event& event = m_pending.front();
+ addEventToBuffer(event);
+ m_pending.pop();
+ }
+
+ Event event;
+ getEvent(event);
+ while (event.getType() != Event::kQuit) {
+ dispatchEvent(event);
+ Event::deleteData(event);
+ getEvent(event);
+ }
+}
+
+Event::Type
+EventQueue::registerTypeOnce(Event::Type& type, const char* name)
+{
+ ArchMutexLock lock(m_mutex);
+ if (type == Event::kUnknown) {
+ m_typeMap.insert(std::make_pair(m_nextType, name));
+ m_nameMap.insert(std::make_pair(name, m_nextType));
+ LOG((CLOG_DEBUG1 "registered event type %s as %d", name, m_nextType));
+ type = m_nextType++;
+ }
+ return type;
+}
+
+const char*
+EventQueue::getTypeName(Event::Type type)
+{
+ switch (type) {
+ case Event::kUnknown:
+ return "nil";
+
+ case Event::kQuit:
+ return "quit";
+
+ case Event::kSystem:
+ return "system";
+
+ case Event::kTimer:
+ return "timer";
+
+ default:
+ TypeMap::const_iterator i = m_typeMap.find(type);
+ if (i == m_typeMap.end()) {
+ return "<unknown>";
+ }
+ else {
+ return i->second;
+ }
+ }
+}
+
+void
+EventQueue::adoptBuffer(IEventQueueBuffer* buffer)
+{
+ ArchMutexLock lock(m_mutex);
+
+ LOG((CLOG_DEBUG "adopting new buffer"));
+
+ if (m_events.size() != 0) {
+ // this can come as a nasty surprise to programmers expecting
+ // their events to be raised, only to have them deleted.
+ LOG((CLOG_DEBUG "discarding %d event(s)", m_events.size()));
+ }
+
+ // discard old buffer and old events
+ delete m_buffer;
+ for (EventTable::iterator i = m_events.begin(); i != m_events.end(); ++i) {
+ Event::deleteData(i->second);
+ }
+ m_events.clear();
+ m_oldEventIDs.clear();
+
+ // use new buffer
+ m_buffer = buffer;
+ if (m_buffer == NULL) {
+ m_buffer = new SimpleEventQueueBuffer;
+ }
+}
+
+bool
+EventQueue::parent_requests_shutdown() const
+{
+ char ch;
+ return m_parentStream.try_read_char(ch) && ch == ShutdownCh;
+}
+
+bool
+EventQueue::getEvent(Event& event, double timeout)
+{
+ Stopwatch timer(true);
+retry:
+ // before handling any events make sure we don't need to shutdown
+ if (parent_requests_shutdown()) {
+ event = Event(Event::kQuit);
+ return false;
+ }
+ // 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:
+ {
+ ArchMutexLock lock(m_mutex);
+ event = removeEvent(dataID);
+ return true;
+ }
+
+ default:
+ assert(0 && "invalid event type");
+ return false;
+ }
+}
+
+bool
+EventQueue::dispatchEvent(const Event& event)
+{
+ void* target = event.getTarget();
+ IEventJob* job = getHandler(event.getType(), target);
+ if (job == NULL) {
+ job = getHandler(Event::kUnknown, target);
+ }
+ if (job != NULL) {
+ job->run(event);
+ return true;
+ }
+ return false;
+}
+
+void
+EventQueue::addEvent(const Event& event)
+{
+ // discard bogus event types
+ switch (event.getType()) {
+ case Event::kUnknown:
+ case Event::kSystem:
+ case Event::kTimer:
+ return;
+
+ default:
+ break;
+ }
+
+ if ((event.getFlags() & Event::kDeliverImmediately) != 0) {
+ dispatchEvent(event);
+ Event::deleteData(event);
+ }
+ else if (!(*m_readyCondVar)) {
+ m_pending.push(event);
+ }
+ else {
+ addEventToBuffer(event);
+ }
+}
+
+void
+EventQueue::addEventToBuffer(const Event& event)
+{
+ ArchMutexLock 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);
+ Event::deleteData(event);
+ }
+}
+
+EventQueueTimer*
+EventQueue::newTimer(double duration, void* target)
+{
+ assert(duration > 0.0);
+
+ EventQueueTimer* timer = m_buffer->newTimer(duration, false);
+ if (target == NULL) {
+ target = timer;
+ }
+ ArchMutexLock 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(Timer(timer, duration,
+ duration + m_time.getTime(), target, false));
+ return timer;
+}
+
+EventQueueTimer*
+EventQueue::newOneShotTimer(double duration, void* target)
+{
+ assert(duration > 0.0);
+
+ EventQueueTimer* timer = m_buffer->newTimer(duration, true);
+ if (target == NULL) {
+ target = timer;
+ }
+ ArchMutexLock 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(Timer(timer, duration,
+ duration + m_time.getTime(), target, true));
+ return timer;
+}
+
+void
+EventQueue::deleteTimer(EventQueueTimer* timer)
+{
+ ArchMutexLock lock(m_mutex);
+ for (TimerQueue::iterator index = m_timerQueue.begin();
+ index != m_timerQueue.end(); ++index) {
+ if (index->getTimer() == timer) {
+ m_timerQueue.erase(index);
+ break;
+ }
+ }
+ Timers::iterator index = m_timers.find(timer);
+ if (index != m_timers.end()) {
+ m_timers.erase(index);
+ }
+ m_buffer->deleteTimer(timer);
+}
+
+void
+EventQueue::adoptHandler(Event::Type type, void* target, IEventJob* handler)
+{
+ ArchMutexLock lock(m_mutex);
+ IEventJob*& job = m_handlers[target][type];
+ delete job;
+ job = handler;
+}
+
+void
+EventQueue::removeHandler(Event::Type type, void* target)
+{
+ IEventJob* handler = NULL;
+ {
+ ArchMutexLock lock(m_mutex);
+ HandlerTable::iterator index = m_handlers.find(target);
+ if (index != m_handlers.end()) {
+ TypeHandlerTable& typeHandlers = index->second;
+ TypeHandlerTable::iterator index2 = typeHandlers.find(type);
+ if (index2 != typeHandlers.end()) {
+ handler = index2->second;
+ typeHandlers.erase(index2);
+ }
+ }
+ }
+ delete handler;
+}
+
+void
+EventQueue::removeHandlers(void* target)
+{
+ std::vector<IEventJob*> handlers;
+ {
+ ArchMutexLock lock(m_mutex);
+ HandlerTable::iterator index = m_handlers.find(target);
+ if (index != m_handlers.end()) {
+ // copy to handlers array and clear table for target
+ TypeHandlerTable& typeHandlers = index->second;
+ for (TypeHandlerTable::iterator index2 = typeHandlers.begin();
+ index2 != typeHandlers.end(); ++index2) {
+ handlers.push_back(index2->second);
+ }
+ typeHandlers.clear();
+ }
+ }
+
+ // delete handlers
+ for (std::vector<IEventJob*>::iterator index = handlers.begin();
+ index != handlers.end(); ++index) {
+ delete *index;
+ }
+}
+
+bool
+EventQueue::isEmpty() const
+{
+ return (m_buffer->isEmpty() && getNextTimerTimeout() != 0.0);
+}
+
+IEventJob*
+EventQueue::getHandler(Event::Type type, void* target) const
+{
+ ArchMutexLock lock(m_mutex);
+ HandlerTable::const_iterator index = m_handlers.find(target);
+ if (index != m_handlers.end()) {
+ const TypeHandlerTable& typeHandlers = index->second;
+ TypeHandlerTable::const_iterator index2 = typeHandlers.find(type);
+ if (index2 != typeHandlers.end()) {
+ return index2->second;
+ }
+ }
+ return NULL;
+}
+
+UInt32
+EventQueue::saveEvent(const Event& 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<UInt32>(m_events.size());
+ }
+
+ // save data
+ m_events[id] = event;
+ return id;
+}
+
+Event
+EventQueue::removeEvent(UInt32 eventID)
+{
+ // look up id
+ EventTable::iterator index = m_events.find(eventID);
+ if (index == m_events.end()) {
+ return Event();
+ }
+
+ // get data
+ Event event = index->second;
+ m_events.erase(index);
+
+ // save old id for reuse
+ m_oldEventIDs.push_back(eventID);
+
+ return event;
+}
+
+bool
+EventQueue::hasTimerExpired(Event& 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 (TimerQueue::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
+ Timer timer = m_timerQueue.top();
+ m_timerQueue.pop();
+
+ // prepare event and reset the timer's clock
+ timer.fillEvent(m_timerEvent);
+ event = Event(Event::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
+EventQueue::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();
+}
+
+Event::Type
+EventQueue::getRegisteredType(const String& name) const
+{
+ NameMap::const_iterator found = m_nameMap.find(name);
+ if (found != m_nameMap.end())
+ return found->second;
+
+ return Event::kUnknown;
+}
+
+void*
+EventQueue::getSystemTarget()
+{
+ // any unique arbitrary pointer will do
+ return &m_systemTarget;
+}
+
+void
+EventQueue::waitForReady() const
+{
+ double timeout = ARCH->time() + 10;
+ Lock lock(m_readyMutex);
+
+ while (!m_readyCondVar->wait()) {
+ if (ARCH->time() > timeout) {
+ throw std::runtime_error("event queue is not ready within 5 sec");
+ }
+ }
+}
+
+//
+// EventQueue::Timer
+//
+
+EventQueue::Timer::Timer(EventQueueTimer* 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);
+}
+
+EventQueue::Timer::~Timer()
+{
+ // do nothing
+}
+
+void
+EventQueue::Timer::reset()
+{
+ m_time = m_timeout;
+}
+
+EventQueue::Timer&
+EventQueue::Timer::operator-=(double dt)
+{
+ m_time -= dt;
+ return *this;
+}
+
+EventQueue::Timer::operator double() const
+{
+ return m_time;
+}
+
+bool
+EventQueue::Timer::isOneShot() const
+{
+ return m_oneShot;
+}
+
+EventQueueTimer*
+EventQueue::Timer::getTimer() const
+{
+ return m_timer;
+}
+
+void*
+EventQueue::Timer::getTarget() const
+{
+ return m_target;
+}
+
+void
+EventQueue::Timer::fillEvent(TimerEvent& event) const
+{
+ event.m_timer = m_timer;
+ event.m_count = 0;
+ if (m_time <= 0.0) {
+ event.m_count = static_cast<UInt32>((m_timeout - m_time) / m_timeout);
+ }
+}
+
+bool
+EventQueue::Timer::operator<(const Timer& t) const
+{
+ return m_time < t.m_time;
+}
diff --git a/src/lib/base/EventQueue.h b/src/lib/base/EventQueue.h
new file mode 100644
index 0000000..97e7fba
--- /dev/null
+++ b/src/lib/base/EventQueue.h
@@ -0,0 +1,200 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mt/CondVar.h"
+#include "arch/IArchMultithread.h"
+#include "base/IEventQueue.h"
+#include "base/Event.h"
+#include "base/PriorityQueue.h"
+#include "base/Stopwatch.h"
+#include "common/stdmap.h"
+#include "common/stdset.h"
+#include "base/NonBlockingStream.h"
+
+#include <queue>
+
+class Mutex;
+
+//! Event queue
+/*!
+An event queue that implements the platform independent parts and
+delegates the platform dependent parts to a subclass.
+*/
+class EventQueue : public IEventQueue {
+public:
+ EventQueue();
+ virtual ~EventQueue();
+
+ // IEventQueue overrides
+ virtual void loop();
+ virtual void adoptBuffer(IEventQueueBuffer*);
+ virtual bool getEvent(Event& event, double timeout = -1.0);
+ virtual bool dispatchEvent(const Event& event);
+ virtual void addEvent(const Event& event);
+ virtual EventQueueTimer*
+ newTimer(double duration, void* target);
+ virtual EventQueueTimer*
+ newOneShotTimer(double duration, void* target);
+ virtual void deleteTimer(EventQueueTimer*);
+ virtual void adoptHandler(Event::Type type,
+ void* target, IEventJob* handler);
+ virtual void removeHandler(Event::Type type, void* target);
+ virtual void removeHandlers(void* target);
+ virtual Event::Type
+ registerTypeOnce(Event::Type& type, const char* name);
+ virtual bool isEmpty() const;
+ virtual IEventJob* getHandler(Event::Type type, void* target) const;
+ virtual const char* getTypeName(Event::Type type);
+ virtual Event::Type
+ getRegisteredType(const String& name) const;
+ void* getSystemTarget();
+ virtual void waitForReady() const;
+
+private:
+ UInt32 saveEvent(const Event& event);
+ Event removeEvent(UInt32 eventID);
+ bool hasTimerExpired(Event& event);
+ double getNextTimerTimeout() const;
+ void addEventToBuffer(const Event& event);
+ bool parent_requests_shutdown() const;
+
+private:
+ class Timer {
+ public:
+ Timer(EventQueueTimer*, double timeout, double initialTime,
+ void* target, bool oneShot);
+ ~Timer();
+
+ void reset();
+
+ Timer& operator-=(double);
+
+ operator double() const;
+
+ bool isOneShot() const;
+ EventQueueTimer*
+ getTimer() const;
+ void* getTarget() const;
+ void fillEvent(TimerEvent&) const;
+
+ bool operator<(const Timer&) const;
+
+ private:
+ EventQueueTimer* m_timer;
+ double m_timeout;
+ void* m_target;
+ bool m_oneShot;
+ double m_time;
+ };
+
+ typedef std::set<EventQueueTimer*> Timers;
+ typedef PriorityQueue<Timer> TimerQueue;
+ typedef std::map<UInt32, Event> EventTable;
+ typedef std::vector<UInt32> EventIDList;
+ typedef std::map<Event::Type, const char*> TypeMap;
+ typedef std::map<String, Event::Type> NameMap;
+ typedef std::map<Event::Type, IEventJob*> TypeHandlerTable;
+ typedef std::map<void*, TypeHandlerTable> HandlerTable;
+
+ int m_systemTarget;
+ ArchMutex m_mutex;
+
+ // registered events
+ Event::Type m_nextType;
+ TypeMap m_typeMap;
+ NameMap m_nameMap;
+
+ // buffer of events
+ IEventQueueBuffer* m_buffer;
+
+ // saved events
+ EventTable m_events;
+ EventIDList m_oldEventIDs;
+
+ // timers
+ Stopwatch m_time;
+ Timers m_timers;
+ TimerQueue m_timerQueue;
+ TimerEvent m_timerEvent;
+
+ // event handlers
+ HandlerTable m_handlers;
+
+public:
+ //
+ // Event type providers.
+ //
+ ClientEvents& forClient();
+ IStreamEvents& forIStream();
+ IpcClientEvents& forIpcClient();
+ IpcClientProxyEvents& forIpcClientProxy();
+ IpcServerEvents& forIpcServer();
+ IpcServerProxyEvents& forIpcServerProxy();
+ IDataSocketEvents& forIDataSocket();
+ IListenSocketEvents& forIListenSocket();
+ ISocketEvents& forISocket();
+ OSXScreenEvents& forOSXScreen();
+ ClientListenerEvents& forClientListener();
+ ClientProxyEvents& forClientProxy();
+ ClientProxyUnknownEvents& forClientProxyUnknown();
+ ServerEvents& forServer();
+ ServerAppEvents& forServerApp();
+ IKeyStateEvents& forIKeyState();
+ IPrimaryScreenEvents& forIPrimaryScreen();
+ IScreenEvents& forIScreen();
+ ClipboardEvents& forClipboard();
+ FileEvents& forFile();
+
+private:
+ ClientEvents* m_typesForClient;
+ IStreamEvents* m_typesForIStream;
+ IpcClientEvents* m_typesForIpcClient;
+ IpcClientProxyEvents* m_typesForIpcClientProxy;
+ IpcServerEvents* m_typesForIpcServer;
+ IpcServerProxyEvents* m_typesForIpcServerProxy;
+ IDataSocketEvents* m_typesForIDataSocket;
+ IListenSocketEvents* m_typesForIListenSocket;
+ ISocketEvents* m_typesForISocket;
+ OSXScreenEvents* m_typesForOSXScreen;
+ ClientListenerEvents* m_typesForClientListener;
+ ClientProxyEvents* m_typesForClientProxy;
+ ClientProxyUnknownEvents* m_typesForClientProxyUnknown;
+ ServerEvents* m_typesForServer;
+ ServerAppEvents* m_typesForServerApp;
+ IKeyStateEvents* m_typesForIKeyState;
+ IPrimaryScreenEvents* m_typesForIPrimaryScreen;
+ IScreenEvents* m_typesForIScreen;
+ ClipboardEvents* m_typesForClipboard;
+ FileEvents* m_typesForFile;
+ Mutex* m_readyMutex;
+ CondVar<bool>* m_readyCondVar;
+ std::queue<Event> m_pending;
+ NonBlockingStream m_parentStream;
+};
+
+#define EVENT_TYPE_ACCESSOR(type_) \
+type_##Events& \
+EventQueue::for##type_() { \
+ if (m_typesFor##type_ == NULL) { \
+ m_typesFor##type_ = new type_##Events(); \
+ m_typesFor##type_->setEvents(dynamic_cast<IEventQueue*>(this)); \
+ } \
+ return *m_typesFor##type_; \
+}
diff --git a/src/lib/base/EventTypes.cpp b/src/lib/base/EventTypes.cpp
new file mode 100644
index 0000000..9a3e46c
--- /dev/null
+++ b/src/lib/base/EventTypes.cpp
@@ -0,0 +1,203 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/EventTypes.h"
+#include "base/IEventQueue.h"
+
+#include <assert.h>
+#include <stddef.h>
+
+EventTypes::EventTypes() :
+ m_events(NULL)
+{
+}
+
+IEventQueue*
+EventTypes::getEvents() const
+{
+ assert(m_events != NULL);
+ return m_events;
+}
+
+void
+EventTypes::setEvents(IEventQueue* events)
+{
+ m_events = events;
+}
+
+//
+// Client
+//
+
+REGISTER_EVENT(Client, connected)
+REGISTER_EVENT(Client, connectionFailed)
+REGISTER_EVENT(Client, disconnected)
+
+//
+// IStream
+//
+
+REGISTER_EVENT(IStream, inputReady)
+REGISTER_EVENT(IStream, outputFlushed)
+REGISTER_EVENT(IStream, outputError)
+REGISTER_EVENT(IStream, inputShutdown)
+REGISTER_EVENT(IStream, outputShutdown)
+
+//
+// IpcClient
+//
+
+REGISTER_EVENT(IpcClient, connected)
+REGISTER_EVENT(IpcClient, messageReceived)
+
+//
+// IpcClientProxy
+//
+
+REGISTER_EVENT(IpcClientProxy, messageReceived)
+REGISTER_EVENT(IpcClientProxy, disconnected)
+
+//
+// IpcServerProxy
+//
+
+REGISTER_EVENT(IpcServerProxy, messageReceived)
+
+//
+// IDataSocket
+//
+
+REGISTER_EVENT(IDataSocket, connected)
+REGISTER_EVENT(IDataSocket, secureConnected)
+REGISTER_EVENT(IDataSocket, connectionFailed)
+
+//
+// IListenSocket
+//
+
+REGISTER_EVENT(IListenSocket, connecting)
+
+//
+// ISocket
+//
+
+REGISTER_EVENT(ISocket, disconnected)
+REGISTER_EVENT(ISocket, stopRetry)
+
+//
+// OSXScreen
+//
+
+REGISTER_EVENT(OSXScreen, confirmSleep)
+
+//
+// ClientListener
+//
+
+REGISTER_EVENT(ClientListener, accepted)
+REGISTER_EVENT(ClientListener, connected)
+
+//
+// ClientProxy
+//
+
+REGISTER_EVENT(ClientProxy, ready)
+REGISTER_EVENT(ClientProxy, disconnected)
+
+//
+// ClientProxyUnknown
+//
+
+REGISTER_EVENT(ClientProxyUnknown, success)
+REGISTER_EVENT(ClientProxyUnknown, failure)
+
+//
+// Server
+//
+
+REGISTER_EVENT(Server, error)
+REGISTER_EVENT(Server, connected)
+REGISTER_EVENT(Server, disconnected)
+REGISTER_EVENT(Server, switchToScreen)
+REGISTER_EVENT(Server, switchInDirection)
+REGISTER_EVENT(Server, keyboardBroadcast)
+REGISTER_EVENT(Server, lockCursorToScreen)
+REGISTER_EVENT(Server, screenSwitched)
+
+//
+// ServerApp
+//
+
+REGISTER_EVENT(ServerApp, reloadConfig)
+REGISTER_EVENT(ServerApp, forceReconnect)
+REGISTER_EVENT(ServerApp, resetServer)
+
+//
+// IKeyState
+//
+
+REGISTER_EVENT(IKeyState, keyDown)
+REGISTER_EVENT(IKeyState, keyUp)
+REGISTER_EVENT(IKeyState, keyRepeat)
+
+//
+// IPrimaryScreen
+//
+
+REGISTER_EVENT(IPrimaryScreen, buttonDown)
+REGISTER_EVENT(IPrimaryScreen, buttonUp)
+REGISTER_EVENT(IPrimaryScreen, motionOnPrimary)
+REGISTER_EVENT(IPrimaryScreen, motionOnSecondary)
+REGISTER_EVENT(IPrimaryScreen, wheel)
+REGISTER_EVENT(IPrimaryScreen, screensaverActivated)
+REGISTER_EVENT(IPrimaryScreen, screensaverDeactivated)
+REGISTER_EVENT(IPrimaryScreen, hotKeyDown)
+REGISTER_EVENT(IPrimaryScreen, hotKeyUp)
+REGISTER_EVENT(IPrimaryScreen, fakeInputBegin)
+REGISTER_EVENT(IPrimaryScreen, fakeInputEnd)
+
+//
+// IScreen
+//
+
+REGISTER_EVENT(IScreen, error)
+REGISTER_EVENT(IScreen, shapeChanged)
+REGISTER_EVENT(IScreen, suspend)
+REGISTER_EVENT(IScreen, resume)
+
+//
+// IpcServer
+//
+
+REGISTER_EVENT(IpcServer, clientConnected)
+REGISTER_EVENT(IpcServer, messageReceived)
+
+//
+// Clipboard
+//
+
+REGISTER_EVENT(Clipboard, clipboardGrabbed)
+REGISTER_EVENT(Clipboard, clipboardChanged)
+REGISTER_EVENT(Clipboard, clipboardSending)
+
+//
+// File
+//
+
+REGISTER_EVENT(File, fileChunkSending)
+REGISTER_EVENT(File, fileRecieveCompleted)
+REGISTER_EVENT(File, keepAlive)
diff --git a/src/lib/base/EventTypes.h b/src/lib/base/EventTypes.h
new file mode 100644
index 0000000..d044c86
--- /dev/null
+++ b/src/lib/base/EventTypes.h
@@ -0,0 +1,754 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/Event.h"
+
+class IEventQueue;
+
+class EventTypes {
+public:
+ EventTypes();
+ void setEvents(IEventQueue* events);
+
+protected:
+ IEventQueue* getEvents() const;
+
+private:
+ IEventQueue* m_events;
+};
+
+#define REGISTER_EVENT(type_, name_) \
+Event::Type \
+type_##Events::name_() \
+{ \
+ return getEvents()->registerTypeOnce(m_##name_, __FUNCTION__); \
+}
+
+class ClientEvents : public EventTypes {
+public:
+ ClientEvents() :
+ m_connected(Event::kUnknown),
+ m_connectionFailed(Event::kUnknown),
+ m_disconnected(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! Get connected event type
+ /*!
+ Returns the connected event type. This is sent when the client has
+ successfully connected to the server.
+ */
+ Event::Type connected();
+
+ //! 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 FailInfo*.
+ */
+ Event::Type connectionFailed();
+
+ //! 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).
+ */
+ Event::Type disconnected();
+
+ //@}
+
+private:
+ Event::Type m_connected;
+ Event::Type m_connectionFailed;
+ Event::Type m_disconnected;
+};
+
+class IStreamEvents : public EventTypes {
+public:
+ IStreamEvents() :
+ m_inputReady(Event::kUnknown),
+ m_outputFlushed(Event::kUnknown),
+ m_outputError(Event::kUnknown),
+ m_inputShutdown(Event::kUnknown),
+ m_outputShutdown(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! Get input ready event type
+ /*!
+ Returns the input ready event type. A stream sends this event
+ when \c read() will return with data.
+ */
+ Event::Type inputReady();
+
+ //! 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.
+ */
+ Event::Type outputFlushed();
+
+ //! Get output error event type
+ /*!
+ Returns the output error event type. A stream sends this event
+ when a write has failed.
+ */
+ Event::Type outputError();
+
+ //! 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.
+ */
+ Event::Type inputShutdown();
+
+ //! 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.
+ */
+ Event::Type outputShutdown();
+
+ //@}
+
+private:
+ Event::Type m_inputReady;
+ Event::Type m_outputFlushed;
+ Event::Type m_outputError;
+ Event::Type m_inputShutdown;
+ Event::Type m_outputShutdown;
+};
+
+class IpcClientEvents : public EventTypes {
+public:
+ IpcClientEvents() :
+ m_connected(Event::kUnknown),
+ m_messageReceived(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! Raised when the socket is connected.
+ Event::Type connected();
+
+ //! Raised when a message is received.
+ Event::Type messageReceived();
+
+ //@}
+
+private:
+ Event::Type m_connected;
+ Event::Type m_messageReceived;
+};
+
+class IpcClientProxyEvents : public EventTypes {
+public:
+ IpcClientProxyEvents() :
+ m_messageReceived(Event::kUnknown),
+ m_disconnected(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! Raised when the server receives a message from a client.
+ Event::Type messageReceived();
+
+ //! Raised when the client disconnects from the server.
+ Event::Type disconnected();
+
+ //@}
+
+private:
+ Event::Type m_messageReceived;
+ Event::Type m_disconnected;
+};
+
+class IpcServerEvents : public EventTypes {
+public:
+ IpcServerEvents() :
+ m_clientConnected(Event::kUnknown),
+ m_messageReceived(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! Raised when we have created the client proxy.
+ Event::Type clientConnected();
+
+ //! Raised when a message is received through a client proxy.
+ Event::Type messageReceived();
+
+ //@}
+
+private:
+ Event::Type m_clientConnected;
+ Event::Type m_messageReceived;
+};
+
+class IpcServerProxyEvents : public EventTypes {
+public:
+ IpcServerProxyEvents() :
+ m_messageReceived(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! Raised when the client receives a message from the server.
+ Event::Type messageReceived();
+
+ //@}
+
+private:
+ Event::Type m_messageReceived;
+};
+
+class IDataSocketEvents : public EventTypes {
+public:
+ IDataSocketEvents() :
+ m_connected(Event::kUnknown),
+ m_secureConnected(Event::kUnknown),
+ m_connectionFailed(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! Get connected event type
+ /*!
+ Returns the socket connected event type. A socket sends this
+ event when a remote connection has been established.
+ */
+ Event::Type connected();
+
+ //! Get secure connected event type
+ /*!
+ Returns the secure socket connected event type. A secure socket sends
+ this event when a remote connection has been established.
+ */
+ Event::Type secureConnected();
+
+ //! 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 ConnectionFailedInfo.
+ */
+ Event::Type connectionFailed();
+
+ //@}
+
+private:
+ Event::Type m_connected;
+ Event::Type m_secureConnected;
+ Event::Type m_connectionFailed;
+};
+
+class IListenSocketEvents : public EventTypes {
+public:
+ IListenSocketEvents() :
+ m_connecting(Event::kUnknown) { }
+
+ //! @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.
+ */
+ Event::Type connecting();
+
+ //@}
+
+private:
+ Event::Type m_connecting;
+};
+
+class ISocketEvents : public EventTypes {
+public:
+ ISocketEvents() :
+ m_disconnected(Event::kUnknown),
+ m_stopRetry(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! 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.
+ */
+ Event::Type disconnected();
+
+ //! Get stop retry event type
+ /*!
+ Returns the stop retry event type. This is sent when the client
+ doesn't want to reconnect after it disconnects from the server.
+ */
+ Event::Type stopRetry();
+
+ //@}
+
+private:
+ Event::Type m_disconnected;
+ Event::Type m_stopRetry;
+};
+
+class OSXScreenEvents : public EventTypes {
+public:
+ OSXScreenEvents() :
+ m_confirmSleep(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ Event::Type confirmSleep();
+
+ //@}
+
+private:
+ Event::Type m_confirmSleep;
+};
+
+class ClientListenerEvents : public EventTypes {
+public:
+ ClientListenerEvents() :
+ m_accepted(Event::kUnknown),
+ m_connected(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! Get accepted event type
+ /*!
+ Returns the accepted event type. This is sent whenever a server
+ accepts a client.
+ */
+ Event::Type accepted();
+
+ //! Get connected event type
+ /*!
+ Returns the connected event type. This is sent whenever a
+ a client connects.
+ */
+ Event::Type connected();
+
+ //@}
+
+private:
+ Event::Type m_accepted;
+ Event::Type m_connected;
+};
+
+class ClientProxyEvents : public EventTypes {
+public:
+ ClientProxyEvents() :
+ m_ready(Event::kUnknown),
+ m_disconnected(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! 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.
+ */
+ Event::Type ready();
+
+ //! Get disconnect event type
+ /*!
+ Returns the disconnect event type. This is sent when the client
+ disconnects or is disconnected. The target is getEventTarget().
+ */
+ Event::Type disconnected();
+
+ //@}
+
+private:
+ Event::Type m_ready;
+ Event::Type m_disconnected;
+};
+
+class ClientProxyUnknownEvents : public EventTypes {
+public:
+ ClientProxyUnknownEvents() :
+ m_success(Event::kUnknown),
+ m_failure(Event::kUnknown) { }
+
+ //! @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.
+ */
+ Event::Type success();
+
+ //! 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.
+ */
+ Event::Type failure();
+
+ //@}
+
+private:
+ Event::Type m_success;
+ Event::Type m_failure;
+};
+
+class ServerEvents : public EventTypes {
+public:
+ ServerEvents() :
+ m_error(Event::kUnknown),
+ m_connected(Event::kUnknown),
+ m_disconnected(Event::kUnknown),
+ m_switchToScreen(Event::kUnknown),
+ m_switchInDirection(Event::kUnknown),
+ m_keyboardBroadcast(Event::kUnknown),
+ m_lockCursorToScreen(Event::kUnknown),
+ m_screenSwitched(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! Get error event type
+ /*!
+ Returns the error event type. This is sent when the server fails
+ for some reason.
+ */
+ Event::Type error();
+
+ //! Get connected event type
+ /*!
+ Returns the connected event type. This is sent when a client screen
+ has connected. The event data is a \c ScreenConnectedInfo* that
+ indicates the connected screen.
+ */
+ Event::Type connected();
+
+ //! Get disconnected event type
+ /*!
+ Returns the disconnected event type. This is sent when all the
+ clients have disconnected.
+ */
+ Event::Type disconnected();
+
+ //! 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 SwitchToScreenInfo*
+ that indicates the target screen.
+ */
+ Event::Type switchToScreen();
+
+ //! 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 SwitchInDirectionInfo*
+ that indicates the target direction.
+ */
+ Event::Type switchInDirection();
+
+ //! 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 KeyboardBroadcastInfo*.
+ */
+ Event::Type keyboardBroadcast();
+
+ //! 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 LockCursorToScreenInfo*.
+ */
+ Event::Type lockCursorToScreen();
+
+ //! Get screen switched event type
+ /*!
+ Returns the screen switched event type. This is raised when the
+ screen has been switched to a client.
+ */
+ Event::Type screenSwitched();
+
+ //@}
+
+private:
+ Event::Type m_error;
+ Event::Type m_connected;
+ Event::Type m_disconnected;
+ Event::Type m_switchToScreen;
+ Event::Type m_switchInDirection;
+ Event::Type m_keyboardBroadcast;
+ Event::Type m_lockCursorToScreen;
+ Event::Type m_screenSwitched;
+};
+
+class ServerAppEvents : public EventTypes {
+public:
+ ServerAppEvents() :
+ m_reloadConfig(Event::kUnknown),
+ m_forceReconnect(Event::kUnknown),
+ m_resetServer(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ Event::Type reloadConfig();
+ Event::Type forceReconnect();
+ Event::Type resetServer();
+
+ //@}
+
+private:
+ Event::Type m_reloadConfig;
+ Event::Type m_forceReconnect;
+ Event::Type m_resetServer;
+};
+
+class IKeyStateEvents : public EventTypes {
+public:
+ IKeyStateEvents() :
+ m_keyDown(Event::kUnknown),
+ m_keyUp(Event::kUnknown),
+ m_keyRepeat(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! Get key down event type. Event data is KeyInfo*, count == 1.
+ Event::Type keyDown();
+
+ //! Get key up event type. Event data is KeyInfo*, count == 1.
+ Event::Type keyUp();
+
+ //! Get key repeat event type. Event data is KeyInfo*.
+ Event::Type keyRepeat();
+
+ //@}
+
+private:
+ Event::Type m_keyDown;
+ Event::Type m_keyUp;
+ Event::Type m_keyRepeat;
+};
+
+class IPrimaryScreenEvents : public EventTypes {
+public:
+ IPrimaryScreenEvents() :
+ m_buttonDown(Event::kUnknown),
+ m_buttonUp(Event::kUnknown),
+ m_motionOnPrimary(Event::kUnknown),
+ m_motionOnSecondary(Event::kUnknown),
+ m_wheel(Event::kUnknown),
+ m_screensaverActivated(Event::kUnknown),
+ m_screensaverDeactivated(Event::kUnknown),
+ m_hotKeyDown(Event::kUnknown),
+ m_hotKeyUp(Event::kUnknown),
+ m_fakeInputBegin(Event::kUnknown),
+ m_fakeInputEnd(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! button down event type. Event data is ButtonInfo*.
+ Event::Type buttonDown();
+
+ //! button up event type. Event data is ButtonInfo*.
+ Event::Type buttonUp();
+
+ //! mouse motion on the primary screen event type
+ /*!
+ Event data is MotionInfo* and the values are an absolute position.
+ */
+ Event::Type motionOnPrimary();
+
+ //! mouse motion on a secondary screen event type
+ /*!
+ Event data is MotionInfo* and the values are motion deltas not
+ absolute coordinates.
+ */
+ Event::Type motionOnSecondary();
+
+ //! mouse wheel event type. Event data is WheelInfo*.
+ Event::Type wheel();
+
+ //! screensaver activated event type
+ Event::Type screensaverActivated();
+
+ //! screensaver deactivated event type
+ Event::Type screensaverDeactivated();
+
+ //! hot key down event type. Event data is HotKeyInfo*.
+ Event::Type hotKeyDown();
+
+ //! hot key up event type. Event data is HotKeyInfo*.
+ Event::Type hotKeyUp();
+
+ //! start of fake input event type
+ Event::Type fakeInputBegin();
+
+ //! end of fake input event type
+ Event::Type fakeInputEnd();
+
+ //@}
+
+private:
+ Event::Type m_buttonDown;
+ Event::Type m_buttonUp;
+ Event::Type m_motionOnPrimary;
+ Event::Type m_motionOnSecondary;
+ Event::Type m_wheel;
+ Event::Type m_screensaverActivated;
+ Event::Type m_screensaverDeactivated;
+ Event::Type m_hotKeyDown;
+ Event::Type m_hotKeyUp;
+ Event::Type m_fakeInputBegin;
+ Event::Type m_fakeInputEnd;
+};
+
+class IScreenEvents : public EventTypes {
+public:
+ IScreenEvents() :
+ m_error(Event::kUnknown),
+ m_shapeChanged(Event::kUnknown),
+ m_suspend(Event::kUnknown),
+ m_resume(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! 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).
+ */
+ Event::Type error();
+
+ //! Get shape changed event type
+ /*!
+ Returns the shape changed event type. This is sent whenever the
+ screen's shape changes.
+ */
+ Event::Type shapeChanged();
+
+ //! 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).
+ */
+ Event::Type suspend();
+
+ //! Get resume event type
+ /*!
+ Returns the resume event type. This is sent whenever the system wakes
+ up or a user session is activated (fast user switching).
+ */
+ Event::Type resume();
+
+ //@}
+
+private:
+ Event::Type m_error;
+ Event::Type m_shapeChanged;
+ Event::Type m_suspend;
+ Event::Type m_resume;
+};
+
+class ClipboardEvents : public EventTypes {
+public:
+ ClipboardEvents() :
+ m_clipboardGrabbed(Event::kUnknown),
+ m_clipboardChanged(Event::kUnknown),
+ m_clipboardSending(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! 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 ClipboardInfo.
+ */
+ Event::Type clipboardGrabbed();
+
+ //! 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::ClipboardInfo.
+ */
+ Event::Type clipboardChanged();
+
+ //! Clipboard sending event type
+ /*!
+ Returns the clipboard sending event type. This is used to send
+ clipboard chunks.
+ */
+ Event::Type clipboardSending();
+
+ //@}
+
+private:
+ Event::Type m_clipboardGrabbed;
+ Event::Type m_clipboardChanged;
+ Event::Type m_clipboardSending;
+};
+
+class FileEvents : public EventTypes {
+public:
+ FileEvents() :
+ m_fileChunkSending(Event::kUnknown),
+ m_fileRecieveCompleted(Event::kUnknown),
+ m_keepAlive(Event::kUnknown) { }
+
+ //! @name accessors
+ //@{
+
+ //! Sending a file chunk
+ Event::Type fileChunkSending();
+
+ //! Completed receiving a file
+ Event::Type fileRecieveCompleted();
+
+ //! Send a keep alive
+ Event::Type keepAlive();
+
+ //@}
+
+private:
+ Event::Type m_fileChunkSending;
+ Event::Type m_fileRecieveCompleted;
+ Event::Type m_keepAlive;
+};
diff --git a/src/lib/base/FunctionEventJob.cpp b/src/lib/base/FunctionEventJob.cpp
new file mode 100644
index 0000000..705e058
--- /dev/null
+++ b/src/lib/base/FunctionEventJob.cpp
@@ -0,0 +1,44 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/FunctionEventJob.h"
+
+//
+// FunctionEventJob
+//
+
+FunctionEventJob::FunctionEventJob(
+ void (*func)(const Event&, void*), void* arg) :
+ m_func(func),
+ m_arg(arg)
+{
+ // do nothing
+}
+
+FunctionEventJob::~FunctionEventJob()
+{
+ // do nothing
+}
+
+void
+FunctionEventJob::run(const Event& event)
+{
+ if (m_func != NULL) {
+ m_func(event, m_arg);
+ }
+}
diff --git a/src/lib/base/FunctionEventJob.h b/src/lib/base/FunctionEventJob.h
new file mode 100644
index 0000000..4b2c2fc
--- /dev/null
+++ b/src/lib/base/FunctionEventJob.h
@@ -0,0 +1,39 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/IEventJob.h"
+
+//! Use a function as an event job
+/*!
+An event job class that invokes a function.
+*/
+class FunctionEventJob : public IEventJob {
+public:
+ //! run() invokes \c func(arg)
+ FunctionEventJob(void (*func)(const Event&, void*), void* arg = NULL);
+ virtual ~FunctionEventJob();
+
+ // IEventJob overrides
+ virtual void run(const Event&);
+
+private:
+ void (*m_func)(const Event&, void*);
+ void* m_arg;
+};
diff --git a/src/lib/base/FunctionJob.cpp b/src/lib/base/FunctionJob.cpp
new file mode 100644
index 0000000..859010e
--- /dev/null
+++ b/src/lib/base/FunctionJob.cpp
@@ -0,0 +1,43 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/FunctionJob.h"
+
+//
+// FunctionJob
+//
+
+FunctionJob::FunctionJob(void (*func)(void*), void* arg) :
+ m_func(func),
+ m_arg(arg)
+{
+ // do nothing
+}
+
+FunctionJob::~FunctionJob()
+{
+ // do nothing
+}
+
+void
+FunctionJob::run()
+{
+ if (m_func != NULL) {
+ m_func(m_arg);
+ }
+}
diff --git a/src/lib/base/FunctionJob.h b/src/lib/base/FunctionJob.h
new file mode 100644
index 0000000..9cdfa9d
--- /dev/null
+++ b/src/lib/base/FunctionJob.h
@@ -0,0 +1,39 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/IJob.h"
+
+//! Use a function as a job
+/*!
+A job class that invokes a function.
+*/
+class FunctionJob : public IJob {
+public:
+ //! run() invokes \c func(arg)
+ FunctionJob(void (*func)(void*), void* arg = NULL);
+ virtual ~FunctionJob();
+
+ // IJob overrides
+ virtual void run();
+
+private:
+ void (*m_func)(void*);
+ void* m_arg;
+};
diff --git a/src/lib/base/IEventJob.h b/src/lib/base/IEventJob.h
new file mode 100644
index 0000000..3e4a420
--- /dev/null
+++ b/src/lib/base/IEventJob.h
@@ -0,0 +1,33 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+
+class Event;
+
+//! 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 Event&) = 0;
+};
diff --git a/src/lib/base/IEventQueue.h b/src/lib/base/IEventQueue.h
new file mode 100644
index 0000000..cd4f0b3
--- /dev/null
+++ b/src/lib/base/IEventQueue.h
@@ -0,0 +1,251 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "base/Event.h"
+#include "base/String.h"
+
+class IEventJob;
+class IEventQueueBuffer;
+
+// Opaque type for timer info. This is defined by subclasses of
+// IEventQueueBuffer.
+class EventQueueTimer;
+
+// Event type registration classes.
+class ClientEvents;
+class IStreamEvents;
+class IpcClientEvents;
+class IpcClientProxyEvents;
+class IpcServerEvents;
+class IpcServerProxyEvents;
+class IDataSocketEvents;
+class IListenSocketEvents;
+class ISocketEvents;
+class OSXScreenEvents;
+class ClientListenerEvents;
+class ClientProxyEvents;
+class ClientProxyUnknownEvents;
+class ServerEvents;
+class ServerAppEvents;
+class IKeyStateEvents;
+class IPrimaryScreenEvents;
+class IScreenEvents;
+class ClipboardEvents;
+class FileEvents;
+
+//! Event queue interface
+/*!
+An event queue provides a queue of Events. 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 TimerEvent {
+ public:
+ EventQueueTimer* m_timer; //!< The timer
+ UInt32 m_count; //!< Number of repeats
+ };
+
+ //! @name manipulators
+ //@{
+
+ //! Loop the event queue until quit
+ /*!
+ Dequeues and dispatches events until the kQuit event is found.
+ */
+ virtual void loop() = 0;
+
+ //! 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(Event& 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 Event& event) = 0;
+
+ //! Add event to queue
+ /*!
+ Adds \p event to the end of the queue.
+ */
+ virtual void addEvent(const Event& 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 TimerEvent. 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 TimerEvent 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 EventQueueTimer*
+ 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 TimerEvent.
+ The c_count member of the \c TimerEvent 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 EventQueueTimer*
+ 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(EventQueueTimer*) = 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(Event::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(Event::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
+ /*!
+ 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 Event::Type
+ registerTypeOnce(Event::Type& type,
+ const char* name) = 0;
+
+ //! Wait for event queue to become ready
+ /*!
+ Blocks on the current thread until the event queue is ready for events to
+ be added.
+ */
+ virtual void waitForReady() const = 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(Event::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(Event::Type type) = 0;
+
+ //! Get an event type by name
+ /*!
+ Returns the registered type for an event for a given name.
+ */
+ virtual Event::Type getRegisteredType(const String& name) const = 0;
+
+ //! Get the system event type target
+ /*!
+ Returns the target to use for dispatching \c Event::kSystem events.
+ */
+ virtual void* getSystemTarget() = 0;
+
+ //@}
+
+ //
+ // Event type providers.
+ //
+
+ virtual ClientEvents& forClient() = 0;
+ virtual IStreamEvents& forIStream() = 0;
+ virtual IpcClientEvents& forIpcClient() = 0;
+ virtual IpcClientProxyEvents& forIpcClientProxy() = 0;
+ virtual IpcServerEvents& forIpcServer() = 0;
+ virtual IpcServerProxyEvents& forIpcServerProxy() = 0;
+ virtual IDataSocketEvents& forIDataSocket() = 0;
+ virtual IListenSocketEvents& forIListenSocket() = 0;
+ virtual ISocketEvents& forISocket() = 0;
+ virtual OSXScreenEvents& forOSXScreen() = 0;
+ virtual ClientListenerEvents& forClientListener() = 0;
+ virtual ClientProxyEvents& forClientProxy() = 0;
+ virtual ClientProxyUnknownEvents& forClientProxyUnknown() = 0;
+ virtual ServerEvents& forServer() = 0;
+ virtual ServerAppEvents& forServerApp() = 0;
+ virtual IKeyStateEvents& forIKeyState() = 0;
+ virtual IPrimaryScreenEvents& forIPrimaryScreen() = 0;
+ virtual IScreenEvents& forIScreen() = 0;
+ virtual ClipboardEvents& forClipboard() = 0;
+ virtual FileEvents& forFile() = 0;
+};
diff --git a/src/lib/base/IEventQueueBuffer.h b/src/lib/base/IEventQueueBuffer.h
new file mode 100644
index 0000000..b594436
--- /dev/null
+++ b/src/lib/base/IEventQueueBuffer.h
@@ -0,0 +1,101 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "common/basic_types.h"
+
+class Event;
+class EventQueueTimer;
+
+//! 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
+ //@{
+
+ //! Initialize
+ /*!
+ Useful for platform-specific initialisation from a specific thread.
+ */
+ virtual void init() = 0;
+
+ //! 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 Event::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(Event& 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 EventQueueTimer*
+ newTimer(double duration, bool oneShot) const = 0;
+
+ //! Destroy a timer object
+ /*!
+ Destroy a timer object previously returned by \c newTimer().
+ */
+ virtual void deleteTimer(EventQueueTimer*) const = 0;
+
+ //@}
+};
diff --git a/src/lib/base/IJob.h b/src/lib/base/IJob.h
new file mode 100644
index 0000000..f966ec0
--- /dev/null
+++ b/src/lib/base/IJob.h
@@ -0,0 +1,31 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/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;
+};
diff --git a/src/lib/base/ILogOutputter.h b/src/lib/base/ILogOutputter.h
new file mode 100644
index 0000000..ab218fc
--- /dev/null
+++ b/src/lib/base/ILogOutputter.h
@@ -0,0 +1,69 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/Log.h"
+#include "base/ELevel.h"
+#include "common/IInterface.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:
+ //! @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 Log 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;
+
+ //@}
+};
diff --git a/src/lib/base/Log.cpp b/src/lib/base/Log.cpp
new file mode 100644
index 0000000..823bf6d
--- /dev/null
+++ b/src/lib/base/Log.cpp
@@ -0,0 +1,309 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+#include "base/Log.h"
+#include "base/String.h"
+#include "base/log_outputters.h"
+#include "common/Version.h"
+
+#include <cstdio>
+#include <cstring>
+#include <iostream>
+#include <ctime>
+
+// names of priorities
+static const char* g_priority[] = {
+ "FATAL",
+ "ERROR",
+ "WARNING",
+ "NOTE",
+ "INFO",
+ "DEBUG",
+ "DEBUG1",
+ "DEBUG2",
+ "DEBUG3",
+ "DEBUG4",
+ "DEBUG5"
+};
+
+// number of priorities
+static const int g_numPriority = (int)(sizeof(g_priority) / sizeof(g_priority[0]));
+
+// the default priority
+#ifndef NDEBUG
+static const int g_defaultMaxPriority = kDEBUG;
+#else
+static const int g_defaultMaxPriority = kINFO;
+#endif
+
+//
+// Log
+//
+
+Log* Log::s_log = NULL;
+
+Log::Log()
+{
+ 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 ConsoleLogOutputter);
+
+ s_log = this;
+}
+
+Log::Log(Log* src)
+{
+ s_log = src;
+}
+
+Log::~Log()
+{
+ // clean up
+ for (OutputterList::iterator index = m_outputters.begin();
+ index != m_outputters.end(); ++index) {
+ delete *index;
+ }
+ for (OutputterList::iterator index = m_alwaysOutputters.begin();
+ index != m_alwaysOutputters.end(); ++index) {
+ delete *index;
+ }
+ ARCH->closeMutex(m_mutex);
+}
+
+Log*
+Log::getInstance()
+{
+ assert(s_log != NULL);
+ return s_log;
+}
+
+const char*
+Log::getFilterName() const
+{
+ return getFilterName(getFilter());
+}
+
+const char*
+Log::getFilterName(int level) const
+{
+ if (level < 0) {
+ return "Message";
+ }
+ return g_priority[level];
+}
+
+void
+Log::print(const char* file, int line, const char* fmt, ...)
+{
+ // check if fmt begins with a priority argument
+ ELevel priority = kINFO;
+ if ((strlen(fmt) > 2) && (fmt[0] == '%' && fmt[1] == 'z')) {
+
+ // 060 in octal is 0 (48 in decimal), so subtracting this converts ascii
+ // number it a true number. we could use atoi instead, but this is how
+ // it was done originally.
+ priority = (ELevel)(fmt[2] - '\060');
+
+ // move the pointer on past the debug priority char
+ fmt += 3;
+ }
+
+ // done if below priority threshold
+ if (priority > getFilter()) {
+ return;
+ }
+
+ // compute prefix padding length
+ char stack[1024];
+
+ // compute suffix padding length
+ int sPad = m_maxNewlineLength;
+
+ // print to buffer, leaving space for a newline at the end and prefix
+ // at the beginning.
+ char* buffer = stack;
+ int len = (int)(sizeof(stack) / sizeof(stack[0]));
+ while (true) {
+ // try printing into the buffer
+ va_list args;
+ va_start(args, fmt);
+ int n = ARCH->vsnprintf(buffer, len - sPad, fmt, args);
+ va_end(args);
+
+ // if the buffer wasn't big enough then make it bigger and try again
+ if (n < 0 || n > (int)len) {
+ if (buffer != stack) {
+ delete[] buffer;
+ }
+ len *= 2;
+ buffer = new char[len];
+ }
+
+ // if the buffer was big enough then continue
+ else {
+ break;
+ }
+ }
+
+ // print the prefix to the buffer. leave space for priority label.
+ // do not prefix time and file for kPRINT (CLOG_PRINT)
+ if (priority != kPRINT) {
+
+ struct tm *tm;
+ char timestamp[50];
+ time_t t;
+ time(&t);
+ tm = localtime(&t);
+ sprintf(timestamp, "%04i-%02i-%02iT%02i:%02i:%02i", tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
+
+ // square brackets, spaces, comma and null terminator take about 10
+ size_t size = 10;
+ size += strlen(timestamp);
+ size += strlen(g_priority[priority]);
+ size += strlen(buffer);
+#ifndef NDEBUG
+ size += strlen(file);
+ // assume there is no file contains over 100k lines of code
+ size += 6;
+#endif
+ char* message = new char[size];
+
+#ifndef NDEBUG
+ sprintf(message, "[%s] %s: %s\n\t%s,%d", timestamp, g_priority[priority], buffer, file, line);
+#else
+ sprintf(message, "[%s] %s: %s", timestamp, g_priority[priority], buffer);
+#endif
+
+ output(priority, message);
+ delete[] message;
+ } else {
+ output(priority, buffer);
+ }
+
+ // clean up
+ if (buffer != stack) {
+ delete[] buffer;
+ }
+}
+
+void
+Log::insert(ILogOutputter* outputter, bool alwaysAtHead)
+{
+ assert(outputter != NULL);
+
+ ArchMutexLock lock(m_mutex);
+ if (alwaysAtHead) {
+ m_alwaysOutputters.push_front(outputter);
+ }
+ else {
+ m_outputters.push_front(outputter);
+ }
+
+ outputter->open(kAppVersion);
+
+ // Issue 41
+ // don't show log unless user requests it, as some users find this
+ // feature irritating (i.e. when they lose network connectivity).
+ // in windows the log window can be displayed by selecting "show log"
+ // from the barrier system tray icon.
+ // if this causes problems for other architectures, then a different
+ // work around should be attempted.
+ //outputter->show(false);
+}
+
+void
+Log::remove(ILogOutputter* outputter)
+{
+ ArchMutexLock lock(m_mutex);
+ m_outputters.remove(outputter);
+ m_alwaysOutputters.remove(outputter);
+}
+
+void
+Log::pop_front(bool alwaysAtHead)
+{
+ ArchMutexLock lock(m_mutex);
+ OutputterList* list = alwaysAtHead ? &m_alwaysOutputters : &m_outputters;
+ if (!list->empty()) {
+ delete list->front();
+ list->pop_front();
+ }
+}
+
+bool
+Log::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
+Log::setFilter(int maxPriority)
+{
+ ArchMutexLock lock(m_mutex);
+ m_maxPriority = maxPriority;
+}
+
+int
+Log::getFilter() const
+{
+ ArchMutexLock lock(m_mutex);
+ return m_maxPriority;
+}
+
+void
+Log::output(ELevel priority, char* msg)
+{
+ assert(priority >= -1 && priority < g_numPriority);
+ assert(msg != NULL);
+ if (!msg) return;
+
+ ArchMutexLock lock(m_mutex);
+
+ OutputterList::const_iterator i;
+
+ for (i = m_alwaysOutputters.begin(); i != m_alwaysOutputters.end(); ++i) {
+
+ // write to outputter
+ (*i)->write(priority, msg);
+ }
+
+ for (i = m_outputters.begin(); i != m_outputters.end(); ++i) {
+
+ // write to outputter and break out of loop if it returns false
+ if (!(*i)->write(priority, msg)) {
+ break;
+ }
+ }
+}
diff --git a/src/lib/base/Log.h b/src/lib/base/Log.h
new file mode 100644
index 0000000..1d09be2
--- /dev/null
+++ b/src/lib/base/Log.h
@@ -0,0 +1,211 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchMultithread.h"
+#include "arch/Arch.h"
+#include "common/common.h"
+#include "common/stdlist.h"
+
+#include <stdarg.h>
+
+#define CLOG (Log::getInstance())
+#define BYE "\nTry `%s --help' for more information."
+
+class ILogOutputter;
+class Thread;
+
+//! 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 Log {
+public:
+ Log();
+ Log(Log* src);
+ ~Log();
+
+ //! @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);
+
+ //! Set the minimum priority filter (by ordinal).
+ 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, ...);
+
+ //! Get the minimum priority level.
+ int getFilter() const;
+
+ //! Get the filter name of the current filter level.
+ const char* getFilterName() const;
+
+ //! Get the filter name of a specified filter level.
+ const char* getFilterName(int level) const;
+
+ //! Get the singleton instance of the log
+ static Log* getInstance();
+
+ //! Get the console filter level (messages above this are not sent to console).
+ int getConsoleMaxLevel() const { return kDEBUG2; }
+
+ //@}
+
+private:
+ void output(ELevel priority, char* msg);
+
+private:
+ typedef std::list<ILogOutputter*> OutputterList;
+
+ static Log* s_log;
+
+ ArchMutex m_mutex;
+ OutputterList m_outputters;
+ OutputterList 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 Log::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 Log::print. Otherwise it expands to a call to Log::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 Log::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 Log::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
+
+// the CLOG_* defines are line and file plus %z and an octal number (060=0,
+// 071=9), but the limitation is that once we run out of numbers at either
+// end, then we resort to using non-numerical chars. this still works (since
+// to deduce the number we subtract octal \060, so '/' is -1, and ':' is 10
+
+#define CLOG_PRINT CLOG_TRACE "%z\057" // char is '/'
+#define CLOG_CRIT CLOG_TRACE "%z\060" // char is '0'
+#define CLOG_ERR CLOG_TRACE "%z\061"
+#define CLOG_WARN CLOG_TRACE "%z\062"
+#define CLOG_NOTE CLOG_TRACE "%z\063"
+#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"
+#define CLOG_DEBUG3 CLOG_TRACE "%z\070"
+#define CLOG_DEBUG4 CLOG_TRACE "%z\071" // char is '9'
+#define CLOG_DEBUG5 CLOG_TRACE "%z\072" // char is ':'
diff --git a/src/lib/base/NonBlockingStream.cpp b/src/lib/base/NonBlockingStream.cpp
new file mode 100644
index 0000000..d44add1
--- /dev/null
+++ b/src/lib/base/NonBlockingStream.cpp
@@ -0,0 +1,60 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2008 Debauchee Open Source Group
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(_WIN32)
+
+#include "base/NonBlockingStream.h"
+
+#include <unistd.h> // tcgetattr/tcsetattr, read
+#include <termios.h> // tcgetattr/tcsetattr
+#include <fcntl.h>
+#include <errno.h>
+#include <assert.h>
+
+NonBlockingStream::NonBlockingStream(int fd) :
+ _fd(fd)
+{
+ // disable ICANON & ECHO so we don't have to wait for a newline
+ // before we get data (and to keep it from being echoed back out)
+ termios ta;
+ tcgetattr(fd, &ta);
+ _p_ta_previous = new termios(ta);
+ ta.c_lflag &= ~(ICANON | ECHO);
+ tcsetattr(fd, TCSANOW, &ta);
+
+ // prevent IO from blocking so we can poll (read())
+ int _cntl_previous = fcntl(fd, F_GETFL);
+ fcntl(fd, F_SETFL, _cntl_previous | O_NONBLOCK);
+}
+
+NonBlockingStream::~NonBlockingStream()
+{
+ tcsetattr(_fd, TCSANOW, _p_ta_previous);
+ fcntl(_fd, F_SETFL, _cntl_previous);
+ delete _p_ta_previous;
+}
+
+bool NonBlockingStream::try_read_char(char &ch) const
+{
+ int result = read(_fd, &ch, 1);
+ if (result == 1)
+ return true;
+ assert(result == -1 && (errno == EAGAIN || errno == EWOULDBLOCK));
+ return false;
+}
+
+#endif // !defined(_WIN32)
diff --git a/src/lib/base/NonBlockingStream.h b/src/lib/base/NonBlockingStream.h
new file mode 100644
index 0000000..4c27762
--- /dev/null
+++ b/src/lib/base/NonBlockingStream.h
@@ -0,0 +1,49 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2008 Debauchee Open Source Group
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+// windows doesn't have a unistd.h so this class won't work as-written.
+// at the moment barrier doesn't need this functionality on windows so
+// it's left as a stub to be optimized out
+#if defined(_WIN32)
+
+class NonBlockingStream
+{
+public:
+ bool try_read_char(char &ch) const { return false; };
+};
+
+#else // non-windows platforms
+
+struct termios;
+
+class NonBlockingStream
+{
+public:
+ explicit NonBlockingStream(int fd = 0);
+ ~NonBlockingStream();
+
+ bool try_read_char(char &ch) const;
+
+private:
+ int _fd;
+ termios * _p_ta_previous;
+ int _cntl_previous;
+};
+
+#endif
diff --git a/src/lib/base/PriorityQueue.h b/src/lib/base/PriorityQueue.h
new file mode 100644
index 0000000..d2ca70e
--- /dev/null
+++ b/src/lib/base/PriorityQueue.h
@@ -0,0 +1,138 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/stdvector.h"
+
+#include <algorithm>
+#include <functional>
+
+//! 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 <class T, class Container = std::vector<T>,
+#if defined(_MSC_VER)
+ class Compare = std::greater<Container::value_type> >
+#else
+ class Compare = std::greater<typename Container::value_type> >
+#endif
+class PriorityQueue {
+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;
+
+ PriorityQueue() { }
+ PriorityQueue(Container& swappedIn) { swap(swappedIn); }
+ ~PriorityQueue() { }
+
+ //! @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(PriorityQueue<T, Container, Compare>& 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;
+};
diff --git a/src/lib/base/SimpleEventQueueBuffer.cpp b/src/lib/base/SimpleEventQueueBuffer.cpp
new file mode 100644
index 0000000..b55fe55
--- /dev/null
+++ b/src/lib/base/SimpleEventQueueBuffer.cpp
@@ -0,0 +1,101 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/SimpleEventQueueBuffer.h"
+#include "base/Stopwatch.h"
+#include "arch/Arch.h"
+
+class EventQueueTimer { };
+
+//
+// SimpleEventQueueBuffer
+//
+
+SimpleEventQueueBuffer::SimpleEventQueueBuffer()
+{
+ m_queueMutex = ARCH->newMutex();
+ m_queueReadyCond = ARCH->newCondVar();
+ m_queueReady = false;
+}
+
+SimpleEventQueueBuffer::~SimpleEventQueueBuffer()
+{
+ ARCH->closeCondVar(m_queueReadyCond);
+ ARCH->closeMutex(m_queueMutex);
+}
+
+void
+SimpleEventQueueBuffer::waitForEvent(double timeout)
+{
+ ArchMutexLock lock(m_queueMutex);
+ Stopwatch 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
+SimpleEventQueueBuffer::getEvent(Event&, UInt32& dataID)
+{
+ ArchMutexLock lock(m_queueMutex);
+ if (!m_queueReady) {
+ return kNone;
+ }
+ dataID = m_queue.back();
+ m_queue.pop_back();
+ m_queueReady = !m_queue.empty();
+ return kUser;
+}
+
+bool
+SimpleEventQueueBuffer::addEvent(UInt32 dataID)
+{
+ ArchMutexLock lock(m_queueMutex);
+ m_queue.push_front(dataID);
+ if (!m_queueReady) {
+ m_queueReady = true;
+ ARCH->broadcastCondVar(m_queueReadyCond);
+ }
+ return true;
+}
+
+bool
+SimpleEventQueueBuffer::isEmpty() const
+{
+ ArchMutexLock lock(m_queueMutex);
+ return !m_queueReady;
+}
+
+EventQueueTimer*
+SimpleEventQueueBuffer::newTimer(double, bool) const
+{
+ return new EventQueueTimer;
+}
+
+void
+SimpleEventQueueBuffer::deleteTimer(EventQueueTimer* timer) const
+{
+ delete timer;
+}
diff --git a/src/lib/base/SimpleEventQueueBuffer.h b/src/lib/base/SimpleEventQueueBuffer.h
new file mode 100644
index 0000000..4aa76d3
--- /dev/null
+++ b/src/lib/base/SimpleEventQueueBuffer.h
@@ -0,0 +1,52 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/IEventQueueBuffer.h"
+#include "arch/IArchMultithread.h"
+#include "common/stddeque.h"
+
+//! In-memory event queue buffer
+/*!
+An event queue buffer provides a queue of events for an IEventQueue.
+*/
+class SimpleEventQueueBuffer : public IEventQueueBuffer {
+public:
+ SimpleEventQueueBuffer();
+ ~SimpleEventQueueBuffer();
+
+ // IEventQueueBuffer overrides
+ void init() { }
+ virtual void waitForEvent(double timeout);
+ virtual Type getEvent(Event& event, UInt32& dataID);
+ virtual bool addEvent(UInt32 dataID);
+ virtual bool isEmpty() const;
+ virtual EventQueueTimer*
+ newTimer(double duration, bool oneShot) const;
+ virtual void deleteTimer(EventQueueTimer*) const;
+
+private:
+ typedef std::deque<UInt32> EventDeque;
+
+ ArchMutex m_queueMutex;
+ ArchCond m_queueReadyCond;
+ bool m_queueReady;
+ EventDeque m_queue;
+};
+
diff --git a/src/lib/base/Stopwatch.cpp b/src/lib/base/Stopwatch.cpp
new file mode 100644
index 0000000..b9ceb85
--- /dev/null
+++ b/src/lib/base/Stopwatch.cpp
@@ -0,0 +1,130 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/Stopwatch.h"
+#include "arch/Arch.h"
+
+//
+// Stopwatch
+//
+
+Stopwatch::Stopwatch(bool triggered) :
+ m_mark(0.0),
+ m_triggered(triggered),
+ m_stopped(triggered)
+{
+ if (!triggered) {
+ m_mark = ARCH->time();
+ }
+}
+
+Stopwatch::~Stopwatch()
+{
+ // do nothing
+}
+
+double
+Stopwatch::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
+Stopwatch::stop()
+{
+ if (m_stopped) {
+ return;
+ }
+
+ // save the elapsed time
+ m_mark = ARCH->time() - m_mark;
+ m_stopped = true;
+}
+
+void
+Stopwatch::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
+Stopwatch::setTrigger()
+{
+ stop();
+ m_triggered = true;
+}
+
+double
+Stopwatch::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;
+ }
+}
+
+Stopwatch::operator double()
+{
+ return getTime();
+}
+
+bool
+Stopwatch::isStopped() const
+{
+ return m_stopped;
+}
+
+double
+Stopwatch::getTime() const
+{
+ if (m_stopped) {
+ return m_mark;
+ }
+ else {
+ return ARCH->time() - m_mark;
+ }
+}
+
+Stopwatch::operator double() const
+{
+ return getTime();
+}
diff --git a/src/lib/base/Stopwatch.h b/src/lib/base/Stopwatch.h
new file mode 100644
index 0000000..dda74ea
--- /dev/null
+++ b/src/lib/base/Stopwatch.h
@@ -0,0 +1,109 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/common.h"
+
+//! A timer class
+/*!
+This class measures time intervals. All time interval measurement
+should use this class.
+*/
+class Stopwatch {
+public:
+ /*!
+ The default constructor does an implicit reset() or setTrigger().
+ If triggered == false then the clock starts ticking.
+ */
+ Stopwatch(bool triggered = false);
+ ~Stopwatch();
+
+ //! @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;
+};
diff --git a/src/lib/base/String.cpp b/src/lib/base/String.cpp
new file mode 100644
index 0000000..97b8997
--- /dev/null
+++ b/src/lib/base/String.cpp
@@ -0,0 +1,295 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/Arch.h"
+#include "base/String.h"
+#include "common/common.h"
+#include "common/stdvector.h"
+
+#include <cctype>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <algorithm>
+#include <stdio.h>
+#include <cstdarg>
+#include <sstream>
+#include <iomanip>
+#include <algorithm>
+#include <cerrno>
+
+namespace barrier {
+namespace string {
+
+String
+format(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ String result = vformat(fmt, args);
+ va_end(args);
+ return result;
+}
+
+String
+vformat(const char* fmt, va_list args)
+{
+ // find highest indexed substitution and the locations of substitutions
+ std::vector<size_t> pos;
+ std::vector<size_t> width;
+ std::vector<size_t> index;
+ size_t 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<size_t>((scan - 1) - fmt));
+ width.push_back(2);
+ }
+ else if (*scan == '{') {
+ // get argument index
+ char* end;
+ errno = 0;
+ long i = strtol(scan + 1, &end, 10);
+ if (errno || (i < 0) || (*end != '}')) {
+ // invalid index -- ignore
+ scan = end - 1; // BUG if there are digits?
+ }
+ else {
+ index.push_back(i);
+ pos.push_back(static_cast<size_t>((scan - 1) - fmt));
+ width.push_back(static_cast<size_t>((end - scan) + 2));
+ if (i > maxIndex) {
+ maxIndex = i;
+ }
+ scan = end;
+ }
+ }
+ else {
+ // improper escape -- ignore
+ }
+ }
+ }
+
+ // get args
+ std::vector<const char*> value;
+ std::vector<size_t> 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<int>(pos.size());
+ for (int i = 0; i < n; ++i) {
+ resultLength -= width[i];
+ resultLength += length[index[i]];
+ }
+
+ // substitute
+ String 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;
+}
+
+String
+sprintf(const char* fmt, ...)
+{
+ char tmp[1024];
+ char* buffer = tmp;
+ int len = (int)(sizeof(tmp) / sizeof(tmp[0]));
+ String 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;
+}
+
+void
+findReplaceAll(
+ String& subject,
+ const String& find,
+ const String& replace)
+{
+ size_t pos = 0;
+ while ((pos = subject.find(find, pos)) != String::npos) {
+ subject.replace(pos, find.length(), replace);
+ pos += replace.length();
+ }
+}
+
+String
+removeFileExt(String filename)
+{
+ size_t dot = filename.find_last_of('.');
+
+ if (dot == String::npos) {
+ return filename;
+ }
+
+ return filename.substr(0, dot);
+}
+
+void
+toHex(String& subject, int width, const char fill)
+{
+ std::stringstream ss;
+ ss << std::hex;
+ for (unsigned int i = 0; i < subject.length(); i++) {
+ ss << std::setw(width) << std::setfill(fill) << (int)(unsigned char)subject[i];
+ }
+
+ subject = ss.str();
+}
+
+void
+uppercase(String& subject)
+{
+ std::transform(subject.begin(), subject.end(), subject.begin(), ::toupper);
+}
+
+void
+removeChar(String& subject, const char c)
+{
+ subject.erase(std::remove(subject.begin(), subject.end(), c), subject.end());
+}
+
+String
+sizeTypeToString(size_t n)
+{
+ std::stringstream ss;
+ ss << n;
+ return ss.str();
+}
+
+size_t
+stringToSizeType(String string)
+{
+ std::istringstream iss(string);
+ size_t value;
+ iss >> value;
+ return value;
+}
+
+std::vector<String>
+splitString(String string, const char c)
+{
+ std::vector<String> results;
+
+ size_t head = 0;
+ size_t separator = string.find(c);
+ while (separator != String::npos) {
+ if (head!=separator) {
+ results.push_back(string.substr(head, separator - head));
+ }
+ head = separator + 1;
+ separator = string.find(c, head);
+ }
+
+ if (head < string.size()) {
+ results.push_back(string.substr(head, string.size() - head));
+ }
+
+ return results;
+}
+
+//
+// CaselessCmp
+//
+
+bool
+CaselessCmp::cmpEqual(
+ const String::value_type& a,
+ const String::value_type& b)
+{
+ // should use std::tolower but not in all versions of libstdc++ have it
+ return tolower(a) == tolower(b);
+}
+
+bool
+CaselessCmp::cmpLess(
+ const String::value_type& a,
+ const String::value_type& b)
+{
+ // should use std::tolower but not in all versions of libstdc++ have it
+ return tolower(a) < tolower(b);
+}
+
+bool
+CaselessCmp::less(const String& a, const String& b)
+{
+ return std::lexicographical_compare(
+ a.begin(), a.end(),
+ b.begin(), b.end(),
+ &barrier::string::CaselessCmp::cmpLess);
+}
+
+bool
+CaselessCmp::equal(const String& a, const String& b)
+{
+ return !(less(a, b) || less(b, a));
+}
+
+bool
+CaselessCmp::operator()(const String& a, const String& b) const
+{
+ return less(a, b);
+}
+
+}
+}
diff --git a/src/lib/base/String.h b/src/lib/base/String.h
new file mode 100644
index 0000000..3661461
--- /dev/null
+++ b/src/lib/base/String.h
@@ -0,0 +1,135 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/common.h"
+#include "common/stdstring.h"
+
+#include <stdarg.h>
+#include <vector>
+
+// use standard C++ string class for our string class
+typedef std::string String;
+
+namespace barrier {
+
+//! String utilities
+/*!
+Provides functions for string manipulation.
+*/
+namespace string {
+
+//! Format positional arguments
+/*!
+Format a string using positional arguments. fmt has literal
+characters and conversion specifications introduced by `\%':
+- \%\% -- literal `\%'
+- \%{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.
+*/
+String format(const char* fmt, ...);
+
+//! Format positional arguments
+/*!
+Same as format() except takes va_list.
+*/
+String vformat(const char* fmt, va_list);
+
+//! Print a string using sprintf-style formatting
+/*!
+Equivalent to sprintf() except the result is returned as a String.
+*/
+String sprintf(const char* fmt, ...);
+
+//! Find and replace all
+/*!
+Finds \c find inside \c subject and replaces it with \c replace
+*/
+void findReplaceAll(String& subject, const String& find, const String& replace);
+
+//! Remove file extension
+/*!
+Finds the last dot and remove all characters from the dot to the end
+*/
+String removeFileExt(String filename);
+
+//! Convert into hexdecimal
+/*!
+Convert each character in \c subject into hexdecimal form with \c width
+*/
+void toHex(String& subject, int width, const char fill = '0');
+
+//! Convert to all uppercase
+/*!
+Convert each character in \c subject to uppercase
+*/
+void uppercase(String& subject);
+
+//! Remove all specific char in suject
+/*!
+Remove all specific \c c in \c suject
+*/
+void removeChar(String& subject, const char c);
+
+//! Convert a size type to a string
+/*!
+Convert an size type to a string
+*/
+String sizeTypeToString(size_t n);
+
+//! Convert a string to a size type
+/*!
+Convert an a \c string to an size type
+*/
+size_t stringToSizeType(String string);
+
+//! Split a string into substrings
+/*!
+Split a \c string that separated by a \c c into substrings
+*/
+std::vector<String> splitString(String string, const char c);
+
+//! Case-insensitive comparisons
+/*!
+This class provides case-insensitve comparison functions.
+*/
+class CaselessCmp {
+ public:
+ //! Same as less()
+ bool operator()(const String& a, const String& b) const;
+
+ //! Returns true iff \c a is lexicographically less than \c b
+ static bool less(const String& a, const String& b);
+
+ //! Returns true iff \c a is lexicographically equal to \c b
+ static bool equal(const String& a, const String& b);
+
+ //! Returns true iff \c a is lexicographically less than \c b
+ static bool cmpLess(const String::value_type& a,
+ const String::value_type& b);
+
+ //! Returns true iff \c a is lexicographically equal to \c b
+ static bool cmpEqual(const String::value_type& a,
+ const String::value_type& b);
+};
+
+}
+}
diff --git a/src/lib/base/TMethodEventJob.h b/src/lib/base/TMethodEventJob.h
new file mode 100644
index 0000000..a65f8c9
--- /dev/null
+++ b/src/lib/base/TMethodEventJob.h
@@ -0,0 +1,71 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "IEventJob.h"
+
+//! Use a member function as an event job
+/*!
+An event job class that invokes a member function.
+*/
+template <class T>
+class TMethodEventJob : public IEventJob {
+public:
+ //! run(event) invokes \c object->method(event, arg)
+ TMethodEventJob(T* object,
+ void (T::*method)(const Event&, void*),
+ void* arg = NULL);
+ virtual ~TMethodEventJob();
+
+ // IJob overrides
+ virtual void run(const Event&);
+
+private:
+ T* m_object;
+ void (T::*m_method)(const Event&, void*);
+ void* m_arg;
+};
+
+template <class T>
+inline
+TMethodEventJob<T>::TMethodEventJob(T* object,
+ void (T::*method)(const Event&, void*), void* arg) :
+ m_object(object),
+ m_method(method),
+ m_arg(arg)
+{
+ // do nothing
+}
+
+template <class T>
+inline
+TMethodEventJob<T>::~TMethodEventJob()
+{
+ // do nothing
+}
+
+template <class T>
+inline
+void
+TMethodEventJob<T>::run(const Event& event)
+{
+ if (m_object != NULL) {
+ (m_object->*m_method)(event, m_arg);
+ }
+}
diff --git a/src/lib/base/TMethodJob.h b/src/lib/base/TMethodJob.h
new file mode 100644
index 0000000..ec88f05
--- /dev/null
+++ b/src/lib/base/TMethodJob.h
@@ -0,0 +1,68 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "IJob.h"
+
+//! Use a function as a job
+/*!
+A job class that invokes a member function.
+*/
+template <class T>
+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 <class T>
+inline
+TMethodJob<T>::TMethodJob(T* object, void (T::*method)(void*), void* arg) :
+ m_object(object),
+ m_method(method),
+ m_arg(arg)
+{
+ // do nothing
+}
+
+template <class T>
+inline
+TMethodJob<T>::~TMethodJob()
+{
+ // do nothing
+}
+
+template <class T>
+inline
+void
+TMethodJob<T>::run()
+{
+ if (m_object != NULL) {
+ (m_object->*m_method)(m_arg);
+ }
+}
diff --git a/src/lib/base/Unicode.cpp b/src/lib/base/Unicode.cpp
new file mode 100644
index 0000000..6a077e7
--- /dev/null
+++ b/src/lib/base/Unicode.cpp
@@ -0,0 +1,784 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "arch/Arch.h"
+#include "base/Unicode.h"
+
+#include <cstring>
+
+//
+// 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;
+ }
+}
+
+
+//
+// Unicode
+//
+
+UInt32 Unicode::s_invalid = 0x0000ffff;
+UInt32 Unicode::s_replacement = 0x0000fffd;
+
+bool
+Unicode::isUTF8(const String& src)
+{
+ // convert and test each character
+ const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str());
+ for (UInt32 n = (UInt32)src.size(); n > 0; ) {
+ if (fromUTF8(data, n) == s_invalid) {
+ return false;
+ }
+ }
+ return true;
+}
+
+String
+Unicode::UTF8ToUCS2(const String& src, bool* errors)
+{
+ // default to success
+ resetError(errors);
+
+ // get size of input string and reserve some space in output
+ UInt32 n = (UInt32)src.size();
+ String dst;
+ dst.reserve(2 * n);
+
+ // convert each character
+ const UInt8* data = reinterpret_cast<const UInt8*>(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<UInt16>(c);
+ dst.append(reinterpret_cast<const char*>(&ucs2), 2);
+ }
+
+ return dst;
+}
+
+String
+Unicode::UTF8ToUCS4(const String& src, bool* errors)
+{
+ // default to success
+ resetError(errors);
+
+ // get size of input string and reserve some space in output
+ UInt32 n = (UInt32)src.size();
+ String dst;
+ dst.reserve(4 * n);
+
+ // convert each character
+ const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str());
+ while (n > 0) {
+ UInt32 c = fromUTF8(data, n);
+ if (c == s_invalid) {
+ c = s_replacement;
+ }
+ dst.append(reinterpret_cast<const char*>(&c), 4);
+ }
+
+ return dst;
+}
+
+String
+Unicode::UTF8ToUTF16(const String& src, bool* errors)
+{
+ // default to success
+ resetError(errors);
+
+ // get size of input string and reserve some space in output
+ UInt32 n = (UInt32)src.size();
+ String dst;
+ dst.reserve(2 * n);
+
+ // convert each character
+ const UInt8* data = reinterpret_cast<const UInt8*>(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<UInt16>(c);
+ dst.append(reinterpret_cast<const char*>(&ucs2), 2);
+ }
+ else {
+ c -= 0x00010000;
+ UInt16 utf16h = static_cast<UInt16>((c >> 10) + 0xd800);
+ UInt16 utf16l = static_cast<UInt16>((c & 0x03ff) + 0xdc00);
+ dst.append(reinterpret_cast<const char*>(&utf16h), 2);
+ dst.append(reinterpret_cast<const char*>(&utf16l), 2);
+ }
+ }
+
+ return dst;
+}
+
+String
+Unicode::UTF8ToUTF32(const String& src, bool* errors)
+{
+ // default to success
+ resetError(errors);
+
+ // get size of input string and reserve some space in output
+ UInt32 n = (UInt32)src.size();
+ String dst;
+ dst.reserve(4 * n);
+
+ // convert each character
+ const UInt8* data = reinterpret_cast<const UInt8*>(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<const char*>(&c), 4);
+ }
+
+ return dst;
+}
+
+String
+Unicode::UTF8ToText(const String& 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);
+ String text(mbs, len);
+
+ // clean up
+ delete[] mbs;
+ delete[] tmp;
+
+ return text;
+}
+
+String
+Unicode::UCS2ToUTF8(const String& src, bool* errors)
+{
+ // default to success
+ resetError(errors);
+
+ // convert
+ UInt32 n = (UInt32)src.size() >> 1;
+ return doUCS2ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors);
+}
+
+String
+Unicode::UCS4ToUTF8(const String& src, bool* errors)
+{
+ // default to success
+ resetError(errors);
+
+ // convert
+ UInt32 n = (UInt32)src.size() >> 2;
+ return doUCS4ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors);
+}
+
+String
+Unicode::UTF16ToUTF8(const String& src, bool* errors)
+{
+ // default to success
+ resetError(errors);
+
+ // convert
+ UInt32 n = (UInt32)src.size() >> 1;
+ return doUTF16ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors);
+}
+
+String
+Unicode::UTF32ToUTF8(const String& src, bool* errors)
+{
+ // default to success
+ resetError(errors);
+
+ // convert
+ UInt32 n = (UInt32)src.size() >> 2;
+ return doUTF32ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors);
+}
+
+String
+Unicode::textToUTF8(const String& src, bool* errors)
+{
+ // default to success
+ resetError(errors);
+
+ // convert string to wide characters
+ UInt32 n = (UInt32)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
+ String utf8 = wideCharToUTF8(wcs, len, errors);
+
+ // clean up
+ delete[] wcs;
+
+ return utf8;
+}
+
+wchar_t*
+Unicode::UTF8ToWideChar(const String& src, UInt32& size, bool* errors)
+{
+ // convert to platform's wide character encoding
+ String tmp;
+ switch (ARCH->getWideCharEncoding()) {
+ case IArchString::kUCS2:
+ tmp = UTF8ToUCS2(src, errors);
+ size = (UInt32)tmp.size() >> 1;
+ break;
+
+ case IArchString::kUCS4:
+ tmp = UTF8ToUCS4(src, errors);
+ size = (UInt32)tmp.size() >> 2;
+ break;
+
+ case IArchString::kUTF16:
+ tmp = UTF8ToUTF16(src, errors);
+ size = (UInt32)tmp.size() >> 1;
+ break;
+
+ case IArchString::kUTF32:
+ tmp = UTF8ToUTF32(src, errors);
+ size = (UInt32)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;
+}
+
+String
+Unicode::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 String's nul character).
+ switch (ARCH->getWideCharEncoding()) {
+ case IArchString::kUCS2:
+ return doUCS2ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors);
+
+ case IArchString::kUCS4:
+ return doUCS4ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors);
+
+ case IArchString::kUTF16:
+ return doUTF16ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors);
+
+ case IArchString::kUTF32:
+ return doUTF32ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors);
+
+ default:
+ assert(0 && "unknown wide character encoding");
+ return String();
+ }
+}
+
+String
+Unicode::doUCS2ToUTF8(const UInt8* data, UInt32 n, bool* errors)
+{
+ // make some space
+ String 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;
+}
+
+String
+Unicode::doUCS4ToUTF8(const UInt8* data, UInt32 n, bool* errors)
+{
+ // make some space
+ String 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;
+}
+
+String
+Unicode::doUTF16ToUTF8(const UInt8* data, UInt32 n, bool* errors)
+{
+ // make some space
+ String 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;
+}
+
+String
+Unicode::doUTF32ToUTF8(const UInt8* data, UInt32 n, bool* errors)
+{
+ // make some space
+ String 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
+Unicode::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<UInt32>(data[0]);
+ break;
+
+ case 2:
+ c = ((static_cast<UInt32>(data[0]) & 0x1f) << 6) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) );
+ break;
+
+ case 3:
+ c = ((static_cast<UInt32>(data[0]) & 0x0f) << 12) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) << 6) |
+ ((static_cast<UInt32>(data[2]) & 0x3f) );
+ break;
+
+ case 4:
+ c = ((static_cast<UInt32>(data[0]) & 0x07) << 18) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) << 12) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) << 6) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) );
+ break;
+
+ case 5:
+ c = ((static_cast<UInt32>(data[0]) & 0x03) << 24) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) << 18) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) << 12) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) << 6) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) );
+ break;
+
+ case 6:
+ c = ((static_cast<UInt32>(data[0]) & 0x01) << 30) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) << 24) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) << 18) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) << 12) |
+ ((static_cast<UInt32>(data[1]) & 0x3f) << 6) |
+ ((static_cast<UInt32>(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
+Unicode::toUTF8(String& 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<UInt8>(c);
+ dst.append(reinterpret_cast<char*>(data), 1);
+ }
+ else if (c < 0x00000800) {
+ data[0] = static_cast<UInt8>(((c >> 6) & 0x0000001f) + 0xc0);
+ data[1] = static_cast<UInt8>((c & 0x0000003f) + 0x80);
+ dst.append(reinterpret_cast<char*>(data), 2);
+ }
+ else if (c < 0x00010000) {
+ data[0] = static_cast<UInt8>(((c >> 12) & 0x0000000f) + 0xe0);
+ data[1] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80);
+ data[2] = static_cast<UInt8>((c & 0x0000003f) + 0x80);
+ dst.append(reinterpret_cast<char*>(data), 3);
+ }
+ else if (c < 0x00200000) {
+ data[0] = static_cast<UInt8>(((c >> 18) & 0x00000007) + 0xf0);
+ data[1] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80);
+ data[2] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80);
+ data[3] = static_cast<UInt8>((c & 0x0000003f) + 0x80);
+ dst.append(reinterpret_cast<char*>(data), 4);
+ }
+ else if (c < 0x04000000) {
+ data[0] = static_cast<UInt8>(((c >> 24) & 0x00000003) + 0xf8);
+ data[1] = static_cast<UInt8>(((c >> 18) & 0x0000003f) + 0x80);
+ data[2] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80);
+ data[3] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80);
+ data[4] = static_cast<UInt8>((c & 0x0000003f) + 0x80);
+ dst.append(reinterpret_cast<char*>(data), 5);
+ }
+ else if (c < 0x80000000) {
+ data[0] = static_cast<UInt8>(((c >> 30) & 0x00000001) + 0xfc);
+ data[1] = static_cast<UInt8>(((c >> 24) & 0x0000003f) + 0x80);
+ data[2] = static_cast<UInt8>(((c >> 18) & 0x0000003f) + 0x80);
+ data[3] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80);
+ data[4] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80);
+ data[5] = static_cast<UInt8>((c & 0x0000003f) + 0x80);
+ dst.append(reinterpret_cast<char*>(data), 6);
+ }
+ else {
+ assert(0 && "character out of range");
+ }
+}
diff --git a/src/lib/base/Unicode.h b/src/lib/base/Unicode.h
new file mode 100644
index 0000000..1391c1e
--- /dev/null
+++ b/src/lib/base/Unicode.h
@@ -0,0 +1,144 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+#include "common/basic_types.h"
+
+//! Unicode utility functions
+/*!
+This class provides functions for converting between various Unicode
+encodings and the current locale encoding.
+*/
+class Unicode {
+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 String&);
+
+ //! 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 String UTF8ToUCS2(const String&, 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 String UTF8ToUCS4(const String&, 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 String UTF8ToUTF16(const String&, 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 String UTF8ToUTF32(const String&, 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 String UTF8ToText(const String&, 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 String UCS2ToUTF8(const String&, 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 String UCS4ToUTF8(const String&, 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 String UTF16ToUTF8(const String&, 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 String UTF32ToUTF8(const String&, 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 String textToUTF8(const String&, 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 String&,
+ UInt32& size, bool* errors);
+
+ // convert nul terminated wchar_t string (in platform's native
+ // encoding) to UTF8.
+ static String wideCharToUTF8(const wchar_t*,
+ UInt32 size, bool* errors);
+
+ // internal conversion to UTF8
+ static String doUCS2ToUTF8(const UInt8* src, UInt32 n, bool* errors);
+ static String doUCS4ToUTF8(const UInt8* src, UInt32 n, bool* errors);
+ static String doUTF16ToUTF8(const UInt8* src, UInt32 n, bool* errors);
+ static String doUTF32ToUTF8(const UInt8* src, UInt32 n, bool* errors);
+
+ // convert characters to/from UTF8
+ static UInt32 fromUTF8(const UInt8*& src, UInt32& size);
+ static void toUTF8(String& dst, UInt32 c, bool* errors);
+
+private:
+ static UInt32 s_invalid;
+ static UInt32 s_replacement;
+};
diff --git a/src/lib/base/XBase.cpp b/src/lib/base/XBase.cpp
new file mode 100644
index 0000000..29ae927
--- /dev/null
+++ b/src/lib/base/XBase.cpp
@@ -0,0 +1,76 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/XBase.h"
+#include "base/String.h"
+
+#include <cerrno>
+#include <cstdarg>
+
+//
+// XBase
+//
+
+XBase::XBase() :
+ std::runtime_error("")
+{
+ // do nothing
+}
+
+XBase::XBase(const String& msg) :
+ std::runtime_error(msg)
+{
+ // do nothing
+}
+
+XBase::~XBase() _NOEXCEPT
+{
+ // do nothing
+}
+
+const char*
+XBase::what() const _NOEXCEPT
+{
+ const char* what = std::runtime_error::what();
+ if (strlen(what) == 0) {
+ m_what = getWhat();
+ return m_what.c_str();
+ }
+ return what;
+}
+
+String
+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
+ String result;
+ va_list args;
+ va_start(args, fmt);
+ try {
+ result = barrier::string::vformat(fmt, args);
+ }
+ catch (...) {
+ // ignore
+ }
+ va_end(args);
+
+ return result;
+}
diff --git a/src/lib/base/XBase.h b/src/lib/base/XBase.h
new file mode 100644
index 0000000..3064b6c
--- /dev/null
+++ b/src/lib/base/XBase.h
@@ -0,0 +1,125 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+#include "common/stdexcept.h"
+
+//! Exception base class
+/*!
+This is the base class of most exception types.
+*/
+class XBase : public std::runtime_error {
+public:
+ //! Use getWhat() as the result of what()
+ XBase();
+ //! Use \c msg as the result of what()
+ XBase(const String& msg);
+ virtual ~XBase() _NOEXCEPT;
+
+ //! Reason for exception
+ virtual const char* what() const _NOEXCEPT;
+
+protected:
+ //! Get a human readable string describing the exception
+ virtual String getWhat() const throw() { return ""; }
+
+ //! 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 String format(const char* id,
+ const char* defaultFormat, ...) const throw();
+private:
+ mutable String m_what;
+};
+
+/*!
+\def XBASE_SUBCLASS
+Convenience macro to subclass from XBase (or a subclass of it),
+providing the c'tor taking a const String&. getWhat() is not
+declared.
+*/
+#define XBASE_SUBCLASS(name_, super_) \
+class name_ : public super_ { \
+public: \
+ name_() : super_() { } \
+ name_(const String& msg) : super_(msg) { } \
+ virtual ~name_() _NOEXCEPT { } \
+}
+
+/*!
+\def XBASE_SUBCLASS
+Convenience macro to subclass from XBase (or a subclass of it),
+providing the c'tor taking a const String&. getWhat() must be
+implemented.
+*/
+#define XBASE_SUBCLASS_WHAT(name_, super_) \
+class name_ : public super_ { \
+public: \
+ name_() : super_() { } \
+ name_(const String& msg) : super_(msg) { } \
+ virtual ~name_() _NOEXCEPT { } \
+ \
+protected: \
+ virtual String 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 String&. 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 String& msg) : super_(msg), m_state(kFirst) { } \
+ virtual ~name_() _NOEXCEPT { } \
+ \
+ virtual const char* what() const _NOEXCEPT \
+ { \
+ 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 String getWhat() const throw(); \
+ \
+private: \
+ mutable EState m_state; \
+ mutable std::string m_formatted; \
+}
diff --git a/src/lib/base/log_outputters.cpp b/src/lib/base/log_outputters.cpp
new file mode 100644
index 0000000..8e56c26
--- /dev/null
+++ b/src/lib/base/log_outputters.cpp
@@ -0,0 +1,337 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/log_outputters.h"
+#include "base/TMethodJob.h"
+#include "arch/Arch.h"
+
+#include <fstream>
+
+enum EFileLogOutputter {
+ kFileSizeLimit = 1024 // kb
+};
+
+//
+// StopLogOutputter
+//
+
+StopLogOutputter::StopLogOutputter()
+{
+ // do nothing
+}
+
+StopLogOutputter::~StopLogOutputter()
+{
+ // do nothing
+}
+
+void
+StopLogOutputter::open(const char*)
+{
+ // do nothing
+}
+
+void
+StopLogOutputter::close()
+{
+ // do nothing
+}
+
+void
+StopLogOutputter::show(bool)
+{
+ // do nothing
+}
+
+bool
+StopLogOutputter::write(ELevel, const char*)
+{
+ return false;
+}
+
+
+//
+// ConsoleLogOutputter
+//
+
+ConsoleLogOutputter::ConsoleLogOutputter()
+{
+}
+
+ConsoleLogOutputter::~ConsoleLogOutputter()
+{
+}
+
+void
+ConsoleLogOutputter::open(const char* title)
+{
+ ARCH->openConsole(title);
+}
+
+void
+ConsoleLogOutputter::close()
+{
+ ARCH->closeConsole();
+}
+
+void
+ConsoleLogOutputter::show(bool showIfEmpty)
+{
+ ARCH->showConsole(showIfEmpty);
+}
+
+bool
+ConsoleLogOutputter::write(ELevel level, const char* msg)
+{
+ ARCH->writeConsole(level, msg);
+ return true;
+}
+
+void
+ConsoleLogOutputter::flush()
+{
+
+}
+
+
+//
+// SystemLogOutputter
+//
+
+SystemLogOutputter::SystemLogOutputter()
+{
+ // do nothing
+}
+
+SystemLogOutputter::~SystemLogOutputter()
+{
+ // do nothing
+}
+
+void
+SystemLogOutputter::open(const char* title)
+{
+ ARCH->openLog(title);
+}
+
+void
+SystemLogOutputter::close()
+{
+ ARCH->closeLog();
+}
+
+void
+SystemLogOutputter::show(bool showIfEmpty)
+{
+ ARCH->showLog(showIfEmpty);
+}
+
+bool
+SystemLogOutputter::write(ELevel level, const char* msg)
+{
+ ARCH->writeLog(level, msg);
+ return true;
+}
+
+//
+// SystemLogger
+//
+
+SystemLogger::SystemLogger(const char* title, bool blockConsole) :
+ m_stop(NULL)
+{
+ // redirect log messages
+ if (blockConsole) {
+ m_stop = new StopLogOutputter;
+ CLOG->insert(m_stop);
+ }
+ m_syslog = new SystemLogOutputter;
+ m_syslog->open(title);
+ CLOG->insert(m_syslog);
+}
+
+SystemLogger::~SystemLogger()
+{
+ CLOG->remove(m_syslog);
+ delete m_syslog;
+ if (m_stop != NULL) {
+ CLOG->remove(m_stop);
+ delete m_stop;
+ }
+}
+
+
+//
+// BufferedLogOutputter
+//
+
+BufferedLogOutputter::BufferedLogOutputter(UInt32 maxBufferSize) :
+ m_maxBufferSize(maxBufferSize)
+{
+ // do nothing
+}
+
+BufferedLogOutputter::~BufferedLogOutputter()
+{
+ // do nothing
+}
+
+BufferedLogOutputter::const_iterator
+BufferedLogOutputter::begin() const
+{
+ return m_buffer.begin();
+}
+
+BufferedLogOutputter::const_iterator
+BufferedLogOutputter::end() const
+{
+ return m_buffer.end();
+}
+
+void
+BufferedLogOutputter::open(const char*)
+{
+ // do nothing
+}
+
+void
+BufferedLogOutputter::close()
+{
+ // remove all elements from the buffer
+ m_buffer.clear();
+}
+
+void
+BufferedLogOutputter::show(bool)
+{
+ // do nothing
+}
+
+bool
+BufferedLogOutputter::write(ELevel, const char* message)
+{
+ while (m_buffer.size() >= m_maxBufferSize) {
+ m_buffer.pop_front();
+ }
+ m_buffer.push_back(String(message));
+ return true;
+}
+
+
+//
+// FileLogOutputter
+//
+
+FileLogOutputter::FileLogOutputter(const char* logFile)
+{
+ setLogFilename(logFile);
+}
+
+FileLogOutputter::~FileLogOutputter()
+{
+}
+
+void
+FileLogOutputter::setLogFilename(const char* logFile)
+{
+ assert(logFile != NULL);
+ m_fileName = logFile;
+}
+
+bool
+FileLogOutputter::write(ELevel level, const char *message)
+{
+ bool moveFile = false;
+
+ std::ofstream m_handle;
+ m_handle.open(m_fileName.c_str(), std::fstream::app);
+ if (m_handle.is_open() && m_handle.fail() != true) {
+ m_handle << message << std::endl;
+
+ // when file size exceeds limits, move to 'old log' filename.
+ size_t p = m_handle.tellp();
+ if (p > (kFileSizeLimit * 1024)) {
+ moveFile = true;
+ }
+ }
+ m_handle.close();
+
+ if (moveFile) {
+ String oldLogFilename = barrier::string::sprintf("%s.1", m_fileName.c_str());
+ remove(oldLogFilename.c_str());
+ rename(m_fileName.c_str(), oldLogFilename.c_str());
+ }
+
+ return true;
+}
+
+void
+FileLogOutputter::open(const char *title) {}
+
+void
+FileLogOutputter::close() {}
+
+void
+FileLogOutputter::show(bool showIfEmpty) {}
+
+//
+// MesssageBoxLogOutputter
+//
+
+MesssageBoxLogOutputter::MesssageBoxLogOutputter()
+{
+ // do nothing
+}
+
+MesssageBoxLogOutputter::~MesssageBoxLogOutputter()
+{
+ // do nothing
+}
+
+void
+MesssageBoxLogOutputter::open(const char* title)
+{
+ // do nothing
+}
+
+void
+MesssageBoxLogOutputter::close()
+{
+ // do nothing
+}
+
+void
+MesssageBoxLogOutputter::show(bool showIfEmpty)
+{
+ // do nothing
+}
+
+bool
+MesssageBoxLogOutputter::write(ELevel level, const char* msg)
+{
+ // don't spam user with messages.
+ if (level > kERROR) {
+ return true;
+ }
+
+#if SYSAPI_WIN32
+ MessageBox(NULL, msg, CLOG->getFilterName(level), MB_OK);
+#endif
+
+ return true;
+}
diff --git a/src/lib/base/log_outputters.h b/src/lib/base/log_outputters.h
new file mode 100644
index 0000000..c4940aa
--- /dev/null
+++ b/src/lib/base/log_outputters.h
@@ -0,0 +1,172 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mt/Thread.h"
+#include "base/ILogOutputter.h"
+#include "base/String.h"
+#include "common/basic_types.h"
+#include "common/stddeque.h"
+
+#include <list>
+#include <fstream>
+
+//! 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 StopLogOutputter : public ILogOutputter {
+public:
+ StopLogOutputter();
+ virtual ~StopLogOutputter();
+
+ // ILogOutputter overrides
+ virtual void open(const char* title);
+ virtual void close();
+ virtual void show(bool showIfEmpty);
+ virtual bool write(ELevel level, const char* message);
+};
+
+//! Write log to console
+/*!
+This outputter writes output to the console. The level for each
+message is ignored.
+*/
+class ConsoleLogOutputter : public ILogOutputter {
+public:
+ ConsoleLogOutputter();
+ virtual ~ConsoleLogOutputter();
+
+ // 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 void flush();
+};
+
+//! Write log to file
+/*!
+This outputter writes output to the file. The level for each
+message is ignored.
+*/
+
+class FileLogOutputter : public ILogOutputter {
+public:
+ FileLogOutputter(const char* logFile);
+ virtual ~FileLogOutputter();
+
+ // ILogOutputter overrides
+ virtual void open(const char* title);
+ virtual void close();
+ virtual void show(bool showIfEmpty);
+ virtual bool write(ELevel level, const char* message);
+
+ void setLogFilename(const char* title);
+
+private:
+ std::string m_fileName;
+};
+
+//! Write log to system log
+/*!
+This outputter writes output to the system log.
+*/
+class SystemLogOutputter : public ILogOutputter {
+public:
+ SystemLogOutputter();
+ virtual ~SystemLogOutputter();
+
+ // ILogOutputter overrides
+ virtual void open(const char* title);
+ virtual void close();
+ virtual void show(bool showIfEmpty);
+ virtual bool write(ELevel level, const char* message);
+};
+
+//! Write log to system log only
+/*!
+Creating an object of this type inserts a StopLogOutputter followed
+by a SystemLogOutputter into Log. 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 SystemLogger {
+public:
+ SystemLogger(const char* title, bool blockConsole);
+ ~SystemLogger();
+
+private:
+ ILogOutputter* m_syslog;
+ ILogOutputter* m_stop;
+};
+
+//! Save log history
+/*!
+This outputter records the last N log messages.
+*/
+class BufferedLogOutputter : public ILogOutputter {
+private:
+ typedef std::deque<String> Buffer;
+
+public:
+ typedef Buffer::const_iterator const_iterator;
+
+ BufferedLogOutputter(UInt32 maxBufferSize);
+ virtual ~BufferedLogOutputter();
+
+ //! @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);
+private:
+ UInt32 m_maxBufferSize;
+ Buffer m_buffer;
+};
+
+//! Write log to message box
+/*!
+The level for each message is ignored.
+*/
+class MesssageBoxLogOutputter : public ILogOutputter {
+public:
+ MesssageBoxLogOutputter();
+ virtual ~MesssageBoxLogOutputter();
+
+ // ILogOutputter overrides
+ virtual void open(const char* title);
+ virtual void close();
+ virtual void show(bool showIfEmpty);
+ virtual bool write(ELevel level, const char* message);
+};
diff --git a/src/lib/client/CMakeLists.txt b/src/lib/client/CMakeLists.txt
new file mode 100644
index 0000000..97dc9db
--- /dev/null
+++ b/src/lib/client/CMakeLists.txt
@@ -0,0 +1,28 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+file(GLOB headers "*.h")
+file(GLOB sources "*.cpp")
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+add_library(client STATIC ${sources})
+
+if (UNIX)
+ target_link_libraries(client synlib io)
+endif()
diff --git a/src/lib/client/Client.cpp b/src/lib/client/Client.cpp
new file mode 100644
index 0000000..2158ee2
--- /dev/null
+++ b/src/lib/client/Client.cpp
@@ -0,0 +1,836 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "client/Client.h"
+
+#include "client/ServerProxy.h"
+#include "barrier/Screen.h"
+#include "barrier/FileChunk.h"
+#include "barrier/DropHelper.h"
+#include "barrier/PacketStreamFilter.h"
+#include "barrier/ProtocolUtil.h"
+#include "barrier/protocol_types.h"
+#include "barrier/XBarrier.h"
+#include "barrier/StreamChunker.h"
+#include "barrier/IPlatformScreen.h"
+#include "mt/Thread.h"
+#include "net/TCPSocket.h"
+#include "net/IDataSocket.h"
+#include "net/ISocketFactory.h"
+#include "net/SecureSocket.h"
+#include "arch/Arch.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+#include "base/TMethodJob.h"
+#include "common/stdexcept.h"
+
+#include <cstring>
+#include <cstdlib>
+#include <sstream>
+#include <fstream>
+
+//
+// Client
+//
+
+Client::Client(
+ IEventQueue* events,
+ const String& name, const NetworkAddress& address,
+ ISocketFactory* socketFactory,
+ barrier::Screen* screen,
+ ClientArgs const& args) :
+ m_mock(false),
+ m_name(name),
+ m_serverAddress(address),
+ m_socketFactory(socketFactory),
+ m_screen(screen),
+ m_stream(NULL),
+ m_timer(NULL),
+ m_server(NULL),
+ m_ready(false),
+ m_active(false),
+ m_suspended(false),
+ m_connectOnResume(false),
+ m_events(events),
+ m_sendFileThread(NULL),
+ m_writeToDropDirThread(NULL),
+ m_socket(NULL),
+ m_useSecureNetwork(args.m_enableCrypto),
+ m_args(args),
+ m_enableClipboard(true)
+{
+ assert(m_socketFactory != NULL);
+ assert(m_screen != NULL);
+
+ // register suspend/resume event handlers
+ m_events->adoptHandler(m_events->forIScreen().suspend(),
+ getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleSuspend));
+ m_events->adoptHandler(m_events->forIScreen().resume(),
+ getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleResume));
+
+ if (m_args.m_enableDragDrop) {
+ m_events->adoptHandler(m_events->forFile().fileChunkSending(),
+ this,
+ new TMethodEventJob<Client>(this,
+ &Client::handleFileChunkSending));
+ m_events->adoptHandler(m_events->forFile().fileRecieveCompleted(),
+ this,
+ new TMethodEventJob<Client>(this,
+ &Client::handleFileRecieveCompleted));
+ }
+}
+
+Client::~Client()
+{
+ if (m_mock) {
+ return;
+ }
+
+ m_events->removeHandler(m_events->forIScreen().suspend(),
+ getEventTarget());
+ m_events->removeHandler(m_events->forIScreen().resume(),
+ getEventTarget());
+
+ cleanupTimer();
+ cleanupScreen();
+ cleanupConnecting();
+ cleanupConnection();
+ delete m_socketFactory;
+}
+
+void
+Client::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();
+
+ // m_serverAddress will be null if the hostname address is not reolved
+ if (m_serverAddress.getAddress() != NULL) {
+ // to help users troubleshoot, show server host name (issue: 60)
+ LOG((CLOG_NOTE "connecting to '%s': %s:%i",
+ m_serverAddress.getHostname().c_str(),
+ ARCH->addrToString(m_serverAddress.getAddress()).c_str(),
+ m_serverAddress.getPort()));
+ }
+
+ // create the socket
+ IDataSocket* socket = m_socketFactory->create(
+ ARCH->getAddrFamily(m_serverAddress.getAddress()),
+ m_useSecureNetwork);
+ m_socket = dynamic_cast<TCPSocket*>(socket);
+
+ // filter socket messages, including a packetizing filter
+ m_stream = socket;
+ m_stream = new PacketStreamFilter(m_events, m_stream, true);
+
+ // connect
+ LOG((CLOG_DEBUG1 "connecting to server"));
+ setupConnecting();
+ setupTimer();
+ socket->connect(m_serverAddress);
+ }
+ catch (XBase& e) {
+ cleanupTimer();
+ cleanupConnecting();
+ cleanupStream();
+ LOG((CLOG_DEBUG1 "connection failed"));
+ sendConnectionFailedEvent(e.what());
+ return;
+ }
+}
+
+void
+Client::disconnect(const char* msg)
+{
+ m_connectOnResume = false;
+ cleanupTimer();
+ cleanupScreen();
+ cleanupConnecting();
+ cleanupConnection();
+ if (msg != NULL) {
+ sendConnectionFailedEvent(msg);
+ }
+ else {
+ sendEvent(m_events->forClient().disconnected(), NULL);
+ }
+}
+
+void
+Client::handshakeComplete()
+{
+ m_ready = true;
+ m_screen->enable();
+ sendEvent(m_events->forClient().connected(), NULL);
+}
+
+bool
+Client::isConnected() const
+{
+ return (m_server != NULL);
+}
+
+bool
+Client::isConnecting() const
+{
+ return (m_timer != NULL);
+}
+
+NetworkAddress
+Client::getServerAddress() const
+{
+ return m_serverAddress;
+}
+
+void*
+Client::getEventTarget() const
+{
+ return m_screen->getEventTarget();
+}
+
+bool
+Client::getClipboard(ClipboardID id, IClipboard* clipboard) const
+{
+ return m_screen->getClipboard(id, clipboard);
+}
+
+void
+Client::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
+{
+ m_screen->getShape(x, y, w, h);
+}
+
+void
+Client::getCursorPos(SInt32& x, SInt32& y) const
+{
+ m_screen->getCursorPos(x, y);
+}
+
+void
+Client::enter(SInt32 xAbs, SInt32 yAbs, UInt32, KeyModifierMask mask, bool)
+{
+ m_active = true;
+ m_screen->mouseMove(xAbs, yAbs);
+ m_screen->enter(mask);
+
+ if (m_sendFileThread != NULL) {
+ StreamChunker::interruptFile();
+ m_sendFileThread = NULL;
+ }
+}
+
+bool
+Client::leave()
+{
+ m_active = false;
+
+ m_screen->leave();
+
+ if (m_enableClipboard) {
+ // 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
+Client::setClipboard(ClipboardID id, const IClipboard* clipboard)
+{
+ m_screen->setClipboard(id, clipboard);
+ m_ownClipboard[id] = false;
+ m_sentClipboard[id] = false;
+}
+
+void
+Client::grabClipboard(ClipboardID id)
+{
+ m_screen->grabClipboard(id);
+ m_ownClipboard[id] = false;
+ m_sentClipboard[id] = false;
+}
+
+void
+Client::setClipboardDirty(ClipboardID, bool)
+{
+ assert(0 && "shouldn't be called");
+}
+
+void
+Client::keyDown(KeyID id, KeyModifierMask mask, KeyButton button)
+{
+ m_screen->keyDown(id, mask, button);
+}
+
+void
+Client::keyRepeat(KeyID id, KeyModifierMask mask,
+ SInt32 count, KeyButton button)
+{
+ m_screen->keyRepeat(id, mask, count, button);
+}
+
+void
+Client::keyUp(KeyID id, KeyModifierMask mask, KeyButton button)
+{
+ m_screen->keyUp(id, mask, button);
+}
+
+void
+Client::mouseDown(ButtonID id)
+{
+ m_screen->mouseDown(id);
+}
+
+void
+Client::mouseUp(ButtonID id)
+{
+ m_screen->mouseUp(id);
+}
+
+void
+Client::mouseMove(SInt32 x, SInt32 y)
+{
+ m_screen->mouseMove(x, y);
+}
+
+void
+Client::mouseRelativeMove(SInt32 dx, SInt32 dy)
+{
+ m_screen->mouseRelativeMove(dx, dy);
+}
+
+void
+Client::mouseWheel(SInt32 xDelta, SInt32 yDelta)
+{
+ m_screen->mouseWheel(xDelta, yDelta);
+}
+
+void
+Client::screensaver(bool activate)
+{
+ m_screen->screensaver(activate);
+}
+
+void
+Client::resetOptions()
+{
+ m_screen->resetOptions();
+}
+
+void
+Client::setOptions(const OptionsList& options)
+{
+ for (OptionsList::const_iterator index = options.begin();
+ index != options.end(); ++index) {
+ const OptionID id = *index;
+ if (id == kOptionClipboardSharing) {
+ index++;
+ if (*index == static_cast<OptionValue>(false)) {
+ LOG((CLOG_NOTE "clipboard sharing is disabled"));
+ }
+ m_enableClipboard = *index;
+
+ break;
+ }
+ }
+
+ m_screen->setOptions(options);
+}
+
+String
+Client::getName() const
+{
+ return m_name;
+}
+
+void
+Client::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.
+ Clipboard 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
+ String 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
+Client::sendEvent(Event::Type type, void* data)
+{
+ m_events->addEvent(Event(type, getEventTarget(), data));
+}
+
+void
+Client::sendConnectionFailedEvent(const char* msg)
+{
+ FailInfo* info = new FailInfo(msg);
+ info->m_retry = true;
+ Event event(m_events->forClient().connectionFailed(), getEventTarget(), info, Event::kDontFreeData);
+ m_events->addEvent(event);
+}
+
+void
+Client::sendFileChunk(const void* data)
+{
+ FileChunk* chunk = static_cast<FileChunk*>(const_cast<void*>(data));
+ LOG((CLOG_DEBUG1 "send file chunk"));
+ assert(m_server != NULL);
+
+ // relay
+ m_server->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize);
+}
+
+void
+Client::setupConnecting()
+{
+ assert(m_stream != NULL);
+
+ if (m_args.m_enableCrypto) {
+ m_events->adoptHandler(m_events->forIDataSocket().secureConnected(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleConnected));
+ }
+ else {
+ m_events->adoptHandler(m_events->forIDataSocket().connected(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleConnected));
+ }
+
+ m_events->adoptHandler(m_events->forIDataSocket().connectionFailed(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleConnectionFailed));
+}
+
+void
+Client::setupConnection()
+{
+ assert(m_stream != NULL);
+
+ m_events->adoptHandler(m_events->forISocket().disconnected(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleDisconnected));
+ m_events->adoptHandler(m_events->forIStream().inputReady(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleHello));
+ m_events->adoptHandler(m_events->forIStream().outputError(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleOutputError));
+ m_events->adoptHandler(m_events->forIStream().inputShutdown(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleDisconnected));
+ m_events->adoptHandler(m_events->forIStream().outputShutdown(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleDisconnected));
+
+ m_events->adoptHandler(m_events->forISocket().stopRetry(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<Client>(this, &Client::handleStopRetry));
+}
+
+void
+Client::setupScreen()
+{
+ assert(m_server == NULL);
+
+ m_ready = false;
+ m_server = new ServerProxy(this, m_stream, m_events);
+ m_events->adoptHandler(m_events->forIScreen().shapeChanged(),
+ getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleShapeChanged));
+ m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(),
+ getEventTarget(),
+ new TMethodEventJob<Client>(this,
+ &Client::handleClipboardGrabbed));
+}
+
+void
+Client::setupTimer()
+{
+ assert(m_timer == NULL);
+
+ m_timer = m_events->newOneShotTimer(15.0, NULL);
+ m_events->adoptHandler(Event::kTimer, m_timer,
+ new TMethodEventJob<Client>(this,
+ &Client::handleConnectTimeout));
+}
+
+void
+Client::cleanupConnecting()
+{
+ if (m_stream != NULL) {
+ m_events->removeHandler(m_events->forIDataSocket().connected(),
+ m_stream->getEventTarget());
+ m_events->removeHandler(m_events->forIDataSocket().connectionFailed(),
+ m_stream->getEventTarget());
+ }
+}
+
+void
+Client::cleanupConnection()
+{
+ if (m_stream != NULL) {
+ m_events->removeHandler(m_events->forIStream().inputReady(),
+ m_stream->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().outputError(),
+ m_stream->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().inputShutdown(),
+ m_stream->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().outputShutdown(),
+ m_stream->getEventTarget());
+ m_events->removeHandler(m_events->forISocket().disconnected(),
+ m_stream->getEventTarget());
+ m_events->removeHandler(m_events->forISocket().stopRetry(),
+ m_stream->getEventTarget());
+ cleanupStream();
+ }
+}
+
+void
+Client::cleanupScreen()
+{
+ if (m_server != NULL) {
+ if (m_ready) {
+ m_screen->disable();
+ m_ready = false;
+ }
+ m_events->removeHandler(m_events->forIScreen().shapeChanged(),
+ getEventTarget());
+ m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(),
+ getEventTarget());
+ delete m_server;
+ m_server = NULL;
+ }
+}
+
+void
+Client::cleanupTimer()
+{
+ if (m_timer != NULL) {
+ m_events->removeHandler(Event::kTimer, m_timer);
+ m_events->deleteTimer(m_timer);
+ m_timer = NULL;
+ }
+}
+
+void
+Client::cleanupStream()
+{
+ delete m_stream;
+ m_stream = NULL;
+}
+
+void
+Client::handleConnected(const Event&, 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
+Client::handleConnectionFailed(const Event& event, void*)
+{
+ IDataSocket::ConnectionFailedInfo* info =
+ static_cast<IDataSocket::ConnectionFailedInfo*>(event.getData());
+
+ cleanupTimer();
+ cleanupConnecting();
+ cleanupStream();
+ LOG((CLOG_DEBUG1 "connection failed"));
+ sendConnectionFailedEvent(info->m_what.c_str());
+ delete info;
+}
+
+void
+Client::handleConnectTimeout(const Event&, void*)
+{
+ cleanupTimer();
+ cleanupConnecting();
+ cleanupConnection();
+ cleanupStream();
+ LOG((CLOG_DEBUG1 "connection timed out"));
+ sendConnectionFailedEvent("Timed out");
+}
+
+void
+Client::handleOutputError(const Event&, void*)
+{
+ cleanupTimer();
+ cleanupScreen();
+ cleanupConnection();
+ LOG((CLOG_WARN "error sending to server"));
+ sendEvent(m_events->forClient().disconnected(), NULL);
+}
+
+void
+Client::handleDisconnected(const Event&, void*)
+{
+ cleanupTimer();
+ cleanupScreen();
+ cleanupConnection();
+ LOG((CLOG_DEBUG1 "disconnected"));
+ sendEvent(m_events->forClient().disconnected(), NULL);
+}
+
+void
+Client::handleShapeChanged(const Event&, void*)
+{
+ LOG((CLOG_DEBUG "resolution changed"));
+ m_server->onInfoChanged();
+}
+
+void
+Client::handleClipboardGrabbed(const Event& event, void*)
+{
+ if (!m_enableClipboard) {
+ return;
+ }
+
+ const IScreen::ClipboardInfo* info =
+ static_cast<const IScreen::ClipboardInfo*>(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
+Client::handleHello(const Event&, void*)
+{
+ SInt16 major, minor;
+ if (!ProtocolUtil::readf(m_stream, kMsgHello, &major, &minor)) {
+ sendConnectionFailedEvent("Protocol error from server, check encryption settings");
+ 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));
+ ProtocolUtil::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()) {
+ m_events->addEvent(Event(m_events->forIStream().inputReady(),
+ m_stream->getEventTarget()));
+ }
+}
+
+void
+Client::handleSuspend(const Event&, void*)
+{
+ LOG((CLOG_INFO "suspend"));
+ m_suspended = true;
+ bool wasConnected = isConnected();
+ disconnect(NULL);
+ m_connectOnResume = wasConnected;
+}
+
+void
+Client::handleResume(const Event&, void*)
+{
+ LOG((CLOG_INFO "resume"));
+ m_suspended = false;
+ if (m_connectOnResume) {
+ m_connectOnResume = false;
+ connect();
+ }
+}
+
+void
+Client::handleFileChunkSending(const Event& event, void*)
+{
+ sendFileChunk(event.getData());
+}
+
+void
+Client::handleFileRecieveCompleted(const Event& event, void*)
+{
+ onFileRecieveCompleted();
+}
+
+void
+Client::onFileRecieveCompleted()
+{
+ if (isReceivedFileSizeValid()) {
+ m_writeToDropDirThread = new Thread(
+ new TMethodJob<Client>(
+ this, &Client::writeToDropDirThread));
+ }
+}
+
+void
+Client::handleStopRetry(const Event&, void*)
+{
+ m_args.m_restartable = false;
+}
+
+void
+Client::writeToDropDirThread(void*)
+{
+ LOG((CLOG_DEBUG "starting write to drop dir thread"));
+
+ while (m_screen->isFakeDraggingStarted()) {
+ ARCH->sleep(.1f);
+ }
+
+ DropHelper::writeToDir(m_screen->getDropTarget(), m_dragFileList,
+ m_receivedFileData);
+}
+
+void
+Client::dragInfoReceived(UInt32 fileNum, String data)
+{
+ // TODO: fix duplicate function from CServer
+ if (!m_args.m_enableDragDrop) {
+ LOG((CLOG_DEBUG "drag drop not enabled, ignoring drag info."));
+ return;
+ }
+
+ DragInformation::parseDragInfo(m_dragFileList, fileNum, data);
+
+ m_screen->startDraggingFiles(m_dragFileList);
+}
+
+bool
+Client::isReceivedFileSizeValid()
+{
+ return m_expectedFileSize == m_receivedFileData.size();
+}
+
+void
+Client::sendFileToServer(const char* filename)
+{
+ if (m_sendFileThread != NULL) {
+ StreamChunker::interruptFile();
+ }
+
+ m_sendFileThread = new Thread(
+ new TMethodJob<Client>(
+ this, &Client::sendFileThread,
+ static_cast<void*>(const_cast<char*>(filename))));
+}
+
+void
+Client::sendFileThread(void* filename)
+{
+ try {
+ char* name = static_cast<char*>(filename);
+ StreamChunker::sendFile(name, m_events, this);
+ }
+ catch (std::runtime_error& error) {
+ LOG((CLOG_ERR "failed sending file chunks: %s", error.what()));
+ }
+
+ m_sendFileThread = NULL;
+}
+
+void
+Client::sendDragInfo(UInt32 fileCount, String& info, size_t size)
+{
+ m_server->sendDragInfo(fileCount, info.c_str(), size);
+}
diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h
new file mode 100644
index 0000000..7ac515d
--- /dev/null
+++ b/src/lib/client/Client.h
@@ -0,0 +1,227 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/IClient.h"
+
+#include "barrier/Clipboard.h"
+#include "barrier/DragInformation.h"
+#include "barrier/INode.h"
+#include "barrier/ClientArgs.h"
+#include "net/NetworkAddress.h"
+#include "base/EventTypes.h"
+#include "mt/CondVar.h"
+
+class EventQueueTimer;
+namespace barrier { class Screen; }
+class ServerProxy;
+class IDataSocket;
+class ISocketFactory;
+namespace barrier { class IStream; }
+class IEventQueue;
+class Thread;
+class TCPSocket;
+
+//! Barrier client
+/*!
+This class implements the top-level client algorithms for barrier.
+*/
+class Client : public IClient, public INode {
+public:
+ class FailInfo {
+ public:
+ FailInfo(const char* what) : m_retry(false), m_what(what) { }
+ bool m_retry;
+ String m_what;
+ };
+
+public:
+ /*!
+ 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.
+ */
+ Client(IEventQueue* events, const String& name,
+ const NetworkAddress& address, ISocketFactory* socketFactory,
+ barrier::Screen* screen, ClientArgs const& args);
+
+ ~Client();
+
+ //! @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.
+ */
+ virtual void handshakeComplete();
+
+ //! Received drag information
+ void dragInfoReceived(UInt32 fileNum, String data);
+
+ //! Create a new thread and use it to send file to Server
+ void sendFileToServer(const char* filename);
+
+ //! Send dragging file information back to server
+ void sendDragInfo(UInt32 fileCount, String& info, size_t size);
+
+
+ //@}
+ //! @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.
+ */
+ NetworkAddress getServerAddress() const;
+
+ //! Return true if recieved file size is valid
+ bool isReceivedFileSizeValid();
+
+ //! Return expected file size
+ size_t& getExpectedFileSize() { return m_expectedFileSize; }
+
+ //! Return received file data
+ String& getReceivedFileData() { return m_receivedFileData; }
+
+ //! Return drag file list
+ DragFileList getDragFileList() { return m_dragFileList; }
+
+ //@}
+
+ // 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 OptionsList& options);
+ virtual String getName() const;
+
+private:
+ void sendClipboard(ClipboardID);
+ void sendEvent(Event::Type, void*);
+ void sendConnectionFailedEvent(const char* msg);
+ void sendFileChunk(const void* data);
+ void sendFileThread(void*);
+ void writeToDropDirThread(void*);
+ void setupConnecting();
+ void setupConnection();
+ void setupScreen();
+ void setupTimer();
+ void cleanupConnecting();
+ void cleanupConnection();
+ void cleanupScreen();
+ void cleanupTimer();
+ void cleanupStream();
+ void handleConnected(const Event&, void*);
+ void handleConnectionFailed(const Event&, void*);
+ void handleConnectTimeout(const Event&, void*);
+ void handleOutputError(const Event&, void*);
+ void handleDisconnected(const Event&, void*);
+ void handleShapeChanged(const Event&, void*);
+ void handleClipboardGrabbed(const Event&, void*);
+ void handleHello(const Event&, void*);
+ void handleSuspend(const Event& event, void*);
+ void handleResume(const Event& event, void*);
+ void handleFileChunkSending(const Event&, void*);
+ void handleFileRecieveCompleted(const Event&, void*);
+ void handleStopRetry(const Event&, void*);
+ void onFileRecieveCompleted();
+ void sendClipboardThread(void*);
+
+public:
+ bool m_mock;
+
+private:
+ String m_name;
+ NetworkAddress m_serverAddress;
+ ISocketFactory* m_socketFactory;
+ barrier::Screen* m_screen;
+ barrier::IStream* m_stream;
+ EventQueueTimer* m_timer;
+ ServerProxy* 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];
+ String m_dataClipboard[kClipboardEnd];
+ IEventQueue* m_events;
+ std::size_t m_expectedFileSize;
+ String m_receivedFileData;
+ DragFileList m_dragFileList;
+ String m_dragFileExt;
+ Thread* m_sendFileThread;
+ Thread* m_writeToDropDirThread;
+ TCPSocket* m_socket;
+ bool m_useSecureNetwork;
+ ClientArgs m_args;
+ bool m_enableClipboard;
+};
diff --git a/src/lib/client/ServerProxy.cpp b/src/lib/client/ServerProxy.cpp
new file mode 100644
index 0000000..9224db6
--- /dev/null
+++ b/src/lib/client/ServerProxy.cpp
@@ -0,0 +1,908 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "client/ServerProxy.h"
+
+#include "client/Client.h"
+#include "barrier/FileChunk.h"
+#include "barrier/ClipboardChunk.h"
+#include "barrier/StreamChunker.h"
+#include "barrier/Clipboard.h"
+#include "barrier/ProtocolUtil.h"
+#include "barrier/option_types.h"
+#include "barrier/protocol_types.h"
+#include "io/IStream.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+#include "base/XBase.h"
+
+#include <memory>
+
+//
+// ServerProxy
+//
+
+ServerProxy::ServerProxy(Client* client, barrier::IStream* stream, IEventQueue* events) :
+ 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(&ServerProxy::parseHandshakeMessage),
+ m_events(events)
+{
+ 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
+ m_events->adoptHandler(m_events->forIStream().inputReady(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<ServerProxy>(this,
+ &ServerProxy::handleData));
+
+ m_events->adoptHandler(m_events->forClipboard().clipboardSending(),
+ this,
+ new TMethodEventJob<ServerProxy>(this,
+ &ServerProxy::handleClipboardSendingEvent));
+
+ // send heartbeat
+ setKeepAliveRate(kKeepAliveRate);
+}
+
+ServerProxy::~ServerProxy()
+{
+ setKeepAliveRate(-1.0);
+ m_events->removeHandler(m_events->forIStream().inputReady(),
+ m_stream->getEventTarget());
+}
+
+void
+ServerProxy::resetKeepAliveAlarm()
+{
+ if (m_keepAliveAlarmTimer != NULL) {
+ m_events->removeHandler(Event::kTimer, m_keepAliveAlarmTimer);
+ m_events->deleteTimer(m_keepAliveAlarmTimer);
+ m_keepAliveAlarmTimer = NULL;
+ }
+ if (m_keepAliveAlarm > 0.0) {
+ m_keepAliveAlarmTimer =
+ m_events->newOneShotTimer(m_keepAliveAlarm, NULL);
+ m_events->adoptHandler(Event::kTimer, m_keepAliveAlarmTimer,
+ new TMethodEventJob<ServerProxy>(this,
+ &ServerProxy::handleKeepAliveAlarm));
+ }
+}
+
+void
+ServerProxy::setKeepAliveRate(double rate)
+{
+ m_keepAliveAlarm = rate * kKeepAlivesUntilDeath;
+ resetKeepAliveAlarm();
+}
+
+void
+ServerProxy::handleData(const Event&, 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();
+}
+
+ServerProxy::EResult
+ServerProxy::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 = &ServerProxy::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
+ ProtocolUtil::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;
+ ProtocolUtil::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;
+}
+
+ServerProxy::EResult
+ServerProxy::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
+ ProtocolUtil::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, kMsgDFileTransfer, 4) == 0) {
+ fileChunkReceived();
+ }
+ else if (memcmp(code, kMsgDDragInfo, 4) == 0) {
+ dragInfoReceived();
+ }
+
+ 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.
+ ProtocolUtil::writef(m_stream, kMsgCNoop);
+
+ return kOkay;
+}
+
+void
+ServerProxy::handleKeepAliveAlarm(const Event&, void*)
+{
+ LOG((CLOG_NOTE "server is dead"));
+ m_client->disconnect("server is not responding");
+}
+
+void
+ServerProxy::onInfoChanged()
+{
+ // ignore mouse motion until we receive acknowledgment of our info
+ // change message.
+ m_ignoreMouse = true;
+
+ // send info update
+ queryInfo();
+}
+
+bool
+ServerProxy::onGrabClipboard(ClipboardID id)
+{
+ LOG((CLOG_DEBUG1 "sending clipboard %d changed", id));
+ ProtocolUtil::writef(m_stream, kMsgCClipboard, id, m_seqNum);
+ return true;
+}
+
+void
+ServerProxy::onClipboardChanged(ClipboardID id, const IClipboard* clipboard)
+{
+ String data = IClipboard::marshall(clipboard);
+ LOG((CLOG_DEBUG "sending clipboard %d seqnum=%d", id, m_seqNum));
+
+ StreamChunker::sendClipboard(data, data.size(), id, m_seqNum, m_events, this);
+}
+
+void
+ServerProxy::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
+ServerProxy::sendInfo(const ClientInfo& info)
+{
+ LOG((CLOG_DEBUG1 "sending info shape=%d,%d %dx%d", info.m_x, info.m_y, info.m_w, info.m_h));
+ ProtocolUtil::writef(m_stream, kMsgDInfo,
+ info.m_x, info.m_y,
+ info.m_w, info.m_h, 0,
+ info.m_mx, info.m_my);
+}
+
+KeyID
+ServerProxy::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 },
+ { kKeyAltGr, kKeyAltGr}
+ };
+
+ 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 kKeyAltGr:
+ id2 = kKeyModifierIDAltGr;
+ side = 1; // there is only one alt gr key on the right side
+ 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
+ServerProxy::translateModifierMask(KeyModifierMask mask) const
+{
+ static const KeyModifierMask s_masks[kKeyModifierIDLast] = {
+ 0x0000,
+ KeyModifierShift,
+ KeyModifierControl,
+ KeyModifierAlt,
+ KeyModifierMeta,
+ KeyModifierSuper,
+ KeyModifierAltGr
+ };
+
+ KeyModifierMask newMask = mask & ~(KeyModifierShift |
+ KeyModifierControl |
+ KeyModifierAlt |
+ KeyModifierMeta |
+ KeyModifierSuper |
+ KeyModifierAltGr );
+ 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 & KeyModifierAltGr) != 0) {
+ newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDAltGr]];
+ }
+ if ((mask & KeyModifierMeta) != 0) {
+ newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDMeta]];
+ }
+ if ((mask & KeyModifierSuper) != 0) {
+ newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDSuper]];
+ }
+ return newMask;
+}
+
+void
+ServerProxy::enter()
+{
+ // parse
+ SInt16 x, y;
+ UInt16 mask;
+ UInt32 seqNum;
+ ProtocolUtil::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<KeyModifierMask>(mask), false);
+}
+
+void
+ServerProxy::leave()
+{
+ // parse
+ LOG((CLOG_DEBUG1 "recv leave"));
+
+ // send last mouse motion
+ flushCompressedMouse();
+
+ // forward
+ m_client->leave();
+}
+
+void
+ServerProxy::setClipboard()
+{
+ // parse
+ static String dataCached;
+ ClipboardID id;
+ UInt32 seq;
+
+ int r = ClipboardChunk::assemble(m_stream, dataCached, id, seq);
+
+ if (r == kStart) {
+ size_t size = ClipboardChunk::getExpectedSize();
+ LOG((CLOG_DEBUG "receiving clipboard %d size=%d", id, size));
+ }
+ else if (r == kFinish) {
+ LOG((CLOG_DEBUG "received clipboard %d size=%d", id, dataCached.size()));
+
+ // forward
+ Clipboard clipboard;
+ clipboard.unmarshall(dataCached, 0);
+ m_client->setClipboard(id, &clipboard);
+
+ LOG((CLOG_INFO "clipboard was updated"));
+ }
+}
+
+void
+ServerProxy::grabClipboard()
+{
+ // parse
+ ClipboardID id;
+ UInt32 seqNum;
+ ProtocolUtil::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
+ServerProxy::keyDown()
+{
+ // get mouse up to date
+ flushCompressedMouse();
+
+ // parse
+ UInt16 id, mask, button;
+ ProtocolUtil::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<KeyID>(id));
+ KeyModifierMask mask2 = translateModifierMask(
+ static_cast<KeyModifierMask>(mask));
+ if (id2 != static_cast<KeyID>(id) ||
+ mask2 != static_cast<KeyModifierMask>(mask))
+ LOG((CLOG_DEBUG1 "key down translated to id=0x%08x, mask=0x%04x", id2, mask2));
+
+ // forward
+ m_client->keyDown(id2, mask2, button);
+}
+
+void
+ServerProxy::keyRepeat()
+{
+ // get mouse up to date
+ flushCompressedMouse();
+
+ // parse
+ UInt16 id, mask, count, button;
+ ProtocolUtil::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<KeyID>(id));
+ KeyModifierMask mask2 = translateModifierMask(
+ static_cast<KeyModifierMask>(mask));
+ if (id2 != static_cast<KeyID>(id) ||
+ mask2 != static_cast<KeyModifierMask>(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
+ServerProxy::keyUp()
+{
+ // get mouse up to date
+ flushCompressedMouse();
+
+ // parse
+ UInt16 id, mask, button;
+ ProtocolUtil::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<KeyID>(id));
+ KeyModifierMask mask2 = translateModifierMask(
+ static_cast<KeyModifierMask>(mask));
+ if (id2 != static_cast<KeyID>(id) ||
+ mask2 != static_cast<KeyModifierMask>(mask))
+ LOG((CLOG_DEBUG1 "key up translated to id=0x%08x, mask=0x%04x", id2, mask2));
+
+ // forward
+ m_client->keyUp(id2, mask2, button);
+}
+
+void
+ServerProxy::mouseDown()
+{
+ // get mouse up to date
+ flushCompressedMouse();
+
+ // parse
+ SInt8 id;
+ ProtocolUtil::readf(m_stream, kMsgDMouseDown + 4, &id);
+ LOG((CLOG_DEBUG1 "recv mouse down id=%d", id));
+
+ // forward
+ m_client->mouseDown(static_cast<ButtonID>(id));
+}
+
+void
+ServerProxy::mouseUp()
+{
+ // get mouse up to date
+ flushCompressedMouse();
+
+ // parse
+ SInt8 id;
+ ProtocolUtil::readf(m_stream, kMsgDMouseUp + 4, &id);
+ LOG((CLOG_DEBUG1 "recv mouse up id=%d", id));
+
+ // forward
+ m_client->mouseUp(static_cast<ButtonID>(id));
+}
+
+void
+ServerProxy::mouseMove()
+{
+ // parse
+ bool ignore;
+ SInt16 x, y;
+ ProtocolUtil::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
+ServerProxy::mouseRelativeMove()
+{
+ // parse
+ bool ignore;
+ SInt16 dx, dy;
+ ProtocolUtil::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
+ServerProxy::mouseWheel()
+{
+ // get mouse up to date
+ flushCompressedMouse();
+
+ // parse
+ SInt16 xDelta, yDelta;
+ ProtocolUtil::readf(m_stream, kMsgDMouseWheel + 4, &xDelta, &yDelta);
+ LOG((CLOG_DEBUG2 "recv mouse wheel %+d,%+d", xDelta, yDelta));
+
+ // forward
+ m_client->mouseWheel(xDelta, yDelta);
+}
+
+void
+ServerProxy::screensaver()
+{
+ // parse
+ SInt8 on;
+ ProtocolUtil::readf(m_stream, kMsgCScreenSaver + 4, &on);
+ LOG((CLOG_DEBUG1 "recv screen saver on=%d", on));
+
+ // forward
+ m_client->screensaver(on != 0);
+}
+
+void
+ServerProxy::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
+ServerProxy::setOptions()
+{
+ // parse
+ OptionsList options;
+ ProtocolUtil::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 = (UInt32)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] == kOptionModifierMapForAltGr) {
+ id = kKeyModifierIDAltGr;
+ }
+ 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<double>(options[i + 1]));
+ }
+
+ if (id != kKeyModifierIDNull) {
+ m_modifierTranslationTable[id] =
+ static_cast<KeyModifierID>(options[i + 1]);
+ LOG((CLOG_DEBUG1 "modifier %d mapped to %d", id, m_modifierTranslationTable[id]));
+ }
+ }
+}
+
+void
+ServerProxy::queryInfo()
+{
+ ClientInfo 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
+ServerProxy::infoAcknowledgment()
+{
+ LOG((CLOG_DEBUG1 "recv info acknowledgment"));
+ m_ignoreMouse = false;
+}
+
+void
+ServerProxy::fileChunkReceived()
+{
+ int result = FileChunk::assemble(
+ m_stream,
+ m_client->getReceivedFileData(),
+ m_client->getExpectedFileSize());
+
+ if (result == kFinish) {
+ m_events->addEvent(Event(m_events->forFile().fileRecieveCompleted(), m_client));
+ }
+ else if (result == kStart) {
+ if (m_client->getDragFileList().size() > 0) {
+ String filename = m_client->getDragFileList().at(0).getFilename();
+ LOG((CLOG_DEBUG "start receiving %s", filename.c_str()));
+ }
+ }
+}
+
+void
+ServerProxy::dragInfoReceived()
+{
+ // parse
+ UInt32 fileNum = 0;
+ String content;
+ ProtocolUtil::readf(m_stream, kMsgDDragInfo + 4, &fileNum, &content);
+
+ m_client->dragInfoReceived(fileNum, content);
+}
+
+void
+ServerProxy::handleClipboardSendingEvent(const Event& event, void*)
+{
+ ClipboardChunk::send(m_stream, event.getData());
+}
+
+void
+ServerProxy::fileChunkSending(UInt8 mark, char* data, size_t dataSize)
+{
+ FileChunk::send(m_stream, mark, data, dataSize);
+}
+
+void
+ServerProxy::sendDragInfo(UInt32 fileCount, const char* info, size_t size)
+{
+ String data(info, size);
+ ProtocolUtil::writef(m_stream, kMsgDDragInfo, fileCount, &data);
+}
diff --git a/src/lib/client/ServerProxy.h b/src/lib/client/ServerProxy.h
new file mode 100644
index 0000000..2ad711a
--- /dev/null
+++ b/src/lib/client/ServerProxy.h
@@ -0,0 +1,133 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/clipboard_types.h"
+#include "barrier/key_types.h"
+#include "base/Event.h"
+#include "base/Stopwatch.h"
+#include "base/String.h"
+
+class Client;
+class ClientInfo;
+class EventQueueTimer;
+class IClipboard;
+namespace barrier { class IStream; }
+class IEventQueue;
+
+//! 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 ServerProxy {
+public:
+ /*!
+ Process messages from the server on \p stream and forward to
+ \p client.
+ */
+ ServerProxy(Client* client, barrier::IStream* stream, IEventQueue* events);
+ ~ServerProxy();
+
+ //! @name manipulators
+ //@{
+
+ void onInfoChanged();
+ bool onGrabClipboard(ClipboardID);
+ void onClipboardChanged(ClipboardID, const IClipboard*);
+
+ //@}
+
+ // sending file chunk to server
+ void fileChunkSending(UInt8 mark, char* data, size_t dataSize);
+
+ // sending dragging information to server
+ void sendDragInfo(UInt32 fileCount, const char* info, size_t size);
+
+#ifdef TEST_ENV
+ void handleDataForTest() { handleData(Event(), NULL); }
+#endif
+
+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 ClientInfo&);
+
+ void resetKeepAliveAlarm();
+ void setKeepAliveRate(double);
+
+ // modifier key translation
+ KeyID translateKey(KeyID) const;
+ KeyModifierMask translateModifierMask(KeyModifierMask) const;
+
+ // event handlers
+ void handleData(const Event&, void*);
+ void handleKeepAliveAlarm(const Event&, 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();
+ void fileChunkReceived();
+ void dragInfoReceived();
+ void handleClipboardSendingEvent(const Event&, void*);
+
+private:
+ typedef EResult (ServerProxy::*MessageParser)(const UInt8*);
+
+ Client* m_client;
+ barrier::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;
+ EventQueueTimer* m_keepAliveAlarmTimer;
+
+ MessageParser m_parser;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/common/CMakeLists.txt b/src/lib/common/CMakeLists.txt
new file mode 100644
index 0000000..56d9fc9
--- /dev/null
+++ b/src/lib/common/CMakeLists.txt
@@ -0,0 +1,24 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+file(GLOB headers "*.h")
+file(GLOB sources "*.cpp")
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+add_library(common STATIC ${sources})
diff --git a/src/lib/common/IInterface.h b/src/lib/common/IInterface.h
new file mode 100644
index 0000000..84a76a9
--- /dev/null
+++ b/src/lib/common/IInterface.h
@@ -0,0 +1,32 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/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() { }
+};
diff --git a/src/lib/common/MacOSXPrecomp.h b/src/lib/common/MacOSXPrecomp.h
new file mode 100644
index 0000000..7dbc8d0
--- /dev/null
+++ b/src/lib/common/MacOSXPrecomp.h
@@ -0,0 +1,23 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+ //
+// Prefix header for all source files of the 'deleteme' target in the 'deleteme' project.
+//
+
+#include <Carbon/Carbon.h>
diff --git a/src/lib/common/Version.cpp b/src/lib/common/Version.cpp
new file mode 100644
index 0000000..94d6c5d
--- /dev/null
+++ b/src/lib/common/Version.cpp
@@ -0,0 +1,29 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/Version.h"
+
+const char* kApplication = "Barrier";
+const char* kCopyright = "Copyright (C) 2018 Debauchee Open Source Group\n"
+ "Copyright (C) 2012-2016 Symless Ltd.\n"
+ "Copyright (C) 2008-2014 Nick Bolton\n"
+ "Copyright (C) 2002-2014 Chris Schoeneman";
+const char* kContact = "Email: todo@mail.com";
+const char* kWebsite = "https://github.com/debauchee/barrier/";
+const char* kVersion = BARRIER_VERSION;
+const char* kAppVersion = "Barrier " BARRIER_VERSION;
diff --git a/src/lib/common/Version.h b/src/lib/common/Version.h
new file mode 100644
index 0000000..66bb2e2
--- /dev/null
+++ b/src/lib/common/Version.h
@@ -0,0 +1,39 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/common.h"
+
+// set version macro if not set yet
+#if !defined(BARRIER_VERSION)
+#error Version was not set (should be passed to compiler).
+#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;
diff --git a/src/lib/common/basic_types.h b/src/lib/common/basic_types.h
new file mode 100644
index 0000000..f84550c
--- /dev/null
+++ b/src/lib/common/basic_types.h
@@ -0,0 +1,92 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/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.
+//
+
+// Added this because it doesn't compile on OS X 10.6 because they are already defined in Carbon
+#if !defined(__MACTYPES__)
+#if defined(__APPLE__)
+#include <CoreServices/CoreServices.h>
+#else
+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;
+#endif
+#endif
+//
+// clean up
+//
+
+#undef TYPE_OF_SIZE_1
+#undef TYPE_OF_SIZE_2
+#undef TYPE_OF_SIZE_4
diff --git a/src/lib/common/common.h b/src/lib/common/common.h
new file mode 100644
index 0000000..5ea215f
--- /dev/null
+++ b/src/lib/common/common.h
@@ -0,0 +1,58 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#if defined(_WIN32)
+# define SYSAPI_WIN32 1
+# define WINAPI_MSWINDOWS 1
+#elif HAVE_CONFIG_H
+# include "config.h"
+#else
+# error "config.h missing"
+#endif
+
+// VC++ has built-in sized types
+#if defined(_MSC_VER)
+# include <wchar.h>
+# 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
+
+// define NULL
+#include <stddef.h>
+
+// make assert available since we use it a lot
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+enum {
+ kExitSuccess = 0, // successful completion
+ kExitFailed = 1, // general failure
+ kExitTerminated = 2, // killed by signal
+ kExitArgs = 3, // bad arguments
+ kExitConfig = 4, // cannot read configuration
+ kExitSubscription = 5 // subscription error
+};
diff --git a/src/lib/common/stdbitset.h b/src/lib/common/stdbitset.h
new file mode 100644
index 0000000..1096249
--- /dev/null
+++ b/src/lib/common/stdbitset.h
@@ -0,0 +1,21 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/stdpre.h"
+#include <bitset>
+#include "common/stdpost.h"
diff --git a/src/lib/common/stddeque.h b/src/lib/common/stddeque.h
new file mode 100644
index 0000000..ffaed24
--- /dev/null
+++ b/src/lib/common/stddeque.h
@@ -0,0 +1,21 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/stdpre.h"
+#include <deque>
+#include "common/stdpost.h"
diff --git a/src/lib/common/stdexcept.h b/src/lib/common/stdexcept.h
new file mode 100644
index 0000000..2bd96b3
--- /dev/null
+++ b/src/lib/common/stdexcept.h
@@ -0,0 +1,23 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdexcept>
+
+// apple declares _NOEXCEPT
+#ifndef _NOEXCEPT
+# define _NOEXCEPT throw()
+#endif
diff --git a/src/lib/common/stdfstream.h b/src/lib/common/stdfstream.h
new file mode 100644
index 0000000..e9aa263
--- /dev/null
+++ b/src/lib/common/stdfstream.h
@@ -0,0 +1,22 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/stdpre.h"
+#include <fstream>
+#include "common/stdpost.h"
+#include "common/stdistream.h"
diff --git a/src/lib/common/stdistream.h b/src/lib/common/stdistream.h
new file mode 100644
index 0000000..b19e2ab
--- /dev/null
+++ b/src/lib/common/stdistream.h
@@ -0,0 +1,47 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/stdpre.h"
+#if HAVE_ISTREAM
+#include <istream>
+#else
+#include <iostream>
+#endif
+#include "common/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/src/lib/common/stdlist.h b/src/lib/common/stdlist.h
new file mode 100644
index 0000000..d530e57
--- /dev/null
+++ b/src/lib/common/stdlist.h
@@ -0,0 +1,21 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/stdpre.h"
+#include <list>
+#include "common/stdpost.h"
diff --git a/src/lib/common/stdmap.h b/src/lib/common/stdmap.h
new file mode 100644
index 0000000..2351074
--- /dev/null
+++ b/src/lib/common/stdmap.h
@@ -0,0 +1,21 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/stdpre.h"
+#include <map>
+#include "common/stdpost.h"
diff --git a/src/lib/common/stdostream.h b/src/lib/common/stdostream.h
new file mode 100644
index 0000000..bb82285
--- /dev/null
+++ b/src/lib/common/stdostream.h
@@ -0,0 +1,25 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/stdpre.h"
+#if HAVE_OSTREAM
+#include <ostream>
+#else
+#include <iostream>
+#endif
+#include "common/stdpost.h"
diff --git a/src/lib/common/stdpost.h b/src/lib/common/stdpost.h
new file mode 100644
index 0000000..8046da0
--- /dev/null
+++ b/src/lib/common/stdpost.h
@@ -0,0 +1,21 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
diff --git a/src/lib/common/stdpre.h b/src/lib/common/stdpre.h
new file mode 100644
index 0000000..8ccd308
--- /dev/null
+++ b/src/lib/common/stdpre.h
@@ -0,0 +1,31 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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/src/lib/common/stdset.h b/src/lib/common/stdset.h
new file mode 100644
index 0000000..1c98971
--- /dev/null
+++ b/src/lib/common/stdset.h
@@ -0,0 +1,21 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/stdpre.h"
+#include <set>
+#include "common/stdpost.h"
diff --git a/src/lib/common/stdsstream.h b/src/lib/common/stdsstream.h
new file mode 100644
index 0000000..43671ff
--- /dev/null
+++ b/src/lib/common/stdsstream.h
@@ -0,0 +1,376 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/stdpre.h"
+
+#if HAVE_SSTREAM || !defined(__GNUC__) || (__GNUC__ >= 3)
+
+#include <sstream>
+
+#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) 2012-2016 Symless Ltd.
+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 LICENSE. 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 <iostream.h>
+#include <streambuf.h>
+#include <string>
+
+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<ios::open_mode>(which)),
+ stream(NULL), stream_len(0)
+ {
+ stringbuf_init();
+ }
+
+ explicit
+ stringbuf(const string &str, int which=ios::in|ios::out)
+ : streambuf(), mode(static_cast<ios::open_mode>(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<stringbuf*>(&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<stringbuf*>(&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<stringbuf*>(&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 <sstream> */
+
+#error "Standard C++ library is missing required sstream header."
+
+#endif /* not g++ 2.95 and no <sstream> */
+
+#include "common/stdpost.h"
+#include "common/stdistream.h"
diff --git a/src/lib/common/stdstring.h b/src/lib/common/stdstring.h
new file mode 100644
index 0000000..f320ca8
--- /dev/null
+++ b/src/lib/common/stdstring.h
@@ -0,0 +1,21 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/stdpre.h"
+#include <string>
+#include "common/stdpost.h"
diff --git a/src/lib/common/stdvector.h b/src/lib/common/stdvector.h
new file mode 100644
index 0000000..ab4b853
--- /dev/null
+++ b/src/lib/common/stdvector.h
@@ -0,0 +1,21 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/stdpre.h"
+#include <vector>
+#include "common/stdpost.h"
diff --git a/src/lib/io/CMakeLists.txt b/src/lib/io/CMakeLists.txt
new file mode 100644
index 0000000..32ae7ec
--- /dev/null
+++ b/src/lib/io/CMakeLists.txt
@@ -0,0 +1,24 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+file(GLOB headers "*.h")
+file(GLOB sources "*.cpp")
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+add_library(io STATIC ${sources})
diff --git a/src/lib/io/IStream.h b/src/lib/io/IStream.h
new file mode 100644
index 0000000..cf93ac4
--- /dev/null
+++ b/src/lib/io/IStream.h
@@ -0,0 +1,120 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "base/Event.h"
+#include "base/IEventQueue.h"
+#include "base/EventTypes.h"
+
+class IEventQueue;
+
+namespace barrier {
+
+//! Bidirectional stream interface
+/*!
+Defines the interface for all streams.
+*/
+class IStream : public IInterface {
+public:
+ IStream() { }
+
+ //! @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;
+
+ //@}
+};
+
+}
diff --git a/src/lib/io/StreamBuffer.cpp b/src/lib/io/StreamBuffer.cpp
new file mode 100644
index 0000000..61f05ba
--- /dev/null
+++ b/src/lib/io/StreamBuffer.cpp
@@ -0,0 +1,146 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "io/StreamBuffer.h"
+
+//
+// StreamBuffer
+//
+
+const UInt32 StreamBuffer::kChunkSize = 4096;
+
+StreamBuffer::StreamBuffer() :
+ m_size(0),
+ m_headUsed(0)
+{
+ // do nothing
+}
+
+StreamBuffer::~StreamBuffer()
+{
+ // do nothing
+}
+
+const void*
+StreamBuffer::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 static_cast<const void*>(&(head->begin()[m_headUsed]));
+}
+
+void
+StreamBuffer::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 -= (UInt32)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
+StreamBuffer::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 = static_cast<const UInt8*>(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 - (UInt32)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
+StreamBuffer::getSize() const
+{
+ return m_size;
+}
diff --git a/src/lib/io/StreamBuffer.h b/src/lib/io/StreamBuffer.h
new file mode 100644
index 0000000..49b666b
--- /dev/null
+++ b/src/lib/io/StreamBuffer.h
@@ -0,0 +1,79 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/EventTypes.h"
+#include "common/stdlist.h"
+#include "common/stdvector.h"
+
+//! FIFO of bytes
+/*!
+This class maintains a FIFO (first-in, last-out) buffer of bytes.
+*/
+class StreamBuffer {
+public:
+ StreamBuffer();
+ ~StreamBuffer();
+
+ //! @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<UInt8> Chunk;
+ typedef std::list<Chunk> ChunkList;
+
+ ChunkList m_chunks;
+ UInt32 m_size;
+ UInt32 m_headUsed;
+};
diff --git a/src/lib/io/StreamFilter.cpp b/src/lib/io/StreamFilter.cpp
new file mode 100644
index 0000000..170e237
--- /dev/null
+++ b/src/lib/io/StreamFilter.cpp
@@ -0,0 +1,118 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "io/StreamFilter.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+//
+// StreamFilter
+//
+
+StreamFilter::StreamFilter(IEventQueue* events, barrier::IStream* stream, bool adoptStream) :
+ m_stream(stream),
+ m_adopted(adoptStream),
+ m_events(events)
+{
+ // replace handlers for m_stream
+ m_events->removeHandlers(m_stream->getEventTarget());
+ m_events->adoptHandler(Event::kUnknown, m_stream->getEventTarget(),
+ new TMethodEventJob<StreamFilter>(this,
+ &StreamFilter::handleUpstreamEvent));
+}
+
+StreamFilter::~StreamFilter()
+{
+ m_events->removeHandler(Event::kUnknown, m_stream->getEventTarget());
+ if (m_adopted) {
+ delete m_stream;
+ }
+}
+
+void
+StreamFilter::close()
+{
+ getStream()->close();
+}
+
+UInt32
+StreamFilter::read(void* buffer, UInt32 n)
+{
+ return getStream()->read(buffer, n);
+}
+
+void
+StreamFilter::write(const void* buffer, UInt32 n)
+{
+ getStream()->write(buffer, n);
+}
+
+void
+StreamFilter::flush()
+{
+ getStream()->flush();
+}
+
+void
+StreamFilter::shutdownInput()
+{
+ getStream()->shutdownInput();
+}
+
+void
+StreamFilter::shutdownOutput()
+{
+ getStream()->shutdownOutput();
+}
+
+void*
+StreamFilter::getEventTarget() const
+{
+ return const_cast<void*>(static_cast<const void*>(this));
+}
+
+bool
+StreamFilter::isReady() const
+{
+ return getStream()->isReady();
+}
+
+UInt32
+StreamFilter::getSize() const
+{
+ return getStream()->getSize();
+}
+
+barrier::IStream*
+StreamFilter::getStream() const
+{
+ return m_stream;
+}
+
+void
+StreamFilter::filterEvent(const Event& event)
+{
+ m_events->dispatchEvent(Event(event.getType(),
+ getEventTarget(), event.getData()));
+}
+
+void
+StreamFilter::handleUpstreamEvent(const Event& event, void*)
+{
+ filterEvent(event);
+}
diff --git a/src/lib/io/StreamFilter.h b/src/lib/io/StreamFilter.h
new file mode 100644
index 0000000..e578e0c
--- /dev/null
+++ b/src/lib/io/StreamFilter.h
@@ -0,0 +1,73 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "io/IStream.h"
+#include "base/IEventQueue.h"
+
+//! A stream filter
+/*!
+This class wraps a stream. Subclasses provide indirect access
+to the wrapped stream, typically performing some filtering.
+*/
+class StreamFilter : public barrier::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.
+ */
+ StreamFilter(IEventQueue* events, barrier::IStream* stream, bool adoptStream = true);
+ virtual ~StreamFilter();
+
+ // 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;
+
+ //! Get the stream
+ /*!
+ Returns the stream passed to the c'tor.
+ */
+ barrier::IStream* getStream() const;
+
+protected:
+ //! 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 Event&);
+
+private:
+ void handleUpstreamEvent(const Event&, void*);
+
+private:
+ barrier::IStream* m_stream;
+ bool m_adopted;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/io/XIO.cpp b/src/lib/io/XIO.cpp
new file mode 100644
index 0000000..1299d23
--- /dev/null
+++ b/src/lib/io/XIO.cpp
@@ -0,0 +1,51 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "io/XIO.h"
+
+//
+// XIOClosed
+//
+
+String
+XIOClosed::getWhat() const throw()
+{
+ return format("XIOClosed", "already closed");
+}
+
+
+//
+// XIOEndOfStream
+//
+
+String
+XIOEndOfStream::getWhat() const throw()
+{
+ return format("XIOEndOfStream", "reached end of stream");
+}
+
+
+//
+// XIOWouldBlock
+//
+
+String
+XIOWouldBlock::getWhat() const throw()
+{
+ return format("XIOWouldBlock", "stream operation would block");
+}
diff --git a/src/lib/io/XIO.h b/src/lib/io/XIO.h
new file mode 100644
index 0000000..4964441
--- /dev/null
+++ b/src/lib/io/XIO.h
@@ -0,0 +1,49 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/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);
diff --git a/src/lib/ipc/CMakeLists.txt b/src/lib/ipc/CMakeLists.txt
new file mode 100644
index 0000000..3c7302a
--- /dev/null
+++ b/src/lib/ipc/CMakeLists.txt
@@ -0,0 +1,28 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+file(GLOB headers "*.h")
+file(GLOB sources "*.cpp")
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+add_library(ipc STATIC ${sources})
+
+if (UNIX)
+ target_link_libraries(ipc arch base common mt io net synlib)
+endif()
diff --git a/src/lib/ipc/Ipc.cpp b/src/lib/ipc/Ipc.cpp
new file mode 100644
index 0000000..78b8407
--- /dev/null
+++ b/src/lib/ipc/Ipc.cpp
@@ -0,0 +1,24 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ipc/Ipc.h"
+
+const char* kIpcMsgHello = "IHEL%1i";
+const char* kIpcMsgLogLine = "ILOG%s";
+const char* kIpcMsgCommand = "ICMD%s%1i";
+const char* kIpcMsgShutdown = "ISDN";
diff --git a/src/lib/ipc/Ipc.h b/src/lib/ipc/Ipc.h
new file mode 100644
index 0000000..bc69c08
--- /dev/null
+++ b/src/lib/ipc/Ipc.h
@@ -0,0 +1,52 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#define IPC_HOST "127.0.0.1"
+#define IPC_PORT 24801
+
+enum EIpcMessage {
+ kIpcHello,
+ kIpcLogLine,
+ kIpcCommand,
+ kIpcShutdown,
+};
+
+enum EIpcClientType {
+ kIpcClientUnknown,
+ kIpcClientGui,
+ kIpcClientNode,
+};
+
+// handshake: node/gui -> daemon
+// $1 = type, the client identifies it's self as gui or node (barrierc/s).
+extern const char* kIpcMsgHello;
+
+// log line: daemon -> gui
+// $1 = aggregate log lines collected from barriers/c or the daemon itself.
+extern const char* kIpcMsgLogLine;
+
+// command: gui -> daemon
+// $1 = command; the command for the daemon to launch, typically the full
+// path to barriers/c. $2 = true when process must be elevated on ms windows.
+extern const char* kIpcMsgCommand;
+
+// shutdown: daemon -> node
+// the daemon tells barriers/c to shut down gracefully.
+extern const char* kIpcMsgShutdown;
diff --git a/src/lib/ipc/IpcClient.cpp b/src/lib/ipc/IpcClient.cpp
new file mode 100644
index 0000000..4eeae5b
--- /dev/null
+++ b/src/lib/ipc/IpcClient.cpp
@@ -0,0 +1,108 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ipc/IpcClient.h"
+#include "ipc/Ipc.h"
+#include "ipc/IpcServerProxy.h"
+#include "ipc/IpcMessage.h"
+#include "base/TMethodEventJob.h"
+
+//
+// IpcClient
+//
+
+IpcClient::IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer) :
+ m_serverAddress(NetworkAddress(IPC_HOST, IPC_PORT)),
+ m_socket(events, socketMultiplexer, IArchNetwork::kINET),
+ m_server(nullptr),
+ m_events(events)
+{
+ init();
+}
+
+IpcClient::IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port) :
+ m_serverAddress(NetworkAddress(IPC_HOST, port)),
+ m_socket(events, socketMultiplexer, IArchNetwork::kINET),
+ m_server(nullptr),
+ m_events(events)
+{
+ init();
+}
+
+void
+IpcClient::init()
+{
+ m_serverAddress.resolve();
+}
+
+IpcClient::~IpcClient()
+{
+}
+
+void
+IpcClient::connect()
+{
+ m_events->adoptHandler(
+ m_events->forIDataSocket().connected(), m_socket.getEventTarget(),
+ new TMethodEventJob<IpcClient>(
+ this, &IpcClient::handleConnected));
+
+ m_socket.connect(m_serverAddress);
+ m_server = new IpcServerProxy(m_socket, m_events);
+
+ m_events->adoptHandler(
+ m_events->forIpcServerProxy().messageReceived(), m_server,
+ new TMethodEventJob<IpcClient>(
+ this, &IpcClient::handleMessageReceived));
+}
+
+void
+IpcClient::disconnect()
+{
+ m_events->removeHandler(m_events->forIDataSocket().connected(), m_socket.getEventTarget());
+ m_events->removeHandler(m_events->forIpcServerProxy().messageReceived(), m_server);
+
+ m_server->disconnect();
+ delete m_server;
+ m_server = nullptr;
+}
+
+void
+IpcClient::send(const IpcMessage& message)
+{
+ assert(m_server != nullptr);
+ m_server->send(message);
+}
+
+void
+IpcClient::handleConnected(const Event&, void*)
+{
+ m_events->addEvent(Event(
+ m_events->forIpcClient().connected(), this, m_server, Event::kDontFreeData));
+
+ IpcHelloMessage message(kIpcClientNode);
+ send(message);
+}
+
+void
+IpcClient::handleMessageReceived(const Event& e, void*)
+{
+ Event event(m_events->forIpcClient().messageReceived(), this);
+ event.setDataObject(e.getDataObject());
+ m_events->addEvent(event);
+}
diff --git a/src/lib/ipc/IpcClient.h b/src/lib/ipc/IpcClient.h
new file mode 100644
index 0000000..1e9bca6
--- /dev/null
+++ b/src/lib/ipc/IpcClient.h
@@ -0,0 +1,64 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "net/NetworkAddress.h"
+#include "net/TCPSocket.h"
+#include "base/EventTypes.h"
+
+class IpcServerProxy;
+class IpcMessage;
+class IEventQueue;
+class SocketMultiplexer;
+
+//! IPC client for communication between daemon and GUI.
+/*!
+ * See \ref IpcServer description.
+ */
+class IpcClient {
+public:
+ IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer);
+ IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port);
+ virtual ~IpcClient();
+
+ //! @name manipulators
+ //@{
+
+ //! Connects to the IPC server at localhost.
+ void connect();
+
+ //! Disconnects from the IPC server.
+ void disconnect();
+
+ //! Sends a message to the server.
+ void send(const IpcMessage& message);
+
+ //@}
+
+private:
+ void init();
+ void handleConnected(const Event&, void*);
+ void handleMessageReceived(const Event&, void*);
+
+private:
+ NetworkAddress m_serverAddress;
+ TCPSocket m_socket;
+ IpcServerProxy* m_server;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/ipc/IpcClientProxy.cpp b/src/lib/ipc/IpcClientProxy.cpp
new file mode 100644
index 0000000..af85eca
--- /dev/null
+++ b/src/lib/ipc/IpcClientProxy.cpp
@@ -0,0 +1,194 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ipc/IpcClientProxy.h"
+
+#include "ipc/Ipc.h"
+#include "ipc/IpcMessage.h"
+#include "barrier/ProtocolUtil.h"
+#include "io/IStream.h"
+#include "arch/Arch.h"
+#include "base/TMethodEventJob.h"
+#include "base/Log.h"
+
+//
+// IpcClientProxy
+//
+
+IpcClientProxy::IpcClientProxy(barrier::IStream& stream, IEventQueue* events) :
+ m_stream(stream),
+ m_clientType(kIpcClientUnknown),
+ m_disconnecting(false),
+ m_readMutex(ARCH->newMutex()),
+ m_writeMutex(ARCH->newMutex()),
+ m_events(events)
+{
+ m_events->adoptHandler(
+ m_events->forIStream().inputReady(), stream.getEventTarget(),
+ new TMethodEventJob<IpcClientProxy>(
+ this, &IpcClientProxy::handleData));
+
+ m_events->adoptHandler(
+ m_events->forIStream().outputError(), stream.getEventTarget(),
+ new TMethodEventJob<IpcClientProxy>(
+ this, &IpcClientProxy::handleWriteError));
+
+ m_events->adoptHandler(
+ m_events->forIStream().inputShutdown(), stream.getEventTarget(),
+ new TMethodEventJob<IpcClientProxy>(
+ this, &IpcClientProxy::handleDisconnect));
+
+ m_events->adoptHandler(
+ m_events->forIStream().outputShutdown(), stream.getEventTarget(),
+ new TMethodEventJob<IpcClientProxy>(
+ this, &IpcClientProxy::handleWriteError));
+}
+
+IpcClientProxy::~IpcClientProxy()
+{
+ m_events->removeHandler(
+ m_events->forIStream().inputReady(), m_stream.getEventTarget());
+ m_events->removeHandler(
+ m_events->forIStream().outputError(), m_stream.getEventTarget());
+ m_events->removeHandler(
+ m_events->forIStream().inputShutdown(), m_stream.getEventTarget());
+ m_events->removeHandler(
+ m_events->forIStream().outputShutdown(), m_stream.getEventTarget());
+
+ // don't delete the stream while it's being used.
+ ARCH->lockMutex(m_readMutex);
+ ARCH->lockMutex(m_writeMutex);
+ delete &m_stream;
+ ARCH->unlockMutex(m_readMutex);
+ ARCH->unlockMutex(m_writeMutex);
+
+ ARCH->closeMutex(m_readMutex);
+ ARCH->closeMutex(m_writeMutex);
+}
+
+void
+IpcClientProxy::handleDisconnect(const Event&, void*)
+{
+ disconnect();
+ LOG((CLOG_DEBUG "ipc client disconnected"));
+}
+
+void
+IpcClientProxy::handleWriteError(const Event&, void*)
+{
+ disconnect();
+ LOG((CLOG_DEBUG "ipc client write error"));
+}
+
+void
+IpcClientProxy::handleData(const Event&, void*)
+{
+ // don't allow the dtor to destroy the stream while we're using it.
+ ArchMutexLock lock(m_readMutex);
+
+ LOG((CLOG_DEBUG "start ipc handle data"));
+
+ UInt8 code[4];
+ UInt32 n = m_stream.read(code, 4);
+ while (n != 0) {
+
+ LOG((CLOG_DEBUG "ipc read: %c%c%c%c",
+ code[0], code[1], code[2], code[3]));
+
+ IpcMessage* m = nullptr;
+ if (memcmp(code, kIpcMsgHello, 4) == 0) {
+ m = parseHello();
+ }
+ else if (memcmp(code, kIpcMsgCommand, 4) == 0) {
+ m = parseCommand();
+ }
+ else {
+ LOG((CLOG_ERR "invalid ipc message"));
+ disconnect();
+ }
+
+ // don't delete with this event; the data is passed to a new event.
+ Event e(m_events->forIpcClientProxy().messageReceived(), this, NULL, Event::kDontFreeData);
+ e.setDataObject(m);
+ m_events->addEvent(e);
+
+ n = m_stream.read(code, 4);
+ }
+
+ LOG((CLOG_DEBUG "finished ipc handle data"));
+}
+
+void
+IpcClientProxy::send(const IpcMessage& message)
+{
+ // don't allow other threads to write until we've finished the entire
+ // message. stream write is locked, but only for that single write.
+ // also, don't allow the dtor to destroy the stream while we're using it.
+ ArchMutexLock lock(m_writeMutex);
+
+ LOG((CLOG_DEBUG4 "ipc write: %d", message.type()));
+
+ switch (message.type()) {
+ case kIpcLogLine: {
+ const IpcLogLineMessage& llm = static_cast<const IpcLogLineMessage&>(message);
+ const String logLine = llm.logLine();
+ ProtocolUtil::writef(&m_stream, kIpcMsgLogLine, &logLine);
+ break;
+ }
+
+ case kIpcShutdown:
+ ProtocolUtil::writef(&m_stream, kIpcMsgShutdown);
+ break;
+
+ default:
+ LOG((CLOG_ERR "ipc message not supported: %d", message.type()));
+ break;
+ }
+}
+
+IpcHelloMessage*
+IpcClientProxy::parseHello()
+{
+ UInt8 type;
+ ProtocolUtil::readf(&m_stream, kIpcMsgHello + 4, &type);
+
+ m_clientType = static_cast<EIpcClientType>(type);
+
+ // must be deleted by event handler.
+ return new IpcHelloMessage(m_clientType);
+}
+
+IpcCommandMessage*
+IpcClientProxy::parseCommand()
+{
+ String command;
+ UInt8 elevate;
+ ProtocolUtil::readf(&m_stream, kIpcMsgCommand + 4, &command, &elevate);
+
+ // must be deleted by event handler.
+ return new IpcCommandMessage(command, elevate != 0);
+}
+
+void
+IpcClientProxy::disconnect()
+{
+ LOG((CLOG_DEBUG "ipc disconnect, closing stream"));
+ m_disconnecting = true;
+ m_stream.close();
+ m_events->addEvent(Event(m_events->forIpcClientProxy().disconnected(), this));
+}
diff --git a/src/lib/ipc/IpcClientProxy.h b/src/lib/ipc/IpcClientProxy.h
new file mode 100644
index 0000000..eaa12c7
--- /dev/null
+++ b/src/lib/ipc/IpcClientProxy.h
@@ -0,0 +1,55 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ipc/Ipc.h"
+#include "arch/IArchMultithread.h"
+#include "base/EventTypes.h"
+#include "base/Event.h"
+
+namespace barrier { class IStream; }
+class IpcMessage;
+class IpcCommandMessage;
+class IpcHelloMessage;
+class IEventQueue;
+
+class IpcClientProxy {
+ friend class IpcServer;
+
+public:
+ IpcClientProxy(barrier::IStream& stream, IEventQueue* events);
+ virtual ~IpcClientProxy();
+
+private:
+ void send(const IpcMessage& message);
+ void handleData(const Event&, void*);
+ void handleDisconnect(const Event&, void*);
+ void handleWriteError(const Event&, void*);
+ IpcHelloMessage* parseHello();
+ IpcCommandMessage* parseCommand();
+ void disconnect();
+
+private:
+ barrier::IStream& m_stream;
+ EIpcClientType m_clientType;
+ bool m_disconnecting;
+ ArchMutex m_readMutex;
+ ArchMutex m_writeMutex;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/ipc/IpcLogOutputter.cpp b/src/lib/ipc/IpcLogOutputter.cpp
new file mode 100644
index 0000000..984793e
--- /dev/null
+++ b/src/lib/ipc/IpcLogOutputter.cpp
@@ -0,0 +1,228 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ipc/IpcLogOutputter.h"
+
+#include "ipc/IpcServer.h"
+#include "ipc/IpcMessage.h"
+#include "ipc/Ipc.h"
+#include "ipc/IpcClientProxy.h"
+#include "mt/Thread.h"
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+#include "base/Event.h"
+#include "base/EventQueue.h"
+#include "base/TMethodEventJob.h"
+#include "base/TMethodJob.h"
+
+enum EIpcLogOutputter {
+ kBufferMaxSize = 1000,
+ kMaxSendLines = 100,
+ kBufferRateWriteLimit = 1000, // writes per kBufferRateTime
+ kBufferRateTimeLimit = 1 // seconds
+};
+
+IpcLogOutputter::IpcLogOutputter(IpcServer& ipcServer, EIpcClientType clientType, bool useThread) :
+ m_ipcServer(ipcServer),
+ m_bufferMutex(ARCH->newMutex()),
+ m_sending(false),
+ m_bufferThread(nullptr),
+ m_running(false),
+ m_notifyCond(ARCH->newCondVar()),
+ m_notifyMutex(ARCH->newMutex()),
+ m_bufferThreadId(0),
+ m_bufferWaiting(false),
+ m_bufferMaxSize(kBufferMaxSize),
+ m_bufferRateWriteLimit(kBufferRateWriteLimit),
+ m_bufferRateTimeLimit(kBufferRateTimeLimit),
+ m_bufferWriteCount(0),
+ m_bufferRateStart(ARCH->time()),
+ m_clientType(clientType),
+ m_runningMutex(ARCH->newMutex())
+{
+ if (useThread) {
+ m_bufferThread = new Thread(new TMethodJob<IpcLogOutputter>(
+ this, &IpcLogOutputter::bufferThread));
+ }
+}
+
+IpcLogOutputter::~IpcLogOutputter()
+{
+ close();
+
+ ARCH->closeMutex(m_bufferMutex);
+
+ if (m_bufferThread != nullptr) {
+ m_bufferThread->cancel();
+ m_bufferThread->wait();
+ delete m_bufferThread;
+ }
+
+ ARCH->closeCondVar(m_notifyCond);
+ ARCH->closeMutex(m_notifyMutex);
+}
+
+void
+IpcLogOutputter::open(const char* title)
+{
+}
+
+void
+IpcLogOutputter::close()
+{
+ if (m_bufferThread != nullptr) {
+ ArchMutexLock lock(m_runningMutex);
+ m_running = false;
+ notifyBuffer();
+ m_bufferThread->wait(5);
+ }
+}
+
+void
+IpcLogOutputter::show(bool showIfEmpty)
+{
+}
+
+bool
+IpcLogOutputter::write(ELevel, const char* text)
+{
+ // ignore events from the buffer thread (would cause recursion).
+ if (m_bufferThread != nullptr &&
+ Thread::getCurrentThread().getID() == m_bufferThreadId) {
+ return true;
+ }
+
+ appendBuffer(text);
+ notifyBuffer();
+
+ return true;
+}
+
+void
+IpcLogOutputter::appendBuffer(const String& text)
+{
+ ArchMutexLock lock(m_bufferMutex);
+
+ double elapsed = ARCH->time() - m_bufferRateStart;
+ if (elapsed < m_bufferRateTimeLimit) {
+ if (m_bufferWriteCount >= m_bufferRateWriteLimit) {
+ // discard the log line if we've logged too much.
+ return;
+ }
+ }
+ else {
+ m_bufferWriteCount = 0;
+ m_bufferRateStart = ARCH->time();
+ }
+
+ if (m_buffer.size() >= m_bufferMaxSize) {
+ // if the queue is exceeds size limit,
+ // throw away the oldest item
+ m_buffer.pop_front();
+ }
+
+ m_buffer.push_back(text);
+ m_bufferWriteCount++;
+}
+
+bool
+IpcLogOutputter::isRunning()
+{
+ ArchMutexLock lock(m_runningMutex);
+ return m_running;
+}
+
+void
+IpcLogOutputter::bufferThread(void*)
+{
+ m_bufferThreadId = m_bufferThread->getID();
+ m_running = true;
+
+ try {
+ while (isRunning()) {
+ if (m_buffer.empty() || !m_ipcServer.hasClients(m_clientType)) {
+ ArchMutexLock lock(m_notifyMutex);
+ ARCH->waitCondVar(m_notifyCond, m_notifyMutex, -1);
+ }
+
+ sendBuffer();
+ }
+ }
+ catch (XArch& e) {
+ LOG((CLOG_ERR "ipc log buffer thread error, %s", e.what()));
+ }
+
+ LOG((CLOG_DEBUG "ipc log buffer thread finished"));
+}
+
+void
+IpcLogOutputter::notifyBuffer()
+{
+ ArchMutexLock lock(m_notifyMutex);
+ ARCH->broadcastCondVar(m_notifyCond);
+}
+
+String
+IpcLogOutputter::getChunk(size_t count)
+{
+ ArchMutexLock lock(m_bufferMutex);
+
+ if (m_buffer.size() < count) {
+ count = m_buffer.size();
+ }
+
+ String chunk;
+ for (size_t i = 0; i < count; i++) {
+ chunk.append(m_buffer.front());
+ chunk.append("\n");
+ m_buffer.pop_front();
+ }
+ return chunk;
+}
+
+void
+IpcLogOutputter::sendBuffer()
+{
+ if (m_buffer.empty() || !m_ipcServer.hasClients(m_clientType)) {
+ return;
+ }
+
+ IpcLogLineMessage message(getChunk(kMaxSendLines));
+ m_sending = true;
+ m_ipcServer.send(message, kIpcClientGui);
+ m_sending = false;
+}
+
+void
+IpcLogOutputter::bufferMaxSize(UInt16 bufferMaxSize)
+{
+ m_bufferMaxSize = bufferMaxSize;
+}
+
+UInt16
+IpcLogOutputter::bufferMaxSize() const
+{
+ return m_bufferMaxSize;
+}
+
+void
+IpcLogOutputter::bufferRateLimit(UInt16 writeLimit, double timeLimit)
+{
+ m_bufferRateWriteLimit = writeLimit;
+ m_bufferRateTimeLimit = timeLimit;
+}
diff --git a/src/lib/ipc/IpcLogOutputter.h b/src/lib/ipc/IpcLogOutputter.h
new file mode 100644
index 0000000..461f022
--- /dev/null
+++ b/src/lib/ipc/IpcLogOutputter.h
@@ -0,0 +1,119 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/Arch.h"
+#include "arch/IArchMultithread.h"
+#include "base/ILogOutputter.h"
+#include "ipc/Ipc.h"
+
+#include <deque>
+
+class IpcServer;
+class Event;
+class IpcClientProxy;
+
+//! Write log to GUI over IPC
+/*!
+This outputter writes output to the GUI via IPC.
+*/
+class IpcLogOutputter : public ILogOutputter {
+public:
+ /*!
+ If \p useThread is \c true, the buffer will be sent using a thread.
+ If \p useThread is \c false, then the buffer needs to be sent manually
+ using the \c sendBuffer() function.
+ */
+ IpcLogOutputter(IpcServer& ipcServer, EIpcClientType clientType, bool useThread);
+ virtual ~IpcLogOutputter();
+
+ // ILogOutputter overrides
+ virtual void open(const char* title);
+ virtual void close();
+ virtual void show(bool showIfEmpty);
+ virtual bool write(ELevel level, const char* message);
+
+ //! @name manipulators
+ //@{
+
+ //! Notify that the buffer should be sent.
+ void notifyBuffer();
+
+ //! Set the buffer size
+ /*!
+ Set the maximum size of the buffer to protect memory
+ from runaway logging.
+ */
+ void bufferMaxSize(UInt16 bufferMaxSize);
+
+ //! Set the rate limit
+ /*!
+ Set the maximum number of \p writeRate for every \p timeRate in seconds.
+ */
+ void bufferRateLimit(UInt16 writeLimit, double timeLimit);
+
+ //! Send the buffer
+ /*!
+ Sends a chunk of the buffer to the IPC server, normally called
+ when threaded mode is on.
+ */
+ void sendBuffer();
+
+ //@}
+
+ //! @name accessors
+ //@{
+
+ //! Get the buffer size
+ /*!
+ Returns the maximum size of the buffer.
+ */
+ UInt16 bufferMaxSize() const;
+
+ //@}
+
+private:
+ void init();
+ void bufferThread(void*);
+ String getChunk(size_t count);
+ void appendBuffer(const String& text);
+ bool isRunning();
+
+private:
+ typedef std::deque<String> Buffer;
+
+ IpcServer& m_ipcServer;
+ Buffer m_buffer;
+ ArchMutex m_bufferMutex;
+ bool m_sending;
+ Thread* m_bufferThread;
+ bool m_running;
+ ArchCond m_notifyCond;
+ ArchMutex m_notifyMutex;
+ bool m_bufferWaiting;
+ IArchMultithread::ThreadID
+ m_bufferThreadId;
+ UInt16 m_bufferMaxSize;
+ UInt16 m_bufferRateWriteLimit;
+ double m_bufferRateTimeLimit;
+ UInt16 m_bufferWriteCount;
+ double m_bufferRateStart;
+ EIpcClientType m_clientType;
+ ArchMutex m_runningMutex;
+};
diff --git a/src/lib/ipc/IpcMessage.cpp b/src/lib/ipc/IpcMessage.cpp
new file mode 100644
index 0000000..deef22d
--- /dev/null
+++ b/src/lib/ipc/IpcMessage.cpp
@@ -0,0 +1,69 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ipc/IpcMessage.h"
+#include "ipc/Ipc.h"
+
+IpcMessage::IpcMessage(UInt8 type) :
+ m_type(type)
+{
+}
+
+IpcMessage::~IpcMessage()
+{
+}
+
+IpcHelloMessage::IpcHelloMessage(EIpcClientType clientType) :
+ IpcMessage(kIpcHello),
+ m_clientType(clientType)
+{
+}
+
+IpcHelloMessage::~IpcHelloMessage()
+{
+}
+
+IpcShutdownMessage::IpcShutdownMessage() :
+IpcMessage(kIpcShutdown)
+{
+}
+
+IpcShutdownMessage::~IpcShutdownMessage()
+{
+}
+
+IpcLogLineMessage::IpcLogLineMessage(const String& logLine) :
+IpcMessage(kIpcLogLine),
+m_logLine(logLine)
+{
+}
+
+IpcLogLineMessage::~IpcLogLineMessage()
+{
+}
+
+IpcCommandMessage::IpcCommandMessage(const String& command, bool elevate) :
+IpcMessage(kIpcCommand),
+m_command(command),
+m_elevate(elevate)
+{
+}
+
+IpcCommandMessage::~IpcCommandMessage()
+{
+}
diff --git a/src/lib/ipc/IpcMessage.h b/src/lib/ipc/IpcMessage.h
new file mode 100644
index 0000000..5cc3d79
--- /dev/null
+++ b/src/lib/ipc/IpcMessage.h
@@ -0,0 +1,85 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ipc/Ipc.h"
+#include "base/EventTypes.h"
+#include "base/String.h"
+#include "base/Event.h"
+
+class IpcMessage : public EventData {
+public:
+ virtual ~IpcMessage();
+
+ //! Gets the message type ID.
+ UInt8 type() const { return m_type; }
+
+protected:
+ IpcMessage(UInt8 type);
+
+private:
+ UInt8 m_type;
+};
+
+class IpcHelloMessage : public IpcMessage {
+public:
+ IpcHelloMessage(EIpcClientType clientType);
+ virtual ~IpcHelloMessage();
+
+ //! Gets the message type ID.
+ EIpcClientType clientType() const { return m_clientType; }
+
+private:
+ EIpcClientType m_clientType;
+};
+
+class IpcShutdownMessage : public IpcMessage {
+public:
+ IpcShutdownMessage();
+ virtual ~IpcShutdownMessage();
+};
+
+
+class IpcLogLineMessage : public IpcMessage {
+public:
+ IpcLogLineMessage(const String& logLine);
+ virtual ~IpcLogLineMessage();
+
+ //! Gets the log line.
+ String logLine() const { return m_logLine; }
+
+private:
+ String m_logLine;
+};
+
+class IpcCommandMessage : public IpcMessage {
+public:
+ IpcCommandMessage(const String& command, bool elevate);
+ virtual ~IpcCommandMessage();
+
+ //! Gets the command.
+ String command() const { return m_command; }
+
+ //! Gets whether or not the process should be elevated on MS Windows.
+ bool elevate() const { return m_elevate; }
+
+private:
+ String m_command;
+ bool m_elevate;
+};
diff --git a/src/lib/ipc/IpcServer.cpp b/src/lib/ipc/IpcServer.cpp
new file mode 100644
index 0000000..e05a913
--- /dev/null
+++ b/src/lib/ipc/IpcServer.cpp
@@ -0,0 +1,187 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ipc/IpcServer.h"
+
+#include "ipc/Ipc.h"
+#include "ipc/IpcClientProxy.h"
+#include "ipc/IpcMessage.h"
+#include "net/IDataSocket.h"
+#include "io/IStream.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+#include "base/Event.h"
+#include "base/Log.h"
+
+//
+// IpcServer
+//
+
+IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer) :
+ m_mock(false),
+ m_events(events),
+ m_socketMultiplexer(socketMultiplexer),
+ m_socket(nullptr),
+ m_address(NetworkAddress(IPC_HOST, IPC_PORT))
+{
+ init();
+}
+
+IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port) :
+ m_mock(false),
+ m_events(events),
+ m_socketMultiplexer(socketMultiplexer),
+ m_address(NetworkAddress(IPC_HOST, port))
+{
+ init();
+}
+
+void
+IpcServer::init()
+{
+ m_socket = new TCPListenSocket(m_events, m_socketMultiplexer, IArchNetwork::kINET);
+
+ m_clientsMutex = ARCH->newMutex();
+ m_address.resolve();
+
+ m_events->adoptHandler(
+ m_events->forIListenSocket().connecting(), m_socket,
+ new TMethodEventJob<IpcServer>(
+ this, &IpcServer::handleClientConnecting));
+}
+
+IpcServer::~IpcServer()
+{
+ if (m_mock) {
+ return;
+ }
+
+ if (m_socket != nullptr) {
+ delete m_socket;
+ }
+
+ ARCH->lockMutex(m_clientsMutex);
+ ClientList::iterator it;
+ for (it = m_clients.begin(); it != m_clients.end(); it++) {
+ deleteClient(*it);
+ }
+ m_clients.clear();
+ ARCH->unlockMutex(m_clientsMutex);
+ ARCH->closeMutex(m_clientsMutex);
+
+ m_events->removeHandler(m_events->forIListenSocket().connecting(), m_socket);
+}
+
+void
+IpcServer::listen()
+{
+ m_socket->bind(m_address);
+}
+
+void
+IpcServer::handleClientConnecting(const Event&, void*)
+{
+ barrier::IStream* stream = m_socket->accept();
+ if (stream == NULL) {
+ return;
+ }
+
+ LOG((CLOG_DEBUG "accepted ipc client connection"));
+
+ ARCH->lockMutex(m_clientsMutex);
+ IpcClientProxy* proxy = new IpcClientProxy(*stream, m_events);
+ m_clients.push_back(proxy);
+ ARCH->unlockMutex(m_clientsMutex);
+
+ m_events->adoptHandler(
+ m_events->forIpcClientProxy().disconnected(), proxy,
+ new TMethodEventJob<IpcServer>(
+ this, &IpcServer::handleClientDisconnected));
+
+ m_events->adoptHandler(
+ m_events->forIpcClientProxy().messageReceived(), proxy,
+ new TMethodEventJob<IpcServer>(
+ this, &IpcServer::handleMessageReceived));
+
+ m_events->addEvent(Event(
+ m_events->forIpcServer().clientConnected(), this, proxy, Event::kDontFreeData));
+}
+
+void
+IpcServer::handleClientDisconnected(const Event& e, void*)
+{
+ IpcClientProxy* proxy = static_cast<IpcClientProxy*>(e.getTarget());
+
+ ArchMutexLock lock(m_clientsMutex);
+ m_clients.remove(proxy);
+ deleteClient(proxy);
+
+ LOG((CLOG_DEBUG "ipc client proxy removed, connected=%d", m_clients.size()));
+}
+
+void
+IpcServer::handleMessageReceived(const Event& e, void*)
+{
+ Event event(m_events->forIpcServer().messageReceived(), this);
+ event.setDataObject(e.getDataObject());
+ m_events->addEvent(event);
+}
+
+void
+IpcServer::deleteClient(IpcClientProxy* proxy)
+{
+ m_events->removeHandler(m_events->forIpcClientProxy().messageReceived(), proxy);
+ m_events->removeHandler(m_events->forIpcClientProxy().disconnected(), proxy);
+ delete proxy;
+}
+
+bool
+IpcServer::hasClients(EIpcClientType clientType) const
+{
+ ArchMutexLock lock(m_clientsMutex);
+
+ if (m_clients.empty()) {
+ return false;
+ }
+
+ ClientList::const_iterator it;
+ for (it = m_clients.begin(); it != m_clients.end(); it++) {
+ // at least one client is alive and type matches, there are clients.
+ IpcClientProxy* p = *it;
+ if (!p->m_disconnecting && p->m_clientType == clientType) {
+ return true;
+ }
+ }
+
+ // all clients must be disconnecting, no active clients.
+ return false;
+}
+
+void
+IpcServer::send(const IpcMessage& message, EIpcClientType filterType)
+{
+ ArchMutexLock lock(m_clientsMutex);
+
+ ClientList::iterator it;
+ for (it = m_clients.begin(); it != m_clients.end(); it++) {
+ IpcClientProxy* proxy = *it;
+ if (proxy->m_clientType == filterType) {
+ proxy->send(message);
+ }
+ }
+}
diff --git a/src/lib/ipc/IpcServer.h b/src/lib/ipc/IpcServer.h
new file mode 100644
index 0000000..d9bbe3e
--- /dev/null
+++ b/src/lib/ipc/IpcServer.h
@@ -0,0 +1,92 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ipc/Ipc.h"
+#include "net/TCPListenSocket.h"
+#include "net/NetworkAddress.h"
+#include "arch/Arch.h"
+#include "base/EventTypes.h"
+
+#include <list>
+
+class Event;
+class IpcClientProxy;
+class IpcMessage;
+class IEventQueue;
+class SocketMultiplexer;
+
+//! IPC server for communication between daemon and GUI.
+/*!
+The IPC server listens on localhost. The IPC client runs on both the
+client/server process or the GUI. The IPC server runs on the daemon process.
+This allows the GUI to send config changes to the daemon and client/server,
+and allows the daemon and client/server to send log data to the GUI.
+*/
+class IpcServer {
+public:
+ IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer);
+ IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port);
+ virtual ~IpcServer();
+
+ //! @name manipulators
+ //@{
+
+ //! Opens a TCP socket only allowing local connections.
+ virtual void listen();
+
+ //! Send a message to all clients matching the filter type.
+ virtual void send(const IpcMessage& message, EIpcClientType filterType);
+
+ //@}
+ //! @name accessors
+ //@{
+
+ //! Returns true when there are clients of the specified type connected.
+ virtual bool hasClients(EIpcClientType clientType) const;
+
+ //@}
+
+private:
+ void init();
+ void handleClientConnecting(const Event&, void*);
+ void handleClientDisconnected(const Event&, void*);
+ void handleMessageReceived(const Event&, void*);
+ void deleteClient(IpcClientProxy* proxy);
+
+private:
+ typedef std::list<IpcClientProxy*> ClientList;
+
+ bool m_mock;
+ IEventQueue* m_events;
+ SocketMultiplexer* m_socketMultiplexer;
+ TCPListenSocket* m_socket;
+ NetworkAddress m_address;
+ ClientList m_clients;
+ ArchMutex m_clientsMutex;
+
+#ifdef TEST_ENV
+public:
+ IpcServer() :
+ m_mock(true),
+ m_events(nullptr),
+ m_socketMultiplexer(nullptr),
+ m_socket(nullptr) { }
+#endif
+};
diff --git a/src/lib/ipc/IpcServerProxy.cpp b/src/lib/ipc/IpcServerProxy.cpp
new file mode 100644
index 0000000..820e1ab
--- /dev/null
+++ b/src/lib/ipc/IpcServerProxy.cpp
@@ -0,0 +1,123 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ipc/IpcServerProxy.h"
+
+#include "ipc/IpcMessage.h"
+#include "ipc/Ipc.h"
+#include "barrier/ProtocolUtil.h"
+#include "io/IStream.h"
+#include "base/TMethodEventJob.h"
+#include "base/Log.h"
+
+//
+// IpcServerProxy
+//
+
+IpcServerProxy::IpcServerProxy(barrier::IStream& stream, IEventQueue* events) :
+ m_stream(stream),
+ m_events(events)
+{
+ m_events->adoptHandler(m_events->forIStream().inputReady(),
+ stream.getEventTarget(),
+ new TMethodEventJob<IpcServerProxy>(
+ this, &IpcServerProxy::handleData));
+}
+
+IpcServerProxy::~IpcServerProxy()
+{
+ m_events->removeHandler(m_events->forIStream().inputReady(),
+ m_stream.getEventTarget());
+}
+
+void
+IpcServerProxy::handleData(const Event&, void*)
+{
+ LOG((CLOG_DEBUG "start ipc handle data"));
+
+ UInt8 code[4];
+ UInt32 n = m_stream.read(code, 4);
+ while (n != 0) {
+
+ LOG((CLOG_DEBUG "ipc read: %c%c%c%c",
+ code[0], code[1], code[2], code[3]));
+
+ IpcMessage* m = nullptr;
+ if (memcmp(code, kIpcMsgLogLine, 4) == 0) {
+ m = parseLogLine();
+ }
+ else if (memcmp(code, kIpcMsgShutdown, 4) == 0) {
+ m = new IpcShutdownMessage();
+ }
+ else {
+ LOG((CLOG_ERR "invalid ipc message"));
+ disconnect();
+ }
+
+ // don't delete with this event; the data is passed to a new event.
+ Event e(m_events->forIpcServerProxy().messageReceived(), this, NULL, Event::kDontFreeData);
+ e.setDataObject(m);
+ m_events->addEvent(e);
+
+ n = m_stream.read(code, 4);
+ }
+
+ LOG((CLOG_DEBUG "finished ipc handle data"));
+}
+
+void
+IpcServerProxy::send(const IpcMessage& message)
+{
+ LOG((CLOG_DEBUG4 "ipc write: %d", message.type()));
+
+ switch (message.type()) {
+ case kIpcHello: {
+ const IpcHelloMessage& hm = static_cast<const IpcHelloMessage&>(message);
+ ProtocolUtil::writef(&m_stream, kIpcMsgHello, hm.clientType());
+ break;
+ }
+
+ case kIpcCommand: {
+ const IpcCommandMessage& cm = static_cast<const IpcCommandMessage&>(message);
+ const String command = cm.command();
+ ProtocolUtil::writef(&m_stream, kIpcMsgCommand, &command);
+ break;
+ }
+
+ default:
+ LOG((CLOG_ERR "ipc message not supported: %d", message.type()));
+ break;
+ }
+}
+
+IpcLogLineMessage*
+IpcServerProxy::parseLogLine()
+{
+ String logLine;
+ ProtocolUtil::readf(&m_stream, kIpcMsgLogLine + 4, &logLine);
+
+ // must be deleted by event handler.
+ return new IpcLogLineMessage(logLine);
+}
+
+void
+IpcServerProxy::disconnect()
+{
+ LOG((CLOG_DEBUG "ipc disconnect, closing stream"));
+ m_stream.close();
+}
diff --git a/src/lib/ipc/IpcServerProxy.h b/src/lib/ipc/IpcServerProxy.h
new file mode 100644
index 0000000..f2218a4
--- /dev/null
+++ b/src/lib/ipc/IpcServerProxy.h
@@ -0,0 +1,46 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/Event.h"
+#include "base/EventTypes.h"
+
+namespace barrier { class IStream; }
+class IpcMessage;
+class IpcLogLineMessage;
+class IEventQueue;
+
+class IpcServerProxy {
+ friend class IpcClient;
+
+public:
+ IpcServerProxy(barrier::IStream& stream, IEventQueue* events);
+ virtual ~IpcServerProxy();
+
+private:
+ void send(const IpcMessage& message);
+
+ void handleData(const Event&, void*);
+ IpcLogLineMessage* parseLogLine();
+ void disconnect();
+
+private:
+ barrier::IStream& m_stream;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/mt/CMakeLists.txt b/src/lib/mt/CMakeLists.txt
new file mode 100644
index 0000000..9ee5ea4
--- /dev/null
+++ b/src/lib/mt/CMakeLists.txt
@@ -0,0 +1,24 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+file(GLOB headers "*.h")
+file(GLOB sources "*.cpp")
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+add_library(mt STATIC ${sources})
diff --git a/src/lib/mt/CondVar.cpp b/src/lib/mt/CondVar.cpp
new file mode 100644
index 0000000..11318f9
--- /dev/null
+++ b/src/lib/mt/CondVar.cpp
@@ -0,0 +1,91 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mt/CondVar.h"
+#include "arch/Arch.h"
+#include "base/Stopwatch.h"
+
+//
+// CondVarBase
+//
+
+CondVarBase::CondVarBase(Mutex* mutex) :
+ m_mutex(mutex)
+{
+ assert(m_mutex != NULL);
+ m_cond = ARCH->newCondVar();
+}
+
+CondVarBase::~CondVarBase()
+{
+ ARCH->closeCondVar(m_cond);
+}
+
+void
+CondVarBase::lock() const
+{
+ m_mutex->lock();
+}
+
+void
+CondVarBase::unlock() const
+{
+ m_mutex->unlock();
+}
+
+void
+CondVarBase::signal()
+{
+ ARCH->signalCondVar(m_cond);
+}
+
+void
+CondVarBase::broadcast()
+{
+ ARCH->broadcastCondVar(m_cond);
+}
+
+bool
+CondVarBase::wait(Stopwatch& timer, double timeout) const
+{
+ double remain = timeout-timer.getTime();
+ // Some ARCH wait()s return prematurely, retry until really timed out
+ // In particular, ArchMultithreadPosix::waitCondVar() returns every 100ms
+ do {
+ // Always call wait at least once, even if remain is 0, to give
+ // other thread a chance to grab the mutex to avoid deadlocks on
+ // busy waiting.
+ if (remain<0.0) remain=0.0;
+ if (wait(remain))
+ return true;
+ remain = timeout - timer.getTime();
+ } while (remain >= 0.0);
+ return false;
+}
+
+bool
+CondVarBase::wait(double timeout) const
+{
+ return ARCH->waitCondVar(m_cond, m_mutex->m_mutex, timeout);
+}
+
+Mutex*
+CondVarBase::getMutex() const
+{
+ return m_mutex;
+}
diff --git a/src/lib/mt/CondVar.h b/src/lib/mt/CondVar.h
new file mode 100644
index 0000000..0ab956b
--- /dev/null
+++ b/src/lib/mt/CondVar.h
@@ -0,0 +1,225 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mt/Mutex.h"
+#include "common/basic_types.h"
+
+class Stopwatch;
+
+//! 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 CondVarBase {
+public:
+ /*!
+ \c mutex must not be NULL. All condition variables have an
+ associated mutex. The mutex needn't be unique to one condition
+ variable.
+ */
+ CondVarBase(Mutex* mutex);
+ ~CondVarBase();
+
+ //! @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 \c 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(Stopwatch& timer, double timeout) const;
+
+ //! Get the mutex
+ /*!
+ Get the mutex passed to the c'tor.
+ */
+ Mutex* getMutex() const;
+
+ //@}
+
+private:
+ // not implemented
+ CondVarBase(const CondVarBase&);
+ CondVarBase& operator=(const CondVarBase&);
+
+private:
+ Mutex* m_mutex;
+ ArchCond m_cond;
+};
+
+//! Condition variable
+/*!
+A condition variable with storage for type \c T.
+*/
+template <class T>
+class CondVar : public CondVarBase {
+public:
+ //! Initialize using \c value
+ CondVar(Mutex* mutex, const T& value);
+ //! Initialize using another condition variable's value
+ CondVar(const CondVar&);
+ ~CondVar();
+
+ //! @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.
+ */
+ CondVar& operator=(const CondVar& cv);
+
+ //! Assigns \c value to this
+ /*!
+ Set the variable's value. The condition variable should be locked
+ before calling this method.
+ */
+ CondVar& 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 <class T>
+inline
+CondVar<T>::CondVar(
+ Mutex* mutex,
+ const T& data) :
+ CondVarBase(mutex),
+ m_data(data)
+{
+ // do nothing
+}
+
+template <class T>
+inline
+CondVar<T>::CondVar(
+ const CondVar& cv) :
+ CondVarBase(cv.getMutex()),
+ m_data(cv.m_data)
+{
+ // do nothing
+}
+
+template <class T>
+inline
+CondVar<T>::~CondVar()
+{
+ // do nothing
+}
+
+template <class T>
+inline
+CondVar<T>&
+CondVar<T>::operator=(const CondVar<T>& cv)
+{
+ m_data = cv.m_data;
+ return *this;
+}
+
+template <class T>
+inline
+CondVar<T>&
+CondVar<T>::operator=(const T& data)
+{
+ m_data = data;
+ return *this;
+}
+
+template <class T>
+inline
+CondVar<T>::operator const volatile T&() const
+{
+ return m_data;
+}
diff --git a/src/lib/mt/Lock.cpp b/src/lib/mt/Lock.cpp
new file mode 100644
index 0000000..80721b9
--- /dev/null
+++ b/src/lib/mt/Lock.cpp
@@ -0,0 +1,42 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mt/Lock.h"
+#include "mt/CondVar.h"
+#include "mt/Mutex.h"
+
+//
+// Lock
+//
+
+Lock::Lock(const Mutex* mutex) :
+ m_mutex(mutex)
+{
+ m_mutex->lock();
+}
+
+Lock::Lock(const CondVarBase* cv) :
+ m_mutex(cv->getMutex())
+{
+ m_mutex->lock();
+}
+
+Lock::~Lock()
+{
+ m_mutex->unlock();
+}
diff --git a/src/lib/mt/Lock.h b/src/lib/mt/Lock.h
new file mode 100644
index 0000000..4a3f311
--- /dev/null
+++ b/src/lib/mt/Lock.h
@@ -0,0 +1,49 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/common.h"
+
+class Mutex;
+class CondVarBase;
+
+//! 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 Lock {
+public:
+ //! Lock the mutex \c mutex
+ Lock(const Mutex* mutex);
+ //! Lock the condition variable \c cv
+ Lock(const CondVarBase* cv);
+ //! Unlock the mutex or condition variable
+ ~Lock();
+
+private:
+ // not implemented
+ Lock(const Lock&);
+ Lock& operator=(const Lock&);
+
+private:
+ const Mutex* m_mutex;
+};
diff --git a/src/lib/mt/Mutex.cpp b/src/lib/mt/Mutex.cpp
new file mode 100644
index 0000000..e9a62e8
--- /dev/null
+++ b/src/lib/mt/Mutex.cpp
@@ -0,0 +1,58 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mt/Mutex.h"
+
+#include "arch/Arch.h"
+
+//
+// Mutex
+//
+
+Mutex::Mutex()
+{
+ m_mutex = ARCH->newMutex();
+}
+
+Mutex::Mutex(const Mutex&)
+{
+ m_mutex = ARCH->newMutex();
+}
+
+Mutex::~Mutex()
+{
+ ARCH->closeMutex(m_mutex);
+}
+
+Mutex&
+Mutex::operator=(const Mutex&)
+{
+ return *this;
+}
+
+void
+Mutex::lock() const
+{
+ ARCH->lockMutex(m_mutex);
+}
+
+void
+Mutex::unlock() const
+{
+ ARCH->unlockMutex(m_mutex);
+}
diff --git a/src/lib/mt/Mutex.h b/src/lib/mt/Mutex.h
new file mode 100644
index 0000000..51a9649
--- /dev/null
+++ b/src/lib/mt/Mutex.h
@@ -0,0 +1,79 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/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 Mutex {
+public:
+ Mutex();
+ //! Equivalent to default c'tor
+ /*!
+ Copy c'tor doesn't copy anything. It just makes it possible to
+ copy objects that contain a mutex.
+ */
+ Mutex(const Mutex&);
+ ~Mutex();
+
+ //! @name manipulators
+ //@{
+
+ //! Does nothing
+ /*!
+ This does nothing. It just makes it possible to assign objects
+ that contain a mutex.
+ */
+ Mutex& operator=(const Mutex&);
+
+ //@}
+ //! @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 CondVarBase;
+ ArchMutex m_mutex;
+};
diff --git a/src/lib/mt/Thread.cpp b/src/lib/mt/Thread.cpp
new file mode 100644
index 0000000..7474c16
--- /dev/null
+++ b/src/lib/mt/Thread.cpp
@@ -0,0 +1,187 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mt/Thread.h"
+
+#include "mt/XMT.h"
+#include "mt/XThread.h"
+#include "arch/Arch.h"
+#include "base/Log.h"
+#include "base/IJob.h"
+
+//
+// Thread
+//
+
+Thread::Thread(IJob* job)
+{
+ m_thread = ARCH->newThread(&Thread::threadFunc, job);
+ if (m_thread == NULL) {
+ // couldn't create thread
+ delete job;
+ throw XMTThreadUnavailable();
+ }
+}
+
+Thread::Thread(const Thread& thread)
+{
+ m_thread = ARCH->copyThread(thread.m_thread);
+}
+
+Thread::Thread(ArchThread adoptedThread)
+{
+ m_thread = adoptedThread;
+}
+
+Thread::~Thread()
+{
+ ARCH->closeThread(m_thread);
+}
+
+Thread&
+Thread::operator=(const Thread& thread)
+{
+ // copy given thread and release ours
+ ArchThread copy = ARCH->copyThread(thread.m_thread);
+ ARCH->closeThread(m_thread);
+
+ // cut over
+ m_thread = copy;
+
+ return *this;
+}
+
+void
+Thread::exit(void* result)
+{
+ throw XThreadExit(result);
+}
+
+void
+Thread::cancel()
+{
+ ARCH->cancelThread(m_thread);
+}
+
+void
+Thread::setPriority(int n)
+{
+ ARCH->setPriorityOfThread(m_thread, n);
+}
+
+void
+Thread::unblockPollSocket()
+{
+ ARCH->unblockPollSocket(m_thread);
+}
+
+Thread
+Thread::getCurrentThread()
+{
+ return Thread(ARCH->newCurrentThread());
+}
+
+void
+Thread::testCancel()
+{
+ ARCH->testCancelThread();
+}
+
+bool
+Thread::wait(double timeout) const
+{
+ return ARCH->wait(m_thread, timeout);
+}
+
+void*
+Thread::getResult() const
+{
+ if (wait())
+ return ARCH->getResultOfThread(m_thread);
+ else
+ return NULL;
+}
+
+IArchMultithread::ThreadID
+Thread::getID() const
+{
+ return ARCH->getIDOfThread(m_thread);
+}
+
+bool
+Thread::operator==(const Thread& thread) const
+{
+ return ARCH->isSameThread(m_thread, thread.m_thread);
+}
+
+bool
+Thread::operator!=(const Thread& thread) const
+{
+ return !ARCH->isSameThread(m_thread, thread.m_thread);
+}
+
+void*
+Thread::threadFunc(void* vjob)
+{
+ // get this thread's id for logging
+ IArchMultithread::ThreadID id;
+ {
+ ArchThread thread = ARCH->newCurrentThread();
+ id = ARCH->getIDOfThread(thread);
+ ARCH->closeThread(thread);
+ }
+
+ // get job
+ IJob* job = static_cast<IJob*>(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: <unknown>", id));
+ delete job;
+ throw;
+ }
+
+ // done with job
+ delete job;
+
+ // return exit result
+ return result;
+}
diff --git a/src/lib/mt/Thread.h b/src/lib/mt/Thread.h
new file mode 100644
index 0000000..a7434fd
--- /dev/null
+++ b/src/lib/mt/Thread.h
@@ -0,0 +1,210 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchMultithread.h"
+
+class IJob;
+
+//! Thread handle
+/*!
+Creating a Thread creates a new context of execution (i.e. thread) that
+runs simulatenously with the calling thread. A Thread is only a handle
+to a thread; deleting a Thread does not cancel or destroy the thread it
+refers to and multiple Thread 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 Thread {
+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.
+ */
+ Thread(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.
+ */
+ Thread(const Thread&);
+
+ //! 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.
+ */
+ ~Thread();
+
+ //! @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.
+ */
+ Thread& operator=(const Thread&);
+
+ //! 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(ThreadExit&) { 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 Lock). 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 Thread object representing the calling thread.
+ */
+ static Thread 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 Thread objects refer to the same thread. Use
+ operator==() for that.
+ */
+ IArchMultithread::ThreadID
+ getID() const;
+
+ //! Compare thread handles
+ /*!
+ Returns true if two Thread objects refer to the same thread.
+ */
+ bool operator==(const Thread&) const;
+
+ //! Compare thread handles
+ /*!
+ Returns true if two Thread objects do not refer to the same thread.
+ */
+ bool operator!=(const Thread&) const;
+
+ //@}
+
+private:
+ Thread(ArchThread);
+
+ static void* threadFunc(void*);
+
+private:
+ ArchThread m_thread;
+};
diff --git a/src/lib/mt/XMT.cpp b/src/lib/mt/XMT.cpp
new file mode 100644
index 0000000..9aa5852
--- /dev/null
+++ b/src/lib/mt/XMT.cpp
@@ -0,0 +1,29 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "XMT.h"
+
+//
+// XMTThreadUnavailable
+//
+
+String
+XMTThreadUnavailable::getWhat() const throw()
+{
+ return format("XMTThreadUnavailable", "cannot create thread");
+}
diff --git a/src/lib/mt/XMT.h b/src/lib/mt/XMT.h
new file mode 100644
index 0000000..9e48fd9
--- /dev/null
+++ b/src/lib/mt/XMT.h
@@ -0,0 +1,30 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/XBase.h"
+
+//! Generic multithreading exception
+XBASE_SUBCLASS(XMT, XBase);
+
+//! Thread creation exception
+/*!
+Thrown when a thread cannot be created.
+*/
+XBASE_SUBCLASS_WHAT(XMTThreadUnavailable, XMT);
diff --git a/src/lib/mt/XThread.h b/src/lib/mt/XThread.h
new file mode 100644
index 0000000..acc32e3
--- /dev/null
+++ b/src/lib/mt/XThread.h
@@ -0,0 +1,37 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/XArch.h"
+
+//! Thread exception to exit
+/*!
+Thrown by Thread::exit() to exit a thread. Clients of Thread
+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;
+};
diff --git a/src/lib/net/CMakeLists.txt b/src/lib/net/CMakeLists.txt
new file mode 100644
index 0000000..5439450
--- /dev/null
+++ b/src/lib/net/CMakeLists.txt
@@ -0,0 +1,28 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+file(GLOB headers "*.h")
+file(GLOB sources "*.cpp")
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+add_library(net STATIC ${sources})
+
+if (UNIX)
+ target_link_libraries(net mt io ${OPENSSL_LIBS})
+endif()
diff --git a/src/lib/net/IDataSocket.cpp b/src/lib/net/IDataSocket.cpp
new file mode 100644
index 0000000..cc679c3
--- /dev/null
+++ b/src/lib/net/IDataSocket.cpp
@@ -0,0 +1,39 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "net/IDataSocket.h"
+#include "base/EventQueue.h"
+
+//
+// IDataSocket
+//
+
+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/src/lib/net/IDataSocket.h b/src/lib/net/IDataSocket.h
new file mode 100644
index 0000000..dc07df5
--- /dev/null
+++ b/src/lib/net/IDataSocket.h
@@ -0,0 +1,73 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "net/ISocket.h"
+#include "io/IStream.h"
+#include "base/String.h"
+#include "base/EventTypes.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 barrier::IStream {
+public:
+ class ConnectionFailedInfo {
+ public:
+ ConnectionFailedInfo(const char* what) : m_what(what) { }
+ String m_what;
+ };
+
+ IDataSocket(IEventQueue* events) { }
+
+ //! @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 NetworkAddress&) = 0;
+
+ //@}
+
+ // 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 NetworkAddress&) = 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 bool isFatal() const = 0;
+ virtual UInt32 getSize() const = 0;
+};
diff --git a/src/lib/net/IListenSocket.h b/src/lib/net/IListenSocket.h
new file mode 100644
index 0000000..73dcc6e
--- /dev/null
+++ b/src/lib/net/IListenSocket.h
@@ -0,0 +1,51 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "net/ISocket.h"
+#include "base/EventTypes.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;
+
+ //@}
+
+ // ISocket overrides
+ virtual void bind(const NetworkAddress&) = 0;
+ virtual void close() = 0;
+ virtual void* getEventTarget() const = 0;
+};
diff --git a/src/lib/net/ISocket.h b/src/lib/net/ISocket.h
new file mode 100644
index 0000000..0e9688b
--- /dev/null
+++ b/src/lib/net/ISocket.h
@@ -0,0 +1,60 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "base/Event.h"
+#include "base/EventTypes.h"
+
+class NetworkAddress;
+
+//! 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 NetworkAddress&) = 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;
+
+ //@}
+};
diff --git a/src/lib/net/ISocketFactory.h b/src/lib/net/ISocketFactory.h
new file mode 100644
index 0000000..e440953
--- /dev/null
+++ b/src/lib/net/ISocketFactory.h
@@ -0,0 +1,48 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "arch/IArchNetwork.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(
+ IArchNetwork::EAddressFamily family,
+ bool secure) const = 0;
+
+ //! Create listen socket
+ virtual IListenSocket* createListen(
+ IArchNetwork::EAddressFamily family,
+ bool secure) const = 0;
+
+ //@}
+};
diff --git a/src/lib/net/ISocketMultiplexerJob.h b/src/lib/net/ISocketMultiplexerJob.h
new file mode 100644
index 0000000..ddd3ba5
--- /dev/null
+++ b/src/lib/net/ISocketMultiplexerJob.h
@@ -0,0 +1,76 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchNetwork.h"
+#include "common/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 ArchSocket 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;
+
+ //@}
+};
diff --git a/src/lib/net/NetworkAddress.cpp b/src/lib/net/NetworkAddress.cpp
new file mode 100644
index 0000000..c395ab0
--- /dev/null
+++ b/src/lib/net/NetworkAddress.cpp
@@ -0,0 +1,214 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "net/NetworkAddress.h"
+
+#include "net/XSocket.h"
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+
+#include <cstdlib>
+
+//
+// NetworkAddress
+//
+
+// name re-resolution adapted from a patch by Brent Priddy.
+
+NetworkAddress::NetworkAddress() :
+ m_address(NULL),
+ m_hostname(),
+ m_port(0)
+{
+ // note -- make no calls to Network socket interface here;
+ // we're often called prior to Network::init().
+}
+
+NetworkAddress::NetworkAddress(int port) :
+ m_address(NULL),
+ m_hostname(),
+ m_port(port)
+{
+ checkPort();
+ m_address = ARCH->newAnyAddr(IArchNetwork::kINET);
+ ARCH->setAddrPort(m_address, m_port);
+}
+
+NetworkAddress::NetworkAddress(const NetworkAddress& 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
+}
+
+NetworkAddress::NetworkAddress(const String& hostname, int port) :
+ m_address(NULL),
+ m_hostname(hostname),
+ m_port(port)
+{
+ // check for port suffix
+ String::size_type i = m_hostname.rfind(':');
+ if (i != String::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 (String::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<int>(suffixPort);
+ }
+ }
+
+ // check port number
+ checkPort();
+}
+
+NetworkAddress::~NetworkAddress()
+{
+ if (m_address != NULL) {
+ ARCH->closeAddr(m_address);
+ }
+}
+
+NetworkAddress&
+NetworkAddress::operator=(const NetworkAddress& addr)
+{
+ ArchNetAddress 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
+NetworkAddress::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
+NetworkAddress::operator==(const NetworkAddress& addr) const
+{
+ return ARCH->isEqualAddr(m_address, addr.m_address);
+}
+
+bool
+NetworkAddress::operator!=(const NetworkAddress& addr) const
+{
+ return !operator==(addr);
+}
+
+bool
+NetworkAddress::isValid() const
+{
+ return (m_address != NULL);
+}
+
+const ArchNetAddress&
+NetworkAddress::getAddress() const
+{
+ return m_address;
+}
+
+int
+NetworkAddress::getPort() const
+{
+ return m_port;
+}
+
+String
+NetworkAddress::getHostname() const
+{
+ return m_hostname;
+}
+
+void
+NetworkAddress::checkPort()
+{
+ // check port number
+ if (m_port <= 0 || m_port > 65535) {
+ throw XSocketAddress(XSocketAddress::kBadPort, m_hostname, m_port);
+ }
+}
diff --git a/src/lib/net/NetworkAddress.h b/src/lib/net/NetworkAddress.h
new file mode 100644
index 0000000..cbd15f5
--- /dev/null
+++ b/src/lib/net/NetworkAddress.h
@@ -0,0 +1,123 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+#include "base/EventTypes.h"
+#include "arch/IArchNetwork.h"
+
+//! Network address type
+/*!
+This class represents a network address.
+*/
+class NetworkAddress {
+public:
+ /*!
+ Constructs the invalid address
+ */
+ NetworkAddress();
+
+ /*!
+ Construct the wildcard address with the given port. \c port must
+ not be zero.
+ */
+ NetworkAddress(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.
+ */
+ NetworkAddress(const String& hostname, int port);
+
+ NetworkAddress(const NetworkAddress&);
+
+ ~NetworkAddress();
+
+ NetworkAddress& operator=(const NetworkAddress&);
+
+ //! @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 NetworkAddress& address) const;
+
+ //! Check address inequality
+ /*!
+ Returns true if this address is not equal to \p address.
+ */
+ bool operator!=(const NetworkAddress& 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 ArchNetAddress& 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.
+ */
+ String getHostname() const;
+
+ //@}
+
+private:
+ void checkPort();
+
+private:
+ ArchNetAddress m_address;
+ String m_hostname;
+ int m_port;
+};
diff --git a/src/lib/net/SecureListenSocket.cpp b/src/lib/net/SecureListenSocket.cpp
new file mode 100644
index 0000000..58ffe09
--- /dev/null
+++ b/src/lib/net/SecureListenSocket.cpp
@@ -0,0 +1,85 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "SecureListenSocket.h"
+
+#include "SecureSocket.h"
+#include "net/NetworkAddress.h"
+#include "net/SocketMultiplexer.h"
+#include "net/TSocketMultiplexerMethodJob.h"
+#include "arch/XArch.h"
+
+static const char s_certificateDir[] = { "SSL" };
+static const char s_certificateFilename[] = { "Barrier.pem" };
+
+//
+// SecureListenSocket
+//
+
+SecureListenSocket::SecureListenSocket(
+ IEventQueue* events,
+ SocketMultiplexer* socketMultiplexer,
+ IArchNetwork::EAddressFamily family) :
+ TCPListenSocket(events, socketMultiplexer, family)
+{
+}
+
+IDataSocket*
+SecureListenSocket::accept()
+{
+ SecureSocket* socket = NULL;
+ try {
+ socket = new SecureSocket(
+ m_events,
+ m_socketMultiplexer,
+ ARCH->acceptSocket(m_socket, NULL));
+ socket->initSsl(true);
+
+ if (socket != NULL) {
+ setListeningJob();
+ }
+
+ String certificateFilename = barrier::string::sprintf("%s/%s/%s",
+ ARCH->getProfileDirectory().c_str(),
+ s_certificateDir,
+ s_certificateFilename);
+
+ bool loaded = socket->loadCertificates(certificateFilename);
+ if (!loaded) {
+ delete socket;
+ return NULL;
+ }
+
+ socket->secureAccept();
+
+ return dynamic_cast<IDataSocket*>(socket);
+ }
+ catch (XArchNetwork&) {
+ if (socket != NULL) {
+ delete socket;
+ setListeningJob();
+ }
+ return NULL;
+ }
+ catch (std::exception &ex) {
+ if (socket != NULL) {
+ delete socket;
+ setListeningJob();
+ }
+ throw ex;
+ }
+}
diff --git a/src/lib/net/SecureListenSocket.h b/src/lib/net/SecureListenSocket.h
new file mode 100644
index 0000000..d0c6e23
--- /dev/null
+++ b/src/lib/net/SecureListenSocket.h
@@ -0,0 +1,36 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "net/TCPListenSocket.h"
+#include "common/stdset.h"
+
+class IEventQueue;
+class SocketMultiplexer;
+class IDataSocket;
+
+class SecureListenSocket : public TCPListenSocket{
+public:
+ SecureListenSocket(IEventQueue* events,
+ SocketMultiplexer* socketMultiplexer,
+ IArchNetwork::EAddressFamily family);
+
+ // IListenSocket overrides
+ virtual IDataSocket*
+ accept();
+};
diff --git a/src/lib/net/SecureSocket.cpp b/src/lib/net/SecureSocket.cpp
new file mode 100644
index 0000000..1fefae0
--- /dev/null
+++ b/src/lib/net/SecureSocket.cpp
@@ -0,0 +1,867 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "SecureSocket.h"
+
+#include "net/TSocketMultiplexerMethodJob.h"
+#include "base/TMethodEventJob.h"
+#include "net/TCPSocket.h"
+#include "mt/Lock.h"
+#include "arch/XArch.h"
+#include "base/Log.h"
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <cstring>
+#include <cstdlib>
+#include <memory>
+#include <fstream>
+#include <memory>
+
+//
+// SecureSocket
+//
+
+#define MAX_ERROR_SIZE 65535
+
+static const float s_retryDelay = 0.01f;
+
+enum {
+ kMsgSize = 128
+};
+
+static const char kFingerprintDirName[] = "SSL/Fingerprints";
+//static const char kFingerprintLocalFilename[] = "Local.txt";
+static const char kFingerprintTrustedServersFilename[] = "TrustedServers.txt";
+//static const char kFingerprintTrustedClientsFilename[] = "TrustedClients.txt";
+
+struct Ssl {
+ SSL_CTX* m_context;
+ SSL* m_ssl;
+};
+
+SecureSocket::SecureSocket(
+ IEventQueue* events,
+ SocketMultiplexer* socketMultiplexer,
+ IArchNetwork::EAddressFamily family) :
+ TCPSocket(events, socketMultiplexer, family),
+ m_ssl(nullptr),
+ m_secureReady(false),
+ m_fatal(false)
+{
+}
+
+SecureSocket::SecureSocket(
+ IEventQueue* events,
+ SocketMultiplexer* socketMultiplexer,
+ ArchSocket socket) :
+ TCPSocket(events, socketMultiplexer, socket),
+ m_ssl(nullptr),
+ m_secureReady(false),
+ m_fatal(false)
+{
+}
+
+SecureSocket::~SecureSocket()
+{
+ isFatal(true);
+ // take socket from multiplexer ASAP otherwise the race condition
+ // could cause events to get called on a dead object. TCPSocket
+ // will do this, too, but the double-call is harmless
+ setJob(NULL);
+ if (m_ssl->m_ssl != NULL) {
+ SSL_shutdown(m_ssl->m_ssl);
+
+ SSL_free(m_ssl->m_ssl);
+ m_ssl->m_ssl = NULL;
+ }
+ if (m_ssl->m_context != NULL) {
+ SSL_CTX_free(m_ssl->m_context);
+ m_ssl->m_context = NULL;
+ }
+ // removing sleep() because I have no idea why you would want to do it
+ // ... smells of trying to cover up a bug you don't understand
+ //ARCH->sleep(1);
+ delete m_ssl;
+}
+
+void
+SecureSocket::close()
+{
+ isFatal(true);
+
+ SSL_shutdown(m_ssl->m_ssl);
+
+ TCPSocket::close();
+}
+
+void
+SecureSocket::connect(const NetworkAddress& addr)
+{
+ m_events->adoptHandler(m_events->forIDataSocket().connected(),
+ getEventTarget(),
+ new TMethodEventJob<SecureSocket>(this,
+ &SecureSocket::handleTCPConnected));
+
+ TCPSocket::connect(addr);
+}
+
+ISocketMultiplexerJob*
+SecureSocket::newJob()
+{
+ // after TCP connection is established, SecureSocket will pick up
+ // connected event and do secureConnect
+ if (m_connected && !m_secureReady) {
+ return NULL;
+ }
+
+ return TCPSocket::newJob();
+}
+
+void
+SecureSocket::secureConnect()
+{
+ setJob(new TSocketMultiplexerMethodJob<SecureSocket>(
+ this, &SecureSocket::serviceConnect,
+ getSocket(), isReadable(), isWritable()));
+}
+
+void
+SecureSocket::secureAccept()
+{
+ setJob(new TSocketMultiplexerMethodJob<SecureSocket>(
+ this, &SecureSocket::serviceAccept,
+ getSocket(), isReadable(), isWritable()));
+}
+
+TCPSocket::EJobResult
+SecureSocket::doRead()
+{
+ static UInt8 buffer[4096];
+ memset(buffer, 0, sizeof(buffer));
+ int bytesRead = 0;
+ int status = 0;
+
+ if (isSecureReady()) {
+ status = secureRead(buffer, sizeof(buffer), bytesRead);
+ if (status < 0) {
+ return kBreak;
+ }
+ else if (status == 0) {
+ return kNew;
+ }
+ }
+ else {
+ return kRetry;
+ }
+
+ if (bytesRead > 0) {
+ bool wasEmpty = (m_inputBuffer.getSize() == 0);
+
+ // slurp up as much as possible
+ do {
+ m_inputBuffer.write(buffer, bytesRead);
+
+ status = secureRead(buffer, sizeof(buffer), bytesRead);
+ if (status < 0) {
+ return kBreak;
+ }
+ } while (bytesRead > 0 || status > 0);
+
+ // send input ready if input buffer was empty
+ if (wasEmpty) {
+ sendEvent(m_events->forIStream().inputReady());
+ }
+ }
+ 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(m_events->forIStream().inputShutdown());
+ if (!m_writable && m_inputBuffer.getSize() == 0) {
+ sendEvent(m_events->forISocket().disconnected());
+ m_connected = false;
+ }
+ m_readable = false;
+ return kNew;
+ }
+
+ return kRetry;
+}
+
+TCPSocket::EJobResult
+SecureSocket::doWrite()
+{
+ static bool s_retry = false;
+ static int s_retrySize = 0;
+ static std::unique_ptr<char[]> s_staticBuffer;
+ static std::size_t s_staticBufferSize = 0;
+
+ // write data
+ int bufferSize = 0;
+ int bytesWrote = 0;
+ int status = 0;
+
+ if (!isSecureReady())
+ return kRetry;
+
+ if (s_retry) {
+ bufferSize = s_retrySize;
+ } else {
+ bufferSize = m_outputBuffer.getSize();
+ if (bufferSize > s_staticBufferSize) {
+ s_staticBuffer.reset(new char[bufferSize]);
+ s_staticBufferSize = bufferSize;
+ }
+ if (bufferSize > 0) {
+ memcpy(s_staticBuffer.get(), m_outputBuffer.peek(bufferSize), bufferSize);
+ }
+ }
+
+ if (bufferSize == 0) {
+ return kRetry;
+ }
+
+ status = secureWrite(s_staticBuffer.get(), bufferSize, bytesWrote);
+ if (status > 0) {
+ s_retry = false;
+ } else if (status < 0) {
+ return kBreak;
+ } else if (status == 0) {
+ s_retry = true;
+ s_retrySize = bufferSize;
+ return kNew;
+ }
+
+ if (bytesWrote > 0) {
+ discardWrittenData(bytesWrote);
+ return kNew;
+ }
+
+ return kRetry;
+}
+
+int
+SecureSocket::secureRead(void* buffer, int size, int& read)
+{
+ if (m_ssl->m_ssl != NULL) {
+ LOG((CLOG_DEBUG2 "reading secure socket"));
+ read = SSL_read(m_ssl->m_ssl, buffer, size);
+
+ static int retry;
+
+ // Check result will cleanup the connection in the case of a fatal
+ checkResult(read, retry);
+
+ if (retry) {
+ return 0;
+ }
+
+ if (isFatal()) {
+ return -1;
+ }
+ }
+ // According to SSL spec, the number of bytes read must not be negative and
+ // not have an error code from SSL_get_error(). If this happens, it is
+ // itself an error. Let the parent handle the case
+ return read;
+}
+
+int
+SecureSocket::secureWrite(const void* buffer, int size, int& wrote)
+{
+ if (m_ssl->m_ssl != NULL) {
+ LOG((CLOG_DEBUG2 "writing secure socket:%p", this));
+
+ wrote = SSL_write(m_ssl->m_ssl, buffer, size);
+
+ static int retry;
+
+ // Check result will cleanup the connection in the case of a fatal
+ checkResult(wrote, retry);
+
+ if (retry) {
+ return 0;
+ }
+
+ if (isFatal()) {
+ return -1;
+ }
+ }
+ // According to SSL spec, r must not be negative and not have an error code
+ // from SSL_get_error(). If this happens, it is itself an error. Let the
+ // parent handle the case
+ return wrote;
+}
+
+bool
+SecureSocket::isSecureReady()
+{
+ return m_secureReady;
+}
+
+void
+SecureSocket::initSsl(bool server)
+{
+ m_ssl = new Ssl();
+ m_ssl->m_context = NULL;
+ m_ssl->m_ssl = NULL;
+
+ initContext(server);
+}
+
+bool
+SecureSocket::loadCertificates(String& filename)
+{
+ if (filename.empty()) {
+ showError("ssl certificate is not specified");
+ return false;
+ }
+ else {
+ std::ifstream file(filename.c_str());
+ bool exist = file.good();
+ file.close();
+
+ if (!exist) {
+ String errorMsg("ssl certificate doesn't exist: ");
+ errorMsg.append(filename);
+ showError(errorMsg.c_str());
+ return false;
+ }
+ }
+
+ int r = 0;
+ r = SSL_CTX_use_certificate_file(m_ssl->m_context, filename.c_str(), SSL_FILETYPE_PEM);
+ if (r <= 0) {
+ showError("could not use ssl certificate");
+ return false;
+ }
+
+ r = SSL_CTX_use_PrivateKey_file(m_ssl->m_context, filename.c_str(), SSL_FILETYPE_PEM);
+ if (r <= 0) {
+ showError("could not use ssl private key");
+ return false;
+ }
+
+ r = SSL_CTX_check_private_key(m_ssl->m_context);
+ if (!r) {
+ showError("could not verify ssl private key");
+ return false;
+ }
+
+ return true;
+}
+
+void
+SecureSocket::initContext(bool server)
+{
+ SSL_library_init();
+
+ const SSL_METHOD* method;
+
+ // load & register all cryptos, etc.
+ OpenSSL_add_all_algorithms();
+
+ // load all error messages
+ SSL_load_error_strings();
+
+ if (CLOG->getFilter() >= kINFO) {
+ showSecureLibInfo();
+ }
+
+ // SSLv23_method uses TLSv1, with the ability to fall back to SSLv3
+ if (server) {
+ method = SSLv23_server_method();
+ }
+ else {
+ method = SSLv23_client_method();
+ }
+
+ // create new context from method
+ SSL_METHOD* m = const_cast<SSL_METHOD*>(method);
+ m_ssl->m_context = SSL_CTX_new(m);
+
+ // drop SSLv3 support
+ SSL_CTX_set_options(m_ssl->m_context, SSL_OP_NO_SSLv3);
+
+ if (m_ssl->m_context == NULL) {
+ showError();
+ }
+}
+
+void
+SecureSocket::createSSL()
+{
+ // I assume just one instance is needed
+ // get new SSL state with context
+ if (m_ssl->m_ssl == NULL) {
+ assert(m_ssl->m_context != NULL);
+ m_ssl->m_ssl = SSL_new(m_ssl->m_context);
+ }
+}
+
+int
+SecureSocket::secureAccept(int socket)
+{
+ createSSL();
+
+ // set connection socket to SSL state
+ SSL_set_fd(m_ssl->m_ssl, socket);
+
+ LOG((CLOG_DEBUG2 "accepting secure socket"));
+ int r = SSL_accept(m_ssl->m_ssl);
+
+ static int retry;
+
+ checkResult(r, retry);
+
+ if (isFatal()) {
+ // tell user and sleep so the socket isn't hammered.
+ LOG((CLOG_ERR "failed to accept secure socket"));
+ LOG((CLOG_INFO "client connection may not be secure"));
+ m_secureReady = false;
+ ARCH->sleep(1);
+ retry = 0;
+ return -1; // Failed, error out
+ }
+
+ // If not fatal and no retry, state is good
+ if (retry == 0) {
+ m_secureReady = true;
+ LOG((CLOG_INFO "accepted secure socket"));
+ if (CLOG->getFilter() >= kDEBUG1) {
+ showSecureCipherInfo();
+ }
+ showSecureConnectInfo();
+ return 1;
+ }
+
+ // If not fatal and retry is set, not ready, and return retry
+ if (retry > 0) {
+ LOG((CLOG_DEBUG2 "retry accepting secure socket"));
+ m_secureReady = false;
+ ARCH->sleep(s_retryDelay);
+ return 0;
+ }
+
+ // no good state exists here
+ LOG((CLOG_ERR "unexpected state attempting to accept connection"));
+ return -1;
+}
+
+int
+SecureSocket::secureConnect(int socket)
+{
+ createSSL();
+
+ // attach the socket descriptor
+ SSL_set_fd(m_ssl->m_ssl, socket);
+
+ LOG((CLOG_DEBUG2 "connecting secure socket"));
+ int r = SSL_connect(m_ssl->m_ssl);
+
+ static int retry;
+
+ checkResult(r, retry);
+
+ if (isFatal()) {
+ LOG((CLOG_ERR "failed to connect secure socket"));
+ retry = 0;
+ return -1;
+ }
+
+ // If we should retry, not ready and return 0
+ if (retry > 0) {
+ LOG((CLOG_DEBUG2 "retry connect secure socket"));
+ m_secureReady = false;
+ ARCH->sleep(s_retryDelay);
+ return 0;
+ }
+
+ retry = 0;
+ // No error, set ready, process and return ok
+ m_secureReady = true;
+ if (verifyCertFingerprint()) {
+ LOG((CLOG_INFO "connected to secure socket"));
+ if (!showCertificate()) {
+ disconnect();
+ return -1;// Cert fail, error
+ }
+ }
+ else {
+ LOG((CLOG_ERR "failed to verify server certificate fingerprint"));
+ disconnect();
+ return -1; // Fingerprint failed, error
+ }
+ LOG((CLOG_DEBUG2 "connected secure socket"));
+ if (CLOG->getFilter() >= kDEBUG1) {
+ showSecureCipherInfo();
+ }
+ showSecureConnectInfo();
+ return 1;
+}
+
+bool
+SecureSocket::showCertificate()
+{
+ X509* cert;
+ char* line;
+
+ // get the server's certificate
+ cert = SSL_get_peer_certificate(m_ssl->m_ssl);
+ if (cert != NULL) {
+ line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
+ LOG((CLOG_INFO "server ssl certificate info: %s", line));
+ OPENSSL_free(line);
+ X509_free(cert);
+ }
+ else {
+ showError("server has no ssl certificate");
+ return false;
+ }
+
+ return true;
+}
+
+void
+SecureSocket::checkResult(int status, int& retry)
+{
+ // ssl errors are a little quirky. the "want" errors are normal and
+ // should result in a retry.
+
+ int errorCode = SSL_get_error(m_ssl->m_ssl, status);
+
+ switch (errorCode) {
+ case SSL_ERROR_NONE:
+ retry = 0;
+ // operation completed
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ // connection closed
+ isFatal(true);
+ LOG((CLOG_DEBUG "ssl connection closed"));
+ break;
+
+ case SSL_ERROR_WANT_READ:
+ retry++;
+ LOG((CLOG_DEBUG2 "want to read, error=%d, attempt=%d", errorCode, retry));
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ // Need to make sure the socket is known to be writable so the impending
+ // select action actually triggers on a write. This isn't necessary for
+ // m_readable because the socket logic is always readable
+ m_writable = true;
+ retry++;
+ LOG((CLOG_DEBUG2 "want to write, error=%d, attempt=%d", errorCode, retry));
+ break;
+
+ case SSL_ERROR_WANT_CONNECT:
+ retry++;
+ LOG((CLOG_DEBUG2 "want to connect, error=%d, attempt=%d", errorCode, retry));
+ break;
+
+ case SSL_ERROR_WANT_ACCEPT:
+ retry++;
+ LOG((CLOG_DEBUG2 "want to accept, error=%d, attempt=%d", errorCode, retry));
+ break;
+
+ case SSL_ERROR_SYSCALL:
+ LOG((CLOG_ERR "ssl error occurred (system call failure)"));
+ if (ERR_peek_error() == 0) {
+ if (status == 0) {
+ LOG((CLOG_ERR "eof violates ssl protocol"));
+ }
+ else if (status == -1) {
+ // underlying socket I/O reproted an error
+ try {
+ ARCH->throwErrorOnSocket(getSocket());
+ }
+ catch (XArchNetwork& e) {
+ LOG((CLOG_ERR "%s", e.what()));
+ }
+ }
+ }
+
+ isFatal(true);
+ break;
+
+ case SSL_ERROR_SSL:
+ LOG((CLOG_ERR "ssl error occurred (generic failure)"));
+ isFatal(true);
+ break;
+
+ default:
+ LOG((CLOG_ERR "ssl error occurred (unknown failure)"));
+ isFatal(true);
+ break;
+ }
+
+ if (isFatal()) {
+ retry = 0;
+ showError();
+ disconnect();
+ }
+}
+
+void
+SecureSocket::showError(const char* reason)
+{
+ if (reason != NULL) {
+ LOG((CLOG_ERR "%s", reason));
+ }
+
+ String error = getError();
+ if (!error.empty()) {
+ LOG((CLOG_ERR "%s", error.c_str()));
+ }
+}
+
+String
+SecureSocket::getError()
+{
+ unsigned long e = ERR_get_error();
+
+ if (e != 0) {
+ char error[MAX_ERROR_SIZE];
+ ERR_error_string_n(e, error, MAX_ERROR_SIZE);
+ return error;
+ }
+ else {
+ return "";
+ }
+}
+
+void
+SecureSocket::disconnect()
+{
+ sendEvent(getEvents()->forISocket().stopRetry());
+ sendEvent(getEvents()->forISocket().disconnected());
+ sendEvent(getEvents()->forIStream().inputShutdown());
+}
+
+void
+SecureSocket::formatFingerprint(String& fingerprint, bool hex, bool separator)
+{
+ if (hex) {
+ // to hexidecimal
+ barrier::string::toHex(fingerprint, 2);
+ }
+
+ // all uppercase
+ barrier::string::uppercase(fingerprint);
+
+ if (separator) {
+ // add colon to separate each 2 charactors
+ size_t separators = fingerprint.size() / 2;
+ for (size_t i = 1; i < separators; i++) {
+ fingerprint.insert(i * 3 - 1, ":");
+ }
+ }
+}
+
+bool
+SecureSocket::verifyCertFingerprint()
+{
+ // calculate received certificate fingerprint
+ X509 *cert = cert = SSL_get_peer_certificate(m_ssl->m_ssl);
+ EVP_MD* tempDigest;
+ unsigned char tempFingerprint[EVP_MAX_MD_SIZE];
+ unsigned int tempFingerprintLen;
+ tempDigest = (EVP_MD*)EVP_sha1();
+ int digestResult = X509_digest(cert, tempDigest, tempFingerprint, &tempFingerprintLen);
+
+ if (digestResult <= 0) {
+ LOG((CLOG_ERR "failed to calculate fingerprint, digest result: %d", digestResult));
+ return false;
+ }
+
+ // format fingerprint into hexdecimal format with colon separator
+ String fingerprint(reinterpret_cast<char*>(tempFingerprint), tempFingerprintLen);
+ formatFingerprint(fingerprint);
+ LOG((CLOG_NOTE "server fingerprint: %s", fingerprint.c_str()));
+
+ String trustedServersFilename;
+ trustedServersFilename = barrier::string::sprintf(
+ "%s/%s/%s",
+ ARCH->getProfileDirectory().c_str(),
+ kFingerprintDirName,
+ kFingerprintTrustedServersFilename);
+
+ // check if this fingerprint exist
+ String fileLine;
+ std::ifstream file;
+ file.open(trustedServersFilename.c_str());
+
+ bool isValid = false;
+ while (!file.eof() && file.is_open()) {
+ getline(file,fileLine);
+ if (!fileLine.empty()) {
+ if (fileLine.compare(fingerprint) == 0) {
+ isValid = true;
+ break;
+ }
+ }
+ }
+
+ file.close();
+ return isValid;
+}
+
+ISocketMultiplexerJob*
+SecureSocket::serviceConnect(ISocketMultiplexerJob* job,
+ bool, bool write, bool error)
+{
+ Lock lock(&getMutex());
+
+ int status = 0;
+#ifdef SYSAPI_WIN32
+ status = secureConnect(static_cast<int>(getSocket()->m_socket));
+#elif SYSAPI_UNIX
+ status = secureConnect(getSocket()->m_fd);
+#endif
+
+ // If status < 0, error happened
+ if (status < 0) {
+ return NULL;
+ }
+
+ // If status > 0, success
+ if (status > 0) {
+ sendEvent(m_events->forIDataSocket().secureConnected());
+ return newJob();
+ }
+
+ // Retry case
+ return new TSocketMultiplexerMethodJob<SecureSocket>(
+ this, &SecureSocket::serviceConnect,
+ getSocket(), isReadable(), isWritable());
+}
+
+ISocketMultiplexerJob*
+SecureSocket::serviceAccept(ISocketMultiplexerJob* job,
+ bool, bool write, bool error)
+{
+ Lock lock(&getMutex());
+
+ int status = 0;
+#ifdef SYSAPI_WIN32
+ status = secureAccept(static_cast<int>(getSocket()->m_socket));
+#elif SYSAPI_UNIX
+ status = secureAccept(getSocket()->m_fd);
+#endif
+ // If status < 0, error happened
+ if (status < 0) {
+ return NULL;
+ }
+
+ // If status > 0, success
+ if (status > 0) {
+ sendEvent(m_events->forClientListener().accepted());
+ return newJob();
+ }
+
+ // Retry case
+ return new TSocketMultiplexerMethodJob<SecureSocket>(
+ this, &SecureSocket::serviceAccept,
+ getSocket(), isReadable(), isWritable());
+}
+
+void
+showCipherStackDesc(STACK_OF(SSL_CIPHER) * stack) {
+ char msg[kMsgSize];
+ int i = 0;
+ for ( ; i < sk_SSL_CIPHER_num(stack) ; i++) {
+ const SSL_CIPHER * cipher = sk_SSL_CIPHER_value(stack,i);
+
+ SSL_CIPHER_description(cipher, msg, kMsgSize);
+
+ // Why does SSL put a newline in the description?
+ int pos = (int)strlen(msg) - 1;
+ if (msg[pos] == '\n') {
+ msg[pos] = '\0';
+ }
+
+ LOG((CLOG_DEBUG1 "%s",msg));
+ }
+}
+
+void
+SecureSocket::showSecureCipherInfo()
+{
+ STACK_OF(SSL_CIPHER) * sStack = SSL_get_ciphers(m_ssl->m_ssl);
+
+ if (sStack == NULL) {
+ LOG((CLOG_DEBUG1 "local cipher list not available"));
+ }
+ else {
+ LOG((CLOG_DEBUG1 "available local ciphers:"));
+ showCipherStackDesc(sStack);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ // m_ssl->m_ssl->session->ciphers is not forward compatable,
+ // In future release of OpenSSL, it's not visible,
+ STACK_OF(SSL_CIPHER) * cStack = m_ssl->m_ssl->session->ciphers;
+#else
+ // Use SSL_get_client_ciphers() for newer versions
+ STACK_OF(SSL_CIPHER) * cStack = SSL_get_client_ciphers(m_ssl->m_ssl);
+#endif
+ if (cStack == NULL) {
+ LOG((CLOG_DEBUG1 "remote cipher list not available"));
+ }
+ else {
+ LOG((CLOG_DEBUG1 "available remote ciphers:"));
+ showCipherStackDesc(cStack);
+ }
+ return;
+}
+
+void
+SecureSocket::showSecureLibInfo()
+{
+ LOG((CLOG_INFO "%s",SSLeay_version(SSLEAY_VERSION)));
+ LOG((CLOG_DEBUG1 "openSSL : %s",SSLeay_version(SSLEAY_CFLAGS)));
+ LOG((CLOG_DEBUG1 "openSSL : %s",SSLeay_version(SSLEAY_BUILT_ON)));
+ LOG((CLOG_DEBUG1 "openSSL : %s",SSLeay_version(SSLEAY_PLATFORM)));
+ LOG((CLOG_DEBUG1 "%s",SSLeay_version(SSLEAY_DIR)));
+ return;
+}
+
+void
+SecureSocket::showSecureConnectInfo()
+{
+ const SSL_CIPHER* cipher = SSL_get_current_cipher(m_ssl->m_ssl);
+
+ if (cipher != NULL) {
+ char msg[kMsgSize];
+ SSL_CIPHER_description(cipher, msg, kMsgSize);
+ LOG((CLOG_INFO "%s", msg));
+ }
+ return;
+}
+
+void
+SecureSocket::handleTCPConnected(const Event& event, void*)
+{
+ if (getSocket() == nullptr) {
+ LOG((CLOG_DEBUG "disregarding stale connect event"));
+ return;
+ }
+ secureConnect();
+}
diff --git a/src/lib/net/SecureSocket.h b/src/lib/net/SecureSocket.h
new file mode 100644
index 0000000..01d3c3f
--- /dev/null
+++ b/src/lib/net/SecureSocket.h
@@ -0,0 +1,95 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "net/TCPSocket.h"
+#include "net/XSocket.h"
+
+class IEventQueue;
+class SocketMultiplexer;
+class ISocketMultiplexerJob;
+
+struct Ssl;
+
+//! Secure socket
+/*!
+A secure socket using SSL.
+*/
+class SecureSocket : public TCPSocket {
+public:
+ SecureSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family);
+ SecureSocket(IEventQueue* events,
+ SocketMultiplexer* socketMultiplexer,
+ ArchSocket socket);
+ ~SecureSocket();
+
+ // ISocket overrides
+ void close();
+
+ // IDataSocket overrides
+ virtual void connect(const NetworkAddress&);
+
+ ISocketMultiplexerJob*
+ newJob();
+ bool isFatal() const { return m_fatal; }
+ void isFatal(bool b) { m_fatal = b; }
+ bool isSecureReady();
+ void secureConnect();
+ void secureAccept();
+ int secureRead(void* buffer, int size, int& read);
+ int secureWrite(const void* buffer, int size, int& wrote);
+ EJobResult doRead();
+ EJobResult doWrite();
+ void initSsl(bool server);
+ bool loadCertificates(String& CertFile);
+
+private:
+ // SSL
+ void initContext(bool server);
+ void createSSL();
+ int secureAccept(int s);
+ int secureConnect(int s);
+ bool showCertificate();
+ void checkResult(int n, int& retry);
+ void showError(const char* reason = NULL);
+ String getError();
+ void disconnect();
+ void formatFingerprint(String& fingerprint,
+ bool hex = true,
+ bool separator = true);
+ bool verifyCertFingerprint();
+
+ ISocketMultiplexerJob*
+ serviceConnect(ISocketMultiplexerJob*,
+ bool, bool, bool);
+
+ ISocketMultiplexerJob*
+ serviceAccept(ISocketMultiplexerJob*,
+ bool, bool, bool);
+
+ void showSecureConnectInfo();
+ void showSecureLibInfo();
+ void showSecureCipherInfo();
+
+ void handleTCPConnected(const Event& event, void*);
+
+private:
+ Ssl* m_ssl;
+ bool m_secureReady;
+ bool m_fatal;
+};
diff --git a/src/lib/net/SocketMultiplexer.cpp b/src/lib/net/SocketMultiplexer.cpp
new file mode 100644
index 0000000..c4bc64a
--- /dev/null
+++ b/src/lib/net/SocketMultiplexer.cpp
@@ -0,0 +1,352 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "net/SocketMultiplexer.h"
+
+#include "net/ISocketMultiplexerJob.h"
+#include "mt/CondVar.h"
+#include "mt/Lock.h"
+#include "mt/Mutex.h"
+#include "mt/Thread.h"
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+#include "base/Log.h"
+#include "base/TMethodJob.h"
+#include "common/stdvector.h"
+
+//
+// SocketMultiplexer
+//
+
+SocketMultiplexer::SocketMultiplexer() :
+ m_mutex(new Mutex),
+ m_thread(NULL),
+ m_update(false),
+ m_jobsReady(new CondVar<bool>(m_mutex, false)),
+ m_jobListLock(new CondVar<bool>(m_mutex, false)),
+ m_jobListLockLocked(new CondVar<bool>(m_mutex, false)),
+ m_jobListLocker(NULL),
+ m_jobListLockLocker(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.
+ // TODO: Remove this evilness
+ m_cursorMark = reinterpret_cast<ISocketMultiplexerJob*>(this);
+
+ // start thread
+ m_thread = new Thread(new TMethodJob<SocketMultiplexer>(
+ this, &SocketMultiplexer::serviceThread));
+}
+
+SocketMultiplexer::~SocketMultiplexer()
+{
+ 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 (SocketJobMap::iterator i = m_socketJobMap.begin();
+ i != m_socketJobMap.end(); ++i) {
+ delete *(i->second);
+ }
+}
+
+void
+SocketMultiplexer::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
+ SocketJobMap::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().
+ JobCursor j = m_socketJobs.insert(m_socketJobs.end(), job);
+ m_update = true;
+ m_socketJobMap.insert(std::make_pair(socket, j));
+ }
+ else {
+ JobCursor j = i->second;
+ if (*j != job) {
+ delete *j;
+ *j = job;
+ }
+ m_update = true;
+ }
+
+ // unlock the job list
+ unlockJobList();
+}
+
+void
+SocketMultiplexer::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().
+ SocketJobMap::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
+SocketMultiplexer::serviceThread(void*)
+{
+ std::vector<IArchNetwork::PollEntry> pfds;
+ IArchNetwork::PollEntry pfd;
+
+ // service the connections
+ for (;;) {
+ Thread::testCancel();
+
+ // wait until there are jobs to handle
+ {
+ Lock 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());
+
+ JobCursor cursor = newCursor();
+ JobCursor 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], (int)pfds.size(), -1);
+ }
+ else {
+ status = 0;
+ }
+ }
+ catch (XArchNetwork& e) {
+ LOG((CLOG_WARN "error in socket multiplexer: %s", e.what()));
+ status = 0;
+ }
+
+ if (status != 0) {
+ // iterate over socket jobs, invoking each and saving the
+ // new job.
+ UInt32 i = 0;
+ JobCursor cursor = newCursor();
+ JobCursor 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) {
+ Lock lock(m_mutex);
+ delete job;
+ *jobCursor = newJob;
+ m_update = true;
+ }
+ ++i;
+ }
+
+ // next job
+ jobCursor = nextCursor(cursor);
+ }
+ deleteCursor(cursor);
+ }
+
+ // delete any removed socket jobs
+ for (SocketJobMap::iterator i = m_socketJobMap.begin();
+ i != m_socketJobMap.end();) {
+ if (*(i->second) == NULL) {
+ m_socketJobs.erase(i->second);
+ m_socketJobMap.erase(i++);
+ m_update = true;
+ }
+ else {
+ ++i;
+ }
+ }
+
+ // unlock the job list
+ unlockJobList();
+ }
+}
+
+SocketMultiplexer::JobCursor
+SocketMultiplexer::newCursor()
+{
+ Lock lock(m_mutex);
+ return m_socketJobs.insert(m_socketJobs.begin(), m_cursorMark);
+}
+
+SocketMultiplexer::JobCursor
+SocketMultiplexer::nextCursor(JobCursor cursor)
+{
+ Lock lock(m_mutex);
+ JobCursor j = m_socketJobs.end();
+ JobCursor 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
+SocketMultiplexer::deleteCursor(JobCursor cursor)
+{
+ Lock lock(m_mutex);
+ m_socketJobs.erase(cursor);
+}
+
+void
+SocketMultiplexer::lockJobListLock()
+{
+ Lock 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 Thread(Thread::getCurrentThread());
+}
+
+void
+SocketMultiplexer::lockJobList()
+{
+ Lock lock(m_mutex);
+
+ // make sure we're the one that called lockJobListLock()
+ assert(*m_jobListLockLocker == Thread::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
+SocketMultiplexer::unlockJobList()
+{
+ Lock lock(m_mutex);
+
+ // make sure we're the one that called lockJobList()
+ assert(*m_jobListLocker == Thread::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/src/lib/net/SocketMultiplexer.h b/src/lib/net/SocketMultiplexer.h
new file mode 100644
index 0000000..4aa39fc
--- /dev/null
+++ b/src/lib/net/SocketMultiplexer.h
@@ -0,0 +1,111 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "arch/IArchNetwork.h"
+#include "common/stdlist.h"
+#include "common/stdmap.h"
+
+template <class T>
+class CondVar;
+class Mutex;
+class Thread;
+class ISocket;
+class ISocketMultiplexerJob;
+
+//! Socket multiplexer
+/*!
+A socket multiplexer services multiple sockets simultaneously.
+*/
+class SocketMultiplexer {
+public:
+ SocketMultiplexer();
+ ~SocketMultiplexer();
+
+ //! @name manipulators
+ //@{
+
+ void addSocket(ISocket*, ISocketMultiplexerJob*);
+
+ void removeSocket(ISocket*);
+
+ //@}
+ //! @name accessors
+ //@{
+
+ // maybe belongs on ISocketMultiplexer
+ static SocketMultiplexer*
+ getInstance();
+
+ //@}
+
+private:
+ // list of jobs. we use a list so we can safely iterate over it
+ // while other threads modify it.
+ typedef std::list<ISocketMultiplexerJob*> SocketJobs;
+ typedef SocketJobs::iterator JobCursor;
+ typedef std::map<ISocket*, JobCursor> SocketJobMap;
+
+ // 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.
+ JobCursor newCursor();
+ JobCursor nextCursor(JobCursor);
+ void deleteCursor(JobCursor);
+
+ // 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:
+ Mutex* m_mutex;
+ Thread* m_thread;
+ bool m_update;
+ CondVar<bool>* m_jobsReady;
+ CondVar<bool>* m_jobListLock;
+ CondVar<bool>* m_jobListLockLocked;
+ Thread* m_jobListLocker;
+ Thread* m_jobListLockLocker;
+
+ SocketJobs m_socketJobs;
+ SocketJobMap m_socketJobMap;
+ ISocketMultiplexerJob*
+ m_cursorMark;
+};
diff --git a/src/lib/net/TCPListenSocket.cpp b/src/lib/net/TCPListenSocket.cpp
new file mode 100644
index 0000000..8e1540e
--- /dev/null
+++ b/src/lib/net/TCPListenSocket.cpp
@@ -0,0 +1,158 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "net/TCPListenSocket.h"
+
+#include "net/NetworkAddress.h"
+#include "net/SocketMultiplexer.h"
+#include "net/TCPSocket.h"
+#include "net/TSocketMultiplexerMethodJob.h"
+#include "net/XSocket.h"
+#include "io/XIO.h"
+#include "mt/Lock.h"
+#include "mt/Mutex.h"
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+#include "base/IEventQueue.h"
+
+//
+// TCPListenSocket
+//
+
+TCPListenSocket::TCPListenSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) :
+ m_events(events),
+ m_socketMultiplexer(socketMultiplexer)
+{
+ m_mutex = new Mutex;
+ try {
+ m_socket = ARCH->newSocket(family, IArchNetwork::kSTREAM);
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketCreate(e.what());
+ }
+}
+
+TCPListenSocket::~TCPListenSocket()
+{
+ try {
+ if (m_socket != NULL) {
+ m_socketMultiplexer->removeSocket(this);
+ ARCH->closeSocket(m_socket);
+ }
+ }
+ catch (...) {
+ // ignore
+ }
+ delete m_mutex;
+}
+
+void
+TCPListenSocket::bind(const NetworkAddress& addr)
+{
+ try {
+ Lock lock(m_mutex);
+ ARCH->setReuseAddrOnSocket(m_socket, true);
+ ARCH->bindSocket(m_socket, addr.getAddress());
+ ARCH->listenOnSocket(m_socket);
+ m_socketMultiplexer->addSocket(this,
+ new TSocketMultiplexerMethodJob<TCPListenSocket>(
+ this, &TCPListenSocket::serviceListening,
+ m_socket, true, false));
+ }
+ catch (XArchNetworkAddressInUse& e) {
+ throw XSocketAddressInUse(e.what());
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketBind(e.what());
+ }
+}
+
+void
+TCPListenSocket::close()
+{
+ Lock lock(m_mutex);
+ if (m_socket == NULL) {
+ throw XIOClosed();
+ }
+ try {
+ m_socketMultiplexer->removeSocket(this);
+ ARCH->closeSocket(m_socket);
+ m_socket = NULL;
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketIOClose(e.what());
+ }
+}
+
+void*
+TCPListenSocket::getEventTarget() const
+{
+ return const_cast<void*>(static_cast<const void*>(this));
+}
+
+IDataSocket*
+TCPListenSocket::accept()
+{
+ IDataSocket* socket = NULL;
+ try {
+ socket = new TCPSocket(m_events, m_socketMultiplexer, ARCH->acceptSocket(m_socket, NULL));
+ if (socket != NULL) {
+ setListeningJob();
+ }
+ return socket;
+ }
+ catch (XArchNetwork&) {
+ if (socket != NULL) {
+ delete socket;
+ setListeningJob();
+ }
+ return NULL;
+ }
+ catch (std::exception &ex) {
+ if (socket != NULL) {
+ delete socket;
+ setListeningJob();
+ }
+ throw ex;
+ }
+}
+
+void
+TCPListenSocket::setListeningJob()
+{
+ m_socketMultiplexer->addSocket(this,
+ new TSocketMultiplexerMethodJob<TCPListenSocket>(
+ this, &TCPListenSocket::serviceListening,
+ m_socket, true, false));
+}
+
+ISocketMultiplexerJob*
+TCPListenSocket::serviceListening(ISocketMultiplexerJob* job,
+ bool read, bool, bool error)
+{
+ if (error) {
+ close();
+ return NULL;
+ }
+ if (read) {
+ m_events->addEvent(Event(m_events->forIListenSocket().connecting(), this, NULL));
+ // stop polling on this socket until the client accepts
+ return NULL;
+ }
+ return job;
+}
diff --git a/src/lib/net/TCPListenSocket.h b/src/lib/net/TCPListenSocket.h
new file mode 100644
index 0000000..1060356
--- /dev/null
+++ b/src/lib/net/TCPListenSocket.h
@@ -0,0 +1,60 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "net/IListenSocket.h"
+#include "arch/IArchNetwork.h"
+
+class Mutex;
+class ISocketMultiplexerJob;
+class IEventQueue;
+class SocketMultiplexer;
+
+//! TCP listen socket
+/*!
+A listen socket using TCP.
+*/
+class TCPListenSocket : public IListenSocket {
+public:
+ TCPListenSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family);
+ virtual ~TCPListenSocket();
+
+ // ISocket overrides
+ virtual void bind(const NetworkAddress&);
+ virtual void close();
+ virtual void* getEventTarget() const;
+
+ // IListenSocket overrides
+ virtual IDataSocket*
+ accept();
+
+protected:
+ void setListeningJob();
+
+public:
+ ISocketMultiplexerJob*
+ serviceListening(ISocketMultiplexerJob*,
+ bool, bool, bool);
+
+protected:
+ ArchSocket m_socket;
+ Mutex* m_mutex;
+ IEventQueue* m_events;
+ SocketMultiplexer* m_socketMultiplexer;
+};
diff --git a/src/lib/net/TCPSocket.cpp b/src/lib/net/TCPSocket.cpp
new file mode 100644
index 0000000..dce81ee
--- /dev/null
+++ b/src/lib/net/TCPSocket.cpp
@@ -0,0 +1,596 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "net/TCPSocket.h"
+
+#include "net/NetworkAddress.h"
+#include "net/SocketMultiplexer.h"
+#include "net/TSocketMultiplexerMethodJob.h"
+#include "net/XSocket.h"
+#include "mt/Lock.h"
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/IEventJob.h"
+
+#include <cstring>
+#include <cstdlib>
+#include <memory>
+
+//
+// TCPSocket
+//
+
+TCPSocket::TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) :
+ IDataSocket(events),
+ m_events(events),
+ m_mutex(),
+ m_flushed(&m_mutex, true),
+ m_socketMultiplexer(socketMultiplexer)
+{
+ try {
+ m_socket = ARCH->newSocket(family, IArchNetwork::kSTREAM);
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketCreate(e.what());
+ }
+
+ LOG((CLOG_DEBUG "Opening new socket: %08X", m_socket));
+
+ init();
+}
+
+TCPSocket::TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, ArchSocket socket) :
+ IDataSocket(events),
+ m_events(events),
+ m_mutex(),
+ m_socket(socket),
+ m_flushed(&m_mutex, true),
+ m_socketMultiplexer(socketMultiplexer)
+{
+ assert(m_socket != NULL);
+
+ LOG((CLOG_DEBUG "Opening new socket: %08X", m_socket));
+
+ // socket starts in connected state
+ init();
+ onConnected();
+ setJob(newJob());
+}
+
+TCPSocket::~TCPSocket()
+{
+ try {
+ close();
+ }
+ catch (...) {
+ // ignore
+ }
+}
+
+void
+TCPSocket::bind(const NetworkAddress& addr)
+{
+ try {
+ ARCH->bindSocket(m_socket, addr.getAddress());
+ }
+ catch (XArchNetworkAddressInUse& e) {
+ throw XSocketAddressInUse(e.what());
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketBind(e.what());
+ }
+}
+
+void
+TCPSocket::close()
+{
+ LOG((CLOG_DEBUG "Closing socket: %08X", m_socket));
+
+ // remove ourself from the multiplexer
+ setJob(NULL);
+
+ Lock lock(&m_mutex);
+
+ // clear buffers and enter disconnected state
+ if (m_connected) {
+ sendEvent(m_events->forISocket().disconnected());
+ }
+ onDisconnected();
+
+ // close the socket
+ if (m_socket != NULL) {
+ ArchSocket 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()));
+ }
+ }
+}
+
+void*
+TCPSocket::getEventTarget() const
+{
+ return const_cast<void*>(static_cast<const void*>(this));
+}
+
+UInt32
+TCPSocket::read(void* buffer, UInt32 n)
+{
+ // copy data directly from our input buffer
+ Lock 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(m_events->forISocket().disconnected());
+ m_connected = false;
+ }
+
+ return n;
+}
+
+void
+TCPSocket::write(const void* buffer, UInt32 n)
+{
+ bool wasEmpty;
+ {
+ Lock lock(&m_mutex);
+
+ // must not have shutdown output
+ if (!m_writable) {
+ sendEvent(m_events->forIStream().outputError());
+ 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
+TCPSocket::flush()
+{
+ Lock lock(&m_mutex);
+ while (m_flushed == false) {
+ m_flushed.wait();
+ }
+}
+
+void
+TCPSocket::shutdownInput()
+{
+ bool useNewJob = false;
+ {
+ Lock lock(&m_mutex);
+
+ // shutdown socket for reading
+ try {
+ ARCH->closeSocketForRead(m_socket);
+ }
+ catch (XArchNetwork&) {
+ // ignore
+ }
+
+ // shutdown buffer for reading
+ if (m_readable) {
+ sendEvent(m_events->forIStream().inputShutdown());
+ onInputShutdown();
+ useNewJob = true;
+ }
+ }
+ if (useNewJob) {
+ setJob(newJob());
+ }
+}
+
+void
+TCPSocket::shutdownOutput()
+{
+ bool useNewJob = false;
+ {
+ Lock lock(&m_mutex);
+
+ // shutdown socket for writing
+ try {
+ ARCH->closeSocketForWrite(m_socket);
+ }
+ catch (XArchNetwork&) {
+ // ignore
+ }
+
+ // shutdown buffer for writing
+ if (m_writable) {
+ sendEvent(m_events->forIStream().outputShutdown());
+ onOutputShutdown();
+ useNewJob = true;
+ }
+ }
+ if (useNewJob) {
+ setJob(newJob());
+ }
+}
+
+bool
+TCPSocket::isReady() const
+{
+ Lock lock(&m_mutex);
+ return (m_inputBuffer.getSize() > 0);
+}
+
+bool
+TCPSocket::isFatal() const
+{
+ // TCP sockets aren't ever left in a fatal state.
+ LOG((CLOG_ERR "isFatal() not valid for non-secure connections"));
+ return false;
+}
+
+UInt32
+TCPSocket::getSize() const
+{
+ Lock lock(&m_mutex);
+ return m_inputBuffer.getSize();
+}
+
+void
+TCPSocket::connect(const NetworkAddress& addr)
+{
+ {
+ Lock 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(m_events->forIDataSocket().connected());
+ onConnected();
+ }
+ else {
+ // connection is in progress
+ m_writable = true;
+ }
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketConnect(e.what());
+ }
+ }
+ setJob(newJob());
+}
+
+void
+TCPSocket::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());
+ }
+}
+
+TCPSocket::EJobResult
+TCPSocket::doRead()
+{
+ UInt8 buffer[4096];
+ memset(buffer, 0, sizeof(buffer));
+ size_t bytesRead = 0;
+
+ bytesRead = ARCH->readSocket(m_socket, buffer, sizeof(buffer));
+
+ if (bytesRead > 0) {
+ bool wasEmpty = (m_inputBuffer.getSize() == 0);
+
+ // slurp up as much as possible
+ do {
+ m_inputBuffer.write(buffer, (UInt32)bytesRead);
+
+ bytesRead = ARCH->readSocket(m_socket, buffer, sizeof(buffer));
+ } while (bytesRead > 0);
+
+ // send input ready if input buffer was empty
+ if (wasEmpty) {
+ sendEvent(m_events->forIStream().inputReady());
+ }
+ }
+ 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(m_events->forIStream().inputShutdown());
+ if (!m_writable && m_inputBuffer.getSize() == 0) {
+ sendEvent(m_events->forISocket().disconnected());
+ m_connected = false;
+ }
+ m_readable = false;
+ return kNew;
+ }
+
+ return kRetry;
+}
+
+TCPSocket::EJobResult
+TCPSocket::doWrite()
+{
+ // write data
+ UInt32 bufferSize = 0;
+ int bytesWrote = 0;
+
+ bufferSize = m_outputBuffer.getSize();
+ const void* buffer = m_outputBuffer.peek(bufferSize);
+ bytesWrote = (UInt32)ARCH->writeSocket(m_socket, buffer, bufferSize);
+
+ if (bytesWrote > 0) {
+ discardWrittenData(bytesWrote);
+ return kNew;
+ }
+
+ return kRetry;
+}
+
+void
+TCPSocket::setJob(ISocketMultiplexerJob* job)
+{
+ // multiplexer will delete the old job
+ if (job == NULL) {
+ m_socketMultiplexer->removeSocket(this);
+ }
+ else {
+ m_socketMultiplexer->addSocket(this, job);
+ }
+}
+
+ISocketMultiplexerJob*
+TCPSocket::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<TCPSocket>(
+ this, &TCPSocket::serviceConnecting,
+ m_socket, m_readable, m_writable);
+ }
+ else {
+ if (!(m_readable || (m_writable && (m_outputBuffer.getSize() > 0)))) {
+ return NULL;
+ }
+ return new TSocketMultiplexerMethodJob<TCPSocket>(
+ this, &TCPSocket::serviceConnected,
+ m_socket, m_readable,
+ m_writable && (m_outputBuffer.getSize() > 0));
+ }
+}
+
+void
+TCPSocket::sendConnectionFailedEvent(const char* msg)
+{
+ ConnectionFailedInfo* info = new ConnectionFailedInfo(msg);
+ m_events->addEvent(Event(m_events->forIDataSocket().connectionFailed(),
+ getEventTarget(), info, Event::kDontFreeData));
+}
+
+void
+TCPSocket::sendEvent(Event::Type type)
+{
+ m_events->addEvent(Event(type, getEventTarget(), NULL));
+}
+
+void
+TCPSocket::discardWrittenData(int bytesWrote)
+{
+ m_outputBuffer.pop(bytesWrote);
+ if (m_outputBuffer.getSize() == 0) {
+ sendEvent(m_events->forIStream().outputFlushed());
+ m_flushed = true;
+ m_flushed.broadcast();
+ }
+}
+
+void
+TCPSocket::onConnected()
+{
+ m_connected = true;
+ m_readable = true;
+ m_writable = true;
+}
+
+void
+TCPSocket::onInputShutdown()
+{
+ m_inputBuffer.pop(m_inputBuffer.getSize());
+ m_readable = false;
+}
+
+void
+TCPSocket::onOutputShutdown()
+{
+ m_outputBuffer.pop(m_outputBuffer.getSize());
+ m_writable = false;
+
+ // we're now flushed
+ m_flushed = true;
+ m_flushed.broadcast();
+}
+
+void
+TCPSocket::onDisconnected()
+{
+ // disconnected
+ onInputShutdown();
+ onOutputShutdown();
+ m_connected = false;
+}
+
+ISocketMultiplexerJob*
+TCPSocket::serviceConnecting(ISocketMultiplexerJob* job,
+ bool, bool write, bool error)
+{
+ Lock 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 barrier client and telling to connect to
+ // another system that's not running a barrier server. it will
+ // claim to have connected then quickly disconnect (i guess because
+ // read returns 0 bytes). unfortunately, barrier 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 barrier 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());
+ onDisconnected();
+ return newJob();
+ }
+ }
+
+ if (write) {
+ sendEvent(m_events->forIDataSocket().connected());
+ onConnected();
+ return newJob();
+ }
+
+ return job;
+}
+
+ISocketMultiplexerJob*
+TCPSocket::serviceConnected(ISocketMultiplexerJob* job,
+ bool read, bool write, bool error)
+{
+ Lock lock(&m_mutex);
+
+ if (error) {
+ sendEvent(m_events->forISocket().disconnected());
+ onDisconnected();
+ return newJob();
+ }
+
+ EJobResult result = kRetry;
+ if (write) {
+ try {
+ result = doWrite();
+ }
+ catch (XArchNetworkShutdown&) {
+ // remote read end of stream hungup. our output side
+ // has therefore shutdown.
+ onOutputShutdown();
+ sendEvent(m_events->forIStream().outputShutdown());
+ if (!m_readable && m_inputBuffer.getSize() == 0) {
+ sendEvent(m_events->forISocket().disconnected());
+ m_connected = false;
+ }
+ result = kNew;
+ }
+ catch (XArchNetworkDisconnected&) {
+ // stream hungup
+ onDisconnected();
+ sendEvent(m_events->forISocket().disconnected());
+ result = kNew;
+ }
+ catch (XArchNetwork& e) {
+ // other write error
+ LOG((CLOG_WARN "error writing socket: %s", e.what()));
+ onDisconnected();
+ sendEvent(m_events->forIStream().outputError());
+ sendEvent(m_events->forISocket().disconnected());
+ result = kNew;
+ }
+ }
+
+ if (read && m_readable) {
+ try {
+ result = doRead();
+ }
+ catch (XArchNetworkDisconnected&) {
+ // stream hungup
+ sendEvent(m_events->forISocket().disconnected());
+ onDisconnected();
+ result = kNew;
+ }
+ catch (XArchNetwork& e) {
+ // ignore other read error
+ LOG((CLOG_WARN "error reading socket: %s", e.what()));
+ }
+ }
+
+ return result == kBreak ? NULL : result == kNew ? newJob() : job;
+}
diff --git a/src/lib/net/TCPSocket.h b/src/lib/net/TCPSocket.h
new file mode 100644
index 0000000..1006f88
--- /dev/null
+++ b/src/lib/net/TCPSocket.h
@@ -0,0 +1,116 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "net/IDataSocket.h"
+#include "io/StreamBuffer.h"
+#include "mt/CondVar.h"
+#include "mt/Mutex.h"
+#include "arch/IArchNetwork.h"
+
+class Mutex;
+class Thread;
+class ISocketMultiplexerJob;
+class IEventQueue;
+class SocketMultiplexer;
+
+//! TCP data socket
+/*!
+A data socket using TCP.
+*/
+class TCPSocket : public IDataSocket {
+public:
+ TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family);
+ TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, ArchSocket socket);
+ virtual ~TCPSocket();
+
+ // ISocket overrides
+ virtual void bind(const NetworkAddress&);
+ 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 bool isFatal() const;
+ virtual UInt32 getSize() const;
+
+ // IDataSocket overrides
+ virtual void connect(const NetworkAddress&);
+
+
+ virtual ISocketMultiplexerJob*
+ newJob();
+
+protected:
+ enum EJobResult {
+ kBreak = -1, //!< Break the Job chain
+ kRetry, //!< Retry the same job
+ kNew //!< Require a new job
+ };
+
+ ArchSocket getSocket() { return m_socket; }
+ IEventQueue* getEvents() { return m_events; }
+ virtual EJobResult doRead();
+ virtual EJobResult doWrite();
+
+ void setJob(ISocketMultiplexerJob*);
+
+ bool isReadable() { return m_readable; }
+ bool isWritable() { return m_writable; }
+
+ Mutex& getMutex() { return m_mutex; }
+
+ void sendEvent(Event::Type);
+ void discardWrittenData(int bytesWrote);
+
+private:
+ void init();
+
+ void sendConnectionFailedEvent(const char*);
+ void onConnected();
+ void onInputShutdown();
+ void onOutputShutdown();
+ void onDisconnected();
+
+ ISocketMultiplexerJob*
+ serviceConnecting(ISocketMultiplexerJob*,
+ bool, bool, bool);
+ ISocketMultiplexerJob*
+ serviceConnected(ISocketMultiplexerJob*,
+ bool, bool, bool);
+
+protected:
+ bool m_readable;
+ bool m_writable;
+ bool m_connected;
+ IEventQueue* m_events;
+ StreamBuffer m_inputBuffer;
+ StreamBuffer m_outputBuffer;
+
+private:
+ Mutex m_mutex;
+ ArchSocket m_socket;
+ CondVar<bool> m_flushed;
+ SocketMultiplexer* m_socketMultiplexer;
+};
diff --git a/src/lib/net/TCPSocketFactory.cpp b/src/lib/net/TCPSocketFactory.cpp
new file mode 100644
index 0000000..6ff4ef8
--- /dev/null
+++ b/src/lib/net/TCPSocketFactory.cpp
@@ -0,0 +1,68 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "net/TCPSocketFactory.h"
+#include "net/TCPSocket.h"
+#include "net/TCPListenSocket.h"
+#include "net/SecureSocket.h"
+#include "net/SecureListenSocket.h"
+#include "arch/Arch.h"
+#include "base/Log.h"
+
+//
+// TCPSocketFactory
+//
+
+TCPSocketFactory::TCPSocketFactory(IEventQueue* events, SocketMultiplexer* socketMultiplexer) :
+ m_events(events),
+ m_socketMultiplexer(socketMultiplexer)
+{
+ // do nothing
+}
+
+TCPSocketFactory::~TCPSocketFactory()
+{
+ // do nothing
+}
+
+IDataSocket*
+TCPSocketFactory::create(IArchNetwork::EAddressFamily family, bool secure) const
+{
+ if (secure) {
+ SecureSocket* secureSocket = new SecureSocket(m_events, m_socketMultiplexer, family);
+ secureSocket->initSsl (false);
+ return secureSocket;
+ }
+ else {
+ return new TCPSocket(m_events, m_socketMultiplexer, family);
+ }
+}
+
+IListenSocket*
+TCPSocketFactory::createListen(IArchNetwork::EAddressFamily family, bool secure) const
+{
+ IListenSocket* socket = NULL;
+ if (secure) {
+ socket = new SecureListenSocket(m_events, m_socketMultiplexer, family);
+ }
+ else {
+ socket = new TCPListenSocket(m_events, m_socketMultiplexer, family);
+ }
+
+ return socket;
+}
diff --git a/src/lib/net/TCPSocketFactory.h b/src/lib/net/TCPSocketFactory.h
new file mode 100644
index 0000000..0195ec4
--- /dev/null
+++ b/src/lib/net/TCPSocketFactory.h
@@ -0,0 +1,44 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "net/ISocketFactory.h"
+#include "arch/IArchNetwork.h"
+
+class IEventQueue;
+class SocketMultiplexer;
+
+//! Socket factory for TCP sockets
+class TCPSocketFactory : public ISocketFactory {
+public:
+ TCPSocketFactory(IEventQueue* events, SocketMultiplexer* socketMultiplexer);
+ virtual ~TCPSocketFactory();
+
+ // ISocketFactory overrides
+ virtual IDataSocket* create(
+ IArchNetwork::EAddressFamily family,
+ bool secure) const;
+ virtual IListenSocket* createListen(
+ IArchNetwork::EAddressFamily family,
+ bool secure) const;
+
+private:
+ IEventQueue* m_events;
+ SocketMultiplexer* m_socketMultiplexer;
+};
diff --git a/src/lib/net/TSocketMultiplexerMethodJob.h b/src/lib/net/TSocketMultiplexerMethodJob.h
new file mode 100644
index 0000000..90efbe7
--- /dev/null
+++ b/src/lib/net/TSocketMultiplexerMethodJob.h
@@ -0,0 +1,109 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "net/ISocketMultiplexerJob.h"
+#include "arch/Arch.h"
+
+//! Use a method as a socket multiplexer job
+/*!
+A socket multiplexer job class that invokes a member function.
+*/
+template <class T>
+class TSocketMultiplexerMethodJob : public ISocketMultiplexerJob {
+public:
+ typedef ISocketMultiplexerJob*
+ (T::*Method)(ISocketMultiplexerJob*, bool, bool, bool);
+
+ //! run() invokes \c object->method(arg)
+ TSocketMultiplexerMethodJob(T* object, Method method,
+ ArchSocket socket, bool readable, bool writeable);
+ virtual ~TSocketMultiplexerMethodJob();
+
+ // IJob overrides
+ virtual ISocketMultiplexerJob*
+ run(bool readable, bool writable, bool error);
+ virtual ArchSocket getSocket() const;
+ virtual bool isReadable() const;
+ virtual bool isWritable() const;
+
+private:
+ T* m_object;
+ Method m_method;
+ ArchSocket m_socket;
+ bool m_readable;
+ bool m_writable;
+ void* m_arg;
+};
+
+template <class T>
+inline
+TSocketMultiplexerMethodJob<T>::TSocketMultiplexerMethodJob(T* object,
+ Method method, ArchSocket 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 <class T>
+inline
+TSocketMultiplexerMethodJob<T>::~TSocketMultiplexerMethodJob()
+{
+ ARCH->closeSocket(m_socket);
+}
+
+template <class T>
+inline
+ISocketMultiplexerJob*
+TSocketMultiplexerMethodJob<T>::run(bool read, bool write, bool error)
+{
+ if (m_object != NULL) {
+ return (m_object->*m_method)(this, read, write, error);
+ }
+ return NULL;
+}
+
+template <class T>
+inline
+ArchSocket
+TSocketMultiplexerMethodJob<T>::getSocket() const
+{
+ return m_socket;
+}
+
+template <class T>
+inline
+bool
+TSocketMultiplexerMethodJob<T>::isReadable() const
+{
+ return m_readable;
+}
+
+template <class T>
+inline
+bool
+TSocketMultiplexerMethodJob<T>::isWritable() const
+{
+ return m_writable;
+}
diff --git a/src/lib/net/XSocket.cpp b/src/lib/net/XSocket.cpp
new file mode 100644
index 0000000..13e0fc3
--- /dev/null
+++ b/src/lib/net/XSocket.cpp
@@ -0,0 +1,117 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "net/XSocket.h"
+#include "base/String.h"
+
+//
+// XSocketAddress
+//
+
+XSocketAddress::XSocketAddress(EError error,
+ const String& hostname, int port) _NOEXCEPT :
+ m_error(error),
+ m_hostname(hostname),
+ m_port(port)
+{
+ // do nothing
+}
+
+XSocketAddress::EError
+XSocketAddress::getError() const throw()
+{
+ return m_error;
+}
+
+String
+XSocketAddress::getHostname() const throw()
+{
+ return m_hostname;
+}
+
+int
+XSocketAddress::getPort() const throw()
+{
+ return m_port;
+}
+
+String
+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(),
+ barrier::string::sprintf("%d", m_port).c_str());
+}
+
+
+//
+// XSocketIOClose
+//
+
+String
+XSocketIOClose::getWhat() const throw()
+{
+ return format("XSocketIOClose", "close: %{1}", what());
+}
+
+
+//
+// XSocketBind
+//
+
+String
+XSocketBind::getWhat() const throw()
+{
+ return format("XSocketBind", "cannot bind address: %{1}", what());
+}
+
+
+//
+// XSocketConnect
+//
+
+String
+XSocketConnect::getWhat() const throw()
+{
+ return format("XSocketConnect", "cannot connect socket: %{1}", what());
+}
+
+
+//
+// XSocketCreate
+//
+
+String
+XSocketCreate::getWhat() const throw()
+{
+ return format("XSocketCreate", "cannot create socket: %{1}", what());
+}
diff --git a/src/lib/net/XSocket.h b/src/lib/net/XSocket.h
new file mode 100644
index 0000000..be02b5b
--- /dev/null
+++ b/src/lib/net/XSocket.h
@@ -0,0 +1,98 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "io/XIO.h"
+#include "base/XBase.h"
+#include "base/String.h"
+#include "common/basic_types.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 String& hostname, int port) _NOEXCEPT;
+ virtual ~XSocketAddress() _NOEXCEPT { }
+
+ //! @name accessors
+ //@{
+
+ //! Get the error code
+ EError getError() const throw();
+ //! Get the hostname
+ String getHostname() const throw();
+ //! Get the port
+ int getPort() const throw();
+
+ //@}
+
+protected:
+ // XBase overrides
+ virtual String getWhat() const throw();
+
+private:
+ EError m_error;
+ String 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);
diff --git a/src/lib/platform/CMakeLists.txt b/src/lib/platform/CMakeLists.txt
new file mode 100644
index 0000000..a1718a1
--- /dev/null
+++ b/src/lib/platform/CMakeLists.txt
@@ -0,0 +1,49 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+if (WIN32)
+ file(GLOB headers "MSWindows*.h" "ImmuneKeysReader.h" "synwinhk.h")
+ file(GLOB sources "MSWindows*.cpp" "ImmuneKeysReader.cpp")
+elseif (APPLE)
+ file(GLOB headers "OSX*.h" "IOSX*.h")
+ file(GLOB sources "OSX*.cpp" "IOSX*.cpp" "OSX*.m" "OSX*.mm")
+elseif (UNIX)
+ file(GLOB headers "XWindows*.h")
+ file(GLOB sources "XWindows*.cpp")
+endif()
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+if (APPLE)
+ list(APPEND inc
+ /System/Library/Frameworks
+ )
+endif()
+
+include_directories(${inc})
+add_library(platform STATIC ${sources})
+target_link_libraries(platform client ${libs})
+
+if (UNIX)
+ target_link_libraries(platform io net ipc synlib client ${libs})
+endif()
+
+if (APPLE)
+ find_library(COCOA_LIBRARY Cocoa)
+ target_link_libraries(platform ${COCOA_LIBRARY})
+endif()
diff --git a/src/lib/platform/IMSWindowsClipboardFacade.h b/src/lib/platform/IMSWindowsClipboardFacade.h
new file mode 100644
index 0000000..03c6248
--- /dev/null
+++ b/src/lib/platform/IMSWindowsClipboardFacade.h
@@ -0,0 +1,36 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IMWINDOWSCLIPBOARDFACADE
+#define IMWINDOWSCLIPBOARDFACADE
+
+#include "common/IInterface.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+class IMSWindowsClipboardConverter;
+
+class IMSWindowsClipboardFacade : public IInterface
+{
+public:
+ virtual void write(HANDLE win32Data, UINT win32Format) = 0;
+ virtual ~IMSWindowsClipboardFacade() { }
+};
+
+#endif \ No newline at end of file
diff --git a/src/lib/platform/IOSXKeyResource.cpp b/src/lib/platform/IOSXKeyResource.cpp
new file mode 100644
index 0000000..0c5abe7
--- /dev/null
+++ b/src/lib/platform/IOSXKeyResource.cpp
@@ -0,0 +1,189 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/IOSXKeyResource.h"
+
+#include <Carbon/Carbon.h>
+
+KeyID
+IOSXKeyResource::getKeyID(UInt8 c)
+{
+ if (c == 0) {
+ return kKeyNone;
+ }
+ else if (c >= 32 && c < 127) {
+ // ASCII
+ return static_cast<KeyID>(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<char>(c);
+ str[1] = 0;
+
+ // get current keyboard script
+ TISInputSourceRef isref = TISCopyCurrentKeyboardInputSource();
+ CFArrayRef langs = (CFArrayRef) TISGetInputSourceProperty(isref, kTISPropertyInputSourceLanguages);
+ CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding(
+ (CFStringRef)CFArrayGetValueAtIndex(langs, 0));
+ // convert to unicode
+ CFStringRef cfString =
+ CFStringCreateWithCStringNoCopy(
+ kCFAllocatorDefault, str, encoding, 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<KeyID>(uc);
+ }
+}
+
+KeyID
+IOSXKeyResource::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<KeyID>(c);
+ }
+}
diff --git a/src/lib/platform/IOSXKeyResource.h b/src/lib/platform/IOSXKeyResource.h
new file mode 100644
index 0000000..fc190ef
--- /dev/null
+++ b/src/lib/platform/IOSXKeyResource.h
@@ -0,0 +1,36 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/KeyState.h"
+
+class IOSXKeyResource : 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);
+};
diff --git a/src/lib/platform/ImmuneKeysReader.cpp b/src/lib/platform/ImmuneKeysReader.cpp
new file mode 100644
index 0000000..72baed3
--- /dev/null
+++ b/src/lib/platform/ImmuneKeysReader.cpp
@@ -0,0 +1,53 @@
+/*
+* barrier -- mouse and keyboard sharing utility
+* Copyright (C) 2018 Deuauche Open Source Group
+*
+* This package is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* found in the file LICENSE that should have accompanied this file.
+*
+* This package is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "ImmuneKeysReader.h"
+
+#include <fstream>
+
+const std::size_t AllocatedLineSize = 1024;
+const char CommentChar = '#';
+
+static void add_key(const char * const buffer, std::vector<DWORD> &keys)
+{
+ const char *first;
+ // skip spaces. ignore blank lines and comment lines
+ for (first = buffer; *first == ' '; ++first);
+ if (*first != 0 && *first != CommentChar)
+ keys.emplace_back(std::stoul(first, 0, 0));
+}
+
+/*static*/ bool ImmuneKeysReader::get_list(const char * const path, std::vector<DWORD> &keys, std::string &badline)
+{
+ // default values for return params
+ keys.clear();
+ badline.clear();
+ std::ifstream stream(path, std::ios::in);
+ if (stream.is_open()) {
+ // size includes the null-terminator
+ char buffer[AllocatedLineSize];
+ while (stream.getline(&buffer[0], AllocatedLineSize)) {
+ try {
+ add_key(buffer, keys);
+ } catch (...) {
+ badline = buffer;
+ return false;
+ }
+ }
+ }
+ return true;
+} \ No newline at end of file
diff --git a/src/lib/platform/ImmuneKeysReader.h b/src/lib/platform/ImmuneKeysReader.h
new file mode 100644
index 0000000..b46cbbe
--- /dev/null
+++ b/src/lib/platform/ImmuneKeysReader.h
@@ -0,0 +1,34 @@
+/*
+* barrier -- mouse and keyboard sharing utility
+* Copyright (C) 2018 Deuauche Open Source Group
+*
+* This package is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* found in the file LICENSE that should have accompanied this file.
+*
+* This package is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <vector>
+#include <string>
+
+// let's not import all of Windows just to get this typedef
+typedef unsigned long DWORD;
+
+class ImmuneKeysReader
+{
+public:
+ static bool get_list(const char * const path, std::vector<DWORD> &keys, std::string &badLine);
+
+private:
+ // static class
+ explicit ImmuneKeysReader() {}
+};
diff --git a/src/lib/platform/MSWindowsClipboard.cpp b/src/lib/platform/MSWindowsClipboard.cpp
new file mode 100644
index 0000000..8ab50df
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboard.cpp
@@ -0,0 +1,232 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsClipboard.h"
+
+#include "platform/MSWindowsClipboardTextConverter.h"
+#include "platform/MSWindowsClipboardUTF16Converter.h"
+#include "platform/MSWindowsClipboardBitmapConverter.h"
+#include "platform/MSWindowsClipboardHTMLConverter.h"
+#include "platform/MSWindowsClipboardFacade.h"
+#include "arch/win32/ArchMiscWindows.h"
+#include "base/Log.h"
+
+//
+// MSWindowsClipboard
+//
+
+UINT MSWindowsClipboard::s_ownershipFormat = 0;
+
+MSWindowsClipboard::MSWindowsClipboard(HWND window) :
+ m_window(window),
+ m_time(0),
+ m_facade(new MSWindowsClipboardFacade()),
+ m_deleteFacade(true)
+{
+ // add converters, most desired first
+ m_converters.push_back(new MSWindowsClipboardUTF16Converter);
+ m_converters.push_back(new MSWindowsClipboardBitmapConverter);
+ m_converters.push_back(new MSWindowsClipboardHTMLConverter);
+}
+
+MSWindowsClipboard::~MSWindowsClipboard()
+{
+ clearConverters();
+
+ // dependency injection causes confusion over ownership, so we need
+ // logic to decide whether or not we delete the facade. there must
+ // be a more elegant way of doing this.
+ if (m_deleteFacade)
+ delete m_facade;
+}
+
+void
+MSWindowsClipboard::setFacade(IMSWindowsClipboardFacade& facade)
+{
+ delete m_facade;
+ m_facade = &facade;
+ m_deleteFacade = false;
+}
+
+bool
+MSWindowsClipboard::emptyUnowned()
+{
+ LOG((CLOG_DEBUG "empty clipboard"));
+
+ // empty the clipboard (and take ownership)
+ if (!EmptyClipboard()) {
+ // unable to cause this in integ tests, but this error has never
+ // actually been reported by users.
+ LOG((CLOG_DEBUG "failed to grab clipboard"));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+MSWindowsClipboard::empty()
+{
+ if (!emptyUnowned()) {
+ return false;
+ }
+
+ // mark clipboard as being owned by barrier
+ HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, 1);
+ if (NULL == SetClipboardData(getOwnershipFormat(), data)) {
+ LOG((CLOG_DEBUG "failed to set clipboard data"));
+ GlobalFree(data);
+ return false;
+ }
+
+ return true;
+}
+
+void
+MSWindowsClipboard::add(EFormat format, const String& 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();
+ m_facade->write(win32Data, win32Format);
+ }
+ }
+ }
+}
+
+bool
+MSWindowsClipboard::open(Time time) const
+{
+ LOG((CLOG_DEBUG "open clipboard"));
+
+ if (!OpenClipboard(m_window)) {
+ // unable to cause this in integ tests; but this can happen!
+ // * http://symless.com/pm/issues/86
+ // * http://symless.com/pm/issues/1256
+ // logging improved to see if we can catch more info next time.
+ LOG((CLOG_WARN "failed to open clipboard: %d", GetLastError()));
+ return false;
+ }
+
+ m_time = time;
+
+ return true;
+}
+
+void
+MSWindowsClipboard::close() const
+{
+ LOG((CLOG_DEBUG "close clipboard"));
+ CloseClipboard();
+}
+
+IClipboard::Time
+MSWindowsClipboard::getTime() const
+{
+ return m_time;
+}
+
+bool
+MSWindowsClipboard::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;
+}
+
+String
+MSWindowsClipboard::get(EFormat format) const
+{
+ // find the converter for the first clipboard format we can handle
+ IMSWindowsClipboardConverter* converter = NULL;
+ for (ConverterList::const_iterator index = m_converters.begin();
+ index != m_converters.end(); ++index) {
+
+ converter = *index;
+ if (converter->getFormat() == format) {
+ break;
+ }
+ converter = NULL;
+ }
+
+ // if no converter then we don't recognize any formats
+ if (converter == NULL) {
+ LOG((CLOG_WARN "no converter for format %d", format));
+ return String();
+ }
+
+ // get a handle to the clipboard data
+ HANDLE win32Data = GetClipboardData(converter->getWin32Format());
+ if (win32Data == NULL) {
+ // nb: can't cause this using integ tests; this is only caused when
+ // the selected converter returns an invalid format -- which you
+ // cannot cause using public functions.
+ return String();
+ }
+
+ // convert
+ return converter->toIClipboard(win32Data);
+}
+
+void
+MSWindowsClipboard::clearConverters()
+{
+ for (ConverterList::iterator index = m_converters.begin();
+ index != m_converters.end(); ++index) {
+ delete *index;
+ }
+ m_converters.clear();
+}
+
+bool
+MSWindowsClipboard::isOwnedByBarrier()
+{
+ // create ownership format if we haven't yet
+ if (s_ownershipFormat == 0) {
+ s_ownershipFormat = RegisterClipboardFormat(TEXT("BarrierOwnership"));
+ }
+ return (IsClipboardFormatAvailable(getOwnershipFormat()) != 0);
+}
+
+UINT
+MSWindowsClipboard::getOwnershipFormat()
+{
+ // create ownership format if we haven't yet
+ if (s_ownershipFormat == 0) {
+ s_ownershipFormat = RegisterClipboardFormat(TEXT("BarrierOwnership"));
+ }
+
+ // return the format
+ return s_ownershipFormat;
+}
diff --git a/src/lib/platform/MSWindowsClipboard.h b/src/lib/platform/MSWindowsClipboard.h
new file mode 100644
index 0000000..3e92a39
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboard.h
@@ -0,0 +1,113 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/MSWindowsClipboardFacade.h"
+#include "barrier/IClipboard.h"
+#include "common/stdvector.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+class IMSWindowsClipboardConverter;
+class IMSWindowsClipboardFacade;
+
+//! Microsoft windows clipboard implementation
+class MSWindowsClipboard : public IClipboard {
+public:
+ MSWindowsClipboard(HWND window);
+ MSWindowsClipboard(HWND window, IMSWindowsClipboardFacade &facade);
+ virtual ~MSWindowsClipboard();
+
+ //! 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(), isOwnedByBarrier() will return false when emptied
+ this way. This is useful when barrier wants to put data on
+ clipboard but pretend (to itself) that some other app did it.
+ When using empty(), barrier assumes the data came from the
+ server and doesn't need to be sent back. emptyUnowned()
+ makes barrier send the data to the server.
+ */
+ bool emptyUnowned();
+
+ //! Test if clipboard is owned by barrier
+ static bool isOwnedByBarrier();
+
+ // IClipboard overrides
+ virtual bool empty();
+ virtual void add(EFormat, const String& data);
+ virtual bool open(Time) const;
+ virtual void close() const;
+ virtual Time getTime() const;
+ virtual bool has(EFormat) const;
+ virtual String get(EFormat) const;
+
+ void setFacade(IMSWindowsClipboardFacade& facade);
+
+private:
+ void clearConverters();
+
+ UINT convertFormatToWin32(EFormat) const;
+ HANDLE convertTextToWin32(const String& data) const;
+ String convertTextFromWin32(HANDLE) const;
+
+ static UINT getOwnershipFormat();
+
+private:
+ typedef std::vector<IMSWindowsClipboardConverter*> ConverterList;
+
+ HWND m_window;
+ mutable Time m_time;
+ ConverterList m_converters;
+ static UINT s_ownershipFormat;
+ IMSWindowsClipboardFacade* m_facade;
+ bool m_deleteFacade;
+};
+
+//! 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 String&) const = 0;
+
+ // convert from the win32 clipboard format to the IClipboard format
+ // (i.e., the reverse of fromIClipboard()).
+ virtual String toIClipboard(HANDLE data) const = 0;
+};
diff --git a/src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp b/src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp
new file mode 100644
index 0000000..decbad6
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp
@@ -0,0 +1,149 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsClipboardAnyTextConverter.h"
+
+//
+// MSWindowsClipboardAnyTextConverter
+//
+
+MSWindowsClipboardAnyTextConverter::MSWindowsClipboardAnyTextConverter()
+{
+ // do nothing
+}
+
+MSWindowsClipboardAnyTextConverter::~MSWindowsClipboardAnyTextConverter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+MSWindowsClipboardAnyTextConverter::getFormat() const
+{
+ return IClipboard::kText;
+}
+
+HANDLE
+MSWindowsClipboardAnyTextConverter::fromIClipboard(const String& data) const
+{
+ // convert linefeeds and then convert to desired encoding
+ String text = doFromIClipboard(convertLinefeedToWin32(data));
+ UInt32 size = (UInt32)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;
+}
+
+String
+MSWindowsClipboardAnyTextConverter::toIClipboard(HANDLE data) const
+{
+ // get datator
+ const char* src = (const char*)GlobalLock(data);
+ UInt32 srcSize = (UInt32)GlobalSize(data);
+ if (src == NULL || srcSize <= 1) {
+ return String();
+ }
+
+ // convert text
+ String text = doToIClipboard(String(src, srcSize));
+
+ // release handle
+ GlobalUnlock(data);
+
+ // convert newlines
+ return convertLinefeedToUnix(text);
+}
+
+String
+MSWindowsClipboardAnyTextConverter::convertLinefeedToWin32(
+ const String& src) const
+{
+ // note -- we assume src is a valid UTF-8 string
+
+ // count newlines in string
+ UInt32 numNewlines = 0;
+ UInt32 n = (UInt32)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
+ String dst;
+ dst.reserve(src.size() + numNewlines);
+
+ // copy string, converting newlines
+ n = (UInt32)src.size();
+ for (const char* scan = src.c_str(); n > 0; ++scan, --n) {
+ if (scan[0] == '\n') {
+ dst += '\r';
+ }
+ dst += scan[0];
+ }
+
+ return dst;
+}
+
+String
+MSWindowsClipboardAnyTextConverter::convertLinefeedToUnix(
+ const String& src) const
+{
+ // count newlines in string
+ UInt32 numNewlines = 0;
+ UInt32 n = (UInt32)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
+ String dst;
+ dst.reserve(src.size());
+
+ // copy string, converting newlines
+ n = (UInt32)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/src/lib/platform/MSWindowsClipboardAnyTextConverter.h b/src/lib/platform/MSWindowsClipboardAnyTextConverter.h
new file mode 100644
index 0000000..cabdb0b
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardAnyTextConverter.h
@@ -0,0 +1,57 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/MSWindowsClipboard.h"
+
+//! Convert to/from some text encoding
+class MSWindowsClipboardAnyTextConverter :
+ public IMSWindowsClipboardConverter {
+public:
+ MSWindowsClipboardAnyTextConverter();
+ virtual ~MSWindowsClipboardAnyTextConverter();
+
+ // IMSWindowsClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+ virtual UINT getWin32Format() const = 0;
+ virtual HANDLE fromIClipboard(const String&) const;
+ virtual String 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 String's nul terminator).
+ */
+ virtual String doFromIClipboard(const String&) const = 0;
+
+ //! Convert to IClipboard format
+ /*!
+ Do UTF-8 conversion only. Memory handle allocation and
+ linefeed conversion is done by this class.
+ */
+ virtual String doToIClipboard(const String&) const = 0;
+
+private:
+ String convertLinefeedToWin32(const String&) const;
+ String convertLinefeedToUnix(const String&) const;
+};
diff --git a/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp b/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp
new file mode 100644
index 0000000..16bd4bf
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp
@@ -0,0 +1,152 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsClipboardBitmapConverter.h"
+
+#include "base/Log.h"
+
+//
+// MSWindowsClipboardBitmapConverter
+//
+
+MSWindowsClipboardBitmapConverter::MSWindowsClipboardBitmapConverter()
+{
+ // do nothing
+}
+
+MSWindowsClipboardBitmapConverter::~MSWindowsClipboardBitmapConverter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+MSWindowsClipboardBitmapConverter::getFormat() const
+{
+ return IClipboard::kBitmap;
+}
+
+UINT
+MSWindowsClipboardBitmapConverter::getWin32Format() const
+{
+ return CF_DIB;
+}
+
+HANDLE
+MSWindowsClipboardBitmapConverter::fromIClipboard(const String& 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;
+}
+
+String
+MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const
+{
+ // get datator
+ LPVOID src = GlobalLock(data);
+ if (src == NULL) {
+ return String();
+ }
+ UInt32 srcSize = (UInt32)GlobalSize(data);
+
+ // check image type
+ const BITMAPINFO* bitmap = static_cast<const BITMAPINFO*>(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
+ String image(static_cast<char const*>(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 {
+ //http://msdn.microsoft.com/en-us/library/ke55d167(VS.80).aspx
+ srcBits += (1i64 << 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
+ String 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/src/lib/platform/MSWindowsClipboardBitmapConverter.h b/src/lib/platform/MSWindowsClipboardBitmapConverter.h
new file mode 100644
index 0000000..52b5547
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardBitmapConverter.h
@@ -0,0 +1,36 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/MSWindowsClipboard.h"
+
+//! Convert to/from some text encoding
+class MSWindowsClipboardBitmapConverter :
+ public IMSWindowsClipboardConverter {
+public:
+ MSWindowsClipboardBitmapConverter();
+ virtual ~MSWindowsClipboardBitmapConverter();
+
+ // IMSWindowsClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+ virtual UINT getWin32Format() const;
+ virtual HANDLE fromIClipboard(const String&) const;
+ virtual String toIClipboard(HANDLE) const;
+};
diff --git a/src/lib/platform/MSWindowsClipboardFacade.cpp b/src/lib/platform/MSWindowsClipboardFacade.cpp
new file mode 100644
index 0000000..3b6478f
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardFacade.cpp
@@ -0,0 +1,31 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsClipboardFacade.h"
+
+#include "platform/MSWindowsClipboard.h"
+
+void MSWindowsClipboardFacade::write(HANDLE win32Data, UINT win32Format)
+{
+ if (SetClipboardData(win32Format, win32Data) == NULL) {
+ // free converted data if we couldn't put it on
+ // the clipboard.
+ // nb: couldn't cause this in integ tests.
+ GlobalFree(win32Data);
+ }
+}
diff --git a/src/lib/platform/MSWindowsClipboardFacade.h b/src/lib/platform/MSWindowsClipboardFacade.h
new file mode 100644
index 0000000..a95e835
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardFacade.h
@@ -0,0 +1,29 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/IMSWindowsClipboardFacade.h"
+
+#include "barrier/IClipboard.h"
+
+class MSWindowsClipboardFacade : public IMSWindowsClipboardFacade
+{
+public:
+ virtual void write(HANDLE win32Data, UINT win32Format);
+};
diff --git a/src/lib/platform/MSWindowsClipboardHTMLConverter.cpp b/src/lib/platform/MSWindowsClipboardHTMLConverter.cpp
new file mode 100644
index 0000000..347a224
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardHTMLConverter.cpp
@@ -0,0 +1,120 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsClipboardHTMLConverter.h"
+
+#include "base/String.h"
+
+//
+// MSWindowsClipboardHTMLConverter
+//
+
+MSWindowsClipboardHTMLConverter::MSWindowsClipboardHTMLConverter()
+{
+ m_format = RegisterClipboardFormat("HTML Format");
+}
+
+MSWindowsClipboardHTMLConverter::~MSWindowsClipboardHTMLConverter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+MSWindowsClipboardHTMLConverter::getFormat() const
+{
+ return IClipboard::kHTML;
+}
+
+UINT
+MSWindowsClipboardHTMLConverter::getWin32Format() const
+{
+ return m_format;
+}
+
+String
+MSWindowsClipboardHTMLConverter::doFromIClipboard(const String& data) const
+{
+ // prepare to CF_HTML format prefix and suffix
+ String prefix("Version:0.9\r\nStartHTML:0000000105\r\n"
+ "EndHTML:ZZZZZZZZZZ\r\n"
+ "StartFragment:XXXXXXXXXX\r\nEndFragment:YYYYYYYYYY\r\n"
+ "<!DOCTYPE><HTML><BODY><!--StartFragment-->");
+ String suffix("<!--EndFragment--></BODY></HTML>\r\n");
+
+ // Get byte offsets for header
+ UInt32 StartFragment = (UInt32)prefix.size();
+ UInt32 EndFragment = StartFragment + (UInt32)data.size();
+ // StartHTML is constant by the design of the prefix
+ UInt32 EndHTML = EndFragment + (UInt32)suffix.size();
+
+ prefix.replace(prefix.find("XXXXXXXXXX"), 10,
+ barrier::string::sprintf("%010u", StartFragment));
+ prefix.replace(prefix.find("YYYYYYYYYY"), 10,
+ barrier::string::sprintf("%010u", EndFragment));
+ prefix.replace(prefix.find("ZZZZZZZZZZ"), 10,
+ barrier::string::sprintf("%010u", EndHTML));
+
+ // concatenate
+ prefix += data;
+ prefix += suffix;
+ return prefix;
+}
+
+String
+MSWindowsClipboardHTMLConverter::doToIClipboard(const String& data) const
+{
+ // get fragment start/end args
+ String startArg = findArg(data, "StartFragment");
+ String endArg = findArg(data, "EndFragment");
+ if (startArg.empty() || endArg.empty()) {
+ return String();
+ }
+
+ // 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 String();
+ }
+
+ // extract the fragment
+ return data.substr(start, end - start);
+}
+
+String
+MSWindowsClipboardHTMLConverter::findArg(
+ const String& data, const String& name) const
+{
+ String::size_type i = data.find(name);
+ if (i == String::npos) {
+ return String();
+ }
+ i = data.find_first_of(":\r\n", i);
+ if (i == String::npos || data[i] != ':') {
+ return String();
+ }
+ i = data.find_first_of("0123456789\r\n", i + 1);
+ if (i == String::npos || data[i] == '\r' || data[i] == '\n') {
+ return String();
+ }
+ String::size_type j = data.find_first_not_of("0123456789", i);
+ if (j == String::npos) {
+ j = data.size();
+ }
+ return data.substr(i, j - i);
+}
diff --git a/src/lib/platform/MSWindowsClipboardHTMLConverter.h b/src/lib/platform/MSWindowsClipboardHTMLConverter.h
new file mode 100644
index 0000000..66c8045
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardHTMLConverter.h
@@ -0,0 +1,45 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/MSWindowsClipboardAnyTextConverter.h"
+
+//! Convert to/from HTML encoding
+class MSWindowsClipboardHTMLConverter :
+ public MSWindowsClipboardAnyTextConverter {
+public:
+ MSWindowsClipboardHTMLConverter();
+ virtual ~MSWindowsClipboardHTMLConverter();
+
+ // IMSWindowsClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+ virtual UINT getWin32Format() const;
+
+protected:
+ // MSWindowsClipboardAnyTextConverter overrides
+ virtual String doFromIClipboard(const String&) const;
+ virtual String doToIClipboard(const String&) const;
+
+private:
+ String findArg(const String& data, const String& name) const;
+
+private:
+ UINT m_format;
+};
diff --git a/src/lib/platform/MSWindowsClipboardTextConverter.cpp b/src/lib/platform/MSWindowsClipboardTextConverter.cpp
new file mode 100644
index 0000000..360c72c
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardTextConverter.cpp
@@ -0,0 +1,60 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsClipboardTextConverter.h"
+
+#include "base/Unicode.h"
+
+//
+// MSWindowsClipboardTextConverter
+//
+
+MSWindowsClipboardTextConverter::MSWindowsClipboardTextConverter()
+{
+ // do nothing
+}
+
+MSWindowsClipboardTextConverter::~MSWindowsClipboardTextConverter()
+{
+ // do nothing
+}
+
+UINT
+MSWindowsClipboardTextConverter::getWin32Format() const
+{
+ return CF_TEXT;
+}
+
+String
+MSWindowsClipboardTextConverter::doFromIClipboard(const String& data) const
+{
+ // convert and add nul terminator
+ return Unicode::UTF8ToText(data) += '\0';
+}
+
+String
+MSWindowsClipboardTextConverter::doToIClipboard(const String& data) const
+{
+ // convert and truncate at first nul terminator
+ String dst = Unicode::textToUTF8(data);
+ String::size_type n = dst.find('\0');
+ if (n != String::npos) {
+ dst.erase(n);
+ }
+ return dst;
+}
diff --git a/src/lib/platform/MSWindowsClipboardTextConverter.h b/src/lib/platform/MSWindowsClipboardTextConverter.h
new file mode 100644
index 0000000..fb081c3
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardTextConverter.h
@@ -0,0 +1,37 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/MSWindowsClipboardAnyTextConverter.h"
+
+//! Convert to/from locale text encoding
+class MSWindowsClipboardTextConverter :
+ public MSWindowsClipboardAnyTextConverter {
+public:
+ MSWindowsClipboardTextConverter();
+ virtual ~MSWindowsClipboardTextConverter();
+
+ // IMSWindowsClipboardConverter overrides
+ virtual UINT getWin32Format() const;
+
+protected:
+ // MSWindowsClipboardAnyTextConverter overrides
+ virtual String doFromIClipboard(const String&) const;
+ virtual String doToIClipboard(const String&) const;
+};
diff --git a/src/lib/platform/MSWindowsClipboardUTF16Converter.cpp b/src/lib/platform/MSWindowsClipboardUTF16Converter.cpp
new file mode 100644
index 0000000..0f8642a
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardUTF16Converter.cpp
@@ -0,0 +1,60 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsClipboardUTF16Converter.h"
+
+#include "base/Unicode.h"
+
+//
+// MSWindowsClipboardUTF16Converter
+//
+
+MSWindowsClipboardUTF16Converter::MSWindowsClipboardUTF16Converter()
+{
+ // do nothing
+}
+
+MSWindowsClipboardUTF16Converter::~MSWindowsClipboardUTF16Converter()
+{
+ // do nothing
+}
+
+UINT
+MSWindowsClipboardUTF16Converter::getWin32Format() const
+{
+ return CF_UNICODETEXT;
+}
+
+String
+MSWindowsClipboardUTF16Converter::doFromIClipboard(const String& data) const
+{
+ // convert and add nul terminator
+ return Unicode::UTF8ToUTF16(data).append(sizeof(wchar_t), 0);
+}
+
+String
+MSWindowsClipboardUTF16Converter::doToIClipboard(const String& data) const
+{
+ // convert and strip nul terminator
+ String dst = Unicode::UTF16ToUTF8(data);
+ String::size_type n = dst.find('\0');
+ if (n != String::npos) {
+ dst.erase(n);
+ }
+ return dst;
+}
diff --git a/src/lib/platform/MSWindowsClipboardUTF16Converter.h b/src/lib/platform/MSWindowsClipboardUTF16Converter.h
new file mode 100644
index 0000000..e7222bc
--- /dev/null
+++ b/src/lib/platform/MSWindowsClipboardUTF16Converter.h
@@ -0,0 +1,37 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/MSWindowsClipboardAnyTextConverter.h"
+
+//! Convert to/from UTF-16 encoding
+class MSWindowsClipboardUTF16Converter :
+ public MSWindowsClipboardAnyTextConverter {
+public:
+ MSWindowsClipboardUTF16Converter();
+ virtual ~MSWindowsClipboardUTF16Converter();
+
+ // IMSWindowsClipboardConverter overrides
+ virtual UINT getWin32Format() const;
+
+protected:
+ // MSWindowsClipboardAnyTextConverter overrides
+ virtual String doFromIClipboard(const String&) const;
+ virtual String doToIClipboard(const String&) const;
+};
diff --git a/src/lib/platform/MSWindowsDebugOutputter.cpp b/src/lib/platform/MSWindowsDebugOutputter.cpp
new file mode 100644
index 0000000..43c38ad
--- /dev/null
+++ b/src/lib/platform/MSWindowsDebugOutputter.cpp
@@ -0,0 +1,58 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsDebugOutputter.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <string>
+
+MSWindowsDebugOutputter::MSWindowsDebugOutputter()
+{
+}
+
+MSWindowsDebugOutputter::~MSWindowsDebugOutputter()
+{
+}
+
+void
+MSWindowsDebugOutputter::open(const char* title)
+{
+}
+
+void
+MSWindowsDebugOutputter::close()
+{
+}
+
+void
+MSWindowsDebugOutputter::show(bool showIfEmpty)
+{
+}
+
+bool
+MSWindowsDebugOutputter::write(ELevel level, const char* msg)
+{
+ OutputDebugString((std::string(msg) + "\n").c_str());
+ return true;
+}
+
+void
+MSWindowsDebugOutputter::flush()
+{
+}
diff --git a/src/lib/platform/MSWindowsDebugOutputter.h b/src/lib/platform/MSWindowsDebugOutputter.h
new file mode 100644
index 0000000..01fd97e
--- /dev/null
+++ b/src/lib/platform/MSWindowsDebugOutputter.h
@@ -0,0 +1,39 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2012 Nick Bolton
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/ILogOutputter.h"
+
+//! Write log to debugger
+/*!
+This outputter writes output to the debugger. In Visual Studio, this
+can be seen in the Output window.
+*/
+class MSWindowsDebugOutputter : public ILogOutputter {
+public:
+ MSWindowsDebugOutputter();
+ virtual ~MSWindowsDebugOutputter();
+
+ // 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 void flush();
+};
diff --git a/src/lib/platform/MSWindowsDesks.cpp b/src/lib/platform/MSWindowsDesks.cpp
new file mode 100644
index 0000000..b43a218
--- /dev/null
+++ b/src/lib/platform/MSWindowsDesks.cpp
@@ -0,0 +1,923 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2018 Debauchee Open Source Group
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsDesks.h"
+
+#include "platform/MSWindowsScreen.h"
+#include "barrier/IScreenSaver.h"
+#include "barrier/XScreen.h"
+#include "mt/Lock.h"
+#include "mt/Thread.h"
+#include "arch/win32/ArchMiscWindows.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/IJob.h"
+#include "base/TMethodEventJob.h"
+#include "base/TMethodJob.h"
+#include "base/IEventQueue.h"
+
+#include <malloc.h>
+
+// 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
+
+// <unused>; <unused>
+#define BARRIER_MSG_SWITCH BARRIER_HOOK_LAST_MSG + 1
+// <unused>; <unused>
+#define BARRIER_MSG_ENTER BARRIER_HOOK_LAST_MSG + 2
+// <unused>; <unused>
+#define BARRIER_MSG_LEAVE BARRIER_HOOK_LAST_MSG + 3
+// wParam = flags, HIBYTE(lParam) = virtual key, LOBYTE(lParam) = scan code
+#define BARRIER_MSG_FAKE_KEY BARRIER_HOOK_LAST_MSG + 4
+ // flags, XBUTTON id
+#define BARRIER_MSG_FAKE_BUTTON BARRIER_HOOK_LAST_MSG + 5
+// x; y
+#define BARRIER_MSG_FAKE_MOVE BARRIER_HOOK_LAST_MSG + 6
+// xDelta; yDelta
+#define BARRIER_MSG_FAKE_WHEEL BARRIER_HOOK_LAST_MSG + 7
+// POINT*; <unused>
+#define BARRIER_MSG_CURSOR_POS BARRIER_HOOK_LAST_MSG + 8
+// IKeyState*; <unused>
+#define BARRIER_MSG_SYNC_KEYS BARRIER_HOOK_LAST_MSG + 9
+// install; <unused>
+#define BARRIER_MSG_SCREENSAVER BARRIER_HOOK_LAST_MSG + 10
+// dx; dy
+#define BARRIER_MSG_FAKE_REL_MOVE BARRIER_HOOK_LAST_MSG + 11
+// enable; <unused>
+#define BARRIER_MSG_FAKE_INPUT BARRIER_HOOK_LAST_MSG + 12
+
+//
+// MSWindowsDesks
+//
+
+MSWindowsDesks::MSWindowsDesks(
+ bool isPrimary, bool noHooks,
+ const IScreenSaver* screensaver, IEventQueue* events,
+ IJob* updateKeys, bool stopOnDeskSwitch) :
+ m_isPrimary(isPrimary),
+ m_noHooks(noHooks),
+ 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),
+ m_events(events),
+ m_stopOnDeskSwitch(stopOnDeskSwitch)
+{
+ m_cursor = createBlankCursor();
+ m_deskClass = createDeskWindowClass(m_isPrimary);
+ m_keyLayout = GetKeyboardLayout(GetCurrentThreadId());
+ resetOptions();
+}
+
+MSWindowsDesks::~MSWindowsDesks()
+{
+ disable();
+ destroyClass(m_deskClass);
+ destroyCursor(m_cursor);
+ delete m_updateKeys;
+}
+
+void
+MSWindowsDesks::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 = m_events->newTimer(0.2, NULL);
+ m_events->adoptHandler(Event::kTimer, m_timer,
+ new TMethodEventJob<MSWindowsDesks>(
+ this, &MSWindowsDesks::handleCheckDesk));
+
+ updateKeys();
+}
+
+void
+MSWindowsDesks::disable()
+{
+ // remove timer
+ if (m_timer != NULL) {
+ m_events->removeHandler(Event::kTimer, m_timer);
+ m_events->deleteTimer(m_timer);
+ m_timer = NULL;
+ }
+
+ // destroy desks
+ removeDesks();
+
+ m_isOnScreen = m_isPrimary;
+}
+
+void
+MSWindowsDesks::enter()
+{
+ sendMessage(BARRIER_MSG_ENTER, 0, 0);
+}
+
+void
+MSWindowsDesks::leave(HKL keyLayout)
+{
+ sendMessage(BARRIER_MSG_LEAVE, (WPARAM)keyLayout, 0);
+}
+
+void
+MSWindowsDesks::resetOptions()
+{
+ m_leaveForegroundOption = false;
+}
+
+void
+MSWindowsDesks::setOptions(const OptionsList& options)
+{
+ for (UInt32 i = 0, n = (UInt32)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
+MSWindowsDesks::updateKeys()
+{
+ sendMessage(BARRIER_MSG_SYNC_KEYS, 0, 0);
+}
+
+void
+MSWindowsDesks::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
+MSWindowsDesks::installScreensaverHooks(bool install)
+{
+ if (m_isPrimary && m_screensaverNotify != install) {
+ m_screensaverNotify = install;
+ sendMessage(BARRIER_MSG_SCREENSAVER, install, 0);
+ }
+}
+
+void
+MSWindowsDesks::fakeInputBegin()
+{
+ sendMessage(BARRIER_MSG_FAKE_INPUT, 1, 0);
+}
+
+void
+MSWindowsDesks::fakeInputEnd()
+{
+ sendMessage(BARRIER_MSG_FAKE_INPUT, 0, 0);
+}
+
+void
+MSWindowsDesks::getCursorPos(SInt32& x, SInt32& y) const
+{
+ POINT pos;
+ sendMessage(BARRIER_MSG_CURSOR_POS, reinterpret_cast<WPARAM>(&pos), 0);
+ x = pos.x;
+ y = pos.y;
+}
+
+void
+MSWindowsDesks::fakeKeyEvent(
+ KeyButton button, UINT virtualKey,
+ bool press, bool /*isAutoRepeat*/) const
+{
+ // synthesize event
+ DWORD flags = 0;
+ if (((button & 0x100u) != 0)) {
+ flags |= KEYEVENTF_EXTENDEDKEY;
+ }
+ if (!press) {
+ flags |= KEYEVENTF_KEYUP;
+ }
+ sendMessage(BARRIER_MSG_FAKE_KEY, flags,
+ MAKEWORD(static_cast<BYTE>(button & 0xffu),
+ static_cast<BYTE>(virtualKey & 0xffu)));
+}
+
+void
+MSWindowsDesks::fakeMouseButton(ButtonID button, bool press)
+{
+ // 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(BARRIER_MSG_FAKE_BUTTON, flags, data);
+}
+
+void
+MSWindowsDesks::fakeMouseMove(SInt32 x, SInt32 y) const
+{
+ sendMessage(BARRIER_MSG_FAKE_MOVE,
+ static_cast<WPARAM>(x),
+ static_cast<LPARAM>(y));
+}
+
+void
+MSWindowsDesks::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const
+{
+ sendMessage(BARRIER_MSG_FAKE_REL_MOVE,
+ static_cast<WPARAM>(dx),
+ static_cast<LPARAM>(dy));
+}
+
+void
+MSWindowsDesks::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
+{
+ sendMessage(BARRIER_MSG_FAKE_WHEEL, xDelta, yDelta);
+}
+
+void
+MSWindowsDesks::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();
+ }
+}
+
+HCURSOR
+MSWindowsDesks::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(MSWindowsScreen::getWindowInstance(),
+ 0, 0, cw, ch, cursorAND, cursorXOR);
+ delete[] cursorXOR;
+ delete[] cursorAND;
+ return c;
+}
+
+void
+MSWindowsDesks::destroyCursor(HCURSOR cursor) const
+{
+ if (cursor != NULL) {
+ DestroyCursor(cursor);
+ }
+}
+
+ATOM
+MSWindowsDesks::createDeskWindowClass(bool isPrimary) const
+{
+ WNDCLASSEX classInfo;
+ classInfo.cbSize = sizeof(classInfo);
+ classInfo.style = CS_DBLCLKS | CS_NOCLOSE;
+ classInfo.lpfnWndProc = isPrimary ?
+ &MSWindowsDesks::primaryDeskProc :
+ &MSWindowsDesks::secondaryDeskProc;
+ classInfo.cbClsExtra = 0;
+ classInfo.cbWndExtra = 0;
+ classInfo.hInstance = MSWindowsScreen::getWindowInstance();
+ classInfo.hIcon = NULL;
+ classInfo.hCursor = m_cursor;
+ classInfo.hbrBackground = NULL;
+ classInfo.lpszMenuName = NULL;
+ classInfo.lpszClassName = "BarrierDesk";
+ classInfo.hIconSm = NULL;
+ return RegisterClassEx(&classInfo);
+}
+
+void
+MSWindowsDesks::destroyClass(ATOM windowClass) const
+{
+ if (windowClass != 0) {
+ UnregisterClass(MAKEINTATOM(windowClass),
+ MSWindowsScreen::getWindowInstance());
+ }
+}
+
+HWND
+MSWindowsDesks::createWindow(ATOM windowClass, const char* name) const
+{
+ HWND window = CreateWindowEx(WS_EX_TRANSPARENT |
+ WS_EX_TOOLWINDOW,
+ MAKEINTATOM(windowClass),
+ name,
+ WS_POPUP,
+ 0, 0, 1, 1,
+ NULL, NULL,
+ MSWindowsScreen::getWindowInstance(),
+ NULL);
+ if (window == NULL) {
+ LOG((CLOG_ERR "failed to create window: %d", GetLastError()));
+ throw XScreenOpenFailure();
+ }
+ return window;
+}
+
+void
+MSWindowsDesks::destroyWindow(HWND hwnd) const
+{
+ if (hwnd != NULL) {
+ DestroyWindow(hwnd);
+ }
+}
+
+LRESULT CALLBACK
+MSWindowsDesks::primaryDeskProc(
+ HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+}
+
+LRESULT CALLBACK
+MSWindowsDesks::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
+MSWindowsDesks::deskMouseMove(SInt32 x, SInt32 y) const
+{
+ // 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);
+}
+
+void
+MSWindowsDesks::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 barrier 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
+MSWindowsDesks::deskEnter(Desk* 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, FALSE);
+ desk->m_foregroundWindow = NULL;
+}
+
+void
+MSWindowsDesks::deskLeave(Desk* 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;
+ // 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;
+ SetWindowPos(desk->m_window, HWND_TOP, x, y, w, h,
+ SWP_NOACTIVATE | SWP_SHOWWINDOW);
+
+ // since we're using low-level hooks, 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.
+ 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_TOP,
+ 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
+ LOG((CLOG_DEBUG2 "warping cursor to center: %+d,%+d", m_xCenter, m_yCenter));
+ deskMouseMove(m_xCenter, m_yCenter);
+ }
+}
+
+void
+MSWindowsDesks::deskThread(void* vdesk)
+{
+ MSG msg;
+
+ // use given desktop for this thread
+ Desk* desk = static_cast<Desk*>(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, "BarrierDesk");
+ 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
+ {
+ Lock 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 BARRIER_MSG_SWITCH:
+ if (m_isPrimary && !m_noHooks) {
+ MSWindowsHook::uninstall();
+ if (m_screensaverNotify) {
+ MSWindowsHook::uninstallScreenSaver();
+ MSWindowsHook::installScreenSaver();
+ }
+ if (!MSWindowsHook::install()) {
+ // we won't work on this desk
+ LOG((CLOG_DEBUG "Cannot hook on this desk"));
+ }
+ // a window on the primary screen with low-level hooks
+ // should never activate.
+ if (desk->m_window)
+ EnableWindow(desk->m_window, FALSE);
+ }
+ break;
+
+ case BARRIER_MSG_ENTER:
+ m_isOnScreen = true;
+ deskEnter(desk);
+ break;
+
+ case BARRIER_MSG_LEAVE:
+ m_isOnScreen = false;
+ m_keyLayout = (HKL)msg.wParam;
+ deskLeave(desk, m_keyLayout);
+ break;
+
+ case BARRIER_MSG_FAKE_KEY:
+ keybd_event(HIBYTE(msg.lParam), LOBYTE(msg.lParam), (DWORD)msg.wParam, 0);
+ break;
+
+ case BARRIER_MSG_FAKE_BUTTON:
+ if (msg.wParam != 0) {
+ mouse_event((DWORD)msg.wParam, 0, 0, (DWORD)msg.lParam, 0);
+ }
+ break;
+
+ case BARRIER_MSG_FAKE_MOVE:
+ deskMouseMove(static_cast<SInt32>(msg.wParam),
+ static_cast<SInt32>(msg.lParam));
+ break;
+
+ case BARRIER_MSG_FAKE_REL_MOVE:
+ deskMouseRelativeMove(static_cast<SInt32>(msg.wParam),
+ static_cast<SInt32>(msg.lParam));
+ break;
+
+ case BARRIER_MSG_FAKE_WHEEL:
+ // XXX -- add support for x-axis scrolling
+ if (msg.lParam != 0) {
+ mouse_event(MOUSEEVENTF_WHEEL, 0, 0, (DWORD)msg.lParam, 0);
+ }
+ break;
+
+ case BARRIER_MSG_CURSOR_POS: {
+ POINT* pos = reinterpret_cast<POINT*>(msg.wParam);
+ if (!GetCursorPos(pos)) {
+ pos->x = m_xCenter;
+ pos->y = m_yCenter;
+ }
+ break;
+ }
+
+ case BARRIER_MSG_SYNC_KEYS:
+ m_updateKeys->run();
+ break;
+
+ case BARRIER_MSG_SCREENSAVER:
+ if (!m_noHooks) {
+ if (msg.wParam != 0) {
+ MSWindowsHook::installScreenSaver();
+ }
+ else {
+ MSWindowsHook::uninstallScreenSaver();
+ }
+ }
+ break;
+
+ case BARRIER_MSG_FAKE_INPUT:
+ keybd_event(BARRIER_HOOK_FAKE_INPUT_VIRTUAL_KEY,
+ BARRIER_HOOK_FAKE_INPUT_SCANCODE,
+ msg.wParam ? 0 : KEYEVENTF_KEYUP, 0);
+ break;
+ }
+
+ // notify that message was processed
+ Lock 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);
+ }
+}
+
+MSWindowsDesks::Desk*
+MSWindowsDesks::addDesk(const String& name, HDESK hdesk)
+{
+ Desk* desk = new Desk;
+ desk->m_name = name;
+ desk->m_desk = hdesk;
+ desk->m_targetID = GetCurrentThreadId();
+ desk->m_thread = new Thread(new TMethodJob<MSWindowsDesks>(
+ this, &MSWindowsDesks::deskThread, desk));
+ waitForDesk();
+ m_desks.insert(std::make_pair(name, desk));
+ return desk;
+}
+
+void
+MSWindowsDesks::removeDesks()
+{
+ for (Desks::iterator index = m_desks.begin();
+ index != m_desks.end(); ++index) {
+ Desk* 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
+MSWindowsDesks::checkDesk()
+{
+ // get current desktop. if we already know about it then return.
+ Desk* desk;
+ HDESK hdesk = openInputDesktop();
+ String name = getDesktopName(hdesk);
+ Desks::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 we are told to shut down on desk switch, and this is not the
+ // first switch, then shut down.
+ if (m_stopOnDeskSwitch && m_activeDesk != NULL && name != m_activeDeskName) {
+ LOG((CLOG_DEBUG "shutting down because of desk switch to \"%s\"", name.c_str()));
+ m_events->addEvent(Event(Event::kQuit));
+ return;
+ }
+
+ // 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(BARRIER_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(BARRIER_MSG_SWITCH, 0, 0);
+
+ // hide cursor on new desk
+ if (!wasOnScreen) {
+ sendMessage(BARRIER_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, BARRIER_MSG_SCREEN_SAVER, TRUE, 0);
+ }
+}
+
+bool
+MSWindowsDesks::isDeskAccessible(const Desk* desk) const
+{
+ return (desk != NULL && desk->m_desk != NULL);
+}
+
+void
+MSWindowsDesks::waitForDesk() const
+{
+ MSWindowsDesks* self = const_cast<MSWindowsDesks*>(this);
+
+ Lock lock(&m_mutex);
+ while (!(bool)m_deskReady) {
+ m_deskReady.wait();
+ }
+ self->m_deskReady = false;
+}
+
+void
+MSWindowsDesks::handleCheckDesk(const Event&, void*)
+{
+ checkDesk();
+
+ // also check if screen saver is running if on a modern OS and
+ // this is the primary screen.
+ if (m_isPrimary) {
+ BOOL running;
+ SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, FALSE);
+ PostThreadMessage(m_threadID, BARRIER_MSG_SCREEN_SAVER, running, 0);
+ }
+}
+
+HDESK
+MSWindowsDesks::openInputDesktop()
+{
+ return OpenInputDesktop(
+ DF_ALLOWOTHERACCOUNTHOOK, TRUE,
+ DESKTOP_CREATEWINDOW | DESKTOP_HOOKCONTROL | GENERIC_WRITE);
+}
+
+void
+MSWindowsDesks::closeDesktop(HDESK desk)
+{
+ if (desk != NULL) {
+ CloseDesktop(desk);
+ }
+}
+
+String
+MSWindowsDesks::getDesktopName(HDESK desk)
+{
+ if (desk == NULL) {
+ return String();
+ }
+ else {
+ DWORD size;
+ GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size);
+ TCHAR* name = (TCHAR*)alloca(size + sizeof(TCHAR));
+ GetUserObjectInformation(desk, UOI_NAME, name, size, &size);
+ String result(name);
+ return result;
+ }
+}
+
+HWND
+MSWindowsDesks::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/src/lib/platform/MSWindowsDesks.h b/src/lib/platform/MSWindowsDesks.h
new file mode 100644
index 0000000..da93c34
--- /dev/null
+++ b/src/lib/platform/MSWindowsDesks.h
@@ -0,0 +1,297 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2018 Debauchee Open Source Group
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/synwinhk.h"
+#include "barrier/key_types.h"
+#include "barrier/mouse_types.h"
+#include "barrier/option_types.h"
+#include "mt/CondVar.h"
+#include "mt/Mutex.h"
+#include "base/String.h"
+#include "common/stdmap.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+class Event;
+class EventQueueTimer;
+class Thread;
+class IJob;
+class IScreenSaver;
+class IEventQueue;
+
+//! 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. Barrier must deal with them mainly because of the login
+window and screensaver but users can create their own desks and
+barrier 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 MSWindowsDesks {
+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.
+ */
+ MSWindowsDesks(
+ bool isPrimary, bool noHooks,
+ const IScreenSaver* screensaver, IEventQueue* events,
+ IJob* updateKeys, bool stopOnDeskSwitch);
+ ~MSWindowsDesks();
+
+ //! @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 OptionsList& 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);
+
+ //! 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 Desk {
+ public:
+ String m_name;
+ Thread* m_thread;
+ DWORD m_threadID;
+ DWORD m_targetID;
+ HDESK m_desk;
+ HWND m_window;
+ HWND m_foregroundWindow;
+ bool m_lowLevel;
+ };
+ typedef std::map<String, Desk*> Desks;
+
+ // initialization and shutdown operations
+ 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(Desk* desk);
+ void deskLeave(Desk* desk, HKL keyLayout);
+ void deskThread(void* vdesk);
+
+ // desk switch checking and handling
+ Desk* addDesk(const String& name, HDESK hdesk);
+ void removeDesks();
+ void checkDesk();
+ bool isDeskAccessible(const Desk* desk) const;
+ void handleCheckDesk(const Event& 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);
+ String 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 hooks are not to be installed (useful for debugging)
+ bool m_noHooks;
+
+ // 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
+ EventQueueTimer* m_timer;
+
+ // screen saver stuff
+ DWORD m_threadID;
+ const IScreenSaver* m_screensaver;
+ bool m_screensaverNotify;
+
+ // the current desk and it's name
+ Desk* m_activeDesk;
+ String m_activeDeskName;
+
+ // one desk per desktop and a cond var to communicate with it
+ Mutex m_mutex;
+ CondVar<bool> m_deskReady;
+ Desks m_desks;
+
+ // keyboard stuff
+ IJob* m_updateKeys;
+ HKL m_keyLayout;
+
+ // options
+ bool m_leaveForegroundOption;
+
+ IEventQueue* m_events;
+
+ // true if program should stop on desk switch.
+ bool m_stopOnDeskSwitch;
+};
diff --git a/src/lib/platform/MSWindowsDropTarget.cpp b/src/lib/platform/MSWindowsDropTarget.cpp
new file mode 100644
index 0000000..d647808
--- /dev/null
+++ b/src/lib/platform/MSWindowsDropTarget.cpp
@@ -0,0 +1,178 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsDropTarget.h"
+
+#include "base/Log.h"
+#include "common/common.h"
+
+#include <stdio.h>
+#include <Shlobj.h>
+
+void getDropData(IDataObject *pDataObject);
+
+MSWindowsDropTarget* MSWindowsDropTarget::s_instance = NULL;
+
+MSWindowsDropTarget::MSWindowsDropTarget() :
+ m_refCount(1),
+ m_allowDrop(false)
+{
+ s_instance = this;
+}
+
+MSWindowsDropTarget::~MSWindowsDropTarget()
+{
+}
+
+MSWindowsDropTarget&
+MSWindowsDropTarget::instance()
+{
+ assert(s_instance != NULL);
+ return *s_instance;
+}
+
+HRESULT
+MSWindowsDropTarget::DragEnter(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect)
+{
+ // check if data object contain drop
+ m_allowDrop = queryDataObject(dataObject);
+ if (m_allowDrop) {
+ getDropData(dataObject);
+ }
+
+ *effect = DROPEFFECT_NONE;
+
+ return S_OK;
+}
+
+HRESULT
+MSWindowsDropTarget::DragOver(DWORD keyState, POINTL point, DWORD* effect)
+{
+ *effect = DROPEFFECT_NONE;
+
+ return S_OK;
+}
+
+HRESULT
+MSWindowsDropTarget::DragLeave(void)
+{
+ return S_OK;
+}
+
+HRESULT
+MSWindowsDropTarget::Drop(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect)
+{
+ *effect = DROPEFFECT_NONE;
+
+ return S_OK;
+}
+
+bool
+MSWindowsDropTarget::queryDataObject(IDataObject* dataObject)
+{
+ // check if it supports CF_HDROP using a HGLOBAL
+ FORMATETC fmtetc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+
+ return dataObject->QueryGetData(&fmtetc) == S_OK ? true : false;
+}
+
+void
+MSWindowsDropTarget::setDraggingFilename(char* const filename)
+{
+ m_dragFilename = filename;
+}
+
+std::string
+MSWindowsDropTarget::getDraggingFilename()
+{
+ return m_dragFilename;
+}
+
+void
+MSWindowsDropTarget::clearDraggingFilename()
+{
+ m_dragFilename.clear();
+}
+
+void
+getDropData(IDataObject* dataObject)
+{
+ // construct a FORMATETC object
+ FORMATETC fmtEtc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ STGMEDIUM stgMed;
+
+ // See if the dataobject contains any DROP stored as a HGLOBAL
+ if (dataObject->QueryGetData(&fmtEtc) == S_OK) {
+ if (dataObject->GetData(&fmtEtc, &stgMed) == S_OK) {
+ // get data here
+ PVOID data = GlobalLock(stgMed.hGlobal);
+
+ // data object global handler contains:
+ // DROPFILESfilename1 filename2 two spaces as the end
+ // TODO: get multiple filenames
+ wchar_t* wcData = (wchar_t*)((LPBYTE)data + sizeof(DROPFILES));
+
+ // convert wchar to char
+ char* filename = new char[wcslen(wcData) + 1];
+ filename[wcslen(wcData)] = '\0';
+ wcstombs(filename, wcData, wcslen(wcData));
+
+ MSWindowsDropTarget::instance().setDraggingFilename(filename);
+
+ GlobalUnlock(stgMed.hGlobal);
+
+ // release the data using the COM API
+ ReleaseStgMedium(&stgMed);
+
+ delete[] filename;
+ }
+ }
+}
+
+HRESULT __stdcall
+MSWindowsDropTarget::QueryInterface (REFIID iid, void ** object)
+{
+ if (iid == IID_IDropTarget || iid == IID_IUnknown) {
+ AddRef();
+ *object = this;
+ return S_OK;
+ }
+ else {
+ *object = 0;
+ return E_NOINTERFACE;
+ }
+}
+
+ULONG __stdcall
+MSWindowsDropTarget::AddRef(void)
+{
+ return InterlockedIncrement(&m_refCount);
+}
+
+ULONG __stdcall
+MSWindowsDropTarget::Release(void)
+{
+ LONG count = InterlockedDecrement(&m_refCount);
+
+ if (count == 0) {
+ delete this;
+ return 0;
+ }
+ else {
+ return count;
+ }
+}
diff --git a/src/lib/platform/MSWindowsDropTarget.h b/src/lib/platform/MSWindowsDropTarget.h
new file mode 100644
index 0000000..6d60845
--- /dev/null
+++ b/src/lib/platform/MSWindowsDropTarget.h
@@ -0,0 +1,59 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <string>
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <oleidl.h>
+
+class MSWindowsScreen;
+
+class MSWindowsDropTarget : public IDropTarget {
+public:
+ MSWindowsDropTarget();
+ ~MSWindowsDropTarget();
+
+ // IUnknown implementation
+ HRESULT __stdcall QueryInterface(REFIID iid, void** object);
+ ULONG __stdcall AddRef(void);
+ ULONG __stdcall Release(void);
+
+ // IDropTarget implementation
+ HRESULT __stdcall DragEnter(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect);
+ HRESULT __stdcall DragOver(DWORD keyState, POINTL point, DWORD* effect);
+ HRESULT __stdcall DragLeave(void);
+ HRESULT __stdcall Drop(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect);
+
+ void setDraggingFilename(char* const);
+ std::string getDraggingFilename();
+ void clearDraggingFilename();
+
+ static MSWindowsDropTarget&
+ instance();
+
+private:
+ bool queryDataObject(IDataObject* dataObject);
+
+ long m_refCount;
+ bool m_allowDrop;
+ std::string m_dragFilename;
+
+ static MSWindowsDropTarget*
+ s_instance;
+};
diff --git a/src/lib/platform/MSWindowsEventQueueBuffer.cpp b/src/lib/platform/MSWindowsEventQueueBuffer.cpp
new file mode 100644
index 0000000..f6de157
--- /dev/null
+++ b/src/lib/platform/MSWindowsEventQueueBuffer.cpp
@@ -0,0 +1,146 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsEventQueueBuffer.h"
+
+#include "arch/win32/ArchMiscWindows.h"
+#include "mt/Thread.h"
+#include "base/IEventQueue.h"
+
+//
+// EventQueueTimer
+//
+
+class EventQueueTimer { };
+
+
+//
+// MSWindowsEventQueueBuffer
+//
+
+MSWindowsEventQueueBuffer::MSWindowsEventQueueBuffer(IEventQueue* events) :
+ m_events(events)
+{
+ // remember thread. we'll be posting messages to it.
+ m_thread = GetCurrentThreadId();
+
+ // create a message type for custom events
+ m_userEvent = RegisterWindowMessage("BARRIER_USER_EVENT");
+
+ // get message type for daemon quit
+ m_daemonQuit = ArchMiscWindows::getDaemonQuitMessage();
+
+ // make sure this thread has a message queue
+ MSG dummy;
+ PeekMessage(&dummy, NULL, WM_USER, WM_USER, PM_NOREMOVE);
+}
+
+MSWindowsEventQueueBuffer::~MSWindowsEventQueueBuffer()
+{
+ // do nothing
+}
+
+void
+MSWindowsEventQueueBuffer::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 (!isEmpty()) {
+ 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
+MSWindowsEventQueueBuffer::getEvent(Event& 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 = Event(Event::kQuit);
+ return kSystem;
+ }
+ else if (m_daemonQuit != 0 && m_event.message == m_daemonQuit) {
+ event = Event(Event::kQuit);
+ return kSystem;
+ }
+ else if (m_event.message == m_userEvent) {
+ dataID = static_cast<UInt32>(m_event.wParam);
+ return kUser;
+ }
+ else {
+ event = Event(Event::kSystem,
+ m_events->getSystemTarget(), &m_event);
+ return kSystem;
+ }
+}
+
+bool
+MSWindowsEventQueueBuffer::addEvent(UInt32 dataID)
+{
+ return (PostThreadMessage(m_thread, m_userEvent,
+ static_cast<WPARAM>(dataID), 0) != 0);
+}
+
+bool
+MSWindowsEventQueueBuffer::isEmpty() const
+{
+ // don't use QS_POINTER, QS_TOUCH, or any meta-flags that include them (like QS_ALLINPUT)
+ // because they can cause GetQueueStatus() to always return 0 and we miss events
+ return (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) == 0);
+}
+
+EventQueueTimer*
+MSWindowsEventQueueBuffer::newTimer(double, bool) const
+{
+ return new EventQueueTimer;
+}
+
+void
+MSWindowsEventQueueBuffer::deleteTimer(EventQueueTimer* timer) const
+{
+ delete timer;
+}
diff --git a/src/lib/platform/MSWindowsEventQueueBuffer.h b/src/lib/platform/MSWindowsEventQueueBuffer.h
new file mode 100644
index 0000000..6a0f9f9
--- /dev/null
+++ b/src/lib/platform/MSWindowsEventQueueBuffer.h
@@ -0,0 +1,50 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/IEventQueueBuffer.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+class IEventQueue;
+
+//! Event queue buffer for Win32
+class MSWindowsEventQueueBuffer : public IEventQueueBuffer {
+public:
+ MSWindowsEventQueueBuffer(IEventQueue* events);
+ virtual ~MSWindowsEventQueueBuffer();
+
+ // IEventQueueBuffer overrides
+ virtual void init() { }
+ virtual void waitForEvent(double timeout);
+ virtual Type getEvent(Event& event, UInt32& dataID);
+ virtual bool addEvent(UInt32 dataID);
+ virtual bool isEmpty() const;
+ virtual EventQueueTimer*
+ newTimer(double duration, bool oneShot) const;
+ virtual void deleteTimer(EventQueueTimer*) const;
+
+private:
+ DWORD m_thread;
+ UINT m_userEvent;
+ MSG m_event;
+ UINT m_daemonQuit;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/platform/MSWindowsHook.cpp b/src/lib/platform/MSWindowsHook.cpp
new file mode 100644
index 0000000..929888e
--- /dev/null
+++ b/src/lib/platform/MSWindowsHook.cpp
@@ -0,0 +1,629 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2018 Debauchee Open Source Group
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2011 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsHook.h"
+#include "platform/MSWindowsHookResource.h"
+#include "platform/ImmuneKeysReader.h"
+#include "barrier/protocol_types.h"
+#include "barrier/XScreen.h"
+#include "base/Log.h"
+
+ //
+ // 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
+
+static const DWORD g_threadID = GetCurrentThreadId();
+
+static WindowsHookResource g_hkMessage;
+static WindowsHookResource g_hkKeyboard;
+static WindowsHookResource g_hkMouse;
+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 WPARAM g_deadRelease = 0;
+static LPARAM g_deadLParam = 0;
+static BYTE g_deadKeyState[256] = { 0 };
+static BYTE g_keyState[256] = { 0 };
+static bool g_fakeServerInput = false;
+static std::vector<DWORD> g_immuneKeys;
+
+static const std::string ImmuneKeysPath = ArchFileWindows().getProfileDirectory() + "\\ImmuneKeys.txt";
+
+static std::vector<DWORD> immune_keys_list()
+{
+ std::vector<DWORD> keys;
+ std::string badLine;
+ if (!ImmuneKeysReader::get_list(ImmuneKeysPath.c_str(), keys, badLine))
+ LOG((CLOG_ERR "Reading immune keys stopped at: %s", badLine.c_str()));
+ return keys;
+}
+
+inline static
+bool is_immune_key(DWORD target)
+{
+ for (auto key : g_immuneKeys) {
+ if (key == target)
+ return true;
+ }
+ return false;
+}
+
+void
+MSWindowsHook::setSides(UInt32 sides)
+{
+ g_zoneSides = sides;
+}
+
+void
+MSWindowsHook::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
+MSWindowsHook::setMode(EHookMode mode)
+{
+ g_mode = mode;
+}
+
+#if !NO_GRAB_KEYBOARD
+static
+void
+keyboardGetState(BYTE keys[256], DWORD vkCode, bool kf_up)
+{
+ // 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.
+ if (vkCode < 0 || vkCode >= 256) {
+ return;
+ }
+
+ // Keep track of key state on our own in case GetAsyncKeyState() fails
+ g_keyState[vkCode] = kf_up ? 0 : 0x80;
+ g_keyState[VK_SHIFT] = g_keyState[VK_LSHIFT] | g_keyState[VK_RSHIFT];
+
+ SHORT key;
+ // Test whether GetAsyncKeyState() is being honest with us
+ key = GetAsyncKeyState(vkCode);
+
+ if (key & 0x80) {
+ // The only time we know for sure that GetAsyncKeyState() is working
+ // is when it tells us that the current key is down.
+ // In this case, update g_keyState to reflect what GetAsyncKeyState()
+ // is telling us, just in case we have gotten out of sync
+
+ for (int i = 0; i < 256; ++i) {
+ key = GetAsyncKeyState(i);
+ g_keyState[i] = (BYTE)((key < 0) ? 0x80u : 0);
+ }
+ }
+
+ // copy g_keyState to keys
+ for (int i = 0; i < 256; ++i) {
+ keys[i] = g_keyState[i];
+ }
+
+ key = GetKeyState(VK_CAPITAL);
+ keys[VK_CAPITAL] = (BYTE)(((key < 0) ? 0x80 : 0) | (key & 1));
+}
+
+static
+WPARAM
+makeKeyMsg(UINT virtKey, char c, bool noAltGr)
+{
+ return MAKEWPARAM(MAKEWORD(virtKey & 0xff, (BYTE)c), noAltGr ? 1 : 0);
+}
+
+static
+bool
+keyboardHookHandler(WPARAM wParam, LPARAM lParam)
+{
+ DWORD vkCode = static_cast<DWORD>(wParam);
+ bool kf_up = (lParam & (KF_UP << 16)) != 0;
+
+ // 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 == BARRIER_HOOK_FAKE_INPUT_VIRTUAL_KEY &&
+ ((lParam >> 16) & 0xffu) == BARRIER_HOOK_FAKE_INPUT_SCANCODE) {
+ // update flag
+ g_fakeServerInput = ((lParam & 0x80000000u) == 0);
+ PostThreadMessage(g_threadID, BARRIER_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_fakeServerInput) {
+ PostThreadMessage(g_threadID, BARRIER_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, BARRIER_MSG_DEBUG, wParam, lParam);
+
+ // ignore dead key release
+ if ((g_deadVirtKey == wParam || g_deadRelease == wParam) &&
+ (lParam & 0x80000000u) != 0) {
+ g_deadRelease = 0;
+ PostThreadMessage(g_threadID, BARRIER_MSG_DEBUG,
+ wParam | 0x04000000, lParam);
+ return false;
+ }
+
+ // we need the keyboard state for ToAscii()
+ BYTE keys[256];
+ keyboardGetState(keys, vkCode, kf_up);
+
+ // 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;
+ }
+ }
+
+ WORD c = 0;
+
+ // 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) {
+ if (ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
+ g_deadKeyState, &c, flags) == 2) {
+ // If ToAscii returned 2, it means that we accidentally removed
+ // a double dead key instead of restoring it. Thus, we call
+ // ToAscii again with the same parameters to restore the
+ // internal dead key state.
+ ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
+ g_deadKeyState, &c, flags);
+
+ // We need to keep track of this because g_deadVirtKey will be
+ // cleared later on; this would cause the dead key release to
+ // incorrectly restore the dead key state.
+ g_deadRelease = g_deadVirtKey;
+ }
+ }
+
+ UINT scanCode = ((lParam & 0x10ff0000u) >> 16);
+ int n = ToAscii((UINT)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 barrier
+ // 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, BARRIER_MSG_DEBUG,
+ wParam | 0x05000000, lParam);
+ if (g_deadVirtKey != 0) {
+ if (ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
+ g_deadKeyState, &c, flags) == 2) {
+ ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
+ g_deadKeyState, &c, flags);
+ g_deadRelease = g_deadVirtKey;
+ }
+ }
+ 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((UINT)wParam, scanCode, keys2, &c, flags);
+ }
+
+ PostThreadMessage(g_threadID, BARRIER_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
+
+ if (lParam & 0x80000000u)
+ // This handles the obscure situation where a key has been
+ // pressed which is both a dead key and a normal character
+ // depending on which modifiers have been pressed. We
+ // break here to prevent it from being considered a dead
+ // key.
+ break;
+
+ 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((UINT)wParam, (char)0, noAltGr);
+ break;
+
+ case 1:
+ // key maps to a character composed with dead key
+ charAndVirtKey = makeKeyMsg((UINT)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((UINT)g_deadVirtKey, (char)LOBYTE(c), noAltGr);
+ PostThreadMessage(g_threadID, BARRIER_MSG_KEY,
+ deadCharAndVirtKey, g_deadLParam & 0x7fffffffu);
+ PostThreadMessage(g_threadID, BARRIER_MSG_KEY,
+ deadCharAndVirtKey, g_deadLParam | 0x80000000u);
+
+ // use uncomposed character
+ charAndVirtKey = makeKeyMsg((UINT)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((UINT)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, BARRIER_MSG_DEBUG,
+ charAndVirtKey | 0x07000000, lParam);
+ PostThreadMessage(g_threadID, BARRIER_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.
+ case VK_HANGUL:
+ // pass event on because we're using a low level hook
+
+ break;
+
+ default:
+ // discard
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static
+LRESULT CALLBACK
+keyboardLLHook(int code, WPARAM wParam, LPARAM lParam)
+{
+ // decode the message
+ KBDLLHOOKSTRUCT* info = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
+
+ // do not filter non-action events nor immune keys
+ if (code == HC_ACTION && !is_immune_key(info->vkCode)) {
+ 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_hkKeyboard, code, wParam, lParam);
+}
+#endif // !NO_GRAB_KEYBOARD
+
+static
+bool
+mouseHookHandler(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, BARRIER_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, BARRIER_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, BARRIER_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.
+ // Server 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, BARRIER_MSG_MOUSE_MOVE, x, y);
+
+ // if inside and not bogus then eat the event
+ return inside && !bogus;
+ }
+ }
+
+ // pass the event
+ return false;
+}
+
+static
+LRESULT CALLBACK
+mouseLLHook(int code, WPARAM wParam, LPARAM lParam)
+{
+ // do not filter non-action events
+ if (code == HC_ACTION) {
+ // decode the message
+ MSLLHOOKSTRUCT* info = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
+ SInt32 x = static_cast<SInt32>(info->pt.x);
+ SInt32 y = static_cast<SInt32>(info->pt.y);
+ SInt32 w = static_cast<SInt16>(HIWORD(info->mouseData));
+
+ // handle the message
+ if (mouseHookHandler(wParam, x, y, w)) {
+ return 1;
+ }
+ }
+
+ return CallNextHookEx(g_hkMouse, code, wParam, lParam);
+}
+
+bool
+MSWindowsHook::install()
+{
+ // discard old dead keys
+ g_deadVirtKey = 0;
+ g_deadLParam = 0;
+
+ // reset fake input flag
+ g_fakeServerInput = false;
+
+ // setup immune keys
+ g_immuneKeys = immune_keys_list();
+ LOG((CLOG_DEBUG "Found %u immune keys in %s", g_immuneKeys.size(), ImmuneKeysPath.c_str()));
+
+#if NO_GRAB_KEYBOARD
+ // we only need the mouse hook
+ if (!g_hkMouse.set(WH_MOUSE_LL, &mouseLLHook, NULL, 0))
+ return false;
+#else
+ // we need both hooks. if either fails, discard the other
+ if (!g_hkMouse.set(WH_MOUSE_LL, &mouseLLHook, NULL, 0) ||
+ !g_hkKeyboard.set(WH_KEYBOARD_LL, &keyboardLLHook, NULL, 0)) {
+ g_hkMouse.unset();
+ g_hkKeyboard.unset();
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+void
+MSWindowsHook::uninstall()
+{
+ // discard old dead keys
+ g_deadVirtKey = 0;
+ g_deadLParam = 0;
+
+ g_hkMouse.unset();
+ g_hkKeyboard.unset();
+
+ uninstallScreenSaver();
+}
+
+static
+LRESULT CALLBACK
+getMessageHook(int code, WPARAM wParam, LPARAM lParam)
+{
+ if (code >= 0) {
+ MSG* msg = reinterpret_cast<MSG*>(lParam);
+ if (msg->message == WM_SYSCOMMAND &&
+ msg->wParam == SC_SCREENSAVE) {
+ // broadcast screen saver started message
+ PostThreadMessage(g_threadID,
+ BARRIER_MSG_SCREEN_SAVER, TRUE, 0);
+ }
+ }
+
+ return CallNextHookEx(g_hkMessage, code, wParam, lParam);
+}
+
+bool
+MSWindowsHook::installScreenSaver()
+{
+ // install hook unless it's already installed
+ if (g_hkMessage.is_set())
+ return true;
+ return g_hkMessage.set(WH_GETMESSAGE, &getMessageHook, NULL, 0);
+}
+
+void
+MSWindowsHook::uninstallScreenSaver()
+{
+ g_hkMessage.unset();
+} \ No newline at end of file
diff --git a/src/lib/platform/MSWindowsHook.h b/src/lib/platform/MSWindowsHook.h
new file mode 100644
index 0000000..7b2121c
--- /dev/null
+++ b/src/lib/platform/MSWindowsHook.h
@@ -0,0 +1,39 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2018 Debauchee Open Source Group
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2011 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/synwinhk.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+//! Loads and provides functions for the Windows hook
+class MSWindowsHook
+{
+public:
+ void setSides(UInt32 sides);
+ void setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize);
+ void setMode(EHookMode mode);
+
+ static bool install();
+ static void uninstall();
+ static bool installScreenSaver();
+ static void uninstallScreenSaver();
+};
diff --git a/src/lib/platform/MSWindowsHookResource.cpp b/src/lib/platform/MSWindowsHookResource.cpp
new file mode 100644
index 0000000..ced5ff1
--- /dev/null
+++ b/src/lib/platform/MSWindowsHookResource.cpp
@@ -0,0 +1,33 @@
+#include "MSWindowsHookResource.h"
+
+WindowsHookResource::WindowsHookResource() :
+ _hook(NULL)
+{
+}
+
+WindowsHookResource::~WindowsHookResource()
+{
+ unset();
+}
+
+bool WindowsHookResource::set(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId)
+{
+ if (is_set())
+ return false;
+ _hook = SetWindowsHookEx(idHook, lpfn, hmod, dwThreadId);
+ return is_set();
+}
+
+bool WindowsHookResource::unset()
+{
+ if (is_set()) {
+ if (UnhookWindowsHookEx(_hook) == 0) {
+ return false;
+ }
+ _hook = NULL;
+ }
+ return true;
+}
+
+bool WindowsHookResource::is_set() const { return _hook != NULL; }
+WindowsHookResource::operator HHOOK() const { return _hook; } \ No newline at end of file
diff --git a/src/lib/platform/MSWindowsHookResource.h b/src/lib/platform/MSWindowsHookResource.h
new file mode 100644
index 0000000..b66c4b8
--- /dev/null
+++ b/src/lib/platform/MSWindowsHookResource.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+class WindowsHookResource
+{
+public:
+ explicit WindowsHookResource();
+ ~WindowsHookResource();
+
+ bool set(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId);
+ bool unset();
+
+ bool is_set() const;
+ operator HHOOK() const;
+
+private:
+ HHOOK _hook;
+}; \ No newline at end of file
diff --git a/src/lib/platform/MSWindowsKeyState.cpp b/src/lib/platform/MSWindowsKeyState.cpp
new file mode 100644
index 0000000..2f29f72
--- /dev/null
+++ b/src/lib/platform/MSWindowsKeyState.cpp
@@ -0,0 +1,1406 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsKeyState.h"
+
+#include "platform/MSWindowsDesks.h"
+#include "mt/Thread.h"
+#include "arch/win32/ArchMiscWindows.h"
+#include "base/FunctionJob.h"
+#include "base/Log.h"
+#include "base/String.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+// extended mouse buttons
+#if !defined(VK_XBUTTON1)
+#define VK_XBUTTON1 0x05
+#define VK_XBUTTON2 0x06
+#endif
+
+//
+// MSWindowsKeyState
+//
+
+// map virtual keys to barrier key enumeration
+const KeyID MSWindowsKeyState::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 */ { kKeyKana }, // VK_HANGUL, VK_KANA
+ /* 0x016 */ { kKeyNone }, // undefined
+ /* 0x017 */ { kKeyNone }, // VK_JUNJA
+ /* 0x018 */ { kKeyNone }, // VK_FINAL
+ /* 0x019 */ { kKeyKanzi }, // VK_HANJA, 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 */ { kKeyHangul }, // VK_HANGUL
+ /* 0x116 */ { kKeyNone }, // undefined
+ /* 0x117 */ { kKeyNone }, // VK_JUNJA
+ /* 0x118 */ { kKeyNone }, // VK_FINAL
+ /* 0x119 */ { kKeyHanja }, // VK_HANJA
+ /* 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 Win32Modifiers {
+public:
+ UINT m_vk;
+ KeyModifierMask m_mask;
+};
+
+static const Win32Modifiers 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 }
+};
+
+MSWindowsKeyState::MSWindowsKeyState(
+ MSWindowsDesks* desks, void* eventTarget, IEventQueue* events) :
+ KeyState(events),
+ 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),
+ m_events(events)
+{
+ init();
+}
+
+MSWindowsKeyState::MSWindowsKeyState(
+ MSWindowsDesks* desks, void* eventTarget, IEventQueue* events, barrier::KeyMap& keyMap) :
+ KeyState(events, keyMap),
+ 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),
+ m_events(events)
+{
+ init();
+}
+
+MSWindowsKeyState::~MSWindowsKeyState()
+{
+ disable();
+}
+
+void
+MSWindowsKeyState::init()
+{
+ // look up symbol that's available on winNT family but not win95
+ HMODULE userModule = GetModuleHandle("user32.dll");
+ m_ToUnicodeEx = (ToUnicodeEx_t)GetProcAddress(userModule, "ToUnicodeEx");
+}
+
+void
+MSWindowsKeyState::disable()
+{
+ if (m_fixTimer != NULL) {
+ m_events->removeHandler(Event::kTimer, m_fixTimer);
+ m_events->deleteTimer(m_fixTimer);
+ m_fixTimer = NULL;
+ }
+ m_lastDown = 0;
+}
+
+KeyButton
+MSWindowsKeyState::virtualKeyToButton(UINT virtualKey) const
+{
+ return m_virtualKeyToButton[virtualKey & 0xffu];
+}
+
+void
+MSWindowsKeyState::setKeyLayout(HKL keyLayout)
+{
+ m_keyLayout = keyLayout;
+}
+
+bool
+MSWindowsKeyState::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
+MSWindowsKeyState::saveModifiers()
+{
+ m_savedModifiers = getActiveModifiers();
+ m_originalSavedModifiers = m_savedModifiers;
+}
+
+void
+MSWindowsKeyState::useSavedModifiers(bool enable)
+{
+ if (enable != m_useSavedModifiers) {
+ m_useSavedModifiers = enable;
+ if (!m_useSavedModifiers) {
+ // transfer any modifier state changes to KeyState's state
+ KeyModifierMask mask = m_originalSavedModifiers ^ m_savedModifiers;
+ getActiveModifiersRValue() =
+ (getActiveModifiers() & ~mask) | (m_savedModifiers & mask);
+ }
+ }
+}
+
+KeyID
+MSWindowsKeyState::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<KeyID>(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<KeyID>(unicode);
+ }
+ else {
+ id = static_cast<KeyID>(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;
+ }
+ if (id == kKeyHangul) {
+ // If shift-space is used to change input mode, clear shift modifier.
+ active &= ~KeyModifierShift;
+ }
+ *maskOut = active;
+ }
+
+ return id;
+}
+
+bool
+MSWindowsKeyState::didGroupsChange() const
+{
+ GroupList groups;
+ return (getGroups(groups) && groups != m_groups);
+}
+
+UINT
+MSWindowsKeyState::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
+MSWindowsKeyState::onKey(KeyButton button, bool down, KeyModifierMask newState)
+{
+ KeyState::onKey(button, down, newState);
+}
+
+void
+MSWindowsKeyState::sendKeyEvent(void* target,
+ bool press, bool isAutoRepeat,
+ KeyID key, KeyModifierMask mask,
+ SInt32 count, KeyButton button)
+{
+ if (press || isAutoRepeat) {
+ // send key
+ if (press && !isAutoRepeat) {
+ KeyState::sendKeyEvent(target, true, false,
+ key, mask, 1, button);
+ if (count > 0) {
+ --count;
+ }
+ }
+ if (count >= 1) {
+ KeyState::sendKeyEvent(target, true, true,
+ key, mask, count, button);
+ }
+ }
+ else {
+ // do key up
+ KeyState::sendKeyEvent(target, false, false, key, mask, 1, button);
+ }
+}
+
+void
+MSWindowsKeyState::fakeKeyDown(KeyID id, KeyModifierMask mask,
+ KeyButton button)
+{
+ KeyState::fakeKeyDown(id, mask, button);
+}
+
+bool
+MSWindowsKeyState::fakeKeyRepeat(KeyID id, KeyModifierMask mask,
+ SInt32 count, KeyButton button)
+{
+ return KeyState::fakeKeyRepeat(id, mask, count, button);
+}
+
+bool
+MSWindowsKeyState::fakeCtrlAltDel()
+{
+ // 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.
+ HANDLE hEvtSendSas = OpenEvent(EVENT_MODIFY_STATE, FALSE, "Global\\SendSAS");
+ if (hEvtSendSas) {
+ LOG((CLOG_DEBUG "found the SendSAS event - signaling my launcher to simulate ctrl+alt+del"));
+ SetEvent(hEvtSendSas);
+ CloseHandle(hEvtSendSas);
+ }
+ else {
+ Thread cad(new FunctionJob(&MSWindowsKeyState::ctrlAltDelThread));
+ cad.wait();
+ }
+
+ return true;
+}
+
+void
+MSWindowsKeyState::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
+MSWindowsKeyState::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
+MSWindowsKeyState::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);
+
+ if (!hkl) {
+ // GetKeyboardLayout failed. Maybe targetWindow is a console window.
+ // We're getting the keyboard layout of the desktop instead.
+ targetWindow = GetDesktopWindow();
+ targetThread = GetWindowThreadProcessId(targetWindow, NULL);
+ 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
+MSWindowsKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const
+{
+ BYTE keyState[256];
+ if (!GetKeyboardState(keyState)) {
+ LOG((CLOG_ERR "GetKeyboardState returned false on pollPressedKeys"));
+ return;
+ }
+ for (KeyButton i = 1; i < 256; ++i) {
+ if ((keyState[i] & 0x80) != 0) {
+ KeyButton keyButton = virtualKeyToButton(i);
+ if (keyButton != 0) {
+ pressedKeys.insert(keyButton);
+ }
+ }
+ }
+}
+
+void
+MSWindowsKeyState::getKeyMap(barrier::KeyMap& 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();
+
+ barrier::KeyMap::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:
+ // this is important for sending the correct modifier to the
+ // client, a patch from bug #242 (right shift broken for ms
+ // remote desktop) removed this to just use left shift, which
+ // caused bug #2799 (right shift broken for osx).
+ // we must not repeat this same mistake and must fix platform
+ // specific bugs in code that only affects that platform.
+ 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:
+ 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<KeyButton>(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;
+ }
+ }
+
+ // set virtual key to button table
+ if (activeLayout == 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
+ barrier::KeyMap::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<KeyButton>(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) {
+ // http://msdn.microsoft.com/en-us/library/ke55d167.aspx
+ if ((j & (1i64 << 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)]) {
+ // http://msdn.microsoft.com/en-us/library/ke55d167.aspx
+ if (id[j] != id[j ^ (1ui64 << 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 & (1i64 << 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
+MSWindowsKeyState::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&
+MSWindowsKeyState::getActiveModifiersRValue()
+{
+ if (m_useSavedModifiers) {
+ return m_savedModifiers;
+ }
+ else {
+ return KeyState::getActiveModifiersRValue();
+ }
+}
+
+bool
+MSWindowsKeyState::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
+MSWindowsKeyState::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);
+}
+
+KeyID
+MSWindowsKeyState::getKeyID(UINT virtualKey, KeyButton button) const
+{
+ // Some virtual keycodes have same values.
+ // VK_HANGUL == VK_KANA, VK_HANJA == NK_KANJI
+ // which are used to change the input mode of IME.
+ // But they have different X11 keysym. So we should distinguish them.
+ if ((LOWORD(m_keyLayout) & 0xffffu) == 0x0412u) { // 0x0412 : Korean Locale ID
+ if (virtualKey == VK_HANGUL || virtualKey == VK_HANJA) {
+ // If shift-space is used to change the input mode,
+ // the extented bit is not set. So add it to get right key id.
+ button |= 0x100u;
+ }
+ }
+
+ if ((button & 0x100u) != 0) {
+ virtualKey += 0x100u;
+ }
+ return s_virtualKey[virtualKey];
+}
+
+UINT
+MSWindowsKeyState::mapButtonToVirtualKey(KeyButton button) const
+{
+ return m_buttonToVK[button];
+}
+
+KeyID
+MSWindowsKeyState::getIDForKey(barrier::KeyMap::KeyItem& item,
+ KeyButton button, UINT virtualKey,
+ PBYTE keyState, HKL hkl) const
+{
+ WCHAR unicode[2];
+ int n = m_ToUnicodeEx(
+ virtualKey, button, keyState, unicode,
+ sizeof(unicode) / sizeof(unicode[0]), 0, hkl);
+ KeyID id = static_cast<KeyID>(unicode[0]);
+
+ switch (n) {
+ case -1:
+ return barrier::KeyMap::getDeadKey(id);
+
+ default:
+ case 0:
+ // unmapped
+ return kKeyNone;
+
+ case 1:
+ return id;
+
+ case 2:
+ // left over dead key in buffer. this used to recurse,
+ // but apparently this causes a stack overflow, so just
+ // return no key instead.
+ return kKeyNone;
+ }
+}
+
+void
+MSWindowsKeyState::addKeyEntry(barrier::KeyMap& keyMap, barrier::KeyMap::KeyItem& item)
+{
+ keyMap.addKeyEntry(item);
+ if (item.m_group == 0) {
+ m_keyToVKMap[item.m_id] = static_cast<UINT>(item.m_client);
+ }
+}
+
diff --git a/src/lib/platform/MSWindowsKeyState.h b/src/lib/platform/MSWindowsKeyState.h
new file mode 100644
index 0000000..b226d8b
--- /dev/null
+++ b/src/lib/platform/MSWindowsKeyState.h
@@ -0,0 +1,233 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/KeyState.h"
+#include "base/String.h"
+#include "common/stdvector.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+class Event;
+class EventQueueTimer;
+class MSWindowsDesks;
+class IEventQueue;
+
+//! Microsoft Windows key mapper
+/*!
+This class maps KeyIDs to keystrokes.
+*/
+class MSWindowsKeyState : public KeyState {
+public:
+ MSWindowsKeyState(MSWindowsDesks* desks, void* eventTarget, IEventQueue* events);
+ MSWindowsKeyState(MSWindowsDesks* desks, void* eventTarget, IEventQueue* events, barrier::KeyMap& keyMap);
+ virtual ~MSWindowsKeyState();
+
+ //! @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.
+ */
+ KeyID getKeyID(UINT virtualKey, KeyButton button) const;
+
+ //! Map button to virtual key
+ /*!
+ Returns the virtual key for button \p button
+ (button should include the extended key bit), or kKeyNone if there is
+ no such key.
+ */
+ UINT mapButtonToVirtualKey(KeyButton button) const;
+
+ //@}
+
+ // IKeyState overrides
+ virtual void fakeKeyDown(KeyID id, KeyModifierMask mask,
+ KeyButton button);
+ virtual bool 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;
+
+ // KeyState 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);
+
+ // Unit test accessors
+ KeyButton getLastDown() const { return m_lastDown; }
+ void setLastDown(KeyButton value) { m_lastDown = value; }
+ KeyModifierMask getSavedModifiers() const { return m_savedModifiers; }
+ void setSavedModifiers(KeyModifierMask value) { m_savedModifiers = value; }
+
+protected:
+ // KeyState overrides
+ virtual void getKeyMap(barrier::KeyMap& keyMap);
+ virtual void fakeKey(const Keystroke& keystroke);
+ virtual KeyModifierMask&
+ getActiveModifiersRValue();
+
+private:
+ typedef std::vector<HKL> GroupList;
+
+ // send ctrl+alt+del hotkey event on NT family
+ static void ctrlAltDelThread(void*);
+
+ bool getGroups(GroupList&) const;
+ void setWindowGroup(SInt32 group);
+
+ KeyID getIDForKey(barrier::KeyMap::KeyItem& item,
+ KeyButton button, UINT virtualKey,
+ PBYTE keyState, HKL hkl) const;
+
+ void addKeyEntry(barrier::KeyMap& keyMap, barrier::KeyMap::KeyItem& item);
+
+ void init();
+
+private:
+ // not implemented
+ MSWindowsKeyState(const MSWindowsKeyState&);
+ MSWindowsKeyState& operator=(const MSWindowsKeyState&);
+
+private:
+ typedef std::map<HKL, SInt32> GroupMap;
+ typedef std::map<KeyID, UINT> KeyToVKMap;
+
+ void* m_eventTarget;
+ MSWindowsDesks* m_desks;
+ HKL m_keyLayout;
+ UINT m_buttonToVK[512];
+ UINT m_buttonToNumpadVK[512];
+ KeyButton m_virtualKeyToButton[256];
+ KeyToVKMap m_keyToVKMap;
+ IEventQueue* m_events;
+
+ // the timer used to check for fixing key state
+ EventQueueTimer* 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[];
+};
diff --git a/src/lib/platform/MSWindowsScreen.cpp b/src/lib/platform/MSWindowsScreen.cpp
new file mode 100644
index 0000000..5246f96
--- /dev/null
+++ b/src/lib/platform/MSWindowsScreen.cpp
@@ -0,0 +1,1959 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2018 Debauchee Open Source Group
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsScreen.h"
+
+#include "platform/MSWindowsDropTarget.h"
+#include "client/Client.h"
+#include "platform/MSWindowsClipboard.h"
+#include "platform/MSWindowsDesks.h"
+#include "platform/MSWindowsEventQueueBuffer.h"
+#include "platform/MSWindowsKeyState.h"
+#include "platform/MSWindowsScreenSaver.h"
+#include "barrier/Clipboard.h"
+#include "barrier/KeyMap.h"
+#include "barrier/XScreen.h"
+#include "barrier/App.h"
+#include "barrier/ArgsBase.h"
+#include "barrier/ClientApp.h"
+#include "mt/Lock.h"
+#include "mt/Thread.h"
+#include "arch/win32/ArchMiscWindows.h"
+#include "arch/Arch.h"
+#include "base/FunctionJob.h"
+#include "base/Log.h"
+#include "base/String.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+#include "base/TMethodJob.h"
+
+#include <string.h>
+#include <Shlobj.h>
+#include <comutil.h>
+#include <algorithm>
+
+//
+// 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 <multimon.h>
+#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
+
+//
+// MSWindowsScreen
+//
+
+HINSTANCE MSWindowsScreen::s_windowInstance = NULL;
+MSWindowsScreen* MSWindowsScreen::s_screen = NULL;
+
+MSWindowsScreen::MSWindowsScreen(
+ bool isPrimary,
+ bool noHooks,
+ bool stopOnDeskSwitch,
+ IEventQueue* events) :
+ PlatformScreen(events),
+ m_isPrimary(isPrimary),
+ m_noHooks(noHooks),
+ 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_keyState(NULL),
+ m_hasMouse(GetSystemMetrics(SM_MOUSEPRESENT) != 0),
+ m_showingMouse(false),
+ m_events(events),
+ m_dropWindow(NULL),
+ m_dropWindowSize(20)
+{
+ assert(s_windowInstance != NULL);
+ assert(s_screen == NULL);
+
+ s_screen = this;
+ try {
+ m_screensaver = new MSWindowsScreenSaver();
+ m_desks = new MSWindowsDesks(
+ m_isPrimary,
+ m_noHooks,
+ m_screensaver,
+ m_events,
+ new TMethodJob<MSWindowsScreen>(
+ this, &MSWindowsScreen::updateKeysCB),
+ stopOnDeskSwitch);
+ m_keyState = new MSWindowsKeyState(m_desks, getEventTarget(), m_events);
+
+ updateScreenShape();
+ m_class = createWindowClass();
+ m_window = createWindow(m_class, "Barrier");
+ 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));
+
+ // SHGetFolderPath is deprecated in vista, but use it for xp support.
+ char desktopPath[MAX_PATH];
+ if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, desktopPath))) {
+ m_desktopPath = String(desktopPath);
+ LOG((CLOG_DEBUG "using desktop for drop target: %s", m_desktopPath.c_str()));
+ }
+ else {
+ LOG((CLOG_ERR "failed to get desktop path, no drop target available, error=%d", GetLastError()));
+ }
+
+ OleInitialize(0);
+ m_dropWindow = createDropWindow(m_class, "DropWindow");
+ m_dropTarget = new MSWindowsDropTarget();
+ RegisterDragDrop(m_dropWindow, m_dropTarget);
+ }
+ catch (...) {
+ delete m_keyState;
+ delete m_desks;
+ delete m_screensaver;
+ destroyWindow(m_window);
+ destroyClass(m_class);
+ s_screen = NULL;
+ throw;
+ }
+
+ // install event handlers
+ m_events->adoptHandler(Event::kSystem, m_events->getSystemTarget(),
+ new TMethodEventJob<MSWindowsScreen>(this,
+ &MSWindowsScreen::handleSystemEvent));
+
+ // install the platform event queue
+ m_events->adoptBuffer(new MSWindowsEventQueueBuffer(m_events));
+}
+
+MSWindowsScreen::~MSWindowsScreen()
+{
+ assert(s_screen != NULL);
+
+ disable();
+ m_events->adoptBuffer(NULL);
+ m_events->removeHandler(Event::kSystem, m_events->getSystemTarget());
+ delete m_keyState;
+ delete m_desks;
+ delete m_screensaver;
+ destroyWindow(m_window);
+ destroyClass(m_class);
+
+ RevokeDragDrop(m_dropWindow);
+ m_dropTarget->Release();
+ OleUninitialize();
+ destroyWindow(m_dropWindow);
+
+ s_screen = NULL;
+}
+
+void
+MSWindowsScreen::init(HINSTANCE windowInstance)
+{
+ assert(s_windowInstance == NULL);
+ assert(windowInstance != NULL);
+
+ s_windowInstance = windowInstance;
+}
+
+HINSTANCE
+MSWindowsScreen::getWindowInstance()
+{
+ return s_windowInstance;
+}
+
+void
+MSWindowsScreen::enable()
+{
+ assert(m_isOnScreen == m_isPrimary);
+
+ // we need to poll some things to fix them
+ m_fixTimer = m_events->newTimer(1.0, NULL);
+ m_events->adoptHandler(Event::kTimer, m_fixTimer,
+ new TMethodEventJob<MSWindowsScreen>(this,
+ &MSWindowsScreen::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_hook.setZone(m_x, m_y, m_w, m_h, getJumpZoneSize());
+
+ // watch jump zones
+ m_hook.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.
+ ArchMiscWindows::addBusyState(ArchMiscWindows::kSYSTEM);
+ }
+}
+
+void
+MSWindowsScreen::disable()
+{
+ // stop tracking the active desk
+ m_desks->disable();
+
+ if (m_isPrimary) {
+ // disable hooks
+ m_hook.setMode(kHOOK_DISABLE);
+
+ // enable special key sequences on win95 family
+ enableSpecialKeys(true);
+ }
+ else {
+ // allow the system to enter power saving mode
+ ArchMiscWindows::removeBusyState(ArchMiscWindows::kSYSTEM |
+ ArchMiscWindows::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) {
+ m_events->removeHandler(Event::kTimer, m_fixTimer);
+ m_events->deleteTimer(m_fixTimer);
+ m_fixTimer = NULL;
+ }
+
+ m_isOnScreen = m_isPrimary;
+ forceShowCursor();
+}
+
+void
+MSWindowsScreen::enter()
+{
+ m_desks->enter();
+ if (m_isPrimary) {
+ // enable special key sequences on win95 family
+ enableSpecialKeys(true);
+
+ // watch jump zones
+ m_hook.setMode(kHOOK_WATCH_JUMP_ZONE);
+
+ // all messages prior to now are invalid
+ nextMark();
+
+ m_primaryKeyDownList.clear();
+ }
+ else {
+ // Entering a secondary screen. Ensure that no screensaver is active
+ // and that the screen is not in powersave mode.
+ ArchMiscWindows::wakeupDisplay();
+
+ if (m_screensaver != NULL && m_screensaverActive)
+ {
+ m_screensaver->deactivate();
+ m_screensaverActive = 0;
+ }
+ }
+
+ // now on screen
+ m_isOnScreen = true;
+ forceShowCursor();
+}
+
+bool
+MSWindowsScreen::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
+ LOG((CLOG_DEBUG1 "warping cursor to center: %+d, %+d", m_xCenter, m_yCenter));
+ 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();
+
+ m_hook.setMode(kHOOK_RELAY_EVENTS);
+
+ m_primaryKeyDownList.clear();
+ for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) {
+ if (m_keyState->isKeyDown(i)) {
+ m_primaryKeyDownList.push_back(i);
+ LOG((CLOG_DEBUG1 "key button %d is down before leaving to another screen", i));
+ }
+ }
+ }
+
+ // now off screen
+ m_isOnScreen = false;
+ forceShowCursor();
+
+ if (isDraggingStarted() && !m_isPrimary) {
+ m_sendDragThread = new Thread(
+ new TMethodJob<MSWindowsScreen>(
+ this,
+ &MSWindowsScreen::sendDragThread));
+ }
+
+ return true;
+}
+
+void
+MSWindowsScreen::sendDragThread(void*)
+{
+ String& draggingFilename = getDraggingFilename();
+ size_t size = draggingFilename.size();
+
+ if (draggingFilename.empty() == false) {
+ ClientApp& app = ClientApp::instance();
+ Client* client = app.getClientPtr();
+ UInt32 fileCount = 1;
+ LOG((CLOG_DEBUG "send dragging info to server: %s", draggingFilename.c_str()));
+ client->sendDragInfo(fileCount, draggingFilename, size);
+ LOG((CLOG_DEBUG "send dragging file to server"));
+ client->sendFileToServer(draggingFilename.c_str());
+ }
+
+ m_draggingStarted = false;
+}
+
+bool
+MSWindowsScreen::setClipboard(ClipboardID, const IClipboard* src)
+{
+ MSWindowsClipboard dst(m_window);
+ if (src != NULL) {
+ // save clipboard data
+ return Clipboard::copy(&dst, src);
+ }
+ else {
+ // assert clipboard ownership
+ if (!dst.open(0)) {
+ return false;
+ }
+ dst.empty();
+ dst.close();
+ return true;
+ }
+}
+
+void
+MSWindowsScreen::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 && !MSWindowsClipboard::isOwnedByBarrier()) {
+ LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received"));
+ m_ownClipboard = false;
+ sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard);
+ sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection);
+ }
+}
+
+void
+MSWindowsScreen::openScreensaver(bool notify)
+{
+ assert(m_screensaver != NULL);
+
+ m_screensaverNotify = notify;
+ if (m_screensaverNotify) {
+ m_desks->installScreensaverHooks(true);
+ }
+ else if (m_screensaver) {
+ m_screensaver->disable();
+ }
+}
+
+void
+MSWindowsScreen::closeScreensaver()
+{
+ if (m_screensaver != NULL) {
+ if (m_screensaverNotify) {
+ m_desks->installScreensaverHooks(false);
+ }
+ else {
+ m_screensaver->enable();
+ }
+ }
+ m_screensaverNotify = false;
+}
+
+void
+MSWindowsScreen::screensaver(bool activate)
+{
+ assert(m_screensaver != NULL);
+ if (m_screensaver==NULL) return;
+
+ if (activate) {
+ m_screensaver->activate();
+ }
+ else {
+ m_screensaver->deactivate();
+ }
+}
+
+void
+MSWindowsScreen::resetOptions()
+{
+ m_desks->resetOptions();
+}
+
+void
+MSWindowsScreen::setOptions(const OptionsList& options)
+{
+ m_desks->setOptions(options);
+}
+
+void
+MSWindowsScreen::setSequenceNumber(UInt32 seqNum)
+{
+ m_sequenceNumber = seqNum;
+}
+
+bool
+MSWindowsScreen::isPrimary() const
+{
+ return m_isPrimary;
+}
+
+void*
+MSWindowsScreen::getEventTarget() const
+{
+ return const_cast<MSWindowsScreen*>(this);
+}
+
+bool
+MSWindowsScreen::getClipboard(ClipboardID, IClipboard* dst) const
+{
+ MSWindowsClipboard src(m_window);
+ Clipboard::copy(dst, &src);
+ return true;
+}
+
+void
+MSWindowsScreen::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
+MSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const
+{
+ m_desks->getCursorPos(x, y);
+}
+
+void
+MSWindowsScreen::reconfigure(UInt32 activeSides)
+{
+ assert(m_isPrimary);
+
+ LOG((CLOG_DEBUG "active sides: %x", activeSides));
+ m_hook.setSides(activeSides);
+}
+
+void
+MSWindowsScreen::warpCursor(SInt32 x, SInt32 y)
+{
+ // warp mouse
+ warpCursorNoFlush(x, y);
+
+ // remove all input events before and including warp
+ MSG msg;
+ while (PeekMessage(&msg, NULL, BARRIER_MSG_INPUT_FIRST,
+ BARRIER_MSG_INPUT_LAST, PM_REMOVE)) {
+ // do nothing
+ }
+
+ // save position to compute delta of next motion
+ saveMousePosition(x, y);
+}
+
+void MSWindowsScreen::saveMousePosition(SInt32 x, SInt32 y) {
+ m_xCursor = x;
+ m_yCursor = y;
+
+ LOG((CLOG_DEBUG5 "saved mouse position for next delta: %+d,%+d", x,y));
+}
+
+UInt32
+MSWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask)
+{
+ // only allow certain modifiers
+ if ((mask & ~(KeyModifierShift | KeyModifierControl |
+ KeyModifierAlt | KeyModifierSuper)) != 0) {
+ // this should be a warning, but this can confuse users,
+ // as this warning happens almost always.
+ LOG((CLOG_DEBUG "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
+ // this should be a warning, but this can confuse users,
+ // as this warning happens almost always.
+ LOG((CLOG_DEBUG "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;
+ id = (UInt32)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(HotKeyItem(vk, modifiers)) > 0);
+ }
+ else {
+ // register with OS
+ err = (RegisterHotKey(NULL, id, modifiers, vk) == 0);
+ }
+
+ if (!err) {
+ m_hotKeys.insert(std::make_pair(id, HotKeyItem(vk, modifiers)));
+ m_hotKeyToIDMap[HotKeyItem(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)", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask));
+ return 0;
+ }
+
+ LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask, id));
+ return id;
+}
+
+void
+MSWindowsScreen::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
+MSWindowsScreen::fakeInputBegin()
+{
+ assert(m_isPrimary);
+
+ if (!m_isOnScreen) {
+ m_keyState->useSavedModifiers(true);
+ }
+ m_desks->fakeInputBegin();
+}
+
+void
+MSWindowsScreen::fakeInputEnd()
+{
+ assert(m_isPrimary);
+
+ m_desks->fakeInputEnd();
+ if (!m_isOnScreen) {
+ m_keyState->useSavedModifiers(false);
+ }
+}
+
+SInt32
+MSWindowsScreen::getJumpZoneSize() const
+{
+ return 1;
+}
+
+bool
+MSWindowsScreen::isAnyMouseButtonDown(UInt32& buttonID) const
+{
+ static const char* buttonToName[] = {
+ "<invalid>",
+ "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]) {
+ buttonID = i;
+ LOG((CLOG_DEBUG "locked by \"%s\"", buttonToName[i]));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+MSWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const
+{
+ x = m_xCenter;
+ y = m_yCenter;
+}
+
+void
+MSWindowsScreen::fakeMouseButton(ButtonID id, bool press)
+{
+ m_desks->fakeMouseButton(id, press);
+
+ if (id == kButtonLeft) {
+ if (press) {
+ m_buttons[kButtonLeft] = true;
+ }
+ else {
+ m_buttons[kButtonLeft] = false;
+ m_fakeDraggingStarted = false;
+ m_draggingStarted = false;
+ }
+ }
+}
+
+void
+MSWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y)
+{
+ m_desks->fakeMouseMove(x, y);
+ if (m_buttons[kButtonLeft]) {
+ m_draggingStarted = true;
+ }
+}
+
+void
+MSWindowsScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const
+{
+ m_desks->fakeMouseRelativeMove(dx, dy);
+}
+
+void
+MSWindowsScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
+{
+ m_desks->fakeMouseWheel(xDelta, yDelta);
+}
+
+void
+MSWindowsScreen::updateKeys()
+{
+ m_desks->updateKeys();
+}
+
+void
+MSWindowsScreen::fakeKeyDown(KeyID id, KeyModifierMask mask,
+ KeyButton button)
+{
+ PlatformScreen::fakeKeyDown(id, mask, button);
+ updateForceShowCursor();
+}
+
+bool
+MSWindowsScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask,
+ SInt32 count, KeyButton button)
+{
+ bool result = PlatformScreen::fakeKeyRepeat(id, mask, count, button);
+ updateForceShowCursor();
+ return result;
+}
+
+bool
+MSWindowsScreen::fakeKeyUp(KeyButton button)
+{
+ bool result = PlatformScreen::fakeKeyUp(button);
+ updateForceShowCursor();
+ return result;
+}
+
+void
+MSWindowsScreen::fakeAllKeysUp()
+{
+ PlatformScreen::fakeAllKeysUp();
+ updateForceShowCursor();
+}
+
+HCURSOR
+MSWindowsScreen::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_windowInstance, 0, 0, cw, ch, cursorAND, cursorXOR);
+ delete[] cursorXOR;
+ delete[] cursorAND;
+ return c;
+}
+
+void
+MSWindowsScreen::destroyCursor(HCURSOR cursor) const
+{
+ if (cursor != NULL) {
+ DestroyCursor(cursor);
+ }
+}
+
+ATOM
+MSWindowsScreen::createWindowClass() const
+{
+ WNDCLASSEX classInfo;
+ classInfo.cbSize = sizeof(classInfo);
+ classInfo.style = CS_DBLCLKS | CS_NOCLOSE;
+ classInfo.lpfnWndProc = &MSWindowsScreen::wndProc;
+ classInfo.cbClsExtra = 0;
+ classInfo.cbWndExtra = 0;
+ classInfo.hInstance = s_windowInstance;
+ classInfo.hIcon = NULL;
+ classInfo.hCursor = NULL;
+ classInfo.hbrBackground = NULL;
+ classInfo.lpszMenuName = NULL;
+ classInfo.lpszClassName = "Barrier";
+ classInfo.hIconSm = NULL;
+ return RegisterClassEx(&classInfo);
+}
+
+void
+MSWindowsScreen::destroyClass(ATOM windowClass) const
+{
+ if (windowClass != 0) {
+ UnregisterClass(MAKEINTATOM(windowClass), s_windowInstance);
+ }
+}
+
+HWND
+MSWindowsScreen::createWindow(ATOM windowClass, const char* name) const
+{
+ HWND window = CreateWindowEx(WS_EX_TOPMOST |
+ WS_EX_TRANSPARENT |
+ WS_EX_TOOLWINDOW,
+ MAKEINTATOM(windowClass),
+ name,
+ WS_POPUP,
+ 0, 0, 1, 1,
+ NULL, NULL,
+ s_windowInstance,
+ NULL);
+ if (window == NULL) {
+ LOG((CLOG_ERR "failed to create window: %d", GetLastError()));
+ throw XScreenOpenFailure();
+ }
+ return window;
+}
+
+HWND
+MSWindowsScreen::createDropWindow(ATOM windowClass, const char* name) const
+{
+ HWND window = CreateWindowEx(
+ WS_EX_TOPMOST |
+ WS_EX_TRANSPARENT |
+ WS_EX_ACCEPTFILES,
+ MAKEINTATOM(m_class),
+ name,
+ WS_POPUP,
+ 0, 0, m_dropWindowSize, m_dropWindowSize,
+ NULL, NULL,
+ s_windowInstance,
+ NULL);
+
+ if (window == NULL) {
+ LOG((CLOG_ERR "failed to create drop window: %d", GetLastError()));
+ throw XScreenOpenFailure();
+ }
+
+ return window;
+}
+
+void
+MSWindowsScreen::destroyWindow(HWND hwnd) const
+{
+ if (hwnd != NULL) {
+ DestroyWindow(hwnd);
+ }
+}
+
+void
+MSWindowsScreen::sendEvent(Event::Type type, void* data)
+{
+ m_events->addEvent(Event(type, getEventTarget(), data));
+}
+
+void
+MSWindowsScreen::sendClipboardEvent(Event::Type type, ClipboardID id)
+{
+ ClipboardInfo* info = (ClipboardInfo*)malloc(sizeof(ClipboardInfo));
+ if (info == NULL) {
+ LOG((CLOG_ERR "malloc failed on %s:%s", __FILE__, __LINE__ ));
+ return;
+ }
+ info->m_id = id;
+ info->m_sequenceNumber = m_sequenceNumber;
+ sendEvent(type, info);
+}
+
+void
+MSWindowsScreen::handleSystemEvent(const Event& event, void*)
+{
+ MSG* msg = static_cast<MSG*>(event.getData());
+ assert(msg != NULL);
+
+ if (ArchMiscWindows::processDialog(msg)) {
+ return;
+ }
+ if (onPreDispatch(msg->hwnd, msg->message, msg->wParam, msg->lParam)) {
+ return;
+ }
+ TranslateMessage(msg);
+ DispatchMessage(msg);
+}
+
+void
+MSWindowsScreen::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*
+MSWindowsScreen::getKeyState() const
+{
+ return m_keyState;
+}
+
+bool
+MSWindowsScreen::onPreDispatch(HWND hwnd,
+ UINT message, WPARAM wParam, LPARAM lParam)
+{
+ // handle event
+ switch (message) {
+ case BARRIER_MSG_SCREEN_SAVER:
+ return onScreensaver(wParam != 0);
+
+ case BARRIER_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
+MSWindowsScreen::onPreDispatchPrimary(HWND,
+ UINT message, WPARAM wParam, LPARAM lParam)
+{
+ LOG((CLOG_DEBUG5 "handling pre-dispatch primary"));
+
+ // handle event
+ switch (message) {
+ case BARRIER_MSG_MARK:
+ return onMark(static_cast<UInt32>(wParam));
+
+ case BARRIER_MSG_KEY:
+ return onKey(wParam, lParam);
+
+ case BARRIER_MSG_MOUSE_BUTTON:
+ return onMouseButton(wParam, lParam);
+
+ case BARRIER_MSG_MOUSE_MOVE:
+ return onMouseMove(static_cast<SInt32>(wParam),
+ static_cast<SInt32>(lParam));
+
+ case BARRIER_MSG_MOUSE_WHEEL:
+ // XXX -- support x-axis scrolling
+ return onMouseWheel(0, static_cast<SInt32>(wParam));
+
+ case BARRIER_MSG_PRE_WARP:
+ {
+ // save position to compute delta of next motion
+ saveMousePosition(static_cast<SInt32>(wParam), static_cast<SInt32>(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, BARRIER_MSG_MOUSE_MOVE,
+ BARRIER_MSG_POST_WARP);
+ } while (msg.message != BARRIER_MSG_POST_WARP);
+ }
+ return true;
+
+ case BARRIER_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
+MSWindowsScreen::onEvent(HWND, UINT msg,
+ WPARAM wParam, LPARAM lParam, LRESULT* result)
+{
+ switch (msg) {
+ 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:
+ m_events->addEvent(Event(m_events->forIScreen().resume(),
+ getEventTarget(), NULL,
+ Event::kDeliverImmediately));
+ break;
+
+ case PBT_APMSUSPEND:
+ m_events->addEvent(Event(m_events->forIScreen().suspend(),
+ getEventTarget(), NULL,
+ Event::kDeliverImmediately));
+ break;
+ }
+ *result = TRUE;
+ return true;
+
+ case WM_DEVICECHANGE:
+ forceShowCursor();
+ break;
+
+ case WM_SETTINGCHANGE:
+ if (wParam == SPI_SETMOUSEKEYS) {
+ forceShowCursor();
+ }
+ break;
+ }
+
+ return false;
+}
+
+bool
+MSWindowsScreen::onMark(UInt32 mark)
+{
+ m_markReceived = mark;
+ return true;
+}
+
+bool
+MSWindowsScreen::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), 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);
+
+ if (!down && m_isPrimary && !m_isOnScreen) {
+ PrimaryKeyDownList::iterator find = std::find(m_primaryKeyDownList.begin(), m_primaryKeyDownList.end(), button);
+ if (find != m_primaryKeyDownList.end()) {
+ LOG((CLOG_DEBUG1 "release key button %d on primary", *find));
+ m_hook.setMode(kHOOK_WATCH_JUMP_ZONE);
+ fakeLocalKey(*find, false);
+ m_primaryKeyDownList.erase(find);
+ m_hook.setMode(kHOOK_RELAY_EVENTS);
+ return true;
+ }
+ }
+
+ // 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;
+ }
+ }
+
+ // stop sending modifier keys over and over again
+ if (isModifierRepeat(oldState, state, wParam)) {
+ 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<KeyButton>((lParam & 0x01ff0000u) >> 16);
+ if (key != kKeyNone) {
+ // 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
+MSWindowsScreen::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(HotKeyItem(virtKey, modifiers));
+ if (i == m_hotKeyToIDMap.end()) {
+ return false;
+ }
+
+ // find what kind of event
+ Event::Type type;
+ if ((lParam & 0x80000000u) == 0u) {
+ if ((lParam & 0x40000000u) != 0u) {
+ // ignore key repeats but it counts as a hot key
+ return true;
+ }
+ type = m_events->forIPrimaryScreen().hotKeyDown();
+ }
+ else {
+ type = m_events->forIPrimaryScreen().hotKeyUp();
+ }
+
+ // generate event
+ m_events->addEvent(Event(type, getEventTarget(),
+ HotKeyInfo::alloc(i->second)));
+
+ return true;
+}
+
+bool
+MSWindowsScreen::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;
+ if (button == kButtonLeft) {
+ m_draggingFilename.clear();
+ LOG((CLOG_DEBUG2 "dragging filename is cleared"));
+ }
+ }
+ else {
+ m_buttons[button] = false;
+ if (m_draggingStarted && button == kButtonLeft) {
+ m_draggingStarted = 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(m_events->forIPrimaryScreen().buttonDown(),
+ ButtonInfo::alloc(button, mask));
+ }
+ }
+ else {
+ LOG((CLOG_DEBUG1 "event: button release button=%d", button));
+ if (button != kButtonNone) {
+ sendEvent(m_events->forIPrimaryScreen().buttonUp(),
+ ButtonInfo::alloc(button, mask));
+ }
+ }
+ }
+
+ return true;
+}
+
+// here's how mouse movements are sent across the network to a client:
+// 1. barrier checks the mouse position on server screen
+// 2. records the delta (current x,y minus last x,y)
+// 3. records the current x,y as "last" (so we can calc delta next time)
+// 4. on the server, puts the cursor back to the center of the screen
+// - remember the cursor is hidden on the server at this point
+// - this actually records the current x,y as "last" a second time (it seems)
+// 5. sends the delta movement to the client (could be +1,+1 or -1,+4 for example)
+bool
+MSWindowsScreen::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;
+
+ LOG((CLOG_DEBUG3
+ "mouse move - motion delta: %+d=(%+d - %+d),%+d=(%+d - %+d)",
+ x, mx, m_xCursor, y, my, m_yCursor));
+
+ // ignore if the mouse didn't move or if message posted prior
+ // to last mark change.
+ if (ignore() || (x == 0 && y == 0)) {
+ return true;
+ }
+
+ // save position to compute delta of next motion
+ saveMousePosition(mx, my);
+
+ if (m_isOnScreen) {
+
+ // motion on primary screen
+ sendEvent(
+ m_events->forIPrimaryScreen().motionOnPrimary(),
+ MotionInfo::alloc(m_xCursor, m_yCursor));
+
+ if (m_buttons[kButtonLeft] == true && m_draggingStarted == false) {
+ m_draggingStarted = true;
+ }
+ }
+ else
+ {
+ // the motion is on the secondary screen, so we warp mouse back to
+ // center on the server screen. if we don't do this, then the mouse
+ // will always try to return to the original entry point on the
+ // secondary screen.
+ LOG((CLOG_DEBUG5 "warping server cursor to center: %+d,%+d", m_xCenter, m_yCenter));
+ warpCursorNoFlush(m_xCenter, m_yCenter);
+
+ // examine the motion. if it's about the distance
+ // 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 delta motion: %+d,%+d", x, y));
+ }
+ else {
+ // send motion
+ sendEvent(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(x, y));
+ }
+ }
+
+ return true;
+}
+
+bool
+MSWindowsScreen::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(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(xDelta, yDelta));
+ }
+ return true;
+}
+
+bool
+MSWindowsScreen::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, BARRIER_MSG_SCREEN_SAVER,
+ BARRIER_MSG_SCREEN_SAVER, PM_NOREMOVE)) {
+ return true;
+ }
+
+ if (activated) {
+ if (!m_screensaverActive &&
+ m_screensaver->checkStarted(BARRIER_MSG_SCREEN_SAVER, FALSE, 0)) {
+ m_screensaverActive = true;
+ sendEvent(m_events->forIPrimaryScreen().screensaverActivated());
+
+ // enable display power down
+ ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY);
+ }
+ }
+ else {
+ if (m_screensaverActive) {
+ m_screensaverActive = false;
+ sendEvent(m_events->forIPrimaryScreen().screensaverDeactivated());
+
+ // disable display power down
+ ArchMiscWindows::addBusyState(ArchMiscWindows::kDISPLAY);
+ }
+ }
+
+ return true;
+}
+
+bool
+MSWindowsScreen::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) {
+
+ LOG((CLOG_DEBUG1 "warping cursor to center: %+d, %+d", m_xCenter, m_yCenter));
+ warpCursor(m_xCenter, m_yCenter);
+ }
+
+ // tell hook about resize if on screen
+ else {
+ m_hook.setZone(m_x, m_y, m_w, m_h, getJumpZoneSize());
+ }
+ }
+
+ // send new screen info
+ sendEvent(m_events->forIScreen().shapeChanged());
+
+ 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
+MSWindowsScreen::onClipboardChange()
+{
+ // now notify client that somebody changed the clipboard (unless
+ // we're the owner).
+ if (!MSWindowsClipboard::isOwnedByBarrier()) {
+ if (m_ownClipboard) {
+ LOG((CLOG_DEBUG "clipboard changed: lost ownership"));
+ m_ownClipboard = false;
+ sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard);
+ sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection);
+ }
+ }
+ else if (!m_ownClipboard) {
+ LOG((CLOG_DEBUG "clipboard changed: barrier owned"));
+ m_ownClipboard = true;
+ }
+
+ return true;
+}
+
+void
+MSWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y)
+{
+ // send an event that we can recognize before the mouse warp
+ PostThreadMessage(GetCurrentThreadId(), BARRIER_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);
+
+ // check to see if the mouse pos was set correctly
+ POINT cursorPos;
+ GetCursorPos(&cursorPos);
+
+ // there is a bug or round error in SetCursorPos and GetCursorPos on
+ // a high DPI setting. The check here is for Vista/7 login screen.
+ // since this feature is mainly for client, so only check on client.
+ if (!isPrimary()) {
+ if ((cursorPos.x != x) && (cursorPos.y != y)) {
+ LOG((CLOG_DEBUG "SetCursorPos did not work; using fakeMouseMove instead"));
+ LOG((CLOG_DEBUG "cursor pos %d, %d expected pos %d, %d", cursorPos.x, cursorPos.y, x, y));
+ // when at Vista/7 login screen, SetCursorPos does not work (which could be
+ // an MS security feature). instead we can use fakeMouseMove, which calls
+ // mouse_event.
+ // IMPORTANT: as of implementing this function, it has an annoying side
+ // effect; instead of the mouse returning to the correct exit point, it
+ // returns to the center of the screen. this could have something to do with
+ // the center screen warping technique used (see comments for onMouseMove
+ // definition).
+ fakeMouseMove(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(), BARRIER_MSG_POST_WARP, 0, 0);
+}
+
+void
+MSWindowsScreen::nextMark()
+{
+ // next mark
+ ++m_mark;
+
+ // mark point in message queue where the mark was changed
+ PostThreadMessage(GetCurrentThreadId(), BARRIER_MSG_MARK, m_mark, 0);
+}
+
+bool
+MSWindowsScreen::ignore() const
+{
+ return (m_mark != m_markReceived);
+}
+
+void
+MSWindowsScreen::updateScreenShape()
+{
+ // get shape and center
+ m_w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ m_h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
+ m_x = GetSystemMetrics(SM_XVIRTUALSCREEN);
+ m_y = GetSystemMetrics(SM_YVIRTUALSCREEN);
+ 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
+MSWindowsScreen::handleFixes(const Event&, void*)
+{
+ // fix clipboard chain
+ fixClipboardViewer();
+
+ // update keys if keyboard layouts have changed
+ if (m_keyState->didGroupsChange()) {
+ updateKeys();
+ }
+}
+
+void
+MSWindowsScreen::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
+MSWindowsScreen::enableSpecialKeys(bool enable) const
+{
+}
+
+ButtonID
+MSWindowsScreen::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
+MSWindowsScreen::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
+MSWindowsScreen::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()) {
+ PlatformScreen::updateKeyMap();
+ }
+
+ // now update the keyboard state
+ PlatformScreen::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
+MSWindowsScreen::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
+MSWindowsScreen::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
+MSWindowsScreen::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;
+}
+
+void
+MSWindowsScreen::fakeLocalKey(KeyButton button, bool press) const
+{
+ INPUT input;
+ input.type = INPUT_KEYBOARD;
+ input.ki.wVk = m_keyState->mapButtonToVirtualKey(button);
+ DWORD pressFlag = press ? KEYEVENTF_EXTENDEDKEY : KEYEVENTF_KEYUP;
+ input.ki.dwFlags = pressFlag;
+ input.ki.time = 0;
+ input.ki.dwExtraInfo = 0;
+ SendInput(1,&input,sizeof(input));
+}
+
+//
+// MSWindowsScreen::HotKeyItem
+//
+
+MSWindowsScreen::HotKeyItem::HotKeyItem(UINT keycode, UINT mask) :
+ m_keycode(keycode),
+ m_mask(mask)
+{
+ // do nothing
+}
+
+UINT
+MSWindowsScreen::HotKeyItem::getVirtualKey() const
+{
+ return m_keycode;
+}
+
+bool
+MSWindowsScreen::HotKeyItem::operator<(const HotKeyItem& x) const
+{
+ return (m_keycode < x.m_keycode ||
+ (m_keycode == x.m_keycode && m_mask < x.m_mask));
+}
+
+void
+MSWindowsScreen::fakeDraggingFiles(DragFileList fileList)
+{
+ // possible design flaw: this function stops a "not implemented"
+ // exception from being thrown.
+}
+
+String&
+MSWindowsScreen::getDraggingFilename()
+{
+ if (m_draggingStarted) {
+ m_dropTarget->clearDraggingFilename();
+ m_draggingFilename.clear();
+
+ int halfSize = m_dropWindowSize / 2;
+
+ SInt32 xPos = m_isPrimary ? m_xCursor : m_xCenter;
+ SInt32 yPos = m_isPrimary ? m_yCursor : m_yCenter;
+ xPos = (xPos - halfSize) < 0 ? 0 : xPos - halfSize;
+ yPos = (yPos - halfSize) < 0 ? 0 : yPos - halfSize;
+ SetWindowPos(
+ m_dropWindow,
+ HWND_TOPMOST,
+ xPos,
+ yPos,
+ m_dropWindowSize,
+ m_dropWindowSize,
+ SWP_SHOWWINDOW);
+
+ // TODO: fake these keys properly
+ fakeKeyDown(kKeyEscape, 8192, 1);
+ fakeKeyUp(1);
+ fakeMouseButton(kButtonLeft, false);
+
+ String filename;
+ DOUBLE timeout = ARCH->time() + .5f;
+ while (ARCH->time() < timeout) {
+ ARCH->sleep(.05f);
+ filename = m_dropTarget->getDraggingFilename();
+ if (!filename.empty()) {
+ break;
+ }
+ }
+
+ ShowWindow(m_dropWindow, SW_HIDE);
+
+ if (!filename.empty()) {
+ if (DragInformation::isFileValid(filename)) {
+ m_draggingFilename = filename;
+ }
+ else {
+ LOG((CLOG_DEBUG "drag file name is invalid: %s", filename.c_str()));
+ }
+ }
+
+ if (m_draggingFilename.empty()) {
+ LOG((CLOG_DEBUG "failed to get drag file name from OLE"));
+ }
+ }
+
+ return m_draggingFilename;
+}
+
+const String&
+MSWindowsScreen::getDropTarget() const
+{
+ return m_desktopPath;
+}
+
+bool
+MSWindowsScreen::isModifierRepeat(KeyModifierMask oldState, KeyModifierMask state, WPARAM wParam) const
+{
+ bool result = false;
+
+ if (oldState == state && state != 0) {
+ UINT virtKey = (wParam & 0xffu);
+ if ((state & KeyModifierShift) != 0
+ && (virtKey == VK_LSHIFT || virtKey == VK_RSHIFT)) {
+ result = true;
+ }
+ if ((state & KeyModifierControl) != 0
+ && (virtKey == VK_LCONTROL || virtKey == VK_RCONTROL)) {
+ result = true;
+ }
+ if ((state & KeyModifierAlt) != 0
+ && (virtKey == VK_LMENU || virtKey == VK_RMENU)) {
+ result = true;
+ }
+ if ((state & KeyModifierSuper) != 0
+ && (virtKey == VK_LWIN || virtKey == VK_RWIN)) {
+ result = true;
+ }
+ }
+
+ return result;
+}
diff --git a/src/lib/platform/MSWindowsScreen.h b/src/lib/platform/MSWindowsScreen.h
new file mode 100644
index 0000000..4245d6c
--- /dev/null
+++ b/src/lib/platform/MSWindowsScreen.h
@@ -0,0 +1,346 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2018 Debauchee Open Source Group
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/MSWindowsHook.h"
+#include "barrier/PlatformScreen.h"
+#include "barrier/DragInformation.h"
+#include "platform/synwinhk.h"
+#include "mt/CondVar.h"
+#include "mt/Mutex.h"
+#include "base/String.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+class EventQueueTimer;
+class MSWindowsDesks;
+class MSWindowsKeyState;
+class MSWindowsScreenSaver;
+class Thread;
+class MSWindowsDropTarget;
+
+//! Implementation of IPlatformScreen for Microsoft Windows
+class MSWindowsScreen : public PlatformScreen {
+public:
+ MSWindowsScreen(
+ bool isPrimary,
+ bool noHooks,
+ bool stopOnDeskSwitch,
+ IEventQueue* events);
+ virtual ~MSWindowsScreen();
+
+ //! @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 getWindowInstance();
+
+ //@}
+
+ // 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(UInt32& buttonID) const;
+ virtual void getCursorCenter(SInt32& x, SInt32& y) const;
+
+ // ISecondaryScreen overrides
+ virtual void fakeMouseButton(ButtonID id, bool press);
+ virtual void fakeMouseMove(SInt32 x, SInt32 y);
+ 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 bool fakeKeyRepeat(KeyID id, KeyModifierMask mask,
+ SInt32 count, KeyButton button);
+ virtual bool 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 OptionsList& options);
+ virtual void setSequenceNumber(UInt32);
+ virtual bool isPrimary() const;
+ virtual void fakeDraggingFiles(DragFileList fileList);
+ virtual String& getDraggingFilename();
+ virtual const String&
+ getDropTarget() const;
+
+protected:
+ // IPlatformScreen overrides
+ virtual void handleSystemEvent(const Event&, void*);
+ virtual void updateButtons();
+ virtual IKeyState* getKeyState() const;
+
+ // simulate a local key to the system directly
+ void fakeLocalKey(KeyButton button, bool press) const;
+
+private:
+ // initialization and shutdown operations
+ 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;
+ HWND createDropWindow(ATOM windowClass, const char* name) const;
+ void destroyWindow(HWND) const;
+
+ // convenience function to send events
+public: // HACK
+ void sendEvent(Event::Type type, void* = NULL);
+private: // HACK
+ void sendClipboardEvent(Event::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 Event&, 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);
+
+ // save last position of mouse to compute next delta movement
+ void saveMousePosition(SInt32 x, SInt32 y);
+
+ // check if it is a modifier key repeating message
+ bool isModifierRepeat(KeyModifierMask oldState,
+ KeyModifierMask state, WPARAM wParam) const;
+
+ // send drag info and data back to server
+ void sendDragThread(void*);
+
+private:
+ struct HotKeyItem {
+ public:
+ HotKeyItem(UINT vk, UINT modifiers);
+
+ UINT getVirtualKey() const;
+
+ bool operator<(const HotKeyItem&) const;
+
+ private:
+ UINT m_keycode;
+ UINT m_mask;
+ };
+ typedef std::map<UInt32, HotKeyItem> HotKeyMap;
+ typedef std::vector<UInt32> HotKeyIDList;
+ typedef std::map<HotKeyItem, UInt32> HotKeyToIDMap;
+ typedef std::vector<KeyButton> PrimaryKeyDownList;
+
+ static HINSTANCE s_windowInstance;
+
+ // true if screen is being used as a primary screen, false otherwise
+ bool m_isPrimary;
+
+ // true if hooks are not to be installed (useful for debugging)
+ bool m_noHooks;
+
+ // true if 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
+ EventQueueTimer* m_fixTimer;
+
+ // the keyboard layout to use when off primary screen
+ HKL m_keyLayout;
+
+ // screen saver stuff
+ MSWindowsScreenSaver*
+ 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
+ MSWindowsDesks* m_desks;
+
+ // keyboard stuff
+ MSWindowsKeyState* 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;
+
+ MSWindowsHook m_hook;
+
+ static MSWindowsScreen*
+ s_screen;
+
+ IEventQueue* m_events;
+
+ String m_desktopPath;
+
+ MSWindowsDropTarget*
+ m_dropTarget;
+ HWND m_dropWindow;
+ const int m_dropWindowSize;
+
+ Thread* m_sendDragThread;
+
+ PrimaryKeyDownList m_primaryKeyDownList;
+};
diff --git a/src/lib/platform/MSWindowsScreenSaver.cpp b/src/lib/platform/MSWindowsScreenSaver.cpp
new file mode 100644
index 0000000..f9c15fb
--- /dev/null
+++ b/src/lib/platform/MSWindowsScreenSaver.cpp
@@ -0,0 +1,359 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsScreenSaver.h"
+
+#include "platform/MSWindowsScreen.h"
+#include "mt/Thread.h"
+#include "arch/Arch.h"
+#include "arch/win32/ArchMiscWindows.h"
+#include "base/Log.h"
+#include "base/TMethodJob.h"
+
+#include <malloc.h>
+#include <tchar.h>
+
+#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
+};
+
+//
+// MSWindowsScreenSaver
+//
+
+MSWindowsScreenSaver::MSWindowsScreenSaver() :
+ m_wasSecure(false),
+ m_wasSecureAnInt(false),
+ m_process(NULL),
+ m_watch(NULL),
+ m_threadID(0),
+ m_msg(0),
+ m_wParam(0),
+ m_lParam(0),
+ m_active(false)
+{
+ // check if screen saver is enabled
+ SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0);
+}
+
+MSWindowsScreenSaver::~MSWindowsScreenSaver()
+{
+ unwatchProcess();
+}
+
+bool
+MSWindowsScreenSaver::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;
+
+ // 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
+MSWindowsScreenSaver::enable()
+{
+ SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, m_wasEnabled, 0, 0);
+
+ // restore password protection
+ if (m_wasSecure) {
+ setSecure(true, m_wasSecureAnInt);
+ }
+
+ // restore display power down
+ ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY);
+}
+
+void
+MSWindowsScreenSaver::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
+ ArchMiscWindows::addBusyState(ArchMiscWindows::kDISPLAY);
+}
+
+void
+MSWindowsScreenSaver::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
+ ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY);
+ }
+}
+
+void
+MSWindowsScreenSaver::deactivate()
+{
+ bool killed = false;
+
+ // NT runs screen saver in another desktop
+ HDESK desktop = OpenDesktop("Screen-saver", 0, FALSE,
+ DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
+ if (desktop != NULL) {
+ EnumDesktopWindows(desktop,
+ &MSWindowsScreenSaver::killScreenSaverFunc,
+ reinterpret_cast<LPARAM>(&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
+ ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY);
+}
+
+bool
+MSWindowsScreenSaver::isActive() const
+{
+ BOOL running;
+ SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, 0);
+ return (running != FALSE);
+}
+
+BOOL CALLBACK
+MSWindowsScreenSaver::killScreenSaverFunc(HWND hwnd, LPARAM arg)
+{
+ if (IsWindowVisible(hwnd)) {
+ HINSTANCE instance = (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
+ if (instance != MSWindowsScreen::getWindowInstance()) {
+ PostMessage(hwnd, WM_CLOSE, 0, 0);
+ *reinterpret_cast<bool*>(arg) = true;
+ }
+ }
+ return TRUE;
+}
+
+void
+MSWindowsScreenSaver::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 Thread(new TMethodJob<MSWindowsScreenSaver>(this,
+ &MSWindowsScreenSaver::watchDesktopThread));
+}
+
+void
+MSWindowsScreenSaver::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 Thread(new TMethodJob<MSWindowsScreenSaver>(this,
+ &MSWindowsScreenSaver::watchProcessThread));
+ }
+}
+
+void
+MSWindowsScreenSaver::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
+MSWindowsScreenSaver::watchDesktopThread(void*)
+{
+ DWORD reserved = 0;
+ TCHAR* name = NULL;
+
+ for (;;) {
+ // wait a bit
+ ARCH->sleep(0.2);
+
+ 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
+MSWindowsScreenSaver::watchProcessThread(void*)
+{
+ for (;;) {
+ Thread::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
+MSWindowsScreenSaver::setSecure(bool secure, bool saveSecureAsInt)
+{
+ HKEY hkey =
+ ArchMiscWindows::addKey(HKEY_CURRENT_USER, g_pathScreenSaverIsSecure);
+ if (hkey == NULL) {
+ return;
+ }
+
+ if (saveSecureAsInt) {
+ ArchMiscWindows::setValue(hkey, g_isSecureNT, secure ? 1 : 0);
+ }
+ else {
+ ArchMiscWindows::setValue(hkey, g_isSecureNT, secure ? "1" : "0");
+ }
+
+ ArchMiscWindows::closeKey(hkey);
+}
+
+bool
+MSWindowsScreenSaver::isSecure(bool* wasSecureFlagAnInt) const
+{
+ // get the password protection setting key
+ HKEY hkey =
+ ArchMiscWindows::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;
+ switch (ArchMiscWindows::typeOfValue(hkey, g_isSecureNT)) {
+ default:
+ result = false;
+ break;
+
+ case ArchMiscWindows::kUINT: {
+ DWORD value =
+ ArchMiscWindows::readValueInt(hkey, g_isSecureNT);
+ *wasSecureFlagAnInt = true;
+ result = (value != 0);
+ break;
+ }
+
+ case ArchMiscWindows::kSTRING: {
+ std::string value =
+ ArchMiscWindows::readValueString(hkey, g_isSecureNT);
+ *wasSecureFlagAnInt = false;
+ result = (value != "0");
+ break;
+ }
+ }
+
+ ArchMiscWindows::closeKey(hkey);
+ return result;
+}
diff --git a/src/lib/platform/MSWindowsScreenSaver.h b/src/lib/platform/MSWindowsScreenSaver.h
new file mode 100644
index 0000000..91df181
--- /dev/null
+++ b/src/lib/platform/MSWindowsScreenSaver.h
@@ -0,0 +1,89 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/IScreenSaver.h"
+#include "base/String.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+class Thread;
+
+//! Microsoft windows screen saver implementation
+class MSWindowsScreenSaver : public IScreenSaver {
+public:
+ MSWindowsScreenSaver();
+ virtual ~MSWindowsScreenSaver();
+
+ //! @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 FindScreenSaverInfo {
+ public:
+ HDESK m_desktop;
+ HWND m_window;
+ };
+
+ static BOOL CALLBACK killScreenSaverFunc(HWND hwnd, LPARAM lParam);
+
+ 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_wasEnabled;
+ bool m_wasSecure;
+ bool m_wasSecureAnInt;
+
+ HANDLE m_process;
+ Thread* 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;
+};
diff --git a/src/lib/platform/MSWindowsSession.cpp b/src/lib/platform/MSWindowsSession.cpp
new file mode 100644
index 0000000..63e8d8f
--- /dev/null
+++ b/src/lib/platform/MSWindowsSession.cpp
@@ -0,0 +1,195 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsSession.h"
+
+#include "arch/win32/XArchWindows.h"
+#include "barrier/XBarrier.h"
+#include "base/Log.h"
+
+#include <Wtsapi32.h>
+
+MSWindowsSession::MSWindowsSession() :
+ m_activeSessionId(-1)
+{
+}
+
+MSWindowsSession::~MSWindowsSession()
+{
+}
+
+bool
+MSWindowsSession::isProcessInSession(const char* name, PHANDLE process = NULL)
+{
+ // first we need to take a snapshot of the running processes
+ HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (snapshot == INVALID_HANDLE_VALUE) {
+ LOG((CLOG_ERR "could not get process snapshot"));
+ throw XArch(new XArchEvalWindows());
+ }
+
+ PROCESSENTRY32 entry;
+ entry.dwSize = sizeof(PROCESSENTRY32);
+
+ // get the first process, and if we can't do that then it's
+ // unlikely we can go any further
+ BOOL gotEntry = Process32First(snapshot, &entry);
+ if (!gotEntry) {
+ LOG((CLOG_ERR "could not get first process entry"));
+ throw XArch(new XArchEvalWindows());
+ }
+
+ // used to record process names for debug info
+ std::list<std::string> nameList;
+
+ // now just iterate until we can find winlogon.exe pid
+ DWORD pid = 0;
+ while(gotEntry) {
+
+ // make sure we're not checking the system process
+ if (entry.th32ProcessID != 0) {
+
+ DWORD processSessionId;
+ BOOL pidToSidRet = ProcessIdToSessionId(
+ entry.th32ProcessID, &processSessionId);
+
+ if (!pidToSidRet) {
+ // if we can not acquire session associated with a specified process,
+ // simply ignore it
+ LOG((CLOG_ERR "could not get session id for process id %i", entry.th32ProcessID));
+ gotEntry = nextProcessEntry(snapshot, &entry);
+ continue;
+ }
+ else {
+ // only pay attention to processes in the active session
+ if (processSessionId == m_activeSessionId) {
+
+ // store the names so we can record them for debug
+ nameList.push_back(entry.szExeFile);
+
+ if (_stricmp(entry.szExeFile, name) == 0) {
+ pid = entry.th32ProcessID;
+ }
+ }
+ }
+
+ }
+
+ // now move on to the next entry (if we're not at the end)
+ gotEntry = nextProcessEntry(snapshot, &entry);
+ }
+
+ std::string nameListJoin;
+ for(std::list<std::string>::iterator it = nameList.begin();
+ it != nameList.end(); it++) {
+ nameListJoin.append(*it);
+ nameListJoin.append(", ");
+ }
+
+ LOG((CLOG_DEBUG "processes in session %d: %s",
+ m_activeSessionId, nameListJoin.c_str()));
+
+ CloseHandle(snapshot);
+
+ if (pid) {
+ if (process != NULL) {
+ // now get the process, which we'll use to get the process token.
+ LOG((CLOG_DEBUG "found %s in session %i", name, m_activeSessionId));
+ *process = OpenProcess(MAXIMUM_ALLOWED, FALSE, pid);
+ }
+ return true;
+ }
+ else {
+ LOG((CLOG_DEBUG "did not find %s in session %i", name, m_activeSessionId));
+ return false;
+ }
+}
+
+HANDLE
+MSWindowsSession::getUserToken(LPSECURITY_ATTRIBUTES security)
+{
+ HANDLE sourceToken;
+ if (!WTSQueryUserToken(m_activeSessionId, &sourceToken)) {
+ LOG((CLOG_ERR "could not get token from session %d", m_activeSessionId));
+ throw XArch(new XArchEvalWindows);
+ }
+
+ HANDLE newToken;
+ if (!DuplicateTokenEx(
+ sourceToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, security,
+ SecurityImpersonation, TokenPrimary, &newToken)) {
+
+ LOG((CLOG_ERR "could not duplicate token"));
+ throw XArch(new XArchEvalWindows);
+ }
+
+ LOG((CLOG_DEBUG "duplicated, new token: %i", newToken));
+ return newToken;
+}
+
+BOOL
+MSWindowsSession::hasChanged()
+{
+ return (m_activeSessionId != WTSGetActiveConsoleSessionId());
+}
+
+void
+MSWindowsSession::updateActiveSession()
+{
+ m_activeSessionId = WTSGetActiveConsoleSessionId();
+}
+
+
+BOOL
+MSWindowsSession::nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry)
+{
+ BOOL gotEntry = Process32Next(snapshot, entry);
+ if (!gotEntry) {
+
+ DWORD err = GetLastError();
+ if (err != ERROR_NO_MORE_FILES) {
+
+ // only worry about error if it's not the end of the snapshot
+ LOG((CLOG_ERR "could not get next process entry"));
+ throw XArch(new XArchEvalWindows());
+ }
+ }
+
+ return gotEntry;
+}
+
+String
+MSWindowsSession::getActiveDesktopName()
+{
+ String result;
+ try {
+ HDESK hd = OpenInputDesktop(0, TRUE, GENERIC_READ);
+ if (hd != NULL) {
+ DWORD size;
+ GetUserObjectInformation(hd, UOI_NAME, NULL, 0, &size);
+ TCHAR* name = (TCHAR*)alloca(size + sizeof(TCHAR));
+ GetUserObjectInformation(hd, UOI_NAME, name, size, &size);
+ result = name;
+ CloseDesktop(hd);
+ }
+ }
+ catch (std::exception& error) {
+ LOG((CLOG_ERR "failed to get active desktop name: %s", error.what()));
+ }
+
+ return result;
+}
diff --git a/src/lib/platform/MSWindowsSession.h b/src/lib/platform/MSWindowsSession.h
new file mode 100644
index 0000000..d777141
--- /dev/null
+++ b/src/lib/platform/MSWindowsSession.h
@@ -0,0 +1,52 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <Tlhelp32.h>
+
+class MSWindowsSession {
+public:
+ MSWindowsSession();
+ ~MSWindowsSession();
+
+ //!
+ /*!
+ Returns true if the session ID has changed since updateActiveSession was called.
+ */
+ BOOL hasChanged();
+
+ bool isProcessInSession(const char* name, PHANDLE process);
+
+ HANDLE getUserToken(LPSECURITY_ATTRIBUTES security);
+
+ DWORD getActiveSessionId() { return m_activeSessionId; }
+
+ void updateActiveSession();
+
+ String getActiveDesktopName();
+
+private:
+ BOOL nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry);
+
+private:
+ DWORD m_activeSessionId;
+};
diff --git a/src/lib/platform/MSWindowsUtil.cpp b/src/lib/platform/MSWindowsUtil.cpp
new file mode 100644
index 0000000..4b51781
--- /dev/null
+++ b/src/lib/platform/MSWindowsUtil.cpp
@@ -0,0 +1,64 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsUtil.h"
+
+#include "base/String.h"
+
+#include <stdio.h>
+
+//
+// MSWindowsUtil
+//
+
+String
+MSWindowsUtil::getString(HINSTANCE instance, DWORD id)
+{
+ char* msg = NULL;
+ int n = LoadString(instance, id, reinterpret_cast<LPSTR>(&msg), 0);
+
+ if (n <= 0) {
+ return String();
+ }
+
+ return String (msg, n);
+}
+
+String
+MSWindowsUtil::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) {
+ String errorString = barrier::string::sprintf("%d", error);
+ return barrier::string::format(getString(hinstance, id).c_str(),
+ errorString.c_str());
+ }
+ else {
+ String result(buffer);
+ LocalFree(buffer);
+ return result;
+ }
+}
diff --git a/src/lib/platform/MSWindowsUtil.h b/src/lib/platform/MSWindowsUtil.h
new file mode 100644
index 0000000..95ec2cf
--- /dev/null
+++ b/src/lib/platform/MSWindowsUtil.h
@@ -0,0 +1,40 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+
+#define WINDOWS_LEAN_AND_MEAN
+#include <Windows.h>
+
+class MSWindowsUtil {
+public:
+ //! Get message string
+ /*!
+ Gets a string for \p id from the string table of \p instance.
+ */
+ static String 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 String getErrorString(HINSTANCE, DWORD error, DWORD id);
+};
diff --git a/src/lib/platform/MSWindowsWatchdog.cpp b/src/lib/platform/MSWindowsWatchdog.cpp
new file mode 100644
index 0000000..ba1890e
--- /dev/null
+++ b/src/lib/platform/MSWindowsWatchdog.cpp
@@ -0,0 +1,611 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2009 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/MSWindowsWatchdog.h"
+
+#include "ipc/IpcLogOutputter.h"
+#include "ipc/IpcServer.h"
+#include "ipc/IpcMessage.h"
+#include "ipc/Ipc.h"
+#include "barrier/App.h"
+#include "barrier/ArgsBase.h"
+#include "mt/Thread.h"
+#include "arch/win32/ArchDaemonWindows.h"
+#include "arch/win32/XArchWindows.h"
+#include "arch/Arch.h"
+#include "base/log_outputters.h"
+#include "base/TMethodJob.h"
+#include "base/Log.h"
+#include "common/Version.h"
+
+#include <sstream>
+#include <UserEnv.h>
+#include <Shellapi.h>
+
+#define MAXIMUM_WAIT_TIME 3
+enum {
+ kOutputBufferSize = 4096
+};
+
+typedef VOID (WINAPI *SendSas)(BOOL asUser);
+
+const char g_activeDesktop[] = {"activeDesktop:"};
+
+MSWindowsWatchdog::MSWindowsWatchdog(
+ bool daemonized,
+ bool autoDetectCommand,
+ IpcServer& ipcServer,
+ IpcLogOutputter& ipcLogOutputter) :
+ m_thread(NULL),
+ m_autoDetectCommand(autoDetectCommand),
+ m_monitoring(true),
+ m_commandChanged(false),
+ m_stdOutWrite(NULL),
+ m_stdOutRead(NULL),
+ m_ipcServer(ipcServer),
+ m_ipcLogOutputter(ipcLogOutputter),
+ m_elevateProcess(false),
+ m_processFailures(0),
+ m_processRunning(false),
+ m_fileLogOutputter(NULL),
+ m_autoElevated(false),
+ m_ready(false),
+ m_daemonized(daemonized)
+{
+ m_mutex = ARCH->newMutex();
+ m_condVar = ARCH->newCondVar();
+}
+
+MSWindowsWatchdog::~MSWindowsWatchdog()
+{
+ if (m_condVar != NULL) {
+ ARCH->closeCondVar(m_condVar);
+ }
+
+ if (m_mutex != NULL) {
+ ARCH->closeMutex(m_mutex);
+ }
+}
+
+void
+MSWindowsWatchdog::startAsync()
+{
+ m_thread = new Thread(new TMethodJob<MSWindowsWatchdog>(
+ this, &MSWindowsWatchdog::mainLoop, nullptr));
+
+ m_outputThread = new Thread(new TMethodJob<MSWindowsWatchdog>(
+ this, &MSWindowsWatchdog::outputLoop, nullptr));
+}
+
+void
+MSWindowsWatchdog::stop()
+{
+ m_monitoring = false;
+
+ m_thread->wait(5);
+ delete m_thread;
+
+ m_outputThread->wait(5);
+ delete m_outputThread;
+}
+
+HANDLE
+MSWindowsWatchdog::duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security)
+{
+ HANDLE sourceToken;
+
+ BOOL tokenRet = OpenProcessToken(
+ process,
+ TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
+ &sourceToken);
+
+ if (!tokenRet) {
+ LOG((CLOG_ERR "could not open token, process handle: %d", process));
+ throw XArch(new XArchEvalWindows());
+ }
+
+ LOG((CLOG_DEBUG "got token %i, duplicating", sourceToken));
+
+ HANDLE newToken;
+ BOOL duplicateRet = DuplicateTokenEx(
+ sourceToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, security,
+ SecurityImpersonation, TokenPrimary, &newToken);
+
+ if (!duplicateRet) {
+ LOG((CLOG_ERR "could not duplicate token %i", sourceToken));
+ throw XArch(new XArchEvalWindows());
+ }
+
+ LOG((CLOG_DEBUG "duplicated, new token: %i", newToken));
+ return newToken;
+}
+
+HANDLE
+MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security)
+{
+ // always elevate if we are at the vista/7 login screen. we could also
+ // elevate for the uac dialog (consent.exe) but this would be pointless,
+ // since barrier would re-launch as non-elevated after the desk switch,
+ // and so would be unusable with the new elevated process taking focus.
+ if (m_elevateProcess || m_autoElevated) {
+ LOG((CLOG_DEBUG "getting elevated token, %s",
+ (m_elevateProcess ? "elevation required" : "at login screen")));
+
+ HANDLE process;
+ if (!m_session.isProcessInSession("winlogon.exe", &process)) {
+ throw XMSWindowsWatchdogError("cannot get user token without winlogon.exe");
+ }
+
+ return duplicateProcessToken(process, security);
+ } else {
+ LOG((CLOG_DEBUG "getting non-elevated token"));
+ return m_session.getUserToken(security);
+ }
+}
+
+void
+MSWindowsWatchdog::mainLoop(void*)
+{
+ shutdownExistingProcesses();
+
+ SendSas sendSasFunc = NULL;
+ HINSTANCE sasLib = LoadLibrary("sas.dll");
+ if (sasLib) {
+ LOG((CLOG_DEBUG "found sas.dll"));
+ sendSasFunc = (SendSas)GetProcAddress(sasLib, "SendSAS");
+ }
+
+ SECURITY_ATTRIBUTES saAttr;
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+
+ if (!CreatePipe(&m_stdOutRead, &m_stdOutWrite, &saAttr, 0)) {
+ throw XArch(new XArchEvalWindows());
+ }
+
+ ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
+
+ while (m_monitoring) {
+ try {
+
+ if (m_processRunning && getCommand().empty()) {
+ LOG((CLOG_INFO "process started but command is empty, shutting down"));
+ shutdownExistingProcesses();
+ m_processRunning = false;
+ continue;
+ }
+
+ if (m_processFailures != 0) {
+ // increasing backoff period, maximum of 10 seconds.
+ int timeout = (m_processFailures * 2) < 10 ? (m_processFailures * 2) : 10;
+ LOG((CLOG_INFO "backing off, wait=%ds, failures=%d", timeout, m_processFailures));
+ ARCH->sleep(timeout);
+ }
+
+ if (!getCommand().empty() && ((m_processFailures != 0) || m_session.hasChanged() || m_commandChanged)) {
+ startProcess();
+ }
+
+ if (m_processRunning && !isProcessActive()) {
+
+ m_processFailures++;
+ m_processRunning = false;
+
+ LOG((CLOG_WARN "detected application not running, pid=%d",
+ m_processInfo.dwProcessId));
+ }
+
+ if (sendSasFunc != NULL) {
+
+ HANDLE sendSasEvent = CreateEvent(NULL, FALSE, FALSE, "Global\\SendSAS");
+ if (sendSasEvent != NULL) {
+
+ // use SendSAS event to wait for next session (timeout 1 second).
+ if (WaitForSingleObject(sendSasEvent, 1000) == WAIT_OBJECT_0) {
+ LOG((CLOG_DEBUG "calling SendSAS"));
+ sendSasFunc(FALSE);
+ }
+
+ CloseHandle(sendSasEvent);
+ continue;
+ }
+ }
+
+ // if the sas event failed, wait by sleeping.
+ ARCH->sleep(1);
+
+ }
+ catch (std::exception& e) {
+ LOG((CLOG_ERR "failed to launch, error: %s", e.what()));
+ m_processFailures++;
+ m_processRunning = false;
+ continue;
+ }
+ catch (...) {
+ LOG((CLOG_ERR "failed to launch, unknown error."));
+ m_processFailures++;
+ m_processRunning = false;
+ continue;
+ }
+ }
+
+ if (m_processRunning) {
+ LOG((CLOG_DEBUG "terminated running process on exit"));
+ shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20);
+ }
+
+ LOG((CLOG_DEBUG "watchdog main thread finished"));
+}
+
+bool
+MSWindowsWatchdog::isProcessActive()
+{
+ DWORD exitCode;
+ GetExitCodeProcess(m_processInfo.hProcess, &exitCode);
+ return exitCode == STILL_ACTIVE;
+}
+
+void
+MSWindowsWatchdog::setFileLogOutputter(FileLogOutputter* outputter)
+{
+ m_fileLogOutputter = outputter;
+}
+
+void
+MSWindowsWatchdog::startProcess()
+{
+ if (m_command.empty()) {
+ throw XMSWindowsWatchdogError("cannot start process, command is empty");
+ }
+
+ m_commandChanged = false;
+
+ if (m_processRunning) {
+ LOG((CLOG_DEBUG "closing existing process to make way for new one"));
+ shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20);
+ m_processRunning = false;
+ }
+
+ m_session.updateActiveSession();
+
+ BOOL createRet;
+ if (!m_daemonized) {
+ createRet = doStartProcessAsSelf(m_command);
+ } else {
+ SECURITY_ATTRIBUTES sa;
+ ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
+
+ getActiveDesktop(&sa);
+
+ ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
+ HANDLE userToken = getUserToken(&sa);
+ m_elevateProcess = m_autoElevated ? m_autoElevated : m_elevateProcess;
+ m_autoElevated = false;
+
+ // patch by Jack Zhou and Henry Tung
+ // set UIAccess to fix Windows 8 GUI interaction
+ // http://symless.com/spit/issues/details/3338/#c70
+ DWORD uiAccess = 1;
+ SetTokenInformation(userToken, TokenUIAccess, &uiAccess, sizeof(DWORD));
+
+ createRet = doStartProcessAsUser(m_command, userToken, &sa);
+ }
+
+ if (!createRet) {
+ LOG((CLOG_ERR "could not launch"));
+ DWORD exitCode = 0;
+ GetExitCodeProcess(m_processInfo.hProcess, &exitCode);
+ LOG((CLOG_ERR "exit code: %d", exitCode));
+ throw XArch(new XArchEvalWindows);
+ }
+ else {
+ // wait for program to fail.
+ ARCH->sleep(1);
+ if (!isProcessActive()) {
+ throw XMSWindowsWatchdogError("process immediately stopped");
+ }
+
+ m_processRunning = true;
+ m_processFailures = 0;
+
+ LOG((CLOG_DEBUG "started process, session=%i, elevated: %s, command=%s",
+ m_session.getActiveSessionId(),
+ m_elevateProcess ? "yes" : "no",
+ m_command.c_str()));
+ }
+}
+
+BOOL
+MSWindowsWatchdog::doStartProcessAsSelf(String& command)
+{
+ DWORD creationFlags =
+ NORMAL_PRIORITY_CLASS |
+ CREATE_NO_WINDOW |
+ CREATE_UNICODE_ENVIRONMENT;
+
+ STARTUPINFO si;
+ ZeroMemory(&si, sizeof(STARTUPINFO));
+ si.cb = sizeof(STARTUPINFO);
+ si.lpDesktop = "winsta0\\Default"; // TODO: maybe this should be \winlogon if we have logonui.exe?
+ si.hStdError = m_stdOutWrite;
+ si.hStdOutput = m_stdOutWrite;
+ si.dwFlags |= STARTF_USESTDHANDLES;
+
+ LOG((CLOG_INFO "starting new process as self"));
+ return CreateProcess(NULL, LPSTR(command.c_str()), NULL, NULL, FALSE, creationFlags, NULL, NULL, &si, &m_processInfo);
+}
+
+BOOL
+MSWindowsWatchdog::doStartProcessAsUser(String& command, HANDLE userToken, LPSECURITY_ATTRIBUTES sa)
+{
+ // clear, as we're reusing process info struct
+ ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
+
+ STARTUPINFO si;
+ ZeroMemory(&si, sizeof(STARTUPINFO));
+ si.cb = sizeof(STARTUPINFO);
+ si.lpDesktop = "winsta0\\Default"; // TODO: maybe this should be \winlogon if we have logonui.exe?
+ si.hStdError = m_stdOutWrite;
+ si.hStdOutput = m_stdOutWrite;
+ si.dwFlags |= STARTF_USESTDHANDLES;
+
+ LPVOID environment;
+ BOOL blockRet = CreateEnvironmentBlock(&environment, userToken, FALSE);
+ if (!blockRet) {
+ LOG((CLOG_ERR "could not create environment block"));
+ throw XArch(new XArchEvalWindows);
+ }
+
+ DWORD creationFlags =
+ NORMAL_PRIORITY_CLASS |
+ CREATE_NO_WINDOW |
+ CREATE_UNICODE_ENVIRONMENT;
+
+ // re-launch in current active user session
+ LOG((CLOG_INFO "starting new process as privileged user"));
+ BOOL createRet = CreateProcessAsUser(
+ userToken, NULL, LPSTR(command.c_str()),
+ sa, NULL, TRUE, creationFlags,
+ environment, NULL, &si, &m_processInfo);
+
+ DestroyEnvironmentBlock(environment);
+ CloseHandle(userToken);
+
+ return createRet;
+}
+
+void
+MSWindowsWatchdog::setCommand(const std::string& command, bool elevate)
+{
+ LOG((CLOG_INFO "service command updated"));
+ m_command = command;
+ m_elevateProcess = elevate;
+ m_commandChanged = true;
+ m_processFailures = 0;
+}
+
+std::string
+MSWindowsWatchdog::getCommand() const
+{
+ if (!m_autoDetectCommand) {
+ return m_command;
+ }
+
+ // seems like a fairly convoluted way to get the process name
+ const char* launchName = App::instance().argsBase().m_pname;
+ std::string args = ARCH->commandLine();
+
+ // build up a full command line
+ std::stringstream cmdTemp;
+ cmdTemp << launchName << args;
+
+ std::string cmd = cmdTemp.str();
+
+ size_t i;
+ std::string find = "--relaunch";
+ while ((i = cmd.find(find)) != std::string::npos) {
+ cmd.replace(i, find.length(), "");
+ }
+
+ return cmd;
+}
+
+void
+MSWindowsWatchdog::outputLoop(void*)
+{
+ // +1 char for \0
+ CHAR buffer[kOutputBufferSize + 1];
+
+ while (m_monitoring) {
+
+ DWORD bytesRead;
+ BOOL success = ReadFile(m_stdOutRead, buffer, kOutputBufferSize, &bytesRead, NULL);
+
+ // assume the process has gone away? slow down
+ // the reads until another one turns up.
+ if (!success || bytesRead == 0) {
+ ARCH->sleep(1);
+ }
+ else {
+ buffer[bytesRead] = '\0';
+
+ testOutput(buffer);
+
+ m_ipcLogOutputter.write(kINFO, buffer);
+
+ if (m_fileLogOutputter != NULL) {
+ m_fileLogOutputter->write(kINFO, buffer);
+ }
+ }
+ }
+}
+
+void
+MSWindowsWatchdog::shutdownProcess(HANDLE handle, DWORD pid, int timeout)
+{
+ DWORD exitCode;
+ GetExitCodeProcess(handle, &exitCode);
+ if (exitCode != STILL_ACTIVE) {
+ return;
+ }
+
+ IpcShutdownMessage shutdown;
+ m_ipcServer.send(shutdown, kIpcClientNode);
+
+ // wait for process to exit gracefully.
+ double start = ARCH->time();
+ while (true) {
+
+ GetExitCodeProcess(handle, &exitCode);
+ if (exitCode != STILL_ACTIVE) {
+ // yay, we got a graceful shutdown. there should be no hook in use errors!
+ LOG((CLOG_INFO "process %d was shutdown gracefully", pid));
+ break;
+ }
+ else {
+
+ double elapsed = (ARCH->time() - start);
+ if (elapsed > timeout) {
+ // if timeout reached, kill forcefully.
+ // calling TerminateProcess on barrier is very bad!
+ // it causes the hook DLL to stay loaded in some apps,
+ // making it impossible to start barrier again.
+ LOG((CLOG_WARN "shutdown timed out after %d secs, forcefully terminating", (int)elapsed));
+ TerminateProcess(handle, kExitSuccess);
+ break;
+ }
+
+ ARCH->sleep(1);
+ }
+ }
+}
+
+void
+MSWindowsWatchdog::shutdownExistingProcesses()
+{
+ // first we need to take a snapshot of the running processes
+ HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (snapshot == INVALID_HANDLE_VALUE) {
+ LOG((CLOG_ERR "could not get process snapshot"));
+ throw XArch(new XArchEvalWindows);
+ }
+
+ PROCESSENTRY32 entry;
+ entry.dwSize = sizeof(PROCESSENTRY32);
+
+ // get the first process, and if we can't do that then it's
+ // unlikely we can go any further
+ BOOL gotEntry = Process32First(snapshot, &entry);
+ if (!gotEntry) {
+ LOG((CLOG_ERR "could not get first process entry"));
+ throw XArch(new XArchEvalWindows);
+ }
+
+ // now just iterate until we can find winlogon.exe pid
+ DWORD pid = 0;
+ while (gotEntry) {
+
+ // make sure we're not checking the system process
+ if (entry.th32ProcessID != 0) {
+
+ if (_stricmp(entry.szExeFile, "barrierc.exe") == 0 ||
+ _stricmp(entry.szExeFile, "barriers.exe") == 0) {
+
+ HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
+ shutdownProcess(handle, entry.th32ProcessID, 10);
+ }
+ }
+
+ // now move on to the next entry (if we're not at the end)
+ gotEntry = Process32Next(snapshot, &entry);
+ if (!gotEntry) {
+
+ DWORD err = GetLastError();
+ if (err != ERROR_NO_MORE_FILES) {
+
+ // only worry about error if it's not the end of the snapshot
+ LOG((CLOG_ERR "could not get subsiquent process entry"));
+ throw XArch(new XArchEvalWindows);
+ }
+ }
+ }
+
+ CloseHandle(snapshot);
+ m_processRunning = false;
+}
+
+void
+MSWindowsWatchdog::getActiveDesktop(LPSECURITY_ATTRIBUTES security)
+{
+ String installedDir = ARCH->getInstalledDirectory();
+ if (!installedDir.empty()) {
+ String syntoolCommand;
+ syntoolCommand.append("\"").append(installedDir).append("\\").append("syntool").append("\"");
+ syntoolCommand.append(" --get-active-desktop");
+
+ m_session.updateActiveSession();
+ bool elevateProcess = m_elevateProcess;
+ m_elevateProcess = true;
+ HANDLE userToken = getUserToken(security);
+ m_elevateProcess = elevateProcess;
+
+ BOOL createRet = doStartProcessAsUser(syntoolCommand, userToken, security);
+
+ if (!createRet) {
+ DWORD rc = GetLastError();
+ RevertToSelf();
+ }
+ else {
+ LOG((CLOG_DEBUG "launched syntool to check active desktop"));
+ }
+
+ ARCH->lockMutex(m_mutex);
+ int waitTime = 0;
+ while (!m_ready) {
+ if (waitTime >= MAXIMUM_WAIT_TIME) {
+ break;
+ }
+
+ ARCH->waitCondVar(m_condVar, m_mutex, 1.0);
+ waitTime++;
+ }
+ m_ready = false;
+ ARCH->unlockMutex(m_mutex);
+ }
+}
+
+void
+MSWindowsWatchdog::testOutput(String buffer)
+{
+ // HACK: check standard output seems hacky.
+ size_t i = buffer.find(g_activeDesktop);
+ if (i != String::npos) {
+ size_t s = sizeof(g_activeDesktop);
+ String defaultDesktop("Default");
+ String sub = buffer.substr(i + s - 1, defaultDesktop.size());
+ if (sub != defaultDesktop) {
+ m_autoElevated = true;
+ }
+
+ ARCH->lockMutex(m_mutex);
+ m_ready = true;
+ ARCH->broadcastCondVar(m_condVar);
+ ARCH->unlockMutex(m_mutex);
+ }
+}
diff --git a/src/lib/platform/MSWindowsWatchdog.h b/src/lib/platform/MSWindowsWatchdog.h
new file mode 100644
index 0000000..64ffab3
--- /dev/null
+++ b/src/lib/platform/MSWindowsWatchdog.h
@@ -0,0 +1,99 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2009 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/MSWindowsSession.h"
+#include "barrier/XBarrier.h"
+#include "arch/IArchMultithread.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <string>
+#include <list>
+
+class Thread;
+class IpcLogOutputter;
+class IpcServer;
+class FileLogOutputter;
+
+class MSWindowsWatchdog {
+public:
+ MSWindowsWatchdog(
+ bool daemonized,
+ bool autoDetectCommand,
+ IpcServer& ipcServer,
+ IpcLogOutputter& ipcLogOutputter);
+ virtual ~MSWindowsWatchdog();
+
+ void startAsync();
+ std::string getCommand() const;
+ void setCommand(const std::string& command, bool elevate);
+ void stop();
+ bool isProcessActive();
+ void setFileLogOutputter(FileLogOutputter* outputter);
+
+private:
+ void mainLoop(void*);
+ void outputLoop(void*);
+ void shutdownProcess(HANDLE handle, DWORD pid, int timeout);
+ void shutdownExistingProcesses();
+ HANDLE duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security);
+ HANDLE getUserToken(LPSECURITY_ATTRIBUTES security);
+ void startProcess();
+ BOOL doStartProcessAsUser(String& command, HANDLE userToken, LPSECURITY_ATTRIBUTES sa);
+ BOOL doStartProcessAsSelf(String& command);
+ void sendSas();
+ void getActiveDesktop(LPSECURITY_ATTRIBUTES security);
+ void testOutput(String buffer);
+
+private:
+ Thread* m_thread;
+ bool m_autoDetectCommand;
+ std::string m_command;
+ bool m_monitoring;
+ bool m_commandChanged;
+ HANDLE m_stdOutWrite;
+ HANDLE m_stdOutRead;
+ Thread* m_outputThread;
+ IpcServer& m_ipcServer;
+ IpcLogOutputter& m_ipcLogOutputter;
+ bool m_elevateProcess;
+ MSWindowsSession m_session;
+ PROCESS_INFORMATION m_processInfo;
+ int m_processFailures;
+ bool m_processRunning;
+ FileLogOutputter* m_fileLogOutputter;
+ bool m_autoElevated;
+ ArchMutex m_mutex;
+ ArchCond m_condVar;
+ bool m_ready;
+ bool m_daemonized;
+};
+
+//! Relauncher error
+/*!
+An error occured in the process watchdog.
+*/
+class XMSWindowsWatchdogError : public XBarrier {
+public:
+ XMSWindowsWatchdogError(const String& msg) : XBarrier(msg) { }
+
+ // XBase overrides
+ virtual String getWhat() const throw() { return what(); }
+};
diff --git a/src/lib/platform/OSXClipboard.cpp b/src/lib/platform/OSXClipboard.cpp
new file mode 100644
index 0000000..710b471
--- /dev/null
+++ b/src/lib/platform/OSXClipboard.cpp
@@ -0,0 +1,259 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXClipboard.h"
+
+#include "barrier/Clipboard.h"
+#include "platform/OSXClipboardUTF16Converter.h"
+#include "platform/OSXClipboardTextConverter.h"
+#include "platform/OSXClipboardBMPConverter.h"
+#include "platform/OSXClipboardHTMLConverter.h"
+#include "base/Log.h"
+#include "arch/XArch.h"
+
+//
+// OSXClipboard
+//
+
+OSXClipboard::OSXClipboard() :
+ m_time(0),
+ m_pboard(NULL)
+{
+ m_converters.push_back(new OSXClipboardHTMLConverter);
+ m_converters.push_back(new OSXClipboardBMPConverter);
+ m_converters.push_back(new OSXClipboardUTF16Converter);
+ m_converters.push_back(new OSXClipboardTextConverter);
+
+
+
+ OSStatus createErr = PasteboardCreate(kPasteboardClipboard, &m_pboard);
+ if (createErr != noErr) {
+ LOG((CLOG_DEBUG "failed to create clipboard reference: error %i", createErr));
+ LOG((CLOG_ERR "unable to connect to pasteboard, clipboard sharing disabled", createErr));
+ m_pboard = NULL;
+ return;
+
+ }
+
+ OSStatus syncErr = PasteboardSynchronize(m_pboard);
+ if (syncErr != noErr) {
+ LOG((CLOG_DEBUG "failed to syncronize clipboard: error %i", syncErr));
+ }
+}
+
+OSXClipboard::~OSXClipboard()
+{
+ clearConverters();
+}
+
+ bool
+OSXClipboard::empty()
+{
+ LOG((CLOG_DEBUG "emptying clipboard"));
+ if (m_pboard == NULL)
+ return false;
+
+ OSStatus err = PasteboardClear(m_pboard);
+ if (err != noErr) {
+ LOG((CLOG_DEBUG "failed to clear clipboard: error %i", err));
+ return false;
+ }
+
+ return true;
+}
+
+ bool
+OSXClipboard::synchronize()
+{
+ if (m_pboard == NULL)
+ return false;
+
+ PasteboardSyncFlags flags = PasteboardSynchronize(m_pboard);
+ LOG((CLOG_DEBUG2 "flags: %x", flags));
+
+ if (flags & kPasteboardModified) {
+ return true;
+ }
+ return false;
+}
+
+ void
+OSXClipboard::add(EFormat format, const String & data)
+{
+ if (m_pboard == NULL)
+ return;
+
+ LOG((CLOG_DEBUG "add %d bytes to clipboard format: %d", data.size(), format));
+ if (format == IClipboard::kText) {
+ LOG((CLOG_DEBUG " format of data to be added to clipboard was kText"));
+ }
+ else if (format == IClipboard::kBitmap) {
+ LOG((CLOG_DEBUG " format of data to be added to clipboard was kBitmap"));
+ }
+ else if (format == IClipboard::kHTML) {
+ LOG((CLOG_DEBUG " format of data to be added to clipboard was kHTML"));
+ }
+
+ 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) {
+ String osXData = converter->fromIClipboard(data);
+ CFStringRef flavorType = converter->getOSXFormat();
+ CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, (UInt8 *)osXData.data(), osXData.size());
+ PasteboardItemID itemID = 0;
+
+ PasteboardPutItemFlavor(
+ m_pboard,
+ itemID,
+ flavorType,
+ dataRef,
+ kPasteboardFlavorNoFlags);
+
+ LOG((CLOG_DEBUG "added %d bytes to clipboard format: %d", data.size(), format));
+ }
+
+ }
+}
+
+bool
+OSXClipboard::open(Time time) const
+{
+ if (m_pboard == NULL)
+ return false;
+
+ LOG((CLOG_DEBUG "opening clipboard"));
+ m_time = time;
+ return true;
+}
+
+void
+OSXClipboard::close() const
+{
+ LOG((CLOG_DEBUG "closing clipboard"));
+ /* not needed */
+}
+
+IClipboard::Time
+OSXClipboard::getTime() const
+{
+ return m_time;
+}
+
+bool
+OSXClipboard::has(EFormat format) const
+{
+ if (m_pboard == NULL)
+ return false;
+
+ PasteboardItemID item;
+ PasteboardGetItemIdentifier(m_pboard, (CFIndex) 1, &item);
+
+ for (ConverterList::const_iterator index = m_converters.begin();
+ index != m_converters.end(); ++index) {
+ IOSXClipboardConverter* converter = *index;
+ if (converter->getFormat() == format) {
+ PasteboardFlavorFlags flags;
+ CFStringRef type = converter->getOSXFormat();
+
+ OSStatus res;
+
+ if ((res = PasteboardGetItemFlavorFlags(m_pboard, item, type, &flags)) == noErr) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+String
+OSXClipboard::get(EFormat format) const
+{
+ CFStringRef type;
+ PasteboardItemID item;
+ String result;
+
+ if (m_pboard == NULL)
+ return result;
+
+ PasteboardGetItemIdentifier(m_pboard, (CFIndex) 1, &item);
+
+
+ // 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;
+
+ PasteboardFlavorFlags flags;
+ type = converter->getOSXFormat();
+
+ if (converter->getFormat() == format &&
+ PasteboardGetItemFlavorFlags(m_pboard, item, type, &flags) == noErr) {
+ break;
+ }
+ converter = NULL;
+ }
+
+ // if no converter then we don't recognize any formats
+ if (converter == NULL) {
+ LOG((CLOG_DEBUG "Unable to find converter for data"));
+ return result;
+ }
+
+ // get the clipboard data.
+ CFDataRef buffer = NULL;
+ try {
+ OSStatus err = PasteboardCopyItemFlavorData(m_pboard, item, type, &buffer);
+
+ if (err != noErr) {
+ throw err;
+ }
+
+ result = String((char *) CFDataGetBytePtr(buffer), CFDataGetLength(buffer));
+ }
+ catch (OSStatus err) {
+ LOG((CLOG_DEBUG "exception thrown in OSXClipboard::get MacError (%d)", err));
+ }
+ catch (...) {
+ LOG((CLOG_DEBUG "unknown exception in OSXClipboard::get"));
+ RETHROW_XTHREAD
+ }
+
+ if (buffer != NULL)
+ CFRelease(buffer);
+
+ return converter->toIClipboard(result);
+}
+
+ void
+OSXClipboard::clearConverters()
+{
+ if (m_pboard == NULL)
+ return;
+
+ for (ConverterList::iterator index = m_converters.begin();
+ index != m_converters.end(); ++index) {
+ delete *index;
+ }
+ m_converters.clear();
+}
diff --git a/src/lib/platform/OSXClipboard.h b/src/lib/platform/OSXClipboard.h
new file mode 100644
index 0000000..ba25c32
--- /dev/null
+++ b/src/lib/platform/OSXClipboard.h
@@ -0,0 +1,95 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/IClipboard.h"
+
+#include <Carbon/Carbon.h>
+#include <vector>
+
+class IOSXClipboardConverter;
+
+//! OS X clipboard implementation
+class OSXClipboard : public IClipboard {
+public:
+ OSXClipboard();
+ virtual ~OSXClipboard();
+
+ //! Test if clipboard is owned by barrier
+ static bool isOwnedByBarrier();
+
+ // IClipboard overrides
+ virtual bool empty();
+ virtual void add(EFormat, const String& data);
+ virtual bool open(Time) const;
+ virtual void close() const;
+ virtual Time getTime() const;
+ virtual bool has(EFormat) const;
+ virtual String get(EFormat) const;
+
+ bool synchronize();
+private:
+ void clearConverters();
+
+private:
+ typedef std::vector<IOSXClipboardConverter*> ConverterList;
+
+ mutable Time m_time;
+ ConverterList m_converters;
+ PasteboardRef m_pboard;
+};
+
+//! 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 CFStringRef
+ 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 String fromIClipboard(const String&) const = 0;
+
+ //! Convert to IClipboard format
+ /*!
+ Convert from the carbon scrap format to the IClipboard format
+ (i.e., the reverse of fromIClipboard()).
+ */
+ virtual String toIClipboard(const String&) const = 0;
+
+ //@}
+};
diff --git a/src/lib/platform/OSXClipboardAnyBitmapConverter.cpp b/src/lib/platform/OSXClipboardAnyBitmapConverter.cpp
new file mode 100644
index 0000000..73f64c7
--- /dev/null
+++ b/src/lib/platform/OSXClipboardAnyBitmapConverter.cpp
@@ -0,0 +1,48 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ * Patch by Ryan Chapman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXClipboardAnyBitmapConverter.h"
+#include <algorithm>
+
+OSXClipboardAnyBitmapConverter::OSXClipboardAnyBitmapConverter()
+{
+ // do nothing
+}
+
+OSXClipboardAnyBitmapConverter::~OSXClipboardAnyBitmapConverter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+OSXClipboardAnyBitmapConverter::getFormat() const
+{
+ return IClipboard::kBitmap;
+}
+
+String
+OSXClipboardAnyBitmapConverter::fromIClipboard(const String& data) const
+{
+ return doFromIClipboard(data);
+}
+
+String
+OSXClipboardAnyBitmapConverter::toIClipboard(const String& data) const
+{
+ return doToIClipboard(data);
+}
diff --git a/src/lib/platform/OSXClipboardAnyBitmapConverter.h b/src/lib/platform/OSXClipboardAnyBitmapConverter.h
new file mode 100644
index 0000000..277e75d
--- /dev/null
+++ b/src/lib/platform/OSXClipboardAnyBitmapConverter.h
@@ -0,0 +1,48 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ * Patch by Ryan Chapman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/OSXClipboard.h"
+
+//! Convert to/from some text encoding
+class OSXClipboardAnyBitmapConverter : public IOSXClipboardConverter {
+public:
+ OSXClipboardAnyBitmapConverter();
+ virtual ~OSXClipboardAnyBitmapConverter();
+
+ // IOSXClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+ virtual CFStringRef getOSXFormat() const = 0;
+ virtual String fromIClipboard(const String &) const;
+ virtual String toIClipboard(const String &) const;
+
+protected:
+ //! Convert from IClipboard format
+ /*!
+ Do UTF-8 conversion and linefeed conversion.
+ */
+ virtual String doFromIClipboard(const String&) const = 0;
+
+ //! Convert to IClipboard format
+ /*!
+ Do UTF-8 conversion and Linefeed conversion.
+ */
+ virtual String doToIClipboard(const String&) const = 0;
+};
diff --git a/src/lib/platform/OSXClipboardAnyTextConverter.cpp b/src/lib/platform/OSXClipboardAnyTextConverter.cpp
new file mode 100644
index 0000000..7095006
--- /dev/null
+++ b/src/lib/platform/OSXClipboardAnyTextConverter.cpp
@@ -0,0 +1,90 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXClipboardAnyTextConverter.h"
+
+#include <algorithm>
+
+//
+// OSXClipboardAnyTextConverter
+//
+
+OSXClipboardAnyTextConverter::OSXClipboardAnyTextConverter()
+{
+ // do nothing
+}
+
+OSXClipboardAnyTextConverter::~OSXClipboardAnyTextConverter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+OSXClipboardAnyTextConverter::getFormat() const
+{
+ return IClipboard::kText;
+}
+
+String
+OSXClipboardAnyTextConverter::fromIClipboard(const String& data) const
+{
+ // convert linefeeds and then convert to desired encoding
+ return doFromIClipboard(convertLinefeedToMacOS(data));
+}
+
+String
+OSXClipboardAnyTextConverter::toIClipboard(const String& 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');
+}
+
+String
+OSXClipboardAnyTextConverter::convertLinefeedToMacOS(const String& src)
+{
+ // note -- we assume src is a valid UTF-8 string
+ String copy = src;
+
+ std::replace_if(copy.begin(), copy.end(), isLF, '\r');
+
+ return copy;
+}
+
+String
+OSXClipboardAnyTextConverter::convertLinefeedToUnix(const String& src)
+{
+ String copy = src;
+
+ std::replace_if(copy.begin(), copy.end(), isCR, '\n');
+
+ return copy;
+}
diff --git a/src/lib/platform/OSXClipboardAnyTextConverter.h b/src/lib/platform/OSXClipboardAnyTextConverter.h
new file mode 100644
index 0000000..ea42994
--- /dev/null
+++ b/src/lib/platform/OSXClipboardAnyTextConverter.h
@@ -0,0 +1,53 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/OSXClipboard.h"
+
+//! Convert to/from some text encoding
+class OSXClipboardAnyTextConverter : public IOSXClipboardConverter {
+public:
+ OSXClipboardAnyTextConverter();
+ virtual ~OSXClipboardAnyTextConverter();
+
+ // IOSXClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+ virtual CFStringRef
+ getOSXFormat() const = 0;
+ virtual String fromIClipboard(const String &) const;
+ virtual String toIClipboard(const String &) const;
+
+protected:
+ //! Convert from IClipboard format
+ /*!
+ Do UTF-8 conversion and linefeed conversion.
+ */
+ virtual String doFromIClipboard(const String&) const = 0;
+
+ //! Convert to IClipboard format
+ /*!
+ Do UTF-8 conversion and Linefeed conversion.
+ */
+ virtual String doToIClipboard(const String&) const = 0;
+
+private:
+ static String convertLinefeedToMacOS(const String&);
+ static String convertLinefeedToUnix(const String&);
+};
diff --git a/src/lib/platform/OSXClipboardBMPConverter.cpp b/src/lib/platform/OSXClipboardBMPConverter.cpp
new file mode 100644
index 0000000..51c44ec
--- /dev/null
+++ b/src/lib/platform/OSXClipboardBMPConverter.cpp
@@ -0,0 +1,134 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ * Patch by Ryan Chapman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXClipboardBMPConverter.h"
+#include "base/Log.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<UInt32>(data[0]) |
+ (static_cast<UInt32>(data[1]) << 8) |
+ (static_cast<UInt32>(data[2]) << 16) |
+ (static_cast<UInt32>(data[3]) << 24);
+}
+
+static
+void
+toLE(UInt8*& dst, char src)
+{
+ dst[0] = static_cast<UInt8>(src);
+ dst += 1;
+}
+
+static
+void
+toLE(UInt8*& dst, UInt16 src)
+{
+ dst[0] = static_cast<UInt8>(src & 0xffu);
+ dst[1] = static_cast<UInt8>((src >> 8) & 0xffu);
+ dst += 2;
+}
+
+static
+void
+toLE(UInt8*& dst, UInt32 src)
+{
+ dst[0] = static_cast<UInt8>(src & 0xffu);
+ dst[1] = static_cast<UInt8>((src >> 8) & 0xffu);
+ dst[2] = static_cast<UInt8>((src >> 16) & 0xffu);
+ dst[3] = static_cast<UInt8>((src >> 24) & 0xffu);
+ dst += 4;
+}
+
+OSXClipboardBMPConverter::OSXClipboardBMPConverter()
+{
+ // do nothing
+}
+
+OSXClipboardBMPConverter::~OSXClipboardBMPConverter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+OSXClipboardBMPConverter::getFormat() const
+{
+ return IClipboard::kBitmap;
+}
+
+CFStringRef
+OSXClipboardBMPConverter::getOSXFormat() const
+{
+ // TODO: does this only work with Windows?
+ return CFSTR("com.microsoft.bmp");
+}
+
+String
+OSXClipboardBMPConverter::fromIClipboard(const String& bmp) const
+{
+ LOG((CLOG_DEBUG1 "ENTER OSXClipboardBMPConverter::doFromIClipboard()"));
+ // create BMP image
+ UInt8 header[14];
+ UInt8* dst = header;
+ toLE(dst, 'B');
+ toLE(dst, 'M');
+ toLE(dst, static_cast<UInt32>(14 + bmp.size()));
+ toLE(dst, static_cast<UInt16>(0));
+ toLE(dst, static_cast<UInt16>(0));
+ toLE(dst, static_cast<UInt32>(14 + 40));
+ return String(reinterpret_cast<const char*>(header), 14) + bmp;
+}
+
+String
+OSXClipboardBMPConverter::toIClipboard(const String& bmp) const
+{
+ // make sure data is big enough for a BMP file
+ if (bmp.size() <= 14 + 40) {
+ return String();
+ }
+
+ // check BMP file header
+ const UInt8* rawBMPHeader = reinterpret_cast<const UInt8*>(bmp.data());
+ if (rawBMPHeader[0] != 'B' || rawBMPHeader[1] != 'M') {
+ return String();
+ }
+
+ // 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/src/lib/platform/OSXClipboardBMPConverter.h b/src/lib/platform/OSXClipboardBMPConverter.h
new file mode 100644
index 0000000..400831d
--- /dev/null
+++ b/src/lib/platform/OSXClipboardBMPConverter.h
@@ -0,0 +1,44 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ * Patch by Ryan Chapman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/OSXClipboard.h"
+
+//! Convert to/from some text encoding
+class OSXClipboardBMPConverter : public IOSXClipboardConverter {
+public:
+ OSXClipboardBMPConverter();
+ virtual ~OSXClipboardBMPConverter();
+
+ // IMSWindowsClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+
+ virtual CFStringRef
+ getOSXFormat() const;
+
+ // OSXClipboardAnyBMPConverter overrides
+ virtual String fromIClipboard(const String&) const;
+ virtual String toIClipboard(const String&) const;
+
+ // generic encoding converter
+ static String convertString(const String& data,
+ CFStringEncoding fromEncoding,
+ CFStringEncoding toEncoding);
+};
diff --git a/src/lib/platform/OSXClipboardHTMLConverter.cpp b/src/lib/platform/OSXClipboardHTMLConverter.cpp
new file mode 100644
index 0000000..b5fdb77
--- /dev/null
+++ b/src/lib/platform/OSXClipboardHTMLConverter.cpp
@@ -0,0 +1,95 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ * Patch by Ryan Chapman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXClipboardHTMLConverter.h"
+
+#include "base/Unicode.h"
+
+OSXClipboardHTMLConverter::OSXClipboardHTMLConverter()
+{
+ // do nothing
+}
+
+OSXClipboardHTMLConverter::~OSXClipboardHTMLConverter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+OSXClipboardHTMLConverter::getFormat() const
+{
+ return IClipboard::kHTML;
+}
+
+CFStringRef
+OSXClipboardHTMLConverter::getOSXFormat() const
+{
+ return CFSTR("public.html");
+}
+
+String
+OSXClipboardHTMLConverter::convertString(
+ const String& data,
+ CFStringEncoding fromEncoding,
+ CFStringEncoding toEncoding)
+{
+ CFStringRef stringRef = CFStringCreateWithCString(
+ kCFAllocatorDefault,
+ data.c_str(), fromEncoding);
+
+ if (stringRef == NULL) {
+ return String();
+ }
+
+ 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 String();
+ }
+
+ CFStringGetBytes(stringRef, entireString, toEncoding,
+ 0, false, (UInt8*)buffer, buffSize, NULL);
+
+ String result(buffer, buffSize);
+
+ delete[] buffer;
+ CFRelease(stringRef);
+
+ return result;
+}
+
+String
+OSXClipboardHTMLConverter::doFromIClipboard(const String& data) const
+{
+ return convertString(data, kCFStringEncodingUTF8,
+ CFStringGetSystemEncoding());
+}
+
+String
+OSXClipboardHTMLConverter::doToIClipboard(const String& data) const
+{
+ return convertString(data, CFStringGetSystemEncoding(),
+ kCFStringEncodingUTF8);
+}
diff --git a/src/lib/platform/OSXClipboardHTMLConverter.h b/src/lib/platform/OSXClipboardHTMLConverter.h
new file mode 100644
index 0000000..21c2b82
--- /dev/null
+++ b/src/lib/platform/OSXClipboardHTMLConverter.h
@@ -0,0 +1,44 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ * Patch by Ryan Chapman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "OSXClipboardAnyTextConverter.h"
+
+//! Convert to/from HTML encoding
+class OSXClipboardHTMLConverter : public OSXClipboardAnyTextConverter {
+public:
+ OSXClipboardHTMLConverter();
+ virtual ~OSXClipboardHTMLConverter();
+
+ // IMSWindowsClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+
+ virtual CFStringRef getOSXFormat() const;
+
+protected:
+ // OSXClipboardAnyTextConverter overrides
+ virtual String doFromIClipboard(const String&) const;
+ virtual String doToIClipboard(const String&) const;
+
+ // generic encoding converter
+ static String convertString(const String& data,
+ CFStringEncoding fromEncoding,
+ CFStringEncoding toEncoding);
+};
diff --git a/src/lib/platform/OSXClipboardTextConverter.cpp b/src/lib/platform/OSXClipboardTextConverter.cpp
new file mode 100644
index 0000000..c18ad3f
--- /dev/null
+++ b/src/lib/platform/OSXClipboardTextConverter.cpp
@@ -0,0 +1,93 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXClipboardTextConverter.h"
+
+#include "base/Unicode.h"
+
+//
+// OSXClipboardTextConverter
+//
+
+OSXClipboardTextConverter::OSXClipboardTextConverter()
+{
+ // do nothing
+}
+
+OSXClipboardTextConverter::~OSXClipboardTextConverter()
+{
+ // do nothing
+}
+
+CFStringRef
+OSXClipboardTextConverter::getOSXFormat() const
+{
+ return CFSTR("public.plain-text");
+}
+
+String
+OSXClipboardTextConverter::convertString(
+ const String& data,
+ CFStringEncoding fromEncoding,
+ CFStringEncoding toEncoding)
+{
+ CFStringRef stringRef =
+ CFStringCreateWithCString(kCFAllocatorDefault,
+ data.c_str(), fromEncoding);
+
+ if (stringRef == NULL) {
+ return String();
+ }
+
+ 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 String();
+ }
+
+ CFStringGetBytes(stringRef, entireString, toEncoding,
+ 0, false, (UInt8*)buffer, buffSize, NULL);
+
+ String result(buffer, buffSize);
+
+ delete[] buffer;
+ CFRelease(stringRef);
+
+ return result;
+}
+
+String
+OSXClipboardTextConverter::doFromIClipboard(const String& data) const
+{
+ return convertString(data, kCFStringEncodingUTF8,
+ CFStringGetSystemEncoding());
+}
+
+String
+OSXClipboardTextConverter::doToIClipboard(const String& data) const
+{
+ return convertString(data, CFStringGetSystemEncoding(),
+ kCFStringEncodingUTF8);
+}
diff --git a/src/lib/platform/OSXClipboardTextConverter.h b/src/lib/platform/OSXClipboardTextConverter.h
new file mode 100644
index 0000000..55d82ce
--- /dev/null
+++ b/src/lib/platform/OSXClipboardTextConverter.h
@@ -0,0 +1,42 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/OSXClipboardAnyTextConverter.h"
+
+//! Convert to/from locale text encoding
+class OSXClipboardTextConverter : public OSXClipboardAnyTextConverter {
+public:
+ OSXClipboardTextConverter();
+ virtual ~OSXClipboardTextConverter();
+
+ // IOSXClipboardAnyTextConverter overrides
+ virtual CFStringRef
+ getOSXFormat() const;
+
+protected:
+ // OSXClipboardAnyTextConverter overrides
+ virtual String doFromIClipboard(const String&) const;
+ virtual String doToIClipboard(const String&) const;
+
+ // generic encoding converter
+ static String convertString(const String& data,
+ CFStringEncoding fromEncoding,
+ CFStringEncoding toEncoding);
+};
diff --git a/src/lib/platform/OSXClipboardUTF16Converter.cpp b/src/lib/platform/OSXClipboardUTF16Converter.cpp
new file mode 100644
index 0000000..02d8fa3
--- /dev/null
+++ b/src/lib/platform/OSXClipboardUTF16Converter.cpp
@@ -0,0 +1,55 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXClipboardUTF16Converter.h"
+
+#include "base/Unicode.h"
+
+//
+// OSXClipboardUTF16Converter
+//
+
+OSXClipboardUTF16Converter::OSXClipboardUTF16Converter()
+{
+ // do nothing
+}
+
+OSXClipboardUTF16Converter::~OSXClipboardUTF16Converter()
+{
+ // do nothing
+}
+
+CFStringRef
+OSXClipboardUTF16Converter::getOSXFormat() const
+{
+ return CFSTR("public.utf16-plain-text");
+}
+
+String
+OSXClipboardUTF16Converter::doFromIClipboard(const String& data) const
+{
+ // convert and add nul terminator
+ return Unicode::UTF8ToUTF16(data);
+}
+
+String
+OSXClipboardUTF16Converter::doToIClipboard(const String& data) const
+{
+ // convert and strip nul terminator
+ return Unicode::UTF16ToUTF8(data);
+}
diff --git a/src/lib/platform/OSXClipboardUTF16Converter.h b/src/lib/platform/OSXClipboardUTF16Converter.h
new file mode 100644
index 0000000..10bb595
--- /dev/null
+++ b/src/lib/platform/OSXClipboardUTF16Converter.h
@@ -0,0 +1,37 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/OSXClipboardAnyTextConverter.h"
+
+//! Convert to/from UTF-16 encoding
+class OSXClipboardUTF16Converter : public OSXClipboardAnyTextConverter {
+public:
+ OSXClipboardUTF16Converter();
+ virtual ~OSXClipboardUTF16Converter();
+
+ // IOSXClipboardAnyTextConverter overrides
+ virtual CFStringRef
+ getOSXFormat() const;
+
+protected:
+ // OSXClipboardAnyTextConverter overrides
+ virtual String doFromIClipboard(const String&) const;
+ virtual String doToIClipboard(const String&) const;
+};
diff --git a/src/lib/platform/OSXDragSimulator.h b/src/lib/platform/OSXDragSimulator.h
new file mode 100644
index 0000000..cb361ca
--- /dev/null
+++ b/src/lib/platform/OSXDragSimulator.h
@@ -0,0 +1,34 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/common.h"
+
+#import <CoreFoundation/CoreFoundation.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+void runCocoaApp();
+void stopCocoaLoop();
+void fakeDragging(const char* str, int cursorX, int cursorY);
+CFStringRef getCocoaDropTarget();
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/src/lib/platform/OSXDragSimulator.m b/src/lib/platform/OSXDragSimulator.m
new file mode 100644
index 0000000..affed38
--- /dev/null
+++ b/src/lib/platform/OSXDragSimulator.m
@@ -0,0 +1,102 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT 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 "platform/OSXDragSimulator.h"
+
+#import "platform/OSXDragView.h"
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+#import <Cocoa/Cocoa.h>
+
+#if defined(MAC_OS_X_VERSION_10_7)
+
+NSWindow* g_dragWindow = NULL;
+OSXDragView* g_dragView = NULL;
+NSString* g_ext = NULL;
+
+void
+runCocoaApp()
+{
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+ [NSApplication sharedApplication];
+
+ NSWindow* window = [[NSWindow alloc]
+ initWithContentRect: NSMakeRect(0, 0, 3, 3)
+ styleMask: NSBorderlessWindowMask
+ backing: NSBackingStoreBuffered
+ defer: NO];
+ [window setTitle: @""];
+ [window setAlphaValue:0.1];
+ [window makeKeyAndOrderFront:nil];
+
+ OSXDragView* dragView = [[OSXDragView alloc] initWithFrame:NSMakeRect(0, 0, 3, 3)];
+
+ g_dragWindow = window;
+ g_dragView = dragView;
+ [window setContentView: dragView];
+
+ NSLog(@"starting cocoa loop");
+ [NSApp run];
+
+ NSLog(@"cocoa: release");
+ [pool release];
+}
+
+void
+stopCocoaLoop()
+{
+ [NSApp stop: g_dragWindow];
+}
+
+void
+fakeDragging(const char* str, int cursorX, int cursorY)
+{
+ g_ext = [NSString stringWithUTF8String:str];
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ NSRect screen = [[NSScreen mainScreen] frame];
+ NSLog ( @"screen size: witdh = %f height = %f", screen.size.width, screen.size.height);
+ NSLog ( @"mouseLocation: %d %d", cursorX, cursorY);
+
+ int newPosX = 0;
+ int newPosY = 0;
+ newPosX = cursorX - 1;
+ newPosY = screen.size.height - cursorY - 1;
+
+ NSRect rect = NSMakeRect(newPosX, newPosY, 3, 3);
+ NSLog ( @"newPosX: %d", newPosX);
+ NSLog ( @"newPosY: %d", newPosY);
+
+ [g_dragWindow setFrame:rect display:NO];
+ [g_dragWindow makeKeyAndOrderFront:nil];
+ [NSApp activateIgnoringOtherApps:YES];
+
+ [g_dragView setFileExt:g_ext];
+
+ CGEventRef down = CGEventCreateMouseEvent(CGEventSourceCreate(kCGEventSourceStateHIDSystemState), kCGEventLeftMouseDown, CGPointMake(cursorX, cursorY), kCGMouseButtonLeft);
+ CGEventPost(kCGHIDEventTap, down);
+ });
+}
+
+CFStringRef
+getCocoaDropTarget()
+{
+ // HACK: sleep, wait for cocoa drop target updated first
+ usleep(1000000);
+ return [g_dragView getDropTarget];
+}
+
+#endif
diff --git a/src/lib/platform/OSXDragView.h b/src/lib/platform/OSXDragView.h
new file mode 100644
index 0000000..9b8aa48
--- /dev/null
+++ b/src/lib/platform/OSXDragView.h
@@ -0,0 +1,34 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+#ifdef MAC_OS_X_VERSION_10_7
+
+@interface OSXDragView : NSView<NSDraggingSource,NSDraggingInfo>
+{
+ NSMutableString* m_dropTarget;
+ NSString* m_dragFileExt;
+}
+
+- (CFStringRef)getDropTarget;
+- (void)clearDropTarget;
+- (void)setFileExt:(NSString*) ext;
+
+@end
+
+#endif
diff --git a/src/lib/platform/OSXDragView.m b/src/lib/platform/OSXDragView.m
new file mode 100644
index 0000000..9f77499
--- /dev/null
+++ b/src/lib/platform/OSXDragView.m
@@ -0,0 +1,177 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "platform/OSXDragView.h"
+
+#ifdef MAC_OS_X_VERSION_10_7
+
+@implementation OSXDragView
+
+@dynamic draggingFormation;
+@dynamic animatesToDestination;
+@dynamic numberOfValidItemsForDrop;
+
+/* springLoadingHighlight is a property that will not be auto-synthesized by
+ clang. explicitly synthesizing it here as well as defining an empty handler
+ for resetSpringLoading() satisfies the compiler */
+@synthesize springLoadingHighlight = _springLoadingHighlight;
+
+/* unused */
+- (void)
+resetSpringLoading
+{
+}
+
+- (id)
+initWithFrame:(NSRect)frame
+{
+ self = [super initWithFrame:frame];
+ m_dropTarget = [[NSMutableString alloc] initWithCapacity:0];
+ m_dragFileExt = [[NSMutableString alloc] initWithCapacity:0];
+ return self;
+}
+
+- (void)
+drawRect:(NSRect)dirtyRect
+{
+}
+
+- (BOOL)
+acceptsFirstMouse:(NSEvent *)theEvent
+{
+ return YES;
+}
+
+- (void)
+mouseDown:(NSEvent *)theEvent
+{
+ NSLog ( @"cocoa mouse down");
+ NSPoint dragPosition;
+ NSRect imageLocation;
+ dragPosition = [self convertPoint:[theEvent locationInWindow]
+ fromView:nil];
+
+ dragPosition.x -= 16;
+ dragPosition.y -= 16;
+ imageLocation.origin = dragPosition;
+ imageLocation.size = NSMakeSize(32,32);
+ [self dragPromisedFilesOfTypes:[NSArray arrayWithObject:m_dragFileExt]
+ fromRect:imageLocation
+ source:self
+ slideBack:NO
+ event:theEvent];
+}
+
+- (NSArray*)
+namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
+{
+ [m_dropTarget setString:@""];
+ [m_dropTarget appendString:dropDestination.path];
+ NSLog ( @"cocoa drop target: %@", m_dropTarget);
+ return nil;
+}
+
+- (NSDragOperation)
+draggingSourceOperationMaskForLocal:(BOOL)flag
+{
+ return NSDragOperationCopy;
+}
+
+- (CFStringRef)
+getDropTarget
+{
+ NSMutableString* string;
+ string = [[NSMutableString alloc] initWithCapacity:0];
+ [string appendString:m_dropTarget];
+ return (CFStringRef)string;
+}
+
+- (void)
+clearDropTarget
+{
+ [m_dropTarget setString:@""];
+}
+
+- (void)
+setFileExt:(NSString*) ext
+{
+ [ext retain];
+ [m_dragFileExt release];
+ m_dragFileExt = ext;
+ NSLog(@"drag file ext: %@", m_dragFileExt);
+}
+
+- (NSWindow *)
+draggingDestinationWindow
+{
+ return nil;
+}
+
+- (NSDragOperation)
+draggingSourceOperationMask
+{
+ return NSDragOperationCopy;
+}
+
+- (NSPoint)draggingLocation
+{
+ NSPoint point;
+ return point;
+}
+
+- (NSPoint)draggedImageLocation
+{
+ NSPoint point;
+ return point;
+}
+
+- (NSImage *)draggedImage
+{
+ return nil;
+}
+
+- (NSPasteboard *)draggingPasteboard
+{
+ return nil;
+}
+
+- (id)draggingSource
+{
+ return nil;
+}
+
+- (NSInteger)draggingSequenceNumber
+{
+ return 0;
+}
+
+- (void)slideDraggedImageTo:(NSPoint)screenPoint
+{
+}
+
+- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
+{
+ return NSDragOperationCopy;
+}
+
+- (void)enumerateDraggingItemsWithOptions:(NSDraggingItemEnumerationOptions)enumOpts forView:(NSView *)view classes:(NSArray *)classArray searchOptions:(NSDictionary *)searchOptions usingBlock:(void (^)(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop))block
+{
+}
+
+@end
+
+#endif
diff --git a/src/lib/platform/OSXEventQueueBuffer.cpp b/src/lib/platform/OSXEventQueueBuffer.cpp
new file mode 100644
index 0000000..8e18afc
--- /dev/null
+++ b/src/lib/platform/OSXEventQueueBuffer.cpp
@@ -0,0 +1,143 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXEventQueueBuffer.h"
+
+#include "base/Event.h"
+#include "base/IEventQueue.h"
+
+//
+// EventQueueTimer
+//
+
+class EventQueueTimer { };
+
+//
+// OSXEventQueueBuffer
+//
+
+OSXEventQueueBuffer::OSXEventQueueBuffer(IEventQueue* events) :
+ m_event(NULL),
+ m_eventQueue(events),
+ m_carbonEventQueue(NULL)
+{
+ // do nothing
+}
+
+OSXEventQueueBuffer::~OSXEventQueueBuffer()
+{
+ // release the last event
+ if (m_event != NULL) {
+ ReleaseEvent(m_event);
+ }
+}
+
+void
+OSXEventQueueBuffer::init()
+{
+ m_carbonEventQueue = GetCurrentEventQueue();
+}
+
+void
+OSXEventQueueBuffer::waitForEvent(double timeout)
+{
+ EventRef event;
+ ReceiveNextEvent(0, NULL, timeout, false, &event);
+}
+
+IEventQueueBuffer::Type
+OSXEventQueueBuffer::getEvent(Event& 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 = Event(Event::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 = Event(Event::kSystem,
+ m_eventQueue->getSystemTarget(), &m_event);
+ return kSystem;
+ }
+ }
+}
+
+bool
+OSXEventQueueBuffer::addEvent(UInt32 dataID)
+{
+ EventRef event;
+ OSStatus error = CreateEvent(
+ kCFAllocatorDefault,
+ 'Syne',
+ dataID,
+ 0,
+ kEventAttributeNone,
+ &event);
+
+ if (error == noErr) {
+
+ assert(m_carbonEventQueue != NULL);
+
+ error = PostEventToQueue(
+ m_carbonEventQueue,
+ event,
+ kEventPriorityStandard);
+
+ ReleaseEvent(event);
+ }
+
+ return (error == noErr);
+}
+
+bool
+OSXEventQueueBuffer::isEmpty() const
+{
+ EventRef event;
+ OSStatus status = ReceiveNextEvent(0, NULL, 0.0, false, &event);
+ return (status == eventLoopTimedOutErr);
+}
+
+EventQueueTimer*
+OSXEventQueueBuffer::newTimer(double, bool) const
+{
+ return new EventQueueTimer;
+}
+
+void
+OSXEventQueueBuffer::deleteTimer(EventQueueTimer* timer) const
+{
+ delete timer;
+}
diff --git a/src/lib/platform/OSXEventQueueBuffer.h b/src/lib/platform/OSXEventQueueBuffer.h
new file mode 100644
index 0000000..28c4a5d
--- /dev/null
+++ b/src/lib/platform/OSXEventQueueBuffer.h
@@ -0,0 +1,47 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/IEventQueueBuffer.h"
+
+#include <Carbon/Carbon.h>
+
+class IEventQueue;
+
+//! Event queue buffer for OS X
+class OSXEventQueueBuffer : public IEventQueueBuffer {
+public:
+ OSXEventQueueBuffer(IEventQueue* eventQueue);
+ virtual ~OSXEventQueueBuffer();
+
+ // IEventQueueBuffer overrides
+ virtual void init();
+ virtual void waitForEvent(double timeout);
+ virtual Type getEvent(Event& event, UInt32& dataID);
+ virtual bool addEvent(UInt32 dataID);
+ virtual bool isEmpty() const;
+ virtual EventQueueTimer*
+ newTimer(double duration, bool oneShot) const;
+ virtual void deleteTimer(EventQueueTimer*) const;
+
+private:
+ EventRef m_event;
+ IEventQueue* m_eventQueue;
+ EventQueueRef m_carbonEventQueue;
+};
diff --git a/src/lib/platform/OSXKeyState.cpp b/src/lib/platform/OSXKeyState.cpp
new file mode 100644
index 0000000..482d7c1
--- /dev/null
+++ b/src/lib/platform/OSXKeyState.cpp
@@ -0,0 +1,912 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXKeyState.h"
+#include "platform/OSXUchrKeyResource.h"
+#include "platform/OSXMediaKeySupport.h"
+#include "arch/Arch.h"
+#include "base/Log.h"
+
+#include <Carbon/Carbon.h>
+#include <IOKit/hidsystem/IOHIDLib.h>
+
+// 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 = kVK_Shift;
+static const UInt32 s_controlVK = kVK_Control;
+static const UInt32 s_altVK = kVK_Option;
+static const UInt32 s_superVK = kVK_Command;
+static const UInt32 s_capsLockVK = kVK_CapsLock;
+static const UInt32 s_numLockVK = kVK_ANSI_KeypadClear; // 71
+
+static const UInt32 s_brightnessUp = 144;
+static const UInt32 s_brightnessDown = 145;
+static const UInt32 s_missionControlVK = 160;
+static const UInt32 s_launchpadVK = 131;
+
+static const UInt32 s_osxNumLock = 1 << 16;
+
+struct KeyEntry {
+public:
+ KeyID m_keyID;
+ UInt32 m_virtualKey;
+};
+static const KeyEntry 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, kVK_LeftArrow },
+ { kKeyRight, kVK_RightArrow },
+ { kKeyUp, kVK_UpArrow },
+ { kKeyDown, kVK_DownArrow },
+ { kKeyHome, kVK_Home },
+ { kKeyEnd, kVK_End },
+ { kKeyPageUp, kVK_PageUp },
+ { kKeyPageDown, kVK_PageDown },
+ { kKeyInsert, kVK_Help }, // Mac Keyboards have 'Help' on 'Insert'
+
+ // function keys
+ { kKeyF1, kVK_F1 },
+ { kKeyF2, kVK_F2 },
+ { kKeyF3, kVK_F3 },
+ { kKeyF4, kVK_F4 },
+ { kKeyF5, kVK_F5 },
+ { kKeyF6, kVK_F6 },
+ { kKeyF7, kVK_F7 },
+ { kKeyF8, kVK_F8 },
+ { kKeyF9, kVK_F9 },
+ { kKeyF10, kVK_F10 },
+ { kKeyF11, kVK_F11 },
+ { kKeyF12, kVK_F12 },
+ { kKeyF13, kVK_F13 },
+ { kKeyF14, kVK_F14 },
+ { kKeyF15, kVK_F15 },
+ { kKeyF16, kVK_F16 },
+
+ { kKeyKP_0, kVK_ANSI_Keypad0 },
+ { kKeyKP_1, kVK_ANSI_Keypad1 },
+ { kKeyKP_2, kVK_ANSI_Keypad2 },
+ { kKeyKP_3, kVK_ANSI_Keypad3 },
+ { kKeyKP_4, kVK_ANSI_Keypad4 },
+ { kKeyKP_5, kVK_ANSI_Keypad5 },
+ { kKeyKP_6, kVK_ANSI_Keypad6 },
+ { kKeyKP_7, kVK_ANSI_Keypad7 },
+ { kKeyKP_8, kVK_ANSI_Keypad8 },
+ { kKeyKP_9, kVK_ANSI_Keypad9 },
+ { kKeyKP_Decimal, kVK_ANSI_KeypadDecimal },
+ { kKeyKP_Equal, kVK_ANSI_KeypadEquals },
+ { kKeyKP_Multiply, kVK_ANSI_KeypadMultiply },
+ { kKeyKP_Add, kVK_ANSI_KeypadPlus },
+ { kKeyKP_Divide, kVK_ANSI_KeypadDivide },
+ { kKeyKP_Subtract, kVK_ANSI_KeypadMinus },
+ { kKeyKP_Enter, kVK_ANSI_KeypadEnter },
+
+ // 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 },
+
+ { kKeyMissionControl, s_missionControlVK },
+ { kKeyLaunchpad, s_launchpadVK },
+ { kKeyBrightnessUp, s_brightnessUp },
+ { kKeyBrightnessDown, s_brightnessDown }
+};
+
+
+//
+// OSXKeyState
+//
+
+OSXKeyState::OSXKeyState(IEventQueue* events) :
+ KeyState(events)
+{
+ init();
+}
+
+OSXKeyState::OSXKeyState(IEventQueue* events, barrier::KeyMap& keyMap) :
+ KeyState(events, keyMap)
+{
+ init();
+}
+
+OSXKeyState::~OSXKeyState()
+{
+}
+
+void
+OSXKeyState::init()
+{
+ m_deadKeyState = 0;
+ m_shiftPressed = false;
+ m_controlPressed = false;
+ m_altPressed = false;
+ m_superPressed = false;
+ m_capsPressed = false;
+
+ // 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;
+ }
+}
+
+KeyModifierMask
+OSXKeyState::mapModifiersFromOSX(UInt32 mask) const
+{
+ KeyModifierMask outMask = 0;
+ if ((mask & kCGEventFlagMaskShift) != 0) {
+ outMask |= KeyModifierShift;
+ }
+ if ((mask & kCGEventFlagMaskControl) != 0) {
+ outMask |= KeyModifierControl;
+ }
+ if ((mask & kCGEventFlagMaskAlternate) != 0) {
+ outMask |= KeyModifierAlt;
+ }
+ if ((mask & kCGEventFlagMaskCommand) != 0) {
+ outMask |= KeyModifierSuper;
+ }
+ if ((mask & kCGEventFlagMaskAlphaShift) != 0) {
+ outMask |= KeyModifierCapsLock;
+ }
+ if ((mask & kCGEventFlagMaskNumericPad) != 0) {
+ outMask |= KeyModifierNumLock;
+ }
+
+ LOG((CLOG_DEBUG1 "mask=%04x outMask=%04x", mask, outMask));
+ return outMask;
+}
+
+KeyModifierMask
+OSXKeyState::mapModifiersToCarbon(UInt32 mask) const
+{
+ KeyModifierMask outMask = 0;
+ if ((mask & kCGEventFlagMaskShift) != 0) {
+ outMask |= shiftKey;
+ }
+ if ((mask & kCGEventFlagMaskControl) != 0) {
+ outMask |= controlKey;
+ }
+ if ((mask & kCGEventFlagMaskCommand) != 0) {
+ outMask |= cmdKey;
+ }
+ if ((mask & kCGEventFlagMaskAlternate) != 0) {
+ outMask |= optionKey;
+ }
+ if ((mask & kCGEventFlagMaskAlphaShift) != 0) {
+ outMask |= alphaLock;
+ }
+ if ((mask & kCGEventFlagMaskNumericPad) != 0) {
+ outMask |= s_osxNumLock;
+ }
+
+ return outMask;
+}
+
+KeyButton
+OSXKeyState::mapKeyFromEvent(KeyIDs& ids,
+ KeyModifierMask* maskOut, CGEventRef event) const
+{
+ ids.clear();
+
+ // map modifier key
+ if (maskOut != NULL) {
+ KeyModifierMask activeMask = getActiveModifiers();
+ activeMask &= ~KeyModifierAltGr;
+ *maskOut = activeMask;
+ }
+
+ // get virtual key
+ UInt32 vkCode = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
+
+ // handle up events
+ UInt32 eventKind = CGEventGetType(event);
+ if (eventKind == kCGEventKeyUp) {
+ // 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
+ VirtualKeyMap::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
+ TISInputSourceRef currentKeyboardLayout = TISCopyCurrentKeyboardLayoutInputSource();
+
+ if (currentKeyboardLayout == NULL) {
+ return kKeyNone;
+ }
+
+ // get the event modifiers and remove the command and control
+ // keys. note if we used them though.
+ // UCKeyTranslate expects old-style Carbon modifiers, so convert.
+ UInt32 modifiers;
+ modifiers = mapModifiersToCarbon(CGEventGetFlags(event));
+ 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;
+ //}
+
+ // choose action
+ UInt16 action;
+ if (eventKind==kCGEventKeyDown) {
+ action = kUCKeyActionDown;
+ }
+ else if (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat)==1) {
+ action = kUCKeyActionAutoKey;
+ }
+ else {
+ return 0;
+ }
+
+ // translate via uchr resource
+ CFDataRef ref = (CFDataRef) TISGetInputSourceProperty(currentKeyboardLayout,
+ kTISPropertyUnicodeKeyLayoutData);
+ const UCKeyboardLayout* layout = (const UCKeyboardLayout*) CFDataGetBytePtr(ref);
+ const bool layoutValid = (layout != NULL);
+
+ if (layoutValid) {
+ // translate key
+ UniCharCount count;
+ UniChar chars[2];
+ LOG((CLOG_DEBUG2 "modifiers: %08x", modifiers & 0xffu));
+ OSStatus status = UCKeyTranslate(layout,
+ 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(IOSXKeyResource::unicharToKeyID(chars[i]));
+ }
+ adjustAltGrModifier(ids, maskOut, isCommand);
+ return mapVirtualKeyToKeyButton(vkCode);
+ }
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+bool
+OSXKeyState::fakeCtrlAltDel()
+{
+ // pass keys through unchanged
+ return false;
+}
+
+bool
+OSXKeyState::fakeMediaKey(KeyID id)
+{
+ return fakeNativeMediaKey(id);;
+}
+
+CGEventFlags
+OSXKeyState::getModifierStateAsOSXFlags()
+{
+ CGEventFlags modifiers = 0;
+
+ if (m_shiftPressed) {
+ modifiers |= kCGEventFlagMaskShift;
+ }
+
+ if (m_controlPressed) {
+ modifiers |= kCGEventFlagMaskControl;
+ }
+
+ if (m_altPressed) {
+ modifiers |= kCGEventFlagMaskAlternate;
+ }
+
+ if (m_superPressed) {
+ modifiers |= kCGEventFlagMaskCommand;
+ }
+
+ if (m_capsPressed) {
+ modifiers |= kCGEventFlagMaskAlphaShift;
+ }
+
+ return modifiers;
+}
+
+KeyModifierMask
+OSXKeyState::pollActiveModifiers() const
+{
+ // falsely assumed that the mask returned by GetCurrentKeyModifiers()
+ // was the same as a CGEventFlags (which is what mapModifiersFromOSX
+ // expects). patch by Marc
+ UInt32 mask = GetCurrentKeyModifiers();
+ KeyModifierMask outMask = 0;
+
+ if ((mask & shiftKey) != 0) {
+ outMask |= KeyModifierShift;
+ }
+ if ((mask & controlKey) != 0) {
+ outMask |= KeyModifierControl;
+ }
+ if ((mask & optionKey) != 0) {
+ outMask |= KeyModifierAlt;
+ }
+ if ((mask & cmdKey) != 0) {
+ outMask |= KeyModifierSuper;
+ }
+ if ((mask & alphaLock) != 0) {
+ outMask |= KeyModifierCapsLock;
+ }
+ if ((mask & s_osxNumLock) != 0) {
+ outMask |= KeyModifierNumLock;
+ }
+
+ LOG((CLOG_DEBUG1 "mask=%04x outMask=%04x", mask, outMask));
+ return outMask;
+}
+
+SInt32
+OSXKeyState::pollActiveGroup() const
+{
+ TISInputSourceRef keyboardLayout = TISCopyCurrentKeyboardLayoutInputSource();
+ CFDataRef id = (CFDataRef)TISGetInputSourceProperty(
+ keyboardLayout, kTISPropertyInputSourceID);
+
+ GroupMap::const_iterator i = m_groupMap.find(id);
+ if (i != m_groupMap.end()) {
+ return i->second;
+ }
+
+ LOG((CLOG_DEBUG "can't get the active group, use the first group instead"));
+
+ return 0;
+}
+
+void
+OSXKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const
+{
+ ::KeyMap km;
+ GetKeys(km);
+ const UInt8* m = reinterpret_cast<const UInt8*>(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
+OSXKeyState::getKeyMap(barrier::KeyMap& keyMap)
+{
+ // update keyboard groups
+ if (getGroups(m_groups)) {
+ m_groupMap.clear();
+ SInt32 numGroups = (SInt32)m_groups.size();
+ for (SInt32 g = 0; g < numGroups; ++g) {
+ CFDataRef id = (CFDataRef)TISGetInputSourceProperty(
+ m_groups[g], kTISPropertyInputSourceID);
+ m_groupMap[id] = g;
+ }
+ }
+
+ UInt32 keyboardType = LMGetKbdType();
+ for (SInt32 g = 0, n = (SInt32)m_groups.size(); g < n; ++g) {
+ // add special keys
+ getKeyMapForSpecialKeys(keyMap, g);
+
+ const void* resource;
+ bool layoutValid = false;
+
+ // add regular keys
+ // try uchr resource first
+ CFDataRef resourceRef = (CFDataRef)TISGetInputSourceProperty(
+ m_groups[g], kTISPropertyUnicodeKeyLayoutData);
+
+ layoutValid = resourceRef != NULL;
+ if (layoutValid)
+ resource = CFDataGetBytePtr(resourceRef);
+
+ if (layoutValid) {
+ OSXUchrKeyResource uchr(resource, keyboardType);
+ if (uchr.isValid()) {
+ LOG((CLOG_DEBUG1 "using uchr resource for group %d", g));
+ getKeyMap(keyMap, g, uchr);
+ continue;
+ }
+ }
+
+ LOG((CLOG_DEBUG1 "no keyboard resource for group %d", g));
+ }
+}
+
+static io_connect_t getEventDriver(void)
+{
+ static mach_port_t sEventDrvrRef = 0;
+ mach_port_t masterPort, service, iter;
+ kern_return_t kr;
+
+ if (!sEventDrvrRef) {
+ // Get master device port
+ kr = IOMasterPort(bootstrap_port, &masterPort);
+ assert(KERN_SUCCESS == kr);
+
+ kr = IOServiceGetMatchingServices(masterPort,
+ IOServiceMatching(kIOHIDSystemClass), &iter);
+ assert(KERN_SUCCESS == kr);
+
+ service = IOIteratorNext(iter);
+ assert(service);
+
+ kr = IOServiceOpen(service, mach_task_self(),
+ kIOHIDParamConnectType, &sEventDrvrRef);
+ assert(KERN_SUCCESS == kr);
+
+ IOObjectRelease(service);
+ IOObjectRelease(iter);
+ }
+
+ return sEventDrvrRef;
+}
+
+void
+OSXKeyState::postHIDVirtualKey(const UInt8 virtualKeyCode,
+ const bool postDown)
+{
+ static UInt32 modifiers = 0;
+
+ NXEventData event;
+ IOGPoint loc = { 0, 0 };
+ UInt32 modifiersDelta = 0;
+
+ bzero(&event, sizeof(NXEventData));
+
+ switch (virtualKeyCode)
+ {
+ case s_shiftVK:
+ case s_superVK:
+ case s_altVK:
+ case s_controlVK:
+ case s_capsLockVK:
+ switch (virtualKeyCode)
+ {
+ case s_shiftVK:
+ modifiersDelta = NX_SHIFTMASK;
+ m_shiftPressed = postDown;
+ break;
+ case s_superVK:
+ modifiersDelta = NX_COMMANDMASK;
+ m_superPressed = postDown;
+ break;
+ case s_altVK:
+ modifiersDelta = NX_ALTERNATEMASK;
+ m_altPressed = postDown;
+ break;
+ case s_controlVK:
+ modifiersDelta = NX_CONTROLMASK;
+ m_controlPressed = postDown;
+ break;
+ case s_capsLockVK:
+ modifiersDelta = NX_ALPHASHIFTMASK;
+ m_capsPressed = postDown;
+ break;
+ }
+
+ // update the modifier bit
+ if (postDown) {
+ modifiers |= modifiersDelta;
+ }
+ else {
+ modifiers &= ~modifiersDelta;
+ }
+
+ kern_return_t kr;
+ kr = IOHIDPostEvent(getEventDriver(), NX_FLAGSCHANGED, loc,
+ &event, kNXEventDataVersion, modifiers, true);
+ assert(KERN_SUCCESS == kr);
+ break;
+
+ default:
+ event.key.repeat = false;
+ event.key.keyCode = virtualKeyCode;
+ event.key.origCharSet = event.key.charSet = NX_ASCIISET;
+ event.key.origCharCode = event.key.charCode = 0;
+ kr = IOHIDPostEvent(getEventDriver(),
+ postDown ? NX_KEYDOWN : NX_KEYUP,
+ loc, &event, kNXEventDataVersion, 0, false);
+ assert(KERN_SUCCESS == kr);
+ break;
+ }
+}
+
+void
+OSXKeyState::fakeKey(const Keystroke& keystroke)
+{
+ switch (keystroke.m_type) {
+ case Keystroke::kButton: {
+
+ KeyButton button = keystroke.m_data.m_button.m_button;
+ bool keyDown = keystroke.m_data.m_button.m_press;
+ CGKeyCode virtualKey = mapKeyButtonToVirtualKey(button);
+
+ LOG((CLOG_DEBUG1
+ " button=0x%04x virtualKey=0x%04x keyDown=%s",
+ button, virtualKey, keyDown ? "down" : "up"));
+
+ postHIDVirtualKey(virtualKey, keyDown);
+
+ break;
+ }
+
+ case Keystroke::kGroup: {
+ SInt32 group = keystroke.m_data.m_group.m_group;
+ if (keystroke.m_data.m_group.m_absolute) {
+ LOG((CLOG_DEBUG1 " group %d", group));
+ setGroup(group);
+ }
+ else {
+ LOG((CLOG_DEBUG1 " group %+d", group));
+ setGroup(getEffectiveGroup(pollActiveGroup(), group));
+ }
+ break;
+ }
+ }
+}
+
+void
+OSXKeyState::getKeyMapForSpecialKeys(barrier::KeyMap& keyMap, SInt32 group) const
+{
+ // special keys are insensitive to modifers and none are dead keys
+ barrier::KeyMap::KeyItem item;
+ for (size_t i = 0; i < sizeof(s_controlKeys) /
+ sizeof(s_controlKeys[0]); ++i) {
+ const KeyEntry& 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;
+ barrier::KeyMap::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 KeyState always provides a mapping from keypad keys to
+ // non-keypad keys so we'll be able to generate the characters
+ // anyway.
+}
+
+bool
+OSXKeyState::getKeyMap(barrier::KeyMap& keyMap,
+ SInt32 group, const IOSXKeyResource& r) const
+{
+ if (!r.isValid()) {
+ return false;
+ }
+
+ // space for all possible modifier combinations
+ std::vector<bool> modifiers(r.getNumModifierCombinations());
+
+ // make space for the keys that any single button can synthesize
+ std::vector<std::pair<KeyID, bool> > buttonKeys(r.getNumTables());
+
+ // iterate over each button
+ barrier::KeyMap::KeyItem item;
+ for (UInt32 i = 0; i < r.getNumButtons(); ++i) {
+ item.m_button = mapVirtualKeyToKeyButton(i);
+
+ // the KeyIDs we've already handled
+ std::set<KeyID> 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 = barrier::KeyMap::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;
+ barrier::KeyMap::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<UInt8> tables;
+ tables.insert(static_cast<UInt8>(j));
+ for (UInt32 k = j + 1; k < r.getNumTables(); ++k) {
+ if (buttonKeys[k].first == id) {
+ tables.insert(static_cast<UInt8>(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<UInt32> 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 << 16);
+ for (std::set<UInt32>::iterator k = required.begin();
+ k != required.end(); ++k) {
+ item.m_required = mapModifiersFromOSX(*k << 16);
+ keyMap.addKeyEntry(item);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+OSXKeyState::mapBarrierHotKeyToMac(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
+OSXKeyState::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
+OSXKeyState::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
+OSXKeyState::getGroups(GroupList& groups) const
+{
+ CFIndex n;
+ bool gotLayouts = false;
+
+ // get number of layouts
+ CFStringRef keys[] = { kTISPropertyInputSourceCategory };
+ CFStringRef values[] = { kTISCategoryKeyboardInputSource };
+ CFDictionaryRef dict = CFDictionaryCreate(NULL, (const void **)keys, (const void **)values, 1, NULL, NULL);
+ CFArrayRef kbds = TISCreateInputSourceList(dict, false);
+ n = CFArrayGetCount(kbds);
+ gotLayouts = (n != 0);
+
+ if (!gotLayouts) {
+ LOG((CLOG_DEBUG1 "can't get keyboard layouts"));
+ return false;
+ }
+
+ // get each layout
+ groups.clear();
+ for (CFIndex i = 0; i < n; ++i) {
+ bool addToGroups = true;
+ TISInputSourceRef keyboardLayout =
+ (TISInputSourceRef)CFArrayGetValueAtIndex(kbds, i);
+
+ if (addToGroups)
+ groups.push_back(keyboardLayout);
+ }
+ return true;
+}
+
+void
+OSXKeyState::setGroup(SInt32 group)
+{
+ TISSetInputMethodKeyboardLayoutOverride(m_groups[group]);
+}
+
+void
+OSXKeyState::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
+OSXKeyState::adjustAltGrModifier(const KeyIDs& ids,
+ KeyModifierMask* mask, bool isCommand) const
+{
+ if (!isCommand) {
+ for (KeyIDs::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
+OSXKeyState::mapVirtualKeyToKeyButton(UInt32 keyCode)
+{
+ // 'A' maps to 0 so shift every id
+ return static_cast<KeyButton>(keyCode + KeyButtonOffset);
+}
+
+UInt32
+OSXKeyState::mapKeyButtonToVirtualKey(KeyButton keyButton)
+{
+ return static_cast<UInt32>(keyButton - KeyButtonOffset);
+}
diff --git a/src/lib/platform/OSXKeyState.h b/src/lib/platform/OSXKeyState.h
new file mode 100644
index 0000000..4d92860
--- /dev/null
+++ b/src/lib/platform/OSXKeyState.h
@@ -0,0 +1,180 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/KeyState.h"
+#include "common/stdmap.h"
+#include "common/stdset.h"
+#include "common/stdvector.h"
+
+#include <Carbon/Carbon.h>
+
+typedef TISInputSourceRef KeyLayout;
+class IOSXKeyResource;
+
+//! OS X key state
+/*!
+A key state for OS X.
+*/
+class OSXKeyState : public KeyState {
+public:
+ typedef std::vector<KeyID> KeyIDs;
+
+ OSXKeyState(IEventQueue* events);
+ OSXKeyState(IEventQueue* events, barrier::KeyMap& keyMap);
+ virtual ~OSXKeyState();
+
+ //! @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 barrier mask
+ /*!
+ Returns the barrier modifier mask corresponding to the OS X modifier
+ mask in \p mask.
+ */
+ KeyModifierMask mapModifiersFromOSX(UInt32 mask) const;
+
+ //! Convert CG flags-style modifier mask to old-style Carbon
+ /*!
+ Still required in a few places for translation calls.
+ */
+ KeyModifierMask mapModifiersToCarbon(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(KeyIDs& ids,
+ KeyModifierMask* maskOut, CGEventRef 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 mapBarrierHotKeyToMac(KeyID key, KeyModifierMask mask,
+ UInt32& macVirtualKey,
+ UInt32& macModifierMask) const;
+
+ //@}
+
+ // IKeyState overrides
+ virtual bool fakeCtrlAltDel();
+ virtual bool fakeMediaKey(KeyID id);
+ virtual KeyModifierMask
+ pollActiveModifiers() const;
+ virtual SInt32 pollActiveGroup() const;
+ virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const;
+
+ CGEventFlags getModifierStateAsOSXFlags();
+protected:
+ // KeyState overrides
+ virtual void getKeyMap(barrier::KeyMap& keyMap);
+ virtual void fakeKey(const Keystroke& keystroke);
+
+private:
+ class KeyResource;
+ typedef std::vector<KeyLayout> GroupList;
+
+ // Add hard coded special keys to a barrier::KeyMap.
+ void getKeyMapForSpecialKeys(
+ barrier::KeyMap& keyMap, SInt32 group) const;
+
+ // Convert keyboard resource to a key map
+ bool getKeyMap(barrier::KeyMap& keyMap,
+ SInt32 group, const IOSXKeyResource& 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 KeyIDs& 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);
+
+ void init();
+
+ // Post a key event to HID manager. It posts an event to HID client, a
+ // much lower level than window manager which's the target from carbon
+ // CGEventPost
+ void postHIDVirtualKey(const UInt8 virtualKeyCode,
+ const bool postDown);
+
+private:
+ // OS X uses a physical key if 0 for the 'A' key. barrier 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<CFDataRef, SInt32> GroupMap;
+ typedef std::map<UInt32, KeyID> VirtualKeyMap;
+
+ VirtualKeyMap m_virtualKeyMap;
+ mutable UInt32 m_deadKeyState;
+ GroupList m_groups;
+ GroupMap m_groupMap;
+ bool m_shiftPressed;
+ bool m_controlPressed;
+ bool m_altPressed;
+ bool m_superPressed;
+ bool m_capsPressed;
+};
diff --git a/src/lib/platform/OSXMediaKeySimulator.h b/src/lib/platform/OSXMediaKeySimulator.h
new file mode 100644
index 0000000..39739d2
--- /dev/null
+++ b/src/lib/platform/OSXMediaKeySimulator.h
@@ -0,0 +1,30 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2016 Symless.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file COPYING that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#import <CoreFoundation/CoreFoundation.h>
+
+#include "barrier/key_types.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+bool fakeNativeMediaKey(KeyID id);
+#if defined(__cplusplus)
+}
+#endif
diff --git a/src/lib/platform/OSXMediaKeySimulator.m b/src/lib/platform/OSXMediaKeySimulator.m
new file mode 100644
index 0000000..5aacd10
--- /dev/null
+++ b/src/lib/platform/OSXMediaKeySimulator.m
@@ -0,0 +1,92 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2016 Symless.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file COPYING that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT 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 "platform/OSXMediaKeySimulator.h"
+
+#import <Cocoa/Cocoa.h>
+
+int convertKeyIDToNXKeyType(KeyID id)
+{
+ // hidsystem/ev_keymap.h
+ // NX_KEYTYPE_SOUND_UP 0
+ // NX_KEYTYPE_SOUND_DOWN 1
+ // NX_KEYTYPE_BRIGHTNESS_UP 2
+ // NX_KEYTYPE_BRIGHTNESS_DOWN 3
+ // NX_KEYTYPE_MUTE 7
+ // NX_KEYTYPE_EJECT 14
+ // NX_KEYTYPE_PLAY 16
+ // NX_KEYTYPE_NEXT 17
+ // NX_KEYTYPE_PREVIOUS 18
+ // NX_KEYTYPE_FAST 19
+ // NX_KEYTYPE_REWIND 20
+
+ int type = -1;
+ switch (id) {
+ case kKeyAudioUp:
+ type = 0;
+ break;
+ case kKeyAudioDown:
+ type = 1;
+ break;
+ case kKeyBrightnessUp:
+ type = 2;
+ break;
+ case kKeyBrightnessDown:
+ type = 3;
+ break;
+ case kKeyAudioMute:
+ type = 7;
+ break;
+ case kKeyEject:
+ type = 14;
+ break;
+ case kKeyAudioPlay:
+ type = 16;
+ break;
+ case kKeyAudioNext:
+ type = 17;
+ break;
+ case kKeyAudioPrev:
+ type = 18;
+ break;
+ default:
+ break;
+ }
+
+ return type;
+}
+
+bool
+fakeNativeMediaKey(KeyID id)
+{
+
+ NSEvent* downRef = [NSEvent otherEventWithType:NSSystemDefined
+ location: NSMakePoint(0, 0) modifierFlags:0xa00
+ timestamp:0 windowNumber:0 context:0 subtype:8
+ data1:(convertKeyIDToNXKeyType(id) << 16) | ((0xa) << 8)
+ data2:-1];
+ CGEventRef downEvent = [downRef CGEvent];
+
+ NSEvent* upRef = [NSEvent otherEventWithType:NSSystemDefined
+ location: NSMakePoint(0, 0) modifierFlags:0xa00
+ timestamp:0 windowNumber:0 context:0 subtype:8
+ data1:(convertKeyIDToNXKeyType(id) << 16) | ((0xb) << 8)
+ data2:-1];
+ CGEventRef upEvent = [upRef CGEvent];
+
+ CGEventPost(0, downEvent);
+ CGEventPost(0, upEvent);
+
+ return true;
+}
diff --git a/src/lib/platform/OSXMediaKeySupport.h b/src/lib/platform/OSXMediaKeySupport.h
new file mode 100644
index 0000000..d64e26e
--- /dev/null
+++ b/src/lib/platform/OSXMediaKeySupport.h
@@ -0,0 +1,33 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2016 Symless.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file COPYING that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#import <CoreFoundation/CoreFoundation.h>
+#import <Carbon/Carbon.h>
+
+#include "barrier/key_types.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+bool fakeNativeMediaKey(KeyID id);
+bool isMediaKeyEvent(CGEventRef event);
+bool getMediaKeyEventInfo(CGEventRef event, KeyID* keyId, bool* down, bool* isRepeat);
+#if defined(__cplusplus)
+}
+#endif
diff --git a/src/lib/platform/OSXMediaKeySupport.m b/src/lib/platform/OSXMediaKeySupport.m
new file mode 100644
index 0000000..9c9dbc3
--- /dev/null
+++ b/src/lib/platform/OSXMediaKeySupport.m
@@ -0,0 +1,154 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2016 Symless.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file COPYING that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT 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 "platform/OSXMediaKeySupport.h"
+#import <Cocoa/Cocoa.h>
+#import <IOKit/hidsystem/ev_keymap.h>
+
+int convertKeyIDToNXKeyType(KeyID id)
+{
+ int type = -1;
+
+ switch (id) {
+ case kKeyAudioUp:
+ type = NX_KEYTYPE_SOUND_UP;
+ break;
+ case kKeyAudioDown:
+ type = NX_KEYTYPE_SOUND_DOWN;
+ break;
+ case kKeyBrightnessUp:
+ type = NX_KEYTYPE_BRIGHTNESS_UP;
+ break;
+ case kKeyBrightnessDown:
+ type = NX_KEYTYPE_BRIGHTNESS_DOWN;
+ break;
+ case kKeyAudioMute:
+ type = NX_KEYTYPE_MUTE;
+ break;
+ case kKeyEject:
+ type = NX_KEYTYPE_EJECT;
+ break;
+ case kKeyAudioPlay:
+ type = NX_KEYTYPE_PLAY;
+ break;
+ case kKeyAudioNext:
+ type = NX_KEYTYPE_NEXT;
+ break;
+ case kKeyAudioPrev:
+ type = NX_KEYTYPE_PREVIOUS;
+ break;
+ default:
+ break;
+ }
+
+ return type;
+}
+
+static KeyID
+convertNXKeyTypeToKeyID(uint32_t const type)
+{
+ KeyID id = 0;
+
+ switch (type) {
+ case NX_KEYTYPE_SOUND_UP:
+ id = kKeyAudioUp;
+ break;
+ case NX_KEYTYPE_SOUND_DOWN:
+ id = kKeyAudioDown;
+ break;
+ case NX_KEYTYPE_MUTE:
+ id = kKeyAudioMute;
+ break;
+ case NX_KEYTYPE_EJECT:
+ id = kKeyEject;
+ break;
+ case NX_KEYTYPE_PLAY:
+ id = kKeyAudioPlay;
+ break;
+ case NX_KEYTYPE_FAST:
+ case NX_KEYTYPE_NEXT:
+ id = kKeyAudioNext;
+ break;
+ case NX_KEYTYPE_REWIND:
+ case NX_KEYTYPE_PREVIOUS:
+ id = kKeyAudioPrev;
+ break;
+ default:
+ break;
+ }
+
+ return id;
+}
+
+bool
+isMediaKeyEvent(CGEventRef event) {
+ NSEvent* nsEvent = nil;
+ @try {
+ nsEvent = [NSEvent eventWithCGEvent: event];
+ if ([nsEvent subtype] != 8) {
+ return false;
+ }
+ uint32_t const nxKeyId = ([nsEvent data1] & 0xFFFF0000) >> 16;
+ if (convertNXKeyTypeToKeyID (nxKeyId)) {
+ return true;
+ }
+ } @catch (NSException* e) {
+ }
+ return false;
+}
+
+bool
+getMediaKeyEventInfo(CGEventRef event, KeyID* const keyId,
+ bool* const down, bool* const isRepeat) {
+ NSEvent* nsEvent = nil;
+ @try {
+ nsEvent = [NSEvent eventWithCGEvent: event];
+ } @catch (NSException* e) {
+ return false;
+ }
+ if (keyId) {
+ *keyId = convertNXKeyTypeToKeyID (([nsEvent data1] & 0xFFFF0000) >> 16);
+ }
+ if (down) {
+ *down = !([nsEvent data1] & 0x100);
+ }
+ if (isRepeat) {
+ *isRepeat = [nsEvent data1] & 0x1;
+ }
+ return true;
+}
+
+bool
+fakeNativeMediaKey(KeyID id)
+{
+
+ NSEvent* downRef = [NSEvent otherEventWithType:NSSystemDefined
+ location: NSMakePoint(0, 0) modifierFlags:0xa00
+ timestamp:0 windowNumber:0 context:0 subtype:8
+ data1:(convertKeyIDToNXKeyType(id) << 16) | ((0xa) << 8)
+ data2:-1];
+ CGEventRef downEvent = [downRef CGEvent];
+
+ NSEvent* upRef = [NSEvent otherEventWithType:NSSystemDefined
+ location: NSMakePoint(0, 0) modifierFlags:0xa00
+ timestamp:0 windowNumber:0 context:0 subtype:8
+ data1:(convertKeyIDToNXKeyType(id) << 16) | ((0xb) << 8)
+ data2:-1];
+ CGEventRef upEvent = [upRef CGEvent];
+
+ CGEventPost(0, downEvent);
+ CGEventPost(0, upEvent);
+
+ return true;
+}
diff --git a/src/lib/platform/OSXPasteboardPeeker.h b/src/lib/platform/OSXPasteboardPeeker.h
new file mode 100644
index 0000000..5105262
--- /dev/null
+++ b/src/lib/platform/OSXPasteboardPeeker.h
@@ -0,0 +1,32 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/common.h"
+
+#import <CoreFoundation/CoreFoundation.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+CFStringRef getDraggedFileURL();
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/src/lib/platform/OSXPasteboardPeeker.m b/src/lib/platform/OSXPasteboardPeeker.m
new file mode 100644
index 0000000..ab39e26
--- /dev/null
+++ b/src/lib/platform/OSXPasteboardPeeker.m
@@ -0,0 +1,37 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT 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 "platform/OSXPasteboardPeeker.h"
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+#import <Cocoa/Cocoa.h>
+
+CFStringRef
+getDraggedFileURL()
+{
+ NSString* pbName = NSDragPboard;
+ NSPasteboard* pboard = [NSPasteboard pasteboardWithName:pbName];
+
+ NSMutableString* string;
+ string = [[NSMutableString alloc] initWithCapacity:0];
+
+ NSArray* files = [pboard propertyListForType:NSFilenamesPboardType];
+ for (id file in files) {
+ [string appendString: (NSString*)file];
+ [string appendString: @"\0"];
+ }
+
+ return (CFStringRef)string;
+}
diff --git a/src/lib/platform/OSXScreen.h b/src/lib/platform/OSXScreen.h
new file mode 100644
index 0000000..27cb7df
--- /dev/null
+++ b/src/lib/platform/OSXScreen.h
@@ -0,0 +1,349 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/OSXClipboard.h"
+#include "barrier/PlatformScreen.h"
+#include "barrier/DragInformation.h"
+#include "base/EventTypes.h"
+#include "common/stdmap.h"
+#include "common/stdvector.h"
+
+#include <bitset>
+#include <Carbon/Carbon.h>
+#include <mach/mach_port.h>
+#include <mach/mach_interface.h>
+#include <mach/mach_init.h>
+#include <IOKit/pwr_mgt/IOPMLib.h>
+#include <IOKit/IOMessage.h>
+
+extern "C" {
+ typedef int CGSConnectionID;
+ CGError CGSSetConnectionProperty(CGSConnectionID cid, CGSConnectionID targetCID, CFStringRef key, CFTypeRef value);
+ int _CGSDefaultConnection();
+}
+
+
+template <class T>
+class CondVar;
+class EventQueueTimer;
+class Mutex;
+class Thread;
+class OSXKeyState;
+class OSXScreenSaver;
+class IEventQueue;
+class Mutex;
+
+//! Implementation of IPlatformScreen for OS X
+class OSXScreen : public PlatformScreen {
+public:
+ OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCursor=true);
+ virtual ~OSXScreen();
+
+ IEventQueue* getEvents() const { return m_events; }
+
+ // 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(UInt32& buttonID) const;
+ virtual void getCursorCenter(SInt32& x, SInt32& y) const;
+
+ // ISecondaryScreen overrides
+ virtual void fakeMouseButton(ButtonID id, bool press);
+ virtual void fakeMouseMove(SInt32 x, SInt32 y);
+ 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 OptionsList& options);
+ virtual void setSequenceNumber(UInt32);
+ virtual bool isPrimary() const;
+ virtual void fakeDraggingFiles(DragFileList fileList);
+ virtual String& getDraggingFilename();
+
+ const String& getDropTarget() const { return m_dropTarget; }
+ void waitForCarbonLoop() const;
+
+protected:
+ // IPlatformScreen overrides
+ virtual void handleSystemEvent(const Event&, void*);
+ virtual void updateButtons();
+ virtual IKeyState* getKeyState() const;
+
+private:
+ void updateScreenShape();
+ void updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags);
+ void postMouseEvent(CGPoint&) const;
+
+ // convenience function to send events
+ void sendEvent(Event::Type type, void* = NULL) const;
+ void sendClipboardEvent(Event::Type type, ClipboardID id) const;
+
+ // message handlers
+ bool onMouseMove(SInt32 mx, SInt32 my);
+ // 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;
+
+ void constructMouseButtonEventMap();
+
+ bool onKey(CGEventRef event);
+
+ void onMediaKey(CGEventRef event);
+
+ bool onHotKey(EventRef event) const;
+
+ // Added here to allow the carbon cursor hack to be called.
+ void showCursor();
+ void hideCursor();
+
+ // map barrier mouse button to mac buttons
+ ButtonID mapBarrierButtonToMac(UInt16) const;
+
+ // map mac mouse button to barrier buttons
+ ButtonID mapMacButtonToBarrier(UInt16) const;
+
+ // map mac scroll wheel value to a barrier scroll wheel value
+ SInt32 mapScrollWheelToBarrier(SInt32) const;
+
+ // map barrier scroll wheel value to a mac scroll wheel value
+ SInt32 mapScrollWheelFromBarrier(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 Event&, void*);
+
+ // clipboard check timer handler
+ void handleClipboardCheck(const Event&, void*);
+
+ // Resolution switch callback
+ static void displayReconfigurationCallback(CGDirectDisplayID,
+ CGDisplayChangeSummaryFlags, void*);
+
+ // 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);
+
+ void handleConfirmSleep(const Event& event, void*);
+
+ // global hotkey operating mode
+ static bool isGlobalHotKeyOperatingModeAvailable();
+ static void setGlobalHotKeysEnabled(bool enabled);
+ static bool getGlobalHotKeysEnabled();
+
+ // Quartz event tap support
+ static CGEventRef handleCGInputEvent(CGEventTapProxy proxy,
+ CGEventType type,
+ CGEventRef event,
+ void* refcon);
+ static CGEventRef handleCGInputEventSecondary(CGEventTapProxy proxy,
+ CGEventType type,
+ CGEventRef event,
+ void* refcon);
+
+ // convert CFString to char*
+ static char* CFStringRefToUTF8String(CFStringRef aString);
+
+ void getDropTargetThread(void*);
+
+private:
+ struct HotKeyItem {
+ public:
+ HotKeyItem(UInt32, UInt32);
+ HotKeyItem(EventHotKeyRef, UInt32, UInt32);
+
+ EventHotKeyRef getRef() const;
+
+ bool operator<(const HotKeyItem&) const;
+
+ private:
+ EventHotKeyRef m_ref;
+ UInt32 m_keycode;
+ UInt32 m_mask;
+ };
+
+ enum EMouseButtonState {
+ kMouseButtonUp = 0,
+ kMouseButtonDragged,
+ kMouseButtonDown,
+ kMouseButtonStateMax
+ };
+
+
+ class MouseButtonState {
+ public:
+ void set(UInt32 button, EMouseButtonState state);
+ bool any();
+ void reset();
+ void overwrite(UInt32 buttons);
+
+ bool test(UInt32 button) const;
+ SInt8 getFirstButtonDown() const;
+ private:
+ std::bitset<NumButtonIDs> m_buttons;
+ };
+
+ typedef std::map<UInt32, HotKeyItem> HotKeyMap;
+ typedef std::vector<UInt32> HotKeyIDList;
+ typedef std::map<KeyModifierMask, UInt32> ModifierHotKeyMap;
+ typedef std::map<HotKeyItem, UInt32> 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;
+
+ /* FIXME: this data structure is explicitly marked mutable due
+ to a need to track the state of buttons since the remote
+ side only lets us know of change events, and because the
+ fakeMouseButton button method is marked 'const'. This is
+ Evil, and this should be moved to a place where it need not
+ be mutable as soon as possible. */
+ mutable MouseButtonState m_buttonState;
+ typedef std::map<UInt16, CGEventType> MouseButtonEventMapType;
+ std::vector<MouseButtonEventMapType> MouseButtonEventMap;
+
+ bool m_cursorHidden;
+ SInt32 m_dragNumButtonsDown;
+ Point m_dragLastPoint;
+ EventQueueTimer* m_dragTimer;
+
+ // keyboard stuff
+ OSXKeyState* m_keyState;
+
+ // clipboards
+ OSXClipboard m_pasteboard;
+ UInt32 m_sequenceNumber;
+
+ // screen saver stuff
+ OSXScreenSaver* m_screensaver;
+ bool m_screensaverNotify;
+
+ // clipboard stuff
+ bool m_ownClipboard;
+ EventQueueTimer* 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;
+
+ // fast user switching
+ EventHandlerRef m_switchEventHandlerRef;
+
+ // sleep / wakeup
+ Mutex* m_pmMutex;
+ Thread* m_pmWatchThread;
+ CondVar<bool>* 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;
+
+ // Quartz input event support
+ CFMachPortRef m_eventTapPort;
+ CFRunLoopSourceRef m_eventTapRLSR;
+
+ // for double click coalescing.
+ double m_lastClickTime;
+ int m_clickState;
+ SInt32 m_lastSingleClickXCursor;
+ SInt32 m_lastSingleClickYCursor;
+
+ // cursor will hide and show on enable and disable if true.
+ bool m_autoShowHideCursor;
+
+ IEventQueue* m_events;
+
+ Thread* m_getDropTargetThread;
+ String m_dropTarget;
+
+#if defined(MAC_OS_X_VERSION_10_7)
+ Mutex* m_carbonLoopMutex;
+ CondVar<bool>* m_carbonLoopReady;
+#endif
+
+ class OSXScreenImpl* m_impl;
+};
diff --git a/src/lib/platform/OSXScreen.mm b/src/lib/platform/OSXScreen.mm
new file mode 100644
index 0000000..1d80521
--- /dev/null
+++ b/src/lib/platform/OSXScreen.mm
@@ -0,0 +1,2162 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXScreen.h"
+
+#include "base/EventQueue.h"
+#include "client/Client.h"
+#include "platform/OSXClipboard.h"
+#include "platform/OSXEventQueueBuffer.h"
+#include "platform/OSXKeyState.h"
+#include "platform/OSXScreenSaver.h"
+#include "platform/OSXDragSimulator.h"
+#include "platform/OSXMediaKeySupport.h"
+#include "platform/OSXPasteboardPeeker.h"
+#include "barrier/Clipboard.h"
+#include "barrier/KeyMap.h"
+#include "barrier/ClientApp.h"
+#include "mt/CondVar.h"
+#include "mt/Lock.h"
+#include "mt/Mutex.h"
+#include "mt/Thread.h"
+#include "arch/XArch.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+#include "base/TMethodJob.h"
+
+#include <math.h>
+#include <mach-o/dyld.h>
+#include <AvailabilityMacros.h>
+#include <IOKit/hidsystem/event_status_driver.h>
+#include <AppKit/NSEvent.h>
+
+// This isn't in any Apple SDK that I know of as of yet.
+enum {
+ kBarrierEventMouseScroll = 11,
+ kBarrierMouseScrollAxisX = 'saxx',
+ kBarrierMouseScrollAxisY = 'saxy'
+};
+
+enum {
+ kCarbonLoopWaitTimeout = 10
+};
+
+// TODO: upgrade deprecated function usage in these functions.
+void setZeroSuppressionInterval();
+void avoidSupression();
+void logCursorVisibility();
+void avoidHesitatingCursor();
+
+//
+// OSXScreen
+//
+
+bool OSXScreen::s_testedForGHOM = false;
+bool OSXScreen::s_hasGHOM = false;
+
+OSXScreen::OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCursor) :
+ PlatformScreen(events),
+ m_isPrimary(isPrimary),
+ m_isOnScreen(m_isPrimary),
+ m_cursorPosValid(false),
+ MouseButtonEventMap(NumButtonIDs),
+ 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_switchEventHandlerRef(0),
+ m_pmMutex(new Mutex),
+ m_pmWatchThread(NULL),
+ m_pmThreadReady(new CondVar<bool>(m_pmMutex, false)),
+ m_pmRootPort(0),
+ m_activeModifierHotKey(0),
+ m_activeModifierHotKeyMask(0),
+ m_eventTapPort(nullptr),
+ m_eventTapRLSR(nullptr),
+ m_lastClickTime(0),
+ m_clickState(1),
+ m_lastSingleClickXCursor(0),
+ m_lastSingleClickYCursor(0),
+ m_autoShowHideCursor(autoShowHideCursor),
+ m_events(events),
+ m_getDropTargetThread(NULL),
+ m_impl(NULL)
+{
+ try {
+ m_displayID = CGMainDisplayID();
+ updateScreenShape(m_displayID, 0);
+ m_screensaver = new OSXScreenSaver(m_events, getEventTarget());
+ m_keyState = new OSXKeyState(m_events);
+
+ // only needed when running as a server.
+ if (m_isPrimary) {
+
+#if defined(MAC_OS_X_VERSION_10_9)
+ // we can't pass options to show the dialog, this must be done by the gui.
+ if (!AXIsProcessTrusted()) {
+ throw XArch("assistive devices does not trust this process, allow it in system settings.");
+ }
+#else
+ // now deprecated in mavericks.
+ if (!AXAPIEnabled()) {
+ throw XArch("assistive devices is not enabled, enable it in system settings.");
+ }
+#endif
+ }
+
+ // install display manager notification handler
+ CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallback, this);
+
+ // 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);
+
+ constructMouseButtonEventMap();
+
+ // watch for requests to sleep
+ m_events->adoptHandler(m_events->forOSXScreen().confirmSleep(),
+ getEventTarget(),
+ new TMethodEventJob<OSXScreen>(this,
+ &OSXScreen::handleConfirmSleep));
+
+ // create thread for monitoring system power state.
+ *m_pmThreadReady = false;
+#if defined(MAC_OS_X_VERSION_10_7)
+ m_carbonLoopMutex = new Mutex();
+ m_carbonLoopReady = new CondVar<bool>(m_carbonLoopMutex, false);
+#endif
+ LOG((CLOG_DEBUG "starting watchSystemPowerThread"));
+ m_pmWatchThread = new Thread(new TMethodJob<OSXScreen>
+ (this, &OSXScreen::watchSystemPowerThread));
+ }
+ catch (...) {
+ m_events->removeHandler(m_events->forOSXScreen().confirmSleep(),
+ getEventTarget());
+ if (m_switchEventHandlerRef != 0) {
+ RemoveEventHandler(m_switchEventHandlerRef);
+ }
+
+ CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this);
+
+ delete m_keyState;
+ delete m_screensaver;
+ throw;
+ }
+
+ // install event handlers
+ m_events->adoptHandler(Event::kSystem, m_events->getSystemTarget(),
+ new TMethodEventJob<OSXScreen>(this,
+ &OSXScreen::handleSystemEvent));
+
+ // install the platform event queue
+ m_events->adoptBuffer(new OSXEventQueueBuffer(m_events));
+}
+
+OSXScreen::~OSXScreen()
+{
+ disable();
+ m_events->adoptBuffer(NULL);
+ m_events->removeHandler(Event::kSystem, m_events->getSystemTarget());
+
+ if (m_pmWatchThread) {
+ // make sure the thread has setup the runloop.
+ {
+ Lock 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;
+
+ m_events->removeHandler(m_events->forOSXScreen().confirmSleep(),
+ getEventTarget());
+
+ RemoveEventHandler(m_switchEventHandlerRef);
+
+ CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this);
+
+ delete m_keyState;
+ delete m_screensaver;
+
+#if defined(MAC_OS_X_VERSION_10_7)
+ delete m_carbonLoopMutex;
+ delete m_carbonLoopReady;
+#endif
+}
+
+void*
+OSXScreen::getEventTarget() const
+{
+ return const_cast<OSXScreen*>(this);
+}
+
+bool
+OSXScreen::getClipboard(ClipboardID, IClipboard* dst) const
+{
+ Clipboard::copy(dst, &m_pasteboard);
+ return true;
+}
+
+void
+OSXScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
+{
+ x = m_x;
+ y = m_y;
+ w = m_w;
+ h = m_h;
+}
+
+void
+OSXScreen::getCursorPos(SInt32& x, SInt32& y) const
+{
+ CGEventRef event = CGEventCreate(NULL);
+ CGPoint mouse = CGEventGetLocation(event);
+ x = mouse.x;
+ y = mouse.y;
+ m_cursorPosValid = true;
+ m_xCursor = x;
+ m_yCursor = y;
+ CFRelease(event);
+}
+
+void
+OSXScreen::reconfigure(UInt32)
+{
+ // do nothing
+}
+
+void
+OSXScreen::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
+OSXScreen::fakeInputBegin()
+{
+ // FIXME -- not implemented
+}
+
+void
+OSXScreen::fakeInputEnd()
+{
+ // FIXME -- not implemented
+}
+
+SInt32
+OSXScreen::getJumpZoneSize() const
+{
+ return 1;
+}
+
+bool
+OSXScreen::isAnyMouseButtonDown(UInt32& buttonID) const
+{
+ if (m_buttonState.test(0)) {
+ buttonID = kButtonLeft;
+ return true;
+ }
+
+ return (GetCurrentButtonState() != 0);
+}
+
+void
+OSXScreen::getCursorCenter(SInt32& x, SInt32& y) const
+{
+ x = m_xCenter;
+ y = m_yCenter;
+}
+
+UInt32
+OSXScreen::registerHotKey(KeyID key, KeyModifierMask mask)
+{
+ // get mac virtual key and modifier mask matching barrier key and mask
+ UInt32 macKey, macMask;
+ if (!m_keyState->mapBarrierHotKeyToMac(key, mask, macKey, macMask)) {
+ LOG((CLOG_DEBUG "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[HotKeyItem(macKey, macMask)] = id;
+ }
+
+ if (!okay) {
+ m_oldHotKeyIDs.push_back(id);
+ m_hotKeyToIDMap.erase(HotKeyItem(macKey, macMask));
+ LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask));
+ return 0;
+ }
+
+ m_hotKeys.insert(std::make_pair(id, HotKeyItem(ref, macKey, macMask)));
+
+ LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask, id));
+ return id;
+}
+
+void
+OSXScreen::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
+OSXScreen::constructMouseButtonEventMap()
+{
+ const CGEventType source[NumButtonIDs][3] = {
+ {kCGEventLeftMouseUp, kCGEventLeftMouseDragged, kCGEventLeftMouseDown},
+ {kCGEventRightMouseUp, kCGEventRightMouseDragged, kCGEventRightMouseDown},
+ {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown},
+ {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown},
+ {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown}
+ };
+
+ for (UInt16 button = 0; button < NumButtonIDs; button++) {
+ MouseButtonEventMapType new_map;
+ for (UInt16 state = (UInt32) kMouseButtonUp; state < kMouseButtonStateMax; state++) {
+ CGEventType curEvent = source[button][state];
+ new_map[state] = curEvent;
+ }
+ MouseButtonEventMap[button] = new_map;
+ }
+}
+
+void
+OSXScreen::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;
+ }
+ }
+ }
+
+ CGEventType type = kCGEventMouseMoved;
+
+ SInt8 button = m_buttonState.getFirstButtonDown();
+ if (button != -1) {
+ MouseButtonEventMapType thisButtonType = MouseButtonEventMap[button];
+ type = thisButtonType[kMouseButtonDragged];
+ }
+
+ CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, static_cast<CGMouseButton>(button));
+
+ // Dragging events also need the click state
+ CGEventSetIntegerValueField(event, kCGMouseEventClickState, m_clickState);
+
+ // Fix for sticky keys
+ CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
+ CGEventSetFlags(event, modifiers);
+
+ // Set movement deltas to fix issues with certain 3D programs
+ SInt64 deltaX = pos.x;
+ deltaX -= m_xCursor;
+
+ SInt64 deltaY = pos.y;
+ deltaY -= m_yCursor;
+
+ CGEventSetIntegerValueField(event, kCGMouseEventDeltaX, deltaX);
+ CGEventSetIntegerValueField(event, kCGMouseEventDeltaY, deltaY);
+
+ double deltaFX = deltaX;
+ double deltaFY = deltaY;
+
+ CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaFX);
+ CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaFY);
+
+ CGEventPost(kCGHIDEventTap, event);
+
+ CFRelease(event);
+}
+
+void
+OSXScreen::fakeMouseButton(ButtonID id, bool press)
+{
+ // Buttons are indexed from one, but the button down array is indexed from zero
+ UInt32 index = mapBarrierButtonToMac(id) - kButtonLeft;
+ if (index >= NumButtonIDs) {
+ return;
+ }
+
+ CGPoint pos;
+ if (!m_cursorPosValid) {
+ SInt32 x, y;
+ getCursorPos(x, y);
+ }
+ pos.x = m_xCursor;
+ pos.y = m_yCursor;
+
+ // variable used to detect mouse coordinate differences between
+ // old & new mouse clicks. Used in double click detection.
+ SInt32 xDiff = m_xCursor - m_lastSingleClickXCursor;
+ SInt32 yDiff = m_yCursor - m_lastSingleClickYCursor;
+ double diff = sqrt(xDiff * xDiff + yDiff * yDiff);
+ // max sqrt(x^2 + y^2) difference allowed to double click
+ // since we don't have double click distance in NX APIs
+ // we define our own defaults.
+ const double maxDiff = sqrt(2) + 0.0001;
+
+ double clickTime = [NSEvent doubleClickInterval];
+
+ // As long as the click is within the time window and distance window
+ // increase clickState (double click, triple click, etc)
+ // This will allow for higher than triple click but the quartz documenation
+ // does not specify that this should be limited to triple click
+ if (press) {
+ if ((ARCH->time() - m_lastClickTime) <= clickTime && diff <= maxDiff){
+ m_clickState++;
+ }
+ else {
+ m_clickState = 1;
+ }
+
+ m_lastClickTime = ARCH->time();
+ }
+
+ if (m_clickState == 1){
+ m_lastSingleClickXCursor = m_xCursor;
+ m_lastSingleClickYCursor = m_yCursor;
+ }
+
+ EMouseButtonState state = press ? kMouseButtonDown : kMouseButtonUp;
+
+ LOG((CLOG_DEBUG1 "faking mouse button id: %d press: %s", index, press ? "pressed" : "released"));
+
+ MouseButtonEventMapType thisButtonMap = MouseButtonEventMap[index];
+ CGEventType type = thisButtonMap[state];
+
+ CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, static_cast<CGMouseButton>(index));
+
+ CGEventSetIntegerValueField(event, kCGMouseEventClickState, m_clickState);
+
+ // Fix for sticky keys
+ CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
+ CGEventSetFlags(event, modifiers);
+
+ m_buttonState.set(index, state);
+ CGEventPost(kCGHIDEventTap, event);
+
+ CFRelease(event);
+
+ if (!press && (id == kButtonLeft)) {
+ if (m_fakeDraggingStarted) {
+ m_getDropTargetThread = new Thread(new TMethodJob<OSXScreen>(
+ this, &OSXScreen::getDropTargetThread));
+ }
+
+ m_draggingStarted = false;
+ }
+}
+
+void
+OSXScreen::getDropTargetThread(void*)
+{
+#if defined(MAC_OS_X_VERSION_10_7)
+ char* cstr = NULL;
+
+ // wait for 5 secs for the drop destinaiton string to be filled.
+ UInt32 timeout = ARCH->time() + 5;
+
+ while (ARCH->time() < timeout) {
+ CFStringRef cfstr = getCocoaDropTarget();
+ cstr = CFStringRefToUTF8String(cfstr);
+ CFRelease(cfstr);
+
+ if (cstr != NULL) {
+ break;
+ }
+ ARCH->sleep(.1f);
+ }
+
+ if (cstr != NULL) {
+ LOG((CLOG_DEBUG "drop target: %s", cstr));
+ m_dropTarget = cstr;
+ }
+ else {
+ LOG((CLOG_ERR "failed to get drop target"));
+ m_dropTarget.clear();
+ }
+#else
+ LOG((CLOG_WARN "drag drop not supported"));
+#endif
+ m_fakeDraggingStarted = false;
+}
+
+void
+OSXScreen::fakeMouseMove(SInt32 x, SInt32 y)
+{
+ if (m_fakeDraggingStarted) {
+ m_buttonState.set(0, kMouseButtonDown);
+ }
+
+ // index 0 means left mouse button
+ if (m_buttonState.test(0)) {
+ m_draggingStarted = true;
+ }
+
+ // synthesize event
+ CGPoint pos;
+ pos.x = x;
+ pos.y = y;
+ postMouseEvent(pos);
+
+ // save new cursor position
+ m_xCursor = static_cast<SInt32>(pos.x);
+ m_yCursor = static_cast<SInt32>(pos.y);
+ m_cursorPosValid = true;
+}
+
+void
+OSXScreen::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
+ CGEventRef event = CGEventCreate(NULL);
+ CGPoint oldPos = CGEventGetLocation(event);
+ CFRelease(event);
+
+ // synthesize event
+ CGPoint pos;
+ m_xCursor = static_cast<SInt32>(oldPos.x);
+ m_yCursor = static_cast<SInt32>(oldPos.y);
+ pos.x = oldPos.x + dx;
+ pos.y = oldPos.y + dy;
+ postMouseEvent(pos);
+
+ // we now assume we don't know the current cursor position
+ m_cursorPosValid = false;
+}
+
+void
+OSXScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
+{
+ if (xDelta != 0 || yDelta != 0) {
+ // create a scroll event, post it and release it. not sure if kCGScrollEventUnitLine
+ // is the right choice here over kCGScrollEventUnitPixel
+ CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(
+ NULL, kCGScrollEventUnitLine, 2,
+ mapScrollWheelFromBarrier(yDelta),
+ -mapScrollWheelFromBarrier(xDelta));
+
+ // Fix for sticky keys
+ CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
+ CGEventSetFlags(scrollEvent, modifiers);
+
+ CGEventPost(kCGHIDEventTap, scrollEvent);
+ CFRelease(scrollEvent);
+ }
+}
+
+void
+OSXScreen::showCursor()
+{
+ LOG((CLOG_DEBUG "showing cursor"));
+
+ CFStringRef propertyString = CFStringCreateWithCString(
+ NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman);
+
+ CGSSetConnectionProperty(
+ _CGSDefaultConnection(), _CGSDefaultConnection(),
+ propertyString, kCFBooleanTrue);
+
+ CFRelease(propertyString);
+
+ CGError error = CGDisplayShowCursor(m_displayID);
+ if (error != kCGErrorSuccess) {
+ LOG((CLOG_ERR "failed to show cursor, error=%d", error));
+ }
+
+ // appears to fix "mouse randomly not showing" bug
+ CGAssociateMouseAndMouseCursorPosition(true);
+
+ logCursorVisibility();
+
+ m_cursorHidden = false;
+}
+
+void
+OSXScreen::hideCursor()
+{
+ LOG((CLOG_DEBUG "hiding cursor"));
+
+ CFStringRef propertyString = CFStringCreateWithCString(
+ NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman);
+
+ CGSSetConnectionProperty(
+ _CGSDefaultConnection(), _CGSDefaultConnection(),
+ propertyString, kCFBooleanTrue);
+
+ CFRelease(propertyString);
+
+ CGError error = CGDisplayHideCursor(m_displayID);
+ if (error != kCGErrorSuccess) {
+ LOG((CLOG_ERR "failed to hide cursor, error=%d", error));
+ }
+
+ // appears to fix "mouse randomly not hiding" bug
+ CGAssociateMouseAndMouseCursorPosition(true);
+
+ logCursorVisibility();
+
+ m_cursorHidden = true;
+}
+
+void
+OSXScreen::enable()
+{
+ // watch the clipboard
+ m_clipboardTimer = m_events->newTimer(1.0, NULL);
+ m_events->adoptHandler(Event::kTimer, m_clipboardTimer,
+ new TMethodEventJob<OSXScreen>(this,
+ &OSXScreen::handleClipboardCheck));
+
+ if (m_isPrimary) {
+ // FIXME -- start watching jump zones
+
+ // kCGEventTapOptionDefault = 0x00000000 (Missing in 10.4, so specified literally)
+ m_eventTapPort = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
+ kCGEventMaskForAllEvents,
+ handleCGInputEvent,
+ this);
+ }
+ else {
+ // FIXME -- prevent system from entering power save mode
+
+ if (m_autoShowHideCursor) {
+ hideCursor();
+ }
+
+ // warp the mouse to the cursor center
+ fakeMouseMove(m_xCenter, m_yCenter);
+
+ // there may be a better way to do this, but we register an event handler even if we're
+ // not on the primary display (acting as a client). This way, if a local event comes in
+ // (either keyboard or mouse), we can make sure to show the cursor if we've hidden it.
+ m_eventTapPort = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
+ kCGEventMaskForAllEvents,
+ handleCGInputEventSecondary,
+ this);
+ }
+
+ if (!m_eventTapPort) {
+ LOG((CLOG_ERR "failed to create quartz event tap"));
+ }
+
+ m_eventTapRLSR = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTapPort, 0);
+ if (!m_eventTapRLSR) {
+ LOG((CLOG_ERR "failed to create a CFRunLoopSourceRef for the quartz event tap"));
+ }
+
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
+}
+
+void
+OSXScreen::disable()
+{
+ if (m_autoShowHideCursor) {
+ showCursor();
+ }
+
+ // FIXME -- stop watching jump zones, stop capturing input
+
+ if (m_eventTapRLSR) {
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
+ CFRelease(m_eventTapRLSR);
+ m_eventTapRLSR = nullptr;
+ }
+
+ if (m_eventTapPort) {
+ CGEventTapEnable(m_eventTapPort, false);
+ CFRelease(m_eventTapPort);
+ m_eventTapPort = nullptr;
+ }
+ // FIXME -- allow system to enter power saving mode
+
+ // disable drag handling
+ m_dragNumButtonsDown = 0;
+ enableDragTimer(false);
+
+ // uninstall clipboard timer
+ if (m_clipboardTimer != NULL) {
+ m_events->removeHandler(Event::kTimer, m_clipboardTimer);
+ m_events->deleteTimer(m_clipboardTimer);
+ m_clipboardTimer = NULL;
+ }
+
+ m_isOnScreen = m_isPrimary;
+}
+
+void
+OSXScreen::enter()
+{
+ showCursor();
+
+ if (m_isPrimary) {
+ setZeroSuppressionInterval();
+ }
+ else {
+ // reset buttons
+ m_buttonState.reset();
+
+ // patch by Yutaka Tsutano
+ // wakes the client screen
+ // http://symless.com/spit/issues/details/3287#c12
+ io_registry_entry_t entry = IORegistryEntryFromPath(
+ kIOMasterPortDefault,
+ "IOService:/IOResources/IODisplayWrangler");
+
+ if (entry != MACH_PORT_NULL) {
+ IORegistryEntrySetCFProperty(entry, CFSTR("IORequestIdle"), kCFBooleanFalse);
+ IOObjectRelease(entry);
+ }
+
+ avoidSupression();
+ }
+
+ // now on screen
+ m_isOnScreen = true;
+}
+
+bool
+OSXScreen::leave()
+{
+ hideCursor();
+
+ if (isDraggingStarted()) {
+ String& fileList = getDraggingFilename();
+
+ if (!m_isPrimary) {
+ if (fileList.empty() == false) {
+ ClientApp& app = ClientApp::instance();
+ Client* client = app.getClientPtr();
+
+ DragInformation di;
+ di.setFilename(fileList);
+ DragFileList dragFileList;
+ dragFileList.push_back(di);
+ String info;
+ UInt32 fileCount = DragInformation::setupDragInfo(
+ dragFileList, info);
+ client->sendDragInfo(fileCount, info, info.size());
+ LOG((CLOG_DEBUG "send dragging file to server"));
+
+ // TODO: what to do with multiple file or even
+ // a folder
+ client->sendFileToServer(fileList.c_str());
+ }
+ }
+ m_draggingStarted = false;
+ }
+
+ if (m_isPrimary) {
+ avoidHesitatingCursor();
+
+ }
+
+ // now off screen
+ m_isOnScreen = false;
+
+ return true;
+}
+
+bool
+OSXScreen::setClipboard(ClipboardID, const IClipboard* src)
+{
+ if (src != NULL) {
+ LOG((CLOG_DEBUG "setting clipboard"));
+ Clipboard::copy(&m_pasteboard, src);
+ }
+ return true;
+}
+
+void
+OSXScreen::checkClipboards()
+{
+ LOG((CLOG_DEBUG2 "checking clipboard"));
+ if (m_pasteboard.synchronize()) {
+ LOG((CLOG_DEBUG "clipboard changed"));
+ sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard);
+ sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection);
+ }
+}
+
+void
+OSXScreen::openScreensaver(bool notify)
+{
+ m_screensaverNotify = notify;
+ if (!m_screensaverNotify) {
+ m_screensaver->disable();
+ }
+}
+
+void
+OSXScreen::closeScreensaver()
+{
+ if (!m_screensaverNotify) {
+ m_screensaver->enable();
+ }
+}
+
+void
+OSXScreen::screensaver(bool activate)
+{
+ if (activate) {
+ m_screensaver->activate();
+ }
+ else {
+ m_screensaver->deactivate();
+ }
+}
+
+void
+OSXScreen::resetOptions()
+{
+ // no options
+}
+
+void
+OSXScreen::setOptions(const OptionsList&)
+{
+ // no options
+}
+
+void
+OSXScreen::setSequenceNumber(UInt32 seqNum)
+{
+ m_sequenceNumber = seqNum;
+}
+
+bool
+OSXScreen::isPrimary() const
+{
+ return m_isPrimary;
+}
+
+void
+OSXScreen::sendEvent(Event::Type type, void* data) const
+{
+ m_events->addEvent(Event(type, getEventTarget(), data));
+}
+
+void
+OSXScreen::sendClipboardEvent(Event::Type type, ClipboardID id) const
+{
+ ClipboardInfo* info = (ClipboardInfo*)malloc(sizeof(ClipboardInfo));
+ info->m_id = id;
+ info->m_sequenceNumber = m_sequenceNumber;
+ sendEvent(type, info);
+}
+
+void
+OSXScreen::handleSystemEvent(const Event& event, void*)
+{
+ EventRef* carbonEvent = static_cast<EventRef*>(event.getData());
+ assert(carbonEvent != NULL);
+
+ UInt32 eventClass = GetEventClass(*carbonEvent);
+
+ switch (eventClass) {
+ case kEventClassMouse:
+ switch (GetEventKind(*carbonEvent)) {
+ case kBarrierEventMouseScroll:
+ {
+ OSStatus r;
+ long xScroll;
+ long yScroll;
+
+ // get scroll amount
+ r = GetEventParameter(*carbonEvent,
+ kBarrierMouseScrollAxisX,
+ typeSInt32,
+ NULL,
+ sizeof(xScroll),
+ NULL,
+ &xScroll);
+ if (r != noErr) {
+ xScroll = 0;
+ }
+ r = GetEventParameter(*carbonEvent,
+ kBarrierMouseScrollAxisY,
+ typeSInt32,
+ NULL,
+ sizeof(yScroll),
+ NULL,
+ &yScroll);
+ if (r != noErr) {
+ yScroll = 0;
+ }
+
+ if (xScroll != 0 || yScroll != 0) {
+ onMouseWheel(-mapScrollWheelToBarrier(xScroll),
+ mapScrollWheelToBarrier(yScroll));
+ }
+ }
+ }
+ break;
+
+ case kEventClassKeyboard:
+ switch (GetEventKind(*carbonEvent)) {
+ case kEventHotKeyPressed:
+ case kEventHotKeyReleased:
+ onHotKey(*carbonEvent);
+ break;
+ }
+
+ break;
+
+ case kEventClassWindow:
+ // 2nd param was formerly GetWindowEventTarget(m_userInputWindow) which is 32-bit only,
+ // however as m_userInputWindow is never initialized to anything we can take advantage of
+ // the fact that GetWindowEventTarget(NULL) == NULL
+ SendEventToEventTarget(*carbonEvent, NULL);
+ 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
+OSXScreen::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(m_events->forIPrimaryScreen().motionOnPrimary(),
+ MotionInfo::alloc(m_xCursor, m_yCursor));
+ if (m_buttonState.test(0)) {
+ m_draggingStarted = true;
+ }
+ }
+ 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(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(x, y));
+ }
+ }
+
+ return true;
+}
+
+bool
+OSXScreen::onMouseButton(bool pressed, UInt16 macButton)
+{
+ // Buttons 2 and 3 are inverted on the mac
+ ButtonID button = mapMacButtonToBarrier(macButton);
+
+ if (pressed) {
+ LOG((CLOG_DEBUG1 "event: button press button=%d", button));
+ if (button != kButtonNone) {
+ KeyModifierMask mask = m_keyState->getActiveModifiers();
+ sendEvent(m_events->forIPrimaryScreen().buttonDown(), ButtonInfo::alloc(button, mask));
+ }
+ }
+ else {
+ LOG((CLOG_DEBUG1 "event: button release button=%d", button));
+ if (button != kButtonNone) {
+ KeyModifierMask mask = m_keyState->getActiveModifiers();
+ sendEvent(m_events->forIPrimaryScreen().buttonUp(), ButtonInfo::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);
+ }
+ }
+ }
+
+ if (macButton == kButtonLeft) {
+ EMouseButtonState state = pressed ? kMouseButtonDown : kMouseButtonUp;
+ m_buttonState.set(kButtonLeft - 1, state);
+ if (pressed) {
+ m_draggingFilename.clear();
+ LOG((CLOG_DEBUG2 "dragging file directory is cleared"));
+ }
+ else {
+ if (m_fakeDraggingStarted) {
+ m_getDropTargetThread = new Thread(new TMethodJob<OSXScreen>(
+ this, &OSXScreen::getDropTargetThread));
+ }
+
+ m_draggingStarted = false;
+ }
+ }
+
+ return true;
+}
+
+bool
+OSXScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) const
+{
+ LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta));
+ sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(xDelta, yDelta));
+ return true;
+}
+
+void
+OSXScreen::handleClipboardCheck(const Event&, void*)
+{
+ checkClipboards();
+}
+
+void
+OSXScreen::displayReconfigurationCallback(CGDirectDisplayID displayID, CGDisplayChangeSummaryFlags flags, void* inUserData)
+{
+ OSXScreen* screen = (OSXScreen*)inUserData;
+
+ // Closing or opening the lid when an external monitor is
+ // connected causes an kCGDisplayBeginConfigurationFlag event
+ CGDisplayChangeSummaryFlags mask = kCGDisplayBeginConfigurationFlag | kCGDisplayMovedFlag |
+ kCGDisplaySetModeFlag | kCGDisplayAddFlag | kCGDisplayRemoveFlag |
+ kCGDisplayEnabledFlag | kCGDisplayDisabledFlag |
+ kCGDisplayMirrorFlag | kCGDisplayUnMirrorFlag |
+ kCGDisplayDesktopShapeChangedFlag;
+
+ LOG((CLOG_DEBUG1 "event: display was reconfigured: %x %x %x", flags, mask, flags & mask));
+
+ if (flags & mask) { /* Something actually did change */
+
+ LOG((CLOG_DEBUG1 "event: screen changed shape; refreshing dimensions"));
+ screen->updateScreenShape(displayID, flags);
+ }
+}
+
+bool
+OSXScreen::onKey(CGEventRef event)
+{
+ CGEventType eventKind = CGEventGetType(event);
+
+ // get the key and active modifiers
+ UInt32 virtualKey = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
+ CGEventFlags macMask = CGEventGetFlags(event);
+ LOG((CLOG_DEBUG1 "event: Key event kind: %d, keycode=%d", eventKind, virtualKey));
+
+ // Special handling to track state of modifiers
+ if (eventKind == kCGEventFlagsChanged) {
+ // 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;
+ m_events->addEvent(Event(m_events->forIPrimaryScreen().hotKeyDown(),
+ getEventTarget(),
+ HotKeyInfo::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) {
+ m_events->addEvent(Event(m_events->forIPrimaryScreen().hotKeyUp(),
+ getEventTarget(),
+ HotKeyInfo::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(HotKeyItem(virtualKey,
+ m_keyState->mapModifiersToCarbon(macMask)
+ & 0xff00u));
+ if (i != m_hotKeyToIDMap.end()) {
+ UInt32 id = i->second;
+
+ // determine event type
+ Event::Type type;
+ //UInt32 eventKind = GetEventKind(event);
+ if (eventKind == kCGEventKeyDown) {
+ type = m_events->forIPrimaryScreen().hotKeyDown();
+ }
+ else if (eventKind == kCGEventKeyUp) {
+ type = m_events->forIPrimaryScreen().hotKeyUp();
+ }
+ else {
+ return false;
+ }
+
+ m_events->addEvent(Event(type, getEventTarget(),
+ HotKeyInfo::alloc(id)));
+
+ return true;
+ }
+ }
+
+ // decode event type
+ bool down = (eventKind == kCGEventKeyDown);
+ bool up = (eventKind == kCGEventKeyUp);
+ bool isRepeat = (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat) == 1);
+
+ // map event to keys
+ KeyModifierMask mask;
+ OSXKeyState::KeyIDs 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 (OSXKeyState::KeyIDs::const_iterator i = keys.begin();
+ i != keys.end(); ++i) {
+ m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat,
+ *i, sendMask, 1, button);
+ }
+
+ return true;
+}
+
+void
+OSXScreen::onMediaKey(CGEventRef event)
+{
+ KeyID keyID;
+ bool down;
+ bool isRepeat;
+
+ if (!getMediaKeyEventInfo (event, &keyID, &down, &isRepeat)) {
+ LOG ((CLOG_ERR "Failed to decode media key event"));
+ return;
+ }
+
+ LOG ((CLOG_DEBUG2 "Media key event: keyID=0x%02x, %s, repeat=%s",
+ keyID, (down ? "down": "up"),
+ (isRepeat ? "yes" : "no")));
+
+ KeyButton button = 0;
+ KeyModifierMask mask = m_keyState->getActiveModifiers();
+ m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat, keyID, mask, 1, button);
+}
+
+bool
+OSXScreen::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
+ Event::Type type;
+ UInt32 eventKind = GetEventKind(event);
+ if (eventKind == kEventHotKeyPressed) {
+ type = m_events->forIPrimaryScreen().hotKeyDown();
+ }
+ else if (eventKind == kEventHotKeyReleased) {
+ type = m_events->forIPrimaryScreen().hotKeyUp();
+ }
+ else {
+ return false;
+ }
+
+ m_events->addEvent(Event(type, getEventTarget(),
+ HotKeyInfo::alloc(id)));
+
+ return true;
+}
+
+ButtonID
+OSXScreen::mapBarrierButtonToMac(UInt16 button) const
+{
+ switch (button) {
+ case 1:
+ return kButtonLeft;
+ case 2:
+ return kMacButtonMiddle;
+ case 3:
+ return kMacButtonRight;
+ }
+
+ return static_cast<ButtonID>(button);
+}
+
+ButtonID
+OSXScreen::mapMacButtonToBarrier(UInt16 macButton) const
+{
+ switch (macButton) {
+ case 1:
+ return kButtonLeft;
+
+ case 2:
+ return kButtonRight;
+
+ case 3:
+ return kButtonMiddle;
+ }
+
+ return static_cast<ButtonID>(macButton);
+}
+
+SInt32
+OSXScreen::mapScrollWheelToBarrier(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<SInt32>(120.0 * d);
+}
+
+SInt32
+OSXScreen::mapScrollWheelFromBarrier(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<SInt32>(3.0 * x / 120.0);
+}
+
+double
+OSXScreen::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<CFNumberRef>(pref);
+ if (CFNumberGetValue(value, kCFNumberDoubleType, &scaling)) {
+ if (scaling < 0.0) {
+ scaling = 0.0;
+ }
+ }
+ }
+ CFRelease(pref);
+ }
+
+ return scaling;
+}
+
+double
+OSXScreen::getScrollSpeedFactor() const
+{
+ return pow(10.0, getScrollSpeed());
+}
+
+void
+OSXScreen::enableDragTimer(bool enable)
+{
+ if (enable && m_dragTimer == NULL) {
+ m_dragTimer = m_events->newTimer(0.01, NULL);
+ m_events->adoptHandler(Event::kTimer, m_dragTimer,
+ new TMethodEventJob<OSXScreen>(this,
+ &OSXScreen::handleDrag));
+ CGEventRef event = CGEventCreate(NULL);
+ CGPoint mouse = CGEventGetLocation(event);
+ m_dragLastPoint.h = (short)mouse.x;
+ m_dragLastPoint.v = (short)mouse.y;
+ CFRelease(event);
+ }
+ else if (!enable && m_dragTimer != NULL) {
+ m_events->removeHandler(Event::kTimer, m_dragTimer);
+ m_events->deleteTimer(m_dragTimer);
+ m_dragTimer = NULL;
+ }
+}
+
+void
+OSXScreen::handleDrag(const Event&, void*)
+{
+ CGEventRef event = CGEventCreate(NULL);
+ CGPoint p = CGEventGetLocation(event);
+ CFRelease(event);
+
+ if ((short)p.x != m_dragLastPoint.h || (short)p.y != m_dragLastPoint.v) {
+ m_dragLastPoint.h = (short)p.x;
+ m_dragLastPoint.v = (short)p.y;
+ onMouseMove((SInt32)p.x, (SInt32)p.y);
+ }
+}
+
+void
+OSXScreen::updateButtons()
+{
+ UInt32 buttons = GetCurrentButtonState();
+
+ m_buttonState.overwrite(buttons);
+}
+
+IKeyState*
+OSXScreen::getKeyState() const
+{
+ return m_keyState;
+}
+
+void
+OSXScreen::updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags flags)
+{
+ updateScreenShape();
+}
+
+void
+OSXScreen::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
+ CGDirectDisplayID main = CGMainDisplayID();
+ const CGRect rect = CGDisplayBounds(main);
+ m_xCenter = (rect.origin.x + rect.size.width) / 2;
+ m_yCenter = (rect.origin.y + rect.size.height) / 2;
+
+ delete[] displays;
+ // We want to notify the peer screen whether we are primary screen or not
+ sendEvent(m_events->forIScreen().shapeChanged());
+
+ LOG((CLOG_DEBUG "screen shape: center=%d,%d size=%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
+//
+// OSXScreen::userSwitchCallback(void*)
+//
+// gets called if a fast user switch occurs
+//
+
+pascal OSStatus
+OSXScreen::userSwitchCallback(EventHandlerCallRef nextHandler,
+ EventRef theEvent,
+ void* inUserData)
+{
+ OSXScreen* screen = (OSXScreen*)inUserData;
+ UInt32 kind = GetEventKind(theEvent);
+ IEventQueue* events = screen->getEvents();
+
+ if (kind == kEventSystemUserSessionDeactivated) {
+ LOG((CLOG_DEBUG "user session deactivated"));
+ events->addEvent(Event(events->forIScreen().suspend(),
+ screen->getEventTarget()));
+ }
+ else if (kind == kEventSystemUserSessionActivated) {
+ LOG((CLOG_DEBUG "user session activated"));
+ events->addEvent(Event(events->forIScreen().resume(),
+ screen->getEventTarget()));
+ }
+ return (CallNextEventHandler(nextHandler, theEvent));
+}
+
+#pragma mark -
+
+//
+// SLEEP/WAKEUP NOTIFICATION SUPPORT
+//
+// OSXScreen::watchSystemPowerThread(void*)
+//
+// main of thread monitoring system power (sleep/wakup) using a CFRunLoop
+//
+
+void
+OSXScreen::watchSystemPowerThread(void*)
+{
+ io_object_t notifier;
+ IONotificationPortRef notificationPortRef;
+ CFRunLoopSourceRef runloopSourceRef = 0;
+
+ m_pmRunloop = CFRunLoopGetCurrent();
+ // install system power change callback
+ m_pmRootPort = IORegisterForSystemPower(this, &notificationPortRef,
+ powerChangeCallback, &notifier);
+ if (m_pmRootPort == 0) {
+ LOG((CLOG_WARN "IORegisterForSystemPower failed"));
+ }
+ else {
+ runloopSourceRef =
+ IONotificationPortGetRunLoopSource(notificationPortRef);
+ CFRunLoopAddSource(m_pmRunloop, runloopSourceRef,
+ kCFRunLoopCommonModes);
+ }
+
+ // thread is ready
+ {
+ Lock 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) {
+ LOG((CLOG_WARN "failed to init watchSystemPowerThread"));
+ return;
+ }
+
+ LOG((CLOG_DEBUG "started watchSystemPowerThread"));
+
+ LOG((CLOG_DEBUG "waiting for event loop"));
+ m_events->waitForReady();
+
+#if defined(MAC_OS_X_VERSION_10_7)
+ {
+ Lock lockCarbon(m_carbonLoopMutex);
+ if (*m_carbonLoopReady == false) {
+
+ // we signalling carbon loop ready before starting
+ // unless we know how to do it within the loop
+ LOG((CLOG_DEBUG "signalling carbon loop ready"));
+
+ *m_carbonLoopReady = true;
+ m_carbonLoopReady->signal();
+ }
+ }
+#endif
+
+ // start the run loop
+ LOG((CLOG_DEBUG "starting carbon loop"));
+ CFRunLoopRun();
+ LOG((CLOG_DEBUG "carbon loop has stopped"));
+
+ // cleanup
+ if (notificationPortRef) {
+ CFRunLoopRemoveSource(m_pmRunloop,
+ runloopSourceRef, kCFRunLoopDefaultMode);
+ CFRunLoopSourceInvalidate(runloopSourceRef);
+ CFRelease(runloopSourceRef);
+ }
+
+ Lock lock(m_pmMutex);
+ IODeregisterForSystemPower(&notifier);
+ m_pmRootPort = 0;
+ LOG((CLOG_DEBUG "stopped watchSystemPowerThread"));
+}
+
+void
+OSXScreen::powerChangeCallback(void* refcon, io_service_t service,
+ natural_t messageType, void* messageArg)
+{
+ ((OSXScreen*)refcon)->handlePowerChangeRequest(messageType, messageArg);
+}
+
+void
+OSXScreen::handlePowerChangeRequest(natural_t messageType, void* messageArg)
+{
+ // we've received a power change notification
+ switch (messageType) {
+ case kIOMessageSystemWillSleep:
+ // OSXScreen 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.
+ m_events->addEvent(Event(m_events->forOSXScreen().confirmSleep(),
+ getEventTarget(), messageArg,
+ Event::kDontFreeData));
+ return;
+
+ case kIOMessageSystemHasPoweredOn:
+ LOG((CLOG_DEBUG "system wakeup"));
+ m_events->addEvent(Event(m_events->forIScreen().resume(),
+ getEventTarget()));
+ break;
+
+ default:
+ break;
+ }
+
+ Lock lock(m_pmMutex);
+ if (m_pmRootPort != 0) {
+ IOAllowPowerChange(m_pmRootPort, (long)messageArg);
+ }
+}
+
+void
+OSXScreen::handleConfirmSleep(const Event& event, void*)
+{
+ long messageArg = (long)event.getData();
+ if (messageArg != 0) {
+ Lock lock(m_pmMutex);
+ if (m_pmRootPort != 0) {
+ // deliver suspend event immediately.
+ m_events->addEvent(Event(m_events->forIScreen().suspend(),
+ getEventTarget(), NULL,
+ Event::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.
+//
+// FIXME: This is hosed as of OS 10.5; patches to repair this are
+// a good thing.
+//
+#if 0
+
+#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
+OSXScreen::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
+OSXScreen::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
+OSXScreen::getGlobalHotKeysEnabled()
+{
+ CGSGlobalHotKeyOperatingMode mode;
+ if (isGlobalHotKeyOperatingModeAvailable()) {
+ CGSConnection conn = s__CGSDefaultConnection();
+ s_CGSGetGlobalHotKeyOperatingMode(conn, &mode);
+ }
+ else {
+ mode = CGSGlobalHotKeyEnable;
+ }
+ return (mode == CGSGlobalHotKeyEnable);
+}
+
+#endif
+
+//
+// OSXScreen::HotKeyItem
+//
+
+OSXScreen::HotKeyItem::HotKeyItem(UInt32 keycode, UInt32 mask) :
+ m_ref(NULL),
+ m_keycode(keycode),
+ m_mask(mask)
+{
+ // do nothing
+}
+
+OSXScreen::HotKeyItem::HotKeyItem(EventHotKeyRef ref,
+ UInt32 keycode, UInt32 mask) :
+ m_ref(ref),
+ m_keycode(keycode),
+ m_mask(mask)
+{
+ // do nothing
+}
+
+EventHotKeyRef
+OSXScreen::HotKeyItem::getRef() const
+{
+ return m_ref;
+}
+
+bool
+OSXScreen::HotKeyItem::operator<(const HotKeyItem& x) const
+{
+ return (m_keycode < x.m_keycode ||
+ (m_keycode == x.m_keycode && m_mask < x.m_mask));
+}
+
+// Quartz event tap support for the secondary display. This makes sure that we
+// will show the cursor if a local event comes in while barrier has the cursor
+// off the screen.
+CGEventRef
+OSXScreen::handleCGInputEventSecondary(
+ CGEventTapProxy proxy,
+ CGEventType type,
+ CGEventRef event,
+ void* refcon)
+{
+ // this fix is really screwing with the correct show/hide behavior. it
+ // should be tested better before reintroducing.
+ return event;
+
+ OSXScreen* screen = (OSXScreen*)refcon;
+ if (screen->m_cursorHidden && type == kCGEventMouseMoved) {
+
+ CGPoint pos = CGEventGetLocation(event);
+ if (pos.x != screen->m_xCenter || pos.y != screen->m_yCenter) {
+
+ LOG((CLOG_DEBUG "show cursor on secondary, type=%d pos=%d,%d",
+ type, pos.x, pos.y));
+ screen->showCursor();
+ }
+ }
+ return event;
+}
+
+// Quartz event tap support
+CGEventRef
+OSXScreen::handleCGInputEvent(CGEventTapProxy proxy,
+ CGEventType type,
+ CGEventRef event,
+ void* refcon)
+{
+ OSXScreen* screen = (OSXScreen*)refcon;
+ CGPoint pos;
+
+ switch(type) {
+ case kCGEventLeftMouseDown:
+ case kCGEventRightMouseDown:
+ case kCGEventOtherMouseDown:
+ screen->onMouseButton(true, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1);
+ break;
+ case kCGEventLeftMouseUp:
+ case kCGEventRightMouseUp:
+ case kCGEventOtherMouseUp:
+ screen->onMouseButton(false, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1);
+ break;
+ case kCGEventLeftMouseDragged:
+ case kCGEventRightMouseDragged:
+ case kCGEventOtherMouseDragged:
+ case kCGEventMouseMoved:
+ pos = CGEventGetLocation(event);
+ screen->onMouseMove(pos.x, pos.y);
+
+ // The system ignores our cursor-centering calls if
+ // we don't return the event. This should be harmless,
+ // but might register as slight movement to other apps
+ // on the system. It hasn't been a problem before, though.
+ return event;
+ break;
+ case kCGEventScrollWheel:
+ screen->onMouseWheel(screen->mapScrollWheelToBarrier(
+ CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis2)),
+ screen->mapScrollWheelToBarrier(
+ CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1)));
+ break;
+ case kCGEventKeyDown:
+ case kCGEventKeyUp:
+ case kCGEventFlagsChanged:
+ screen->onKey(event);
+ break;
+ case kCGEventTapDisabledByTimeout:
+ // Re-enable our event-tap
+ CGEventTapEnable(screen->m_eventTapPort, true);
+ LOG((CLOG_INFO "quartz event tap was disabled by timeout, re-enabling"));
+ break;
+ case kCGEventTapDisabledByUserInput:
+ LOG((CLOG_ERR "quartz event tap was disabled by user input"));
+ break;
+ case NX_NULLEVENT:
+ break;
+ default:
+ if (type == NX_SYSDEFINED) {
+ if (isMediaKeyEvent (event)) {
+ LOG((CLOG_DEBUG2 "detected media key event"));
+ screen->onMediaKey (event);
+ } else {
+ LOG((CLOG_DEBUG2 "ignoring unknown system defined event"));
+ return event;
+ }
+ break;
+ }
+
+ LOG((CLOG_DEBUG3 "unknown quartz event type: 0x%02x", type));
+ }
+
+ if (screen->m_isOnScreen) {
+ return event;
+ } else {
+ return NULL;
+ }
+}
+
+void
+OSXScreen::MouseButtonState::set(UInt32 button, EMouseButtonState state)
+{
+ bool newState = (state == kMouseButtonDown);
+ m_buttons.set(button, newState);
+}
+
+bool
+OSXScreen::MouseButtonState::any()
+{
+ return m_buttons.any();
+}
+
+void
+OSXScreen::MouseButtonState::reset()
+{
+ m_buttons.reset();
+}
+
+void
+OSXScreen::MouseButtonState::overwrite(UInt32 buttons)
+{
+ m_buttons = std::bitset<NumButtonIDs>(buttons);
+}
+
+bool
+OSXScreen::MouseButtonState::test(UInt32 button) const
+{
+ return m_buttons.test(button);
+}
+
+SInt8
+OSXScreen::MouseButtonState::getFirstButtonDown() const
+{
+ if (m_buttons.any()) {
+ for (unsigned short button = 0; button < m_buttons.size(); button++) {
+ if (m_buttons.test(button)) {
+ return button;
+ }
+ }
+ }
+ return -1;
+}
+
+char*
+OSXScreen::CFStringRefToUTF8String(CFStringRef aString)
+{
+ if (aString == NULL) {
+ return NULL;
+ }
+
+ CFIndex length = CFStringGetLength(aString);
+ CFIndex maxSize = CFStringGetMaximumSizeForEncoding(
+ length,
+ kCFStringEncodingUTF8);
+ char* buffer = (char*)malloc(maxSize);
+ if (CFStringGetCString(aString, buffer, maxSize, kCFStringEncodingUTF8)) {
+ return buffer;
+ }
+ return NULL;
+}
+
+void
+OSXScreen::fakeDraggingFiles(DragFileList fileList)
+{
+ m_fakeDraggingStarted = true;
+ String fileExt;
+ if (fileList.size() == 1) {
+ fileExt = DragInformation::getDragFileExtension(
+ fileList.at(0).getFilename());
+ }
+
+#if defined(MAC_OS_X_VERSION_10_7)
+ fakeDragging(fileExt.c_str(), m_xCursor, m_yCursor);
+#else
+ LOG((CLOG_WARN "drag drop not supported"));
+#endif
+}
+
+String&
+OSXScreen::getDraggingFilename()
+{
+ if (m_draggingStarted) {
+ CFStringRef dragInfo = getDraggedFileURL();
+ char* info = NULL;
+ info = CFStringRefToUTF8String(dragInfo);
+ if (info == NULL) {
+ m_draggingFilename.clear();
+ }
+ else {
+ LOG((CLOG_DEBUG "drag info: %s", info));
+ CFRelease(dragInfo);
+ String fileList(info);
+ m_draggingFilename = fileList;
+ }
+
+ // fake a escape key down and up then left mouse button up
+ fakeKeyDown(kKeyEscape, 8192, 1);
+ fakeKeyUp(1);
+ fakeMouseButton(kButtonLeft, false);
+ }
+ return m_draggingFilename;
+}
+
+void
+OSXScreen::waitForCarbonLoop() const
+{
+#if defined(MAC_OS_X_VERSION_10_7)
+ if (*m_carbonLoopReady) {
+ LOG((CLOG_DEBUG "carbon loop already ready"));
+ return;
+ }
+
+ Lock lock(m_carbonLoopMutex);
+
+ LOG((CLOG_DEBUG "waiting for carbon loop"));
+
+ double timeout = ARCH->time() + kCarbonLoopWaitTimeout;
+ while (!m_carbonLoopReady->wait()) {
+ if (ARCH->time() > timeout) {
+ LOG((CLOG_DEBUG "carbon loop not ready, waiting again"));
+ timeout = ARCH->time() + kCarbonLoopWaitTimeout;
+ }
+ }
+
+ LOG((CLOG_DEBUG "carbon loop ready"));
+#endif
+
+}
+
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+void
+setZeroSuppressionInterval()
+{
+ CGSetLocalEventsSuppressionInterval(0.0);
+}
+
+void
+avoidSupression()
+{
+ // avoid suppression of local hardware events
+ // stkamp@users.sourceforge.net
+ CGSetLocalEventsFilterDuringSupressionState(
+ kCGEventFilterMaskPermitAllEvents,
+ kCGEventSupressionStateSupressionInterval);
+ CGSetLocalEventsFilterDuringSupressionState(
+ (kCGEventFilterMaskPermitLocalKeyboardEvents |
+ kCGEventFilterMaskPermitSystemDefinedEvents),
+ kCGEventSupressionStateRemoteMouseDrag);
+}
+
+void
+logCursorVisibility()
+{
+ // CGCursorIsVisible is probably deprecated because its unreliable.
+ if (!CGCursorIsVisible()) {
+ LOG((CLOG_WARN "cursor may not be visible"));
+ }
+}
+
+void
+avoidHesitatingCursor()
+{
+ // This used to be necessary to get smooth mouse motion on other screens,
+ // but now is just to avoid a hesitating cursor when transitioning to
+ // the primary (this) screen.
+ CGSetLocalEventsSuppressionInterval(0.0001);
+}
+
+#pragma GCC diagnostic error "-Wdeprecated-declarations"
diff --git a/src/lib/platform/OSXScreenSaver.cpp b/src/lib/platform/OSXScreenSaver.cpp
new file mode 100644
index 0000000..a0282d9
--- /dev/null
+++ b/src/lib/platform/OSXScreenSaver.cpp
@@ -0,0 +1,201 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "platform/OSXScreenSaver.h"
+
+#import "platform/OSXScreenSaverUtil.h"
+#import "barrier/IPrimaryScreen.h"
+#import "base/Log.h"
+#import "base/IEventQueue.h"
+
+#import <string.h>
+#import <sys/sysctl.h>
+
+// TODO: upgrade deprecated function usage in these functions.
+void getProcessSerialNumber(const char* name, ProcessSerialNumber& psn);
+bool testProcessName(const char* name, const ProcessSerialNumber& psn);
+
+//
+// OSXScreenSaver
+//
+
+OSXScreenSaver::OSXScreenSaver(IEventQueue* events, void* eventTarget) :
+ m_eventTarget(eventTarget),
+ m_enabled(true),
+ m_events(events)
+{
+ 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;
+
+ if (isActive()) {
+ getProcessSerialNumber("ScreenSaverEngine", m_screenSaverPSN);
+ }
+}
+
+OSXScreenSaver::~OSXScreenSaver()
+{
+ RemoveEventHandler(m_launchTerminationEventHandlerRef);
+// screenSaverUtilReleaseController(m_screenSaverController);
+ screenSaverUtilReleasePool(m_autoReleasePool);
+}
+
+void
+OSXScreenSaver::enable()
+{
+ m_enabled = true;
+ screenSaverUtilEnable(m_screenSaverController);
+}
+
+void
+OSXScreenSaver::disable()
+{
+ m_enabled = false;
+ screenSaverUtilDisable(m_screenSaverController);
+}
+
+void
+OSXScreenSaver::activate()
+{
+ screenSaverUtilActivate(m_screenSaverController);
+}
+
+void
+OSXScreenSaver::deactivate()
+{
+ screenSaverUtilDeactivate(m_screenSaverController, m_enabled);
+}
+
+bool
+OSXScreenSaver::isActive() const
+{
+ return (screenSaverUtilIsActive(m_screenSaverController) != 0);
+}
+
+void
+OSXScreenSaver::processLaunched(ProcessSerialNumber psn)
+{
+ if (testProcessName("ScreenSaverEngine", psn)) {
+ m_screenSaverPSN = psn;
+ LOG((CLOG_DEBUG1 "ScreenSaverEngine launched. Enabled=%d", m_enabled));
+ if (m_enabled) {
+ m_events->addEvent(
+ Event(m_events->forIPrimaryScreen().screensaverActivated(),
+ m_eventTarget));
+ }
+ }
+}
+
+void
+OSXScreenSaver::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) {
+ m_events->addEvent(
+ Event(m_events->forIPrimaryScreen().screensaverDeactivated(),
+ m_eventTarget));
+ }
+
+ m_screenSaverPSN.highLongOfPSN = 0;
+ m_screenSaverPSN.lowLongOfPSN = 0;
+ }
+}
+
+pascal OSStatus
+OSXScreenSaver::launchTerminationCallback(
+ EventHandlerCallRef nextHandler,
+ EventRef theEvent, void* userData)
+{
+ OSStatus result;
+ ProcessSerialNumber psn;
+ EventParamType actualType;
+ ByteCount actualSize;
+
+ result = GetEventParameter(theEvent, kEventParamProcessID,
+ typeProcessSerialNumber, &actualType,
+ sizeof(psn), &actualSize, &psn);
+
+ if ((result == noErr) &&
+ (actualSize > 0) &&
+ (actualType == typeProcessSerialNumber)) {
+ OSXScreenSaver* screenSaver = (OSXScreenSaver*)userData;
+ UInt32 eventKind = GetEventKind(theEvent);
+ if (eventKind == kEventAppLaunched) {
+ screenSaver->processLaunched(psn);
+ }
+ else if (eventKind == kEventAppTerminated) {
+ screenSaver->processTerminated(psn);
+ }
+ }
+ return (CallNextEventHandler(nextHandler, theEvent));
+}
+
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+void
+getProcessSerialNumber(const char* name, ProcessSerialNumber& psn)
+{
+ ProcessInfoRec procInfo;
+ Str31 procName; // pascal string. first byte holds length.
+ memset(&procInfo, 0, sizeof(procInfo));
+ procInfo.processName = procName;
+ procInfo.processInfoLength = sizeof(ProcessInfoRec);
+
+ ProcessSerialNumber checkPsn;
+ OSErr err = GetNextProcess(&checkPsn);
+ while (err == 0) {
+ memset(procName, 0, sizeof(procName));
+ err = GetProcessInformation(&checkPsn, &procInfo);
+ if (err != 0) {
+ break;
+ }
+ if (strcmp(name, (const char*)&procName[1]) == 0) {
+ psn = checkPsn;
+ break;
+ }
+ err = GetNextProcess(&checkPsn);
+ }
+}
+
+bool
+testProcessName(const char* name, const ProcessSerialNumber& psn)
+{
+ CFStringRef processName;
+ OSStatus err = CopyProcessName(&psn, &processName);
+ return (err == 0 && CFEqual(CFSTR("ScreenSaverEngine"), processName));
+}
+
+#pragma GCC diagnostic error "-Wdeprecated-declarations"
diff --git a/src/lib/platform/OSXScreenSaver.h b/src/lib/platform/OSXScreenSaver.h
new file mode 100644
index 0000000..07f2a7b
--- /dev/null
+++ b/src/lib/platform/OSXScreenSaver.h
@@ -0,0 +1,59 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/IScreenSaver.h"
+
+#include <Carbon/Carbon.h>
+
+class IEventQueue;
+
+//! OSX screen saver implementation
+class OSXScreenSaver : public IScreenSaver {
+public:
+ OSXScreenSaver(IEventQueue* events, void* eventTarget);
+ virtual ~OSXScreenSaver();
+
+ // 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;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/platform/OSXScreenSaverControl.h b/src/lib/platform/OSXScreenSaverControl.h
new file mode 100644
index 0000000..76f8875
--- /dev/null
+++ b/src/lib/platform/OSXScreenSaverControl.h
@@ -0,0 +1,54 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2009 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// ScreenSaver.framework private API
+// Class dumping by Alex Harper http://www.ragingmenace.com/
+
+#import <Foundation/NSObject.h>
+
+@protocol ScreenSaverControl
+- (double)screenSaverTimeRemaining;
+- (void)restartForUser:fp12;
+- (void)screenSaverStopNow;
+- (void)screenSaverStartNow;
+- (void)setScreenSaverCanRun:(char)fp12;
+- (BOOL)screenSaverCanRun;
+- (BOOL)screenSaverIsRunning;
+@end
+
+
+@interface ScreenSaverController:NSObject <ScreenSaverControl>
+
++ 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/src/lib/platform/OSXScreenSaverUtil.h b/src/lib/platform/OSXScreenSaverUtil.h
new file mode 100644
index 0000000..045553d
--- /dev/null
+++ b/src/lib/platform/OSXScreenSaverUtil.h
@@ -0,0 +1,40 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "common/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
diff --git a/src/lib/platform/OSXScreenSaverUtil.m b/src/lib/platform/OSXScreenSaverUtil.m
new file mode 100644
index 0000000..6d82f10
--- /dev/null
+++ b/src/lib/platform/OSXScreenSaverUtil.m
@@ -0,0 +1,83 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2004 Chris Schoeneman, Nick Bolton, Sorin Sbarnea
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT 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 "platform/OSXScreenSaverUtil.h"
+
+#import "platform/OSXScreenSaverControl.h"
+
+#import <Foundation/NSAutoreleasePool.h>
+
+//
+// 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/src/lib/platform/OSXUchrKeyResource.cpp b/src/lib/platform/OSXUchrKeyResource.cpp
new file mode 100644
index 0000000..e0230e9
--- /dev/null
+++ b/src/lib/platform/OSXUchrKeyResource.cpp
@@ -0,0 +1,296 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXUchrKeyResource.h"
+
+#include <Carbon/Carbon.h>
+
+//
+// OSXUchrKeyResource
+//
+
+OSXUchrKeyResource::OSXUchrKeyResource(const void* resource,
+ UInt32 keyboardType) :
+ m_m(NULL),
+ m_cti(NULL),
+ m_sdi(NULL),
+ m_sri(NULL),
+ m_st(NULL)
+{
+ m_resource = static_cast<const UCKeyboardLayout*>(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* const base = reinterpret_cast<const UInt8*>(m_resource);
+ m_m = reinterpret_cast<const UCKeyModifiersToTableNum*>(base +
+ th->keyModifiersToTableNumOffset);
+ m_cti = reinterpret_cast<const UCKeyToCharTableIndex*>(base +
+ th->keyToCharTableIndexOffset);
+ m_sdi = reinterpret_cast<const UCKeySequenceDataIndex*>(base +
+ th->keySequenceDataIndexOffset);
+ if (th->keyStateRecordsIndexOffset != 0) {
+ m_sri = reinterpret_cast<const UCKeyStateRecordsIndex*>(base +
+ th->keyStateRecordsIndexOffset);
+ }
+ if (th->keyStateTerminatorsOffset != 0) {
+ m_st = reinterpret_cast<const UCKeyStateTerminators*>(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<const UCKeyOutput*>(base +
+ m_cti->keyToCharTableOffsets[table])[button];
+ if ((c & kUCKeyOutputTestForIndexMask) ==
+ kUCKeyOutputStateIndexMask) {
+ m_spaceOutput = (c & kUCKeyOutputGetIndexMask);
+ break;
+ }
+ }
+ }
+}
+
+bool
+OSXUchrKeyResource::isValid() const
+{
+ return (m_m != NULL);
+}
+
+UInt32
+OSXUchrKeyResource::getNumModifierCombinations() const
+{
+ // only 32 (not 256) because the righthanded modifier bits are ignored
+ return 32;
+}
+
+UInt32
+OSXUchrKeyResource::getNumTables() const
+{
+ return m_cti->keyToCharTableCount;
+}
+
+UInt32
+OSXUchrKeyResource::getNumButtons() const
+{
+ return m_cti->keyToCharTableSize;
+}
+
+UInt32
+OSXUchrKeyResource::getTableForModifier(UInt32 mask) const
+{
+ if (mask >= m_m->modifiersCount) {
+ return m_m->defaultTableNum;
+ }
+ else {
+ return m_m->tableNum[mask];
+ }
+}
+
+KeyID
+OSXUchrKeyResource::getKey(UInt32 table, UInt32 button) const
+{
+ assert(table < getNumTables());
+ assert(button < getNumButtons());
+
+ const UInt8* const base = reinterpret_cast<const UInt8*>(m_resource);
+ const UCKeyOutput* cPtr = reinterpret_cast<const UCKeyOutput*>(base +
+ m_cti->keyToCharTableOffsets[table]);
+
+ const UCKeyOutput c = cPtr[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
+OSXUchrKeyResource::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 = barrier::KeyMap::getDeadKey(*i);
+ }
+
+ return true;
+}
+
+bool
+OSXUchrKeyResource::getKeyRecord(
+ KeySequence& keys, UInt16 index, UInt16& state) const
+{
+ const UInt8* const base = reinterpret_cast<const UInt8*>(m_resource);
+ const UCKeyStateRecord* sr =
+ reinterpret_cast<const UCKeyStateRecord*>(base +
+ m_sri->keyStateRecordOffsets[index]);
+ const UCKeyStateEntryTerminal* kset =
+ reinterpret_cast<const UCKeyStateEntryTerminal*>(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
+OSXUchrKeyResource::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/src/lib/platform/OSXUchrKeyResource.h b/src/lib/platform/OSXUchrKeyResource.h
new file mode 100644
index 0000000..47b63c9
--- /dev/null
+++ b/src/lib/platform/OSXUchrKeyResource.h
@@ -0,0 +1,55 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/KeyState.h"
+#include "platform/IOSXKeyResource.h"
+
+#include <Carbon/Carbon.h>
+
+typedef TISInputSourceRef KeyLayout;
+
+class OSXUchrKeyResource : public IOSXKeyResource {
+public:
+ OSXUchrKeyResource(const void*, UInt32 keyboardType);
+
+ // KeyResource 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<KeyID> 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;
+};
diff --git a/src/lib/platform/XWindowsClipboard.cpp b/src/lib/platform/XWindowsClipboard.cpp
new file mode 100644
index 0000000..8e7d864
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboard.cpp
@@ -0,0 +1,1525 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsClipboard.h"
+
+#include "platform/XWindowsClipboardTextConverter.h"
+#include "platform/XWindowsClipboardUCS2Converter.h"
+#include "platform/XWindowsClipboardUTF8Converter.h"
+#include "platform/XWindowsClipboardHTMLConverter.h"
+#include "platform/XWindowsClipboardBMPConverter.h"
+#include "platform/XWindowsUtil.h"
+#include "mt/Thread.h"
+#include "arch/Arch.h"
+#include "base/Log.h"
+#include "base/Stopwatch.h"
+#include "common/stdvector.h"
+
+#include <cstdio>
+#include <cstring>
+#include <X11/Xatom.h>
+
+//
+// XWindowsClipboard
+//
+
+XWindowsClipboard::XWindowsClipboard(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 XWindowsClipboardHTMLConverter(m_display,
+ "text/html"));
+ m_converters.push_back(new XWindowsClipboardBMPConverter(m_display));
+ m_converters.push_back(new XWindowsClipboardUTF8Converter(m_display,
+ "text/plain;charset=UTF-8"));
+ m_converters.push_back(new XWindowsClipboardUTF8Converter(m_display,
+ "UTF8_STRING"));
+ m_converters.push_back(new XWindowsClipboardUCS2Converter(m_display,
+ "text/plain;charset=ISO-10646-UCS-2"));
+ m_converters.push_back(new XWindowsClipboardUCS2Converter(m_display,
+ "text/unicode"));
+ m_converters.push_back(new XWindowsClipboardTextConverter(m_display,
+ "text/plain"));
+ m_converters.push_back(new XWindowsClipboardTextConverter(m_display,
+ "STRING"));
+
+ // we have no data
+ clearCache();
+}
+
+XWindowsClipboard::~XWindowsClipboard()
+{
+ clearReplies();
+ clearConverters();
+}
+
+void
+XWindowsClipboard::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
+XWindowsClipboard::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, XWindowsUtil::atomToString(m_display, target).c_str(), requestor, XWindowsUtil::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 Reply(requestor, target, time));
+ }
+
+ // send notifications that are pending
+ pushReplies();
+}
+
+bool
+XWindowsClipboard::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
+ String 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 Reply(requestor, target, time,
+ property, data, type, format));
+ return true;
+ }
+ else {
+ // failure
+ LOG((CLOG_DEBUG1 "failed"));
+ insertReply(new Reply(requestor, target, time));
+ return false;
+ }
+}
+
+bool
+XWindowsClipboard::processRequest(Window requestor,
+ ::Time /*time*/, Atom property)
+{
+ ReplyMap::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", XWindowsUtil::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.
+ ReplyList& replies = index->second;
+ for (ReplyList::iterator index2 = replies.begin();
+ index2 != replies.end(); ++index2) {
+ Reply* 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
+XWindowsClipboard::destroyRequest(Window requestor)
+{
+ ReplyMap::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
+XWindowsClipboard::getWindow() const
+{
+ return m_window;
+}
+
+Atom
+XWindowsClipboard::getSelection() const
+{
+ return m_selection;
+}
+
+bool
+XWindowsClipboard::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
+XWindowsClipboard::add(EFormat format, const String& 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
+XWindowsClipboard::open(Time time) const
+{
+ if (m_open) {
+ LOG((CLOG_DEBUG "failed to open clipboard: already opened"));
+ return false;
+ }
+
+ 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
+XWindowsClipboard::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
+XWindowsClipboard::getTime() const
+{
+ checkCache();
+ return m_timeOwned;
+}
+
+bool
+XWindowsClipboard::has(EFormat format) const
+{
+ assert(m_open);
+
+ fillCache();
+ return m_added[format];
+}
+
+String
+XWindowsClipboard::get(EFormat format) const
+{
+ assert(m_open);
+
+ fillCache();
+ return m_data[format];
+}
+
+void
+XWindowsClipboard::clearConverters()
+{
+ for (ConverterList::iterator index = m_converters.begin();
+ index != m_converters.end(); ++index) {
+ delete *index;
+ }
+ m_converters.clear();
+}
+
+IXWindowsClipboardConverter*
+XWindowsClipboard::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", XWindowsUtil::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
+XWindowsClipboard::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
+XWindowsClipboard::clearCache() const
+{
+ const_cast<XWindowsClipboard*>(this)->doClearCache();
+}
+
+void
+XWindowsClipboard::doClearCache()
+{
+ m_checkCache = false;
+ m_cached = false;
+ for (SInt32 index = 0; index < kNumFormats; ++index) {
+ m_data[index] = "";
+ m_added[index] = false;
+ }
+}
+
+void
+XWindowsClipboard::fillCache() const
+{
+ // get the selection data if not already cached
+ checkCache();
+ if (!m_cached) {
+ const_cast<XWindowsClipboard*>(this)->doFillCache();
+ }
+}
+
+void
+XWindowsClipboard::doFillCache()
+{
+ if (m_motif) {
+ motifFillCache();
+ }
+ else {
+ icccmFillCache();
+ }
+ m_checkCache = false;
+ m_cached = true;
+ m_cacheTime = m_timeOwned;
+}
+
+void
+XWindowsClipboard::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;
+ String data;
+ if (!icccmGetSelection(atomTargets, &target, &data) ||
+ (target != m_atomAtom && target != m_atomTargets)) {
+ LOG((CLOG_DEBUG1 "selection doesn't support TARGETS"));
+ data = "";
+ XWindowsUtil::appendAtomData(data, XA_STRING);
+ }
+
+ XWindowsUtil::convertAtomProperty(data);
+ const Atom* targets = reinterpret_cast<const Atom*>(data.data()); // TODO: Safe?
+ const UInt32 numTargets = data.size() / sizeof(Atom);
+ LOG((CLOG_DEBUG " available targets: %s", XWindowsUtil::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;
+ String targetData;
+ if (!icccmGetSelection(target, &actualTarget, &targetData)) {
+ LOG((CLOG_DEBUG1 " no data for target %s", XWindowsUtil::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, XWindowsUtil::atomToString(m_display, target).c_str(), targetData.size(), targetData.size() == 1 ? "byte" : "bytes"));
+ }
+}
+
+bool
+XWindowsClipboard::icccmGetSelection(Atom target,
+ Atom* actualTarget, String* 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", XWindowsUtil::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", XWindowsUtil::atomToString(m_display, target).c_str()));
+ return false;
+ }
+ return true;
+}
+
+IClipboard::Time
+XWindowsClipboard::icccmGetTime() const
+{
+ Atom actualTarget;
+ String data;
+ if (icccmGetSelection(m_atomTimestamp, &actualTarget, &data) &&
+ actualTarget == m_atomInteger) {
+ Time time = *reinterpret_cast<const Time*>(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
+XWindowsClipboard::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 = XWindowsUtil::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
+XWindowsClipboard::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 = XWindowsUtil::getCurrentTime(m_display, m_window);
+ XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time);
+}
+
+bool
+XWindowsClipboard::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;
+ String data;
+ Window root = RootWindow(m_display, DefaultScreen(m_display));
+ if (!XWindowsUtil::getWindowProperty(m_display, root,
+ m_atomMotifClipHeader,
+ &data, &target, &format, False)) {
+ return false;
+ }
+
+ // check the owner window against the current clipboard owner
+ if (data.size() >= sizeof(MotifClipHeader)) {
+ MotifClipHeader header;
+ std::memcpy (&header, data.data(), sizeof(header));
+ if ((header.m_id == kMotifClipHeader) &&
+ (static_cast<Window>(header.m_selectionOwner) == owner)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+XWindowsClipboard::motifFillCache()
+{
+ LOG((CLOG_DEBUG "Motif fill clipboard %d", m_id));
+
+ // get the Motif clipboard header property from the root window
+ Atom target;
+ SInt32 format;
+ String data;
+ Window root = RootWindow(m_display, DefaultScreen(m_display));
+ if (!XWindowsUtil::getWindowProperty(m_display, root,
+ m_atomMotifClipHeader,
+ &data, &target, &format, False)) {
+ return;
+ }
+
+ MotifClipHeader header;
+ if (data.size() < sizeof(header)) { // check that the header is okay
+ return;
+ }
+ std::memcpy (&header, data.data(), sizeof(header));
+ if (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 (!XWindowsUtil::getWindowProperty(m_display, root,
+ atomItem, &data,
+ &target, &format, False)) {
+ return;
+ }
+
+ MotifClipItem item;
+ if (data.size() < sizeof(item)) { // check that the item is okay
+ return;
+ }
+ std::memcpy (&item, data.data(), sizeof(item));
+ if (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<const SInt32*>(item.m_size +
+ static_cast<const char*>(data.data()));
+
+ // get the available formats
+ typedef std::map<Atom, String> MotifFormatMap;
+ MotifFormatMap 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);
+ String data;
+ if (!XWindowsUtil::getWindowProperty(m_display, root,
+ atomFormat, &data,
+ &target, &format, False)) {
+ continue;
+ }
+
+ // check that the format is okay
+ MotifClipFormat motifFormat;
+ if (data.size() < sizeof(motifFormat)) {
+ continue;
+ }
+ std::memcpy (&motifFormat, data.data(), sizeof(motifFormat));
+ if (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
+ MotifFormatMap::const_iterator index2 =
+ motifFormats.find(converter->getAtom());
+ if (index2 == motifFormats.end()) {
+ continue;
+ }
+
+ // get format
+ MotifClipFormat motifFormat;
+ std::memcpy (&motifFormat, index2->second.data(), sizeof(motifFormat));
+ const Atom target = motifFormat.m_type;
+
+ // get the data (finally)
+ Atom actualTarget;
+ String targetData;
+ if (!motifGetSelection(&motifFormat, &actualTarget, &targetData)) {
+ LOG((CLOG_DEBUG1 " no data for target %s", XWindowsUtil::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, XWindowsUtil::atomToString(m_display, target).c_str()));
+ }
+}
+
+bool
+XWindowsClipboard::motifGetSelection(const MotifClipFormat* format,
+ Atom* actualTarget, String* 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 XWindowsUtil::getWindowProperty(m_display, root,
+ target, data,
+ actualTarget, NULL, False);
+}
+
+IClipboard::Time
+XWindowsClipboard::motifGetTime() const
+{
+ return icccmGetTime();
+}
+
+bool
+XWindowsClipboard::insertMultipleReply(Window requestor,
+ ::Time time, Atom property)
+{
+ // get the requested targets
+ Atom target;
+ SInt32 format;
+ String data;
+ if (!XWindowsUtil::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
+ XWindowsUtil::convertAtomProperty(data);
+ const Atom* targets = reinterpret_cast<const Atom*>(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
+ XWindowsUtil::replaceAtomData(data, i, None);
+ changed = true;
+ }
+ }
+
+ // update the targets property if we changed it
+ if (changed) {
+ XWindowsUtil::setWindowProperty(m_display, requestor,
+ property, data.data(), data.size(),
+ target, format);
+ }
+
+ // add reply for MULTIPLE request
+ insertReply(new Reply(requestor, m_atomMultiple,
+ time, property, String(), None, 32));
+
+ return true;
+}
+
+void
+XWindowsClipboard::insertReply(Reply* 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;
+ {
+ XWindowsUtil::ErrorLock 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
+XWindowsClipboard::pushReplies()
+{
+ // send the first reply for each window if that reply hasn't
+ // been sent yet.
+ for (ReplyMap::iterator index = m_replies.begin();
+ index != m_replies.end(); ) {
+ assert(!index->second.empty());
+ ReplyList::iterator listit = index->second.begin();
+ while (listit != index->second.end()) {
+ if (!(*listit)->m_replied)
+ break;
+ ++listit;
+ }
+ if (listit != index->second.end() && !(*listit)->m_replied) {
+ pushReplies(index, index->second, listit);
+ }
+ else {
+ ++index;
+ }
+ }
+}
+
+void
+XWindowsClipboard::pushReplies(ReplyMap::iterator& mapIndex,
+ ReplyList& replies, ReplyList::iterator index)
+{
+ Reply* 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()) {
+ XWindowsUtil::ErrorLock 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
+XWindowsClipboard::sendReply(Reply* 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 (!XWindowsUtil::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 (!XWindowsUtil::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) {
+ XWindowsUtil::ErrorLock 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;
+ XWindowsUtil::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() >= kDEBUG2) {
+ XWindowsUtil::ErrorLock 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;
+ String data;
+ char* name = XGetAtomName(m_display, props[i]);
+ if (!XWindowsUtil::getWindowProperty(m_display,
+ reply->m_requestor,
+ props[i], &data, &target, NULL, False)) {
+ LOG((CLOG_DEBUG2 " %s: <can't read property>", name));
+ }
+ else {
+ // if there are any non-ascii characters in string
+ // then print the binary data.
+ static const char* hex = "0123456789abcdef";
+ for (String::size_type j = 0; j < data.size(); ++j) {
+ if (data[j] < 32 || data[j] > 126) {
+ String 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
+XWindowsClipboard::clearReplies()
+{
+ for (ReplyMap::iterator index = m_replies.begin();
+ index != m_replies.end(); ++index) {
+ clearReplies(index->second);
+ }
+ m_replies.clear();
+ m_eventMasks.clear();
+}
+
+void
+XWindowsClipboard::clearReplies(ReplyList& replies)
+{
+ for (ReplyList::iterator index = replies.begin();
+ index != replies.end(); ++index) {
+ delete *index;
+ }
+ replies.clear();
+}
+
+void
+XWindowsClipboard::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;
+ XWindowsUtil::ErrorLock lock(m_display);
+ XSendEvent(m_display, requestor, False, 0, &event);
+}
+
+bool
+XWindowsClipboard::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 = XWindowsUtil::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
+XWindowsClipboard::getTargetsData(String& data, int* format) const
+{
+ assert(format != NULL);
+
+ // add standard targets
+ XWindowsUtil::appendAtomData(data, m_atomTargets);
+ XWindowsUtil::appendAtomData(data, m_atomMultiple);
+ XWindowsUtil::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()]) {
+ XWindowsUtil::appendAtomData(data, converter->getAtom());
+ }
+ }
+
+ *format = 32;
+ return m_atomAtom;
+}
+
+Atom
+XWindowsClipboard::getTimestampData(String& data, int* format) const
+{
+ assert(format != NULL);
+
+ checkCache();
+ XWindowsUtil::appendTimeData(data, m_timeOwned);
+ *format = 32;
+ return m_atomInteger;
+}
+
+
+//
+// XWindowsClipboard::CICCCMGetClipboard
+//
+
+XWindowsClipboard::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
+}
+
+XWindowsClipboard::CICCCMGetClipboard::~CICCCMGetClipboard()
+{
+ // do nothing
+}
+
+bool
+XWindowsClipboard::CICCCMGetClipboard::readClipboard(Display* display,
+ Atom selection, Atom target, Atom* actualTarget, String* data)
+{
+ assert(actualTarget != NULL);
+ assert(data != NULL);
+
+ LOG((CLOG_DEBUG1 "request selection=%s, target=%s, window=%x", XWindowsUtil::atomToString(display, selection).c_str(), XWindowsUtil::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<XEvent> events;
+ Stopwatch timeout(false); // timer not stopped, not triggered
+ 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 after %fs", m_failed ? "failed" : "succeeded", timeout.getTime()));
+ return !m_failed;
+}
+
+bool
+XWindowsClipboard::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 String::size_type oldSize = m_data->size();
+ if (!XWindowsUtil::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", XWindowsUtil::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", XWindowsUtil::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;
+}
+
+
+//
+// XWindowsClipboard::Reply
+//
+
+XWindowsClipboard::Reply::Reply(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
+}
+
+XWindowsClipboard::Reply::Reply(Window requestor, Atom target, ::Time time,
+ Atom property, const String& 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/src/lib/platform/XWindowsClipboard.h b/src/lib/platform/XWindowsClipboard.h
new file mode 100644
index 0000000..cf20e82
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboard.h
@@ -0,0 +1,378 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/clipboard_types.h"
+#include "barrier/IClipboard.h"
+#include "common/stdmap.h"
+#include "common/stdlist.h"
+#include "common/stdvector.h"
+
+#if X_DISPLAY_MISSING
+# error X11 is required to build barrier
+#else
+# include <X11/Xlib.h>
+#endif
+
+class IXWindowsClipboardConverter;
+
+//! X11 clipboard implementation
+class XWindowsClipboard : public IClipboard {
+public:
+ /*!
+ Use \c window as the window that owns or interacts with the
+ clipboard identified by \c id.
+ */
+ XWindowsClipboard(Display*, Window window, ClipboardID id);
+ virtual ~XWindowsClipboard();
+
+ //! 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 String& data);
+ virtual bool open(Time) const;
+ virtual void close() const;
+ virtual Time getTime() const;
+ virtual bool has(EFormat) const;
+ virtual String 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, String* 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
+ String* 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 MotifClipHeader {
+ 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 MotifClipItem {
+ 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 MotifClipFormat {
+ 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 Reply {
+ public:
+ Reply(Window, Atom target, ::Time);
+ Reply(Window, Atom target, ::Time, Atom property,
+ const String& 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
+ String m_data;
+ Atom m_type;
+ int m_format;
+
+ // index of next byte in m_data to send
+ UInt32 m_ptr;
+ };
+ typedef std::list<Reply*> ReplyList;
+ typedef std::map<Window, ReplyList> ReplyMap;
+ typedef std::map<Window, long> ReplyEventMask;
+
+ // ICCCM interoperability methods
+ void icccmFillCache();
+ bool icccmGetSelection(Atom target,
+ Atom* actualTarget, String* data) const;
+ Time icccmGetTime() const;
+
+ // motif interoperability methods
+ bool motifLockClipboard() const;
+ void motifUnlockClipboard() const;
+ bool motifOwnsClipboard() const;
+ void motifFillCache();
+ bool motifGetSelection(const MotifClipFormat*,
+ Atom* actualTarget, String* data) const;
+ Time motifGetTime() const;
+
+ // reply methods
+ bool insertMultipleReply(Window, ::Time, Atom);
+ void insertReply(Reply*);
+ void pushReplies();
+ void pushReplies(ReplyMap::iterator&,
+ ReplyList&, ReplyList::iterator);
+ bool sendReply(Reply*);
+ void clearReplies();
+ void clearReplies(ReplyList&);
+ void sendNotify(Window requestor, Atom selection,
+ Atom target, Atom property, Time time);
+ bool wasOwnedAtTime(::Time) const;
+
+ // data conversion methods
+ Atom getTargetsData(String&, int* format) const;
+ Atom getTimestampData(String&, int* format) const;
+
+private:
+ typedef std::vector<IXWindowsClipboardConverter*> 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];
+ String m_data[kNumFormats];
+
+ // conversion request replies
+ ReplyMap m_replies;
+ ReplyEventMask 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 String fromIClipboard(const String&) const = 0;
+
+ //! Convert to IClipboard format
+ /*!
+ Convert from the X selection format to the IClipboard format
+ (i.e., the reverse of fromIClipboard()).
+ */
+ virtual String toIClipboard(const String&) const = 0;
+
+ //@}
+};
diff --git a/src/lib/platform/XWindowsClipboardAnyBitmapConverter.cpp b/src/lib/platform/XWindowsClipboardAnyBitmapConverter.cpp
new file mode 100644
index 0000000..493b1e8
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardAnyBitmapConverter.cpp
@@ -0,0 +1,191 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsClipboardAnyBitmapConverter.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<UInt8>(src & 0xffu);
+ dst[1] = static_cast<UInt8>((src >> 8) & 0xffu);
+ dst += 2;
+}
+
+static
+void
+toLE(UInt8*& dst, SInt32 src)
+{
+ dst[0] = static_cast<UInt8>(src & 0xffu);
+ dst[1] = static_cast<UInt8>((src >> 8) & 0xffu);
+ dst[2] = static_cast<UInt8>((src >> 16) & 0xffu);
+ dst[3] = static_cast<UInt8>((src >> 24) & 0xffu);
+ dst += 4;
+}
+
+static
+void
+toLE(UInt8*& dst, UInt32 src)
+{
+ dst[0] = static_cast<UInt8>(src & 0xffu);
+ dst[1] = static_cast<UInt8>((src >> 8) & 0xffu);
+ dst[2] = static_cast<UInt8>((src >> 16) & 0xffu);
+ dst[3] = static_cast<UInt8>((src >> 24) & 0xffu);
+ dst += 4;
+}
+
+static inline
+UInt16
+fromLEU16(const UInt8* data)
+{
+ return static_cast<UInt16>(data[0]) |
+ (static_cast<UInt16>(data[1]) << 8);
+}
+
+static inline
+SInt32
+fromLES32(const UInt8* data)
+{
+ return static_cast<SInt32>(static_cast<UInt32>(data[0]) |
+ (static_cast<UInt32>(data[1]) << 8) |
+ (static_cast<UInt32>(data[2]) << 16) |
+ (static_cast<UInt32>(data[3]) << 24));
+}
+
+static inline
+UInt32
+fromLEU32(const UInt8* data)
+{
+ return static_cast<UInt32>(data[0]) |
+ (static_cast<UInt32>(data[1]) << 8) |
+ (static_cast<UInt32>(data[2]) << 16) |
+ (static_cast<UInt32>(data[3]) << 24);
+}
+
+
+//
+// XWindowsClipboardAnyBitmapConverter
+//
+
+XWindowsClipboardAnyBitmapConverter::XWindowsClipboardAnyBitmapConverter()
+{
+ // do nothing
+}
+
+XWindowsClipboardAnyBitmapConverter::~XWindowsClipboardAnyBitmapConverter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+XWindowsClipboardAnyBitmapConverter::getFormat() const
+{
+ return IClipboard::kBitmap;
+}
+
+int
+XWindowsClipboardAnyBitmapConverter::getDataSize() const
+{
+ return 8;
+}
+
+String
+XWindowsClipboardAnyBitmapConverter::fromIClipboard(const String& bmp) const
+{
+ // fill BMP info header with native-endian data
+ CBMPInfoHeader infoHeader;
+ const UInt8* rawBMPInfoHeader = reinterpret_cast<const UInt8*>(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 String();
+ }
+
+ // 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);
+ }
+}
+
+String
+XWindowsClipboardAnyBitmapConverter::toIClipboard(const String& image) const
+{
+ // convert to raw BMP data
+ UInt32 w, h, depth;
+ String rawBMP = doToIClipboard(image, w, h, depth);
+ if (rawBMP.empty() || w == 0 || h == 0 || (depth != 24 && depth != 32)) {
+ return String();
+ }
+
+ // fill BMP info header with little-endian data
+ UInt8 infoHeader[40];
+ UInt8* dst = infoHeader;
+ toLE(dst, static_cast<UInt32>(40));
+ toLE(dst, static_cast<SInt32>(w));
+ toLE(dst, static_cast<SInt32>(h));
+ toLE(dst, static_cast<UInt16>(1));
+ toLE(dst, static_cast<UInt16>(depth));
+ toLE(dst, static_cast<UInt32>(0)); // BI_RGB
+ toLE(dst, static_cast<UInt32>(image.size()));
+ toLE(dst, static_cast<SInt32>(2834)); // 72 dpi
+ toLE(dst, static_cast<SInt32>(2834)); // 72 dpi
+ toLE(dst, static_cast<UInt32>(0));
+ toLE(dst, static_cast<UInt32>(0));
+
+ // construct image
+ return String(reinterpret_cast<const char*>(infoHeader),
+ sizeof(infoHeader)) + rawBMP;
+}
diff --git a/src/lib/platform/XWindowsClipboardAnyBitmapConverter.h b/src/lib/platform/XWindowsClipboardAnyBitmapConverter.h
new file mode 100644
index 0000000..d723a33
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardAnyBitmapConverter.h
@@ -0,0 +1,60 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/XWindowsClipboard.h"
+
+//! Convert to/from some text encoding
+class XWindowsClipboardAnyBitmapConverter :
+ public IXWindowsClipboardConverter {
+public:
+ XWindowsClipboardAnyBitmapConverter();
+ virtual ~XWindowsClipboardAnyBitmapConverter();
+
+ // IXWindowsClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+ virtual Atom getAtom() const = 0;
+ virtual int getDataSize() const;
+ virtual String fromIClipboard(const String&) const;
+ virtual String toIClipboard(const String&) const;
+
+protected:
+ //! Convert from IClipboard format
+ /*!
+ Convert raw BGR pixel data to another image format.
+ */
+ virtual String doBGRFromIClipboard(const UInt8* bgrData,
+ UInt32 w, UInt32 h) const = 0;
+
+ //! Convert from IClipboard format
+ /*!
+ Convert raw BGRA pixel data to another image format.
+ */
+ virtual String 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 String doToIClipboard(const String&,
+ UInt32& w, UInt32& h, UInt32& depth) const = 0;
+};
diff --git a/src/lib/platform/XWindowsClipboardBMPConverter.cpp b/src/lib/platform/XWindowsClipboardBMPConverter.cpp
new file mode 100644
index 0000000..b4def5b
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardBMPConverter.cpp
@@ -0,0 +1,143 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsClipboardBMPConverter.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<UInt32>(data[0]) |
+ (static_cast<UInt32>(data[1]) << 8) |
+ (static_cast<UInt32>(data[2]) << 16) |
+ (static_cast<UInt32>(data[3]) << 24);
+}
+
+static
+void
+toLE(UInt8*& dst, char src)
+{
+ dst[0] = static_cast<UInt8>(src);
+ dst += 1;
+}
+
+static
+void
+toLE(UInt8*& dst, UInt16 src)
+{
+ dst[0] = static_cast<UInt8>(src & 0xffu);
+ dst[1] = static_cast<UInt8>((src >> 8) & 0xffu);
+ dst += 2;
+}
+
+static
+void
+toLE(UInt8*& dst, UInt32 src)
+{
+ dst[0] = static_cast<UInt8>(src & 0xffu);
+ dst[1] = static_cast<UInt8>((src >> 8) & 0xffu);
+ dst[2] = static_cast<UInt8>((src >> 16) & 0xffu);
+ dst[3] = static_cast<UInt8>((src >> 24) & 0xffu);
+ dst += 4;
+}
+
+//
+// XWindowsClipboardBMPConverter
+//
+
+XWindowsClipboardBMPConverter::XWindowsClipboardBMPConverter(
+ Display* display) :
+ m_atom(XInternAtom(display, "image/bmp", False))
+{
+ // do nothing
+}
+
+XWindowsClipboardBMPConverter::~XWindowsClipboardBMPConverter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+XWindowsClipboardBMPConverter::getFormat() const
+{
+ return IClipboard::kBitmap;
+}
+
+Atom
+XWindowsClipboardBMPConverter::getAtom() const
+{
+ return m_atom;
+}
+
+int
+XWindowsClipboardBMPConverter::getDataSize() const
+{
+ return 8;
+}
+
+String
+XWindowsClipboardBMPConverter::fromIClipboard(const String& bmp) const
+{
+ // create BMP image
+ UInt8 header[14];
+ UInt8* dst = header;
+ toLE(dst, 'B');
+ toLE(dst, 'M');
+ toLE(dst, static_cast<UInt32>(14 + bmp.size()));
+ toLE(dst, static_cast<UInt16>(0));
+ toLE(dst, static_cast<UInt16>(0));
+ toLE(dst, static_cast<UInt32>(14 + 40));
+ return String(reinterpret_cast<const char*>(header), 14) + bmp;
+}
+
+String
+XWindowsClipboardBMPConverter::toIClipboard(const String& bmp) const
+{
+ // make sure data is big enough for a BMP file
+ if (bmp.size() <= 14 + 40) {
+ return String();
+ }
+
+ // check BMP file header
+ const UInt8* rawBMPHeader = reinterpret_cast<const UInt8*>(bmp.data());
+ if (rawBMPHeader[0] != 'B' || rawBMPHeader[1] != 'M') {
+ return String();
+ }
+
+ // 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/src/lib/platform/XWindowsClipboardBMPConverter.h b/src/lib/platform/XWindowsClipboardBMPConverter.h
new file mode 100644
index 0000000..d7813a0
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardBMPConverter.h
@@ -0,0 +1,40 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/XWindowsClipboard.h"
+
+//! Convert to/from some text encoding
+class XWindowsClipboardBMPConverter :
+ public IXWindowsClipboardConverter {
+public:
+ XWindowsClipboardBMPConverter(Display* display);
+ virtual ~XWindowsClipboardBMPConverter();
+
+ // IXWindowsClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+ virtual Atom getAtom() const;
+ virtual int getDataSize() const;
+ virtual String fromIClipboard(const String&) const;
+ virtual String toIClipboard(const String&) const;
+
+private:
+ Atom m_atom;
+};
diff --git a/src/lib/platform/XWindowsClipboardHTMLConverter.cpp b/src/lib/platform/XWindowsClipboardHTMLConverter.cpp
new file mode 100644
index 0000000..32db724
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardHTMLConverter.cpp
@@ -0,0 +1,67 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsClipboardHTMLConverter.h"
+
+#include "base/Unicode.h"
+
+//
+// XWindowsClipboardHTMLConverter
+//
+
+XWindowsClipboardHTMLConverter::XWindowsClipboardHTMLConverter(
+ Display* display, const char* name) :
+ m_atom(XInternAtom(display, name, False))
+{
+ // do nothing
+}
+
+XWindowsClipboardHTMLConverter::~XWindowsClipboardHTMLConverter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+XWindowsClipboardHTMLConverter::getFormat() const
+{
+ return IClipboard::kHTML;
+}
+
+Atom
+XWindowsClipboardHTMLConverter::getAtom() const
+{
+ return m_atom;
+}
+
+int
+XWindowsClipboardHTMLConverter::getDataSize() const
+{
+ return 8;
+}
+
+String
+XWindowsClipboardHTMLConverter::fromIClipboard(const String& data) const
+{
+ return Unicode::UTF8ToUTF16(data);
+}
+
+String
+XWindowsClipboardHTMLConverter::toIClipboard(const String& data) const
+{
+ return Unicode::UTF16ToUTF8(data);
+}
diff --git a/src/lib/platform/XWindowsClipboardHTMLConverter.h b/src/lib/platform/XWindowsClipboardHTMLConverter.h
new file mode 100644
index 0000000..013aa99
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardHTMLConverter.h
@@ -0,0 +1,42 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/XWindowsClipboard.h"
+
+//! Convert to/from HTML encoding
+class XWindowsClipboardHTMLConverter : public IXWindowsClipboardConverter {
+public:
+ /*!
+ \c name is converted to an atom and that is reported by getAtom().
+ */
+ XWindowsClipboardHTMLConverter(Display* display, const char* name);
+ virtual ~XWindowsClipboardHTMLConverter();
+
+ // IXWindowsClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+ virtual Atom getAtom() const;
+ virtual int getDataSize() const;
+ virtual String fromIClipboard(const String&) const;
+ virtual String toIClipboard(const String&) const;
+
+private:
+ Atom m_atom;
+};
diff --git a/src/lib/platform/XWindowsClipboardTextConverter.cpp b/src/lib/platform/XWindowsClipboardTextConverter.cpp
new file mode 100644
index 0000000..71b7a84
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardTextConverter.cpp
@@ -0,0 +1,79 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsClipboardTextConverter.h"
+
+#include "base/Unicode.h"
+
+//
+// XWindowsClipboardTextConverter
+//
+
+XWindowsClipboardTextConverter::XWindowsClipboardTextConverter(
+ Display* display, const char* name) :
+ m_atom(XInternAtom(display, name, False))
+{
+ // do nothing
+}
+
+XWindowsClipboardTextConverter::~XWindowsClipboardTextConverter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+XWindowsClipboardTextConverter::getFormat() const
+{
+ return IClipboard::kText;
+}
+
+Atom
+XWindowsClipboardTextConverter::getAtom() const
+{
+ return m_atom;
+}
+
+int
+XWindowsClipboardTextConverter::getDataSize() const
+{
+ return 8;
+}
+
+String
+XWindowsClipboardTextConverter::fromIClipboard(const String& data) const
+{
+ return Unicode::UTF8ToText(data);
+}
+
+String
+XWindowsClipboardTextConverter::toIClipboard(const String& data) const
+{
+ // convert to UTF-8
+ bool errors;
+ String utf8 = Unicode::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 && Unicode::isUTF8(data)) {
+ return data;
+ }
+
+ return utf8;
+}
diff --git a/src/lib/platform/XWindowsClipboardTextConverter.h b/src/lib/platform/XWindowsClipboardTextConverter.h
new file mode 100644
index 0000000..0e6d598
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardTextConverter.h
@@ -0,0 +1,42 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/XWindowsClipboard.h"
+
+//! Convert to/from locale text encoding
+class XWindowsClipboardTextConverter : public IXWindowsClipboardConverter {
+public:
+ /*!
+ \c name is converted to an atom and that is reported by getAtom().
+ */
+ XWindowsClipboardTextConverter(Display* display, const char* name);
+ virtual ~XWindowsClipboardTextConverter();
+
+ // IXWindowsClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+ virtual Atom getAtom() const;
+ virtual int getDataSize() const;
+ virtual String fromIClipboard(const String&) const;
+ virtual String toIClipboard(const String&) const;
+
+private:
+ Atom m_atom;
+};
diff --git a/src/lib/platform/XWindowsClipboardUCS2Converter.cpp b/src/lib/platform/XWindowsClipboardUCS2Converter.cpp
new file mode 100644
index 0000000..988b909
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardUCS2Converter.cpp
@@ -0,0 +1,67 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsClipboardUCS2Converter.h"
+
+#include "base/Unicode.h"
+
+//
+// XWindowsClipboardUCS2Converter
+//
+
+XWindowsClipboardUCS2Converter::XWindowsClipboardUCS2Converter(
+ Display* display, const char* name) :
+ m_atom(XInternAtom(display, name, False))
+{
+ // do nothing
+}
+
+XWindowsClipboardUCS2Converter::~XWindowsClipboardUCS2Converter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+XWindowsClipboardUCS2Converter::getFormat() const
+{
+ return IClipboard::kText;
+}
+
+Atom
+XWindowsClipboardUCS2Converter::getAtom() const
+{
+ return m_atom;
+}
+
+int
+XWindowsClipboardUCS2Converter::getDataSize() const
+{
+ return 16;
+}
+
+String
+XWindowsClipboardUCS2Converter::fromIClipboard(const String& data) const
+{
+ return Unicode::UTF8ToUCS2(data);
+}
+
+String
+XWindowsClipboardUCS2Converter::toIClipboard(const String& data) const
+{
+ return Unicode::UCS2ToUTF8(data);
+}
diff --git a/src/lib/platform/XWindowsClipboardUCS2Converter.h b/src/lib/platform/XWindowsClipboardUCS2Converter.h
new file mode 100644
index 0000000..6491408
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardUCS2Converter.h
@@ -0,0 +1,42 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/XWindowsClipboard.h"
+
+//! Convert to/from UCS-2 encoding
+class XWindowsClipboardUCS2Converter : public IXWindowsClipboardConverter {
+public:
+ /*!
+ \c name is converted to an atom and that is reported by getAtom().
+ */
+ XWindowsClipboardUCS2Converter(Display* display, const char* name);
+ virtual ~XWindowsClipboardUCS2Converter();
+
+ // IXWindowsClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+ virtual Atom getAtom() const;
+ virtual int getDataSize() const;
+ virtual String fromIClipboard(const String&) const;
+ virtual String toIClipboard(const String&) const;
+
+private:
+ Atom m_atom;
+};
diff --git a/src/lib/platform/XWindowsClipboardUTF8Converter.cpp b/src/lib/platform/XWindowsClipboardUTF8Converter.cpp
new file mode 100644
index 0000000..0e43cce
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardUTF8Converter.cpp
@@ -0,0 +1,65 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsClipboardUTF8Converter.h"
+
+//
+// XWindowsClipboardUTF8Converter
+//
+
+XWindowsClipboardUTF8Converter::XWindowsClipboardUTF8Converter(
+ Display* display, const char* name) :
+ m_atom(XInternAtom(display, name, False))
+{
+ // do nothing
+}
+
+XWindowsClipboardUTF8Converter::~XWindowsClipboardUTF8Converter()
+{
+ // do nothing
+}
+
+IClipboard::EFormat
+XWindowsClipboardUTF8Converter::getFormat() const
+{
+ return IClipboard::kText;
+}
+
+Atom
+XWindowsClipboardUTF8Converter::getAtom() const
+{
+ return m_atom;
+}
+
+int
+XWindowsClipboardUTF8Converter::getDataSize() const
+{
+ return 8;
+}
+
+String
+XWindowsClipboardUTF8Converter::fromIClipboard(const String& data) const
+{
+ return data;
+}
+
+String
+XWindowsClipboardUTF8Converter::toIClipboard(const String& data) const
+{
+ return data;
+}
diff --git a/src/lib/platform/XWindowsClipboardUTF8Converter.h b/src/lib/platform/XWindowsClipboardUTF8Converter.h
new file mode 100644
index 0000000..e3eeca0
--- /dev/null
+++ b/src/lib/platform/XWindowsClipboardUTF8Converter.h
@@ -0,0 +1,42 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "platform/XWindowsClipboard.h"
+
+//! Convert to/from UTF-8 encoding
+class XWindowsClipboardUTF8Converter : public IXWindowsClipboardConverter {
+public:
+ /*!
+ \c name is converted to an atom and that is reported by getAtom().
+ */
+ XWindowsClipboardUTF8Converter(Display* display, const char* name);
+ virtual ~XWindowsClipboardUTF8Converter();
+
+ // IXWindowsClipboardConverter overrides
+ virtual IClipboard::EFormat
+ getFormat() const;
+ virtual Atom getAtom() const;
+ virtual int getDataSize() const;
+ virtual String fromIClipboard(const String&) const;
+ virtual String toIClipboard(const String&) const;
+
+private:
+ Atom m_atom;
+};
diff --git a/src/lib/platform/XWindowsEventQueueBuffer.cpp b/src/lib/platform/XWindowsEventQueueBuffer.cpp
new file mode 100644
index 0000000..234cd62
--- /dev/null
+++ b/src/lib/platform/XWindowsEventQueueBuffer.cpp
@@ -0,0 +1,291 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsEventQueueBuffer.h"
+
+#include "mt/Lock.h"
+#include "mt/Thread.h"
+#include "base/Event.h"
+#include "base/IEventQueue.h"
+
+#include <fcntl.h>
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#if HAVE_POLL
+# include <poll.h>
+#else
+# if HAVE_SYS_SELECT_H
+# include <sys/select.h>
+# endif
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# endif
+# if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+# endif
+#endif
+
+//
+// EventQueueTimer
+//
+
+class EventQueueTimer { };
+
+
+//
+// XWindowsEventQueueBuffer
+//
+
+XWindowsEventQueueBuffer::XWindowsEventQueueBuffer(
+ Display* display, Window window, IEventQueue* events) :
+ m_events(events),
+ m_display(display),
+ m_window(window),
+ m_waiting(false)
+{
+ assert(m_display != NULL);
+ assert(m_window != None);
+
+ m_userEvent = XInternAtom(m_display, "BARRIER_USER_EVENT", False);
+ // set up for pipe hack
+ int result = pipe(m_pipefd);
+ assert(result == 0);
+
+ int pipeflags;
+ pipeflags = fcntl(m_pipefd[0], F_GETFL);
+ fcntl(m_pipefd[0], F_SETFL, pipeflags | O_NONBLOCK);
+ pipeflags = fcntl(m_pipefd[1], F_GETFL);
+ fcntl(m_pipefd[1], F_SETFL, pipeflags | O_NONBLOCK);
+}
+
+XWindowsEventQueueBuffer::~XWindowsEventQueueBuffer()
+{
+ // release pipe hack resources
+ close(m_pipefd[0]);
+ close(m_pipefd[1]);
+}
+
+void
+XWindowsEventQueueBuffer::waitForEvent(double dtimeout)
+{
+ Thread::testCancel();
+
+ // clear out the pipe in preparation for waiting.
+
+ char buf[16];
+ ssize_t read_response = read(m_pipefd[0], buf, 15);
+
+ // with linux automake, warnings are treated as errors by default
+ if (read_response < 0)
+ {
+ // todo: handle read response
+ }
+
+ {
+ Lock lock(&m_mutex);
+ // we're now waiting for events
+ m_waiting = true;
+
+ // push out pending events
+ flush();
+ }
+ // calling flush may have queued up a new event.
+ if (!XWindowsEventQueueBuffer::isEmpty()) {
+ Thread::testCancel();
+ return;
+ }
+
+ // 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[2];
+ pfds[0].fd = ConnectionNumber(m_display);
+ pfds[0].events = POLLIN;
+ pfds[1].fd = m_pipefd[0];
+ pfds[1].events = POLLIN;
+ int timeout = (dtimeout < 0.0) ? -1 :
+ static_cast<int>(1000.0 * dtimeout);
+ int remaining = timeout;
+ int retval = 0;
+#else
+ struct timeval timeout;
+ struct timeval* timeoutPtr;
+ if (dtimeout < 0.0) {
+ timeoutPtr = NULL;
+ }
+ else {
+ timeout.tv_sec = static_cast<int>(dtimeout);
+ timeout.tv_usec = static_cast<int>(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);
+ FD_SET(m_pipefd[0], &rfds);
+ int nfds;
+ if (ConnectionNumber(m_display) > m_pipefd[0]) {
+ nfds = ConnectionNumber(m_display) + 1;
+ }
+ else {
+ nfds = m_pipefd[0] + 1;
+ }
+#endif
+ // It's possible that the X server has queued events locally
+ // in xlib's event buffer and not pushed on to the fd. Hence we
+ // can't simply monitor the fd as we may never be woken up.
+ // ie addEvent calls flush, XFlush may not send via the fd hence
+ // there is an event waiting to be sent but we must exit the poll
+ // before it can.
+ // Instead we poll for a brief period of time (so if events
+ // queued locally in the xlib buffer can be processed)
+ // and continue doing this until timeout is reached.
+ // The human eye can notice 60hz (ansi) which is 16ms, however
+ // we want to give the cpu a chance s owe up this to 25ms
+#define TIMEOUT_DELAY 25
+
+ while (((dtimeout < 0.0) || (remaining > 0)) && QLength(m_display)==0 && retval==0){
+#if HAVE_POLL
+ retval = poll(pfds, 2, TIMEOUT_DELAY); //16ms = 60hz, but we make it > to play nicely with the cpu
+ if (pfds[1].revents & POLLIN) {
+ ssize_t read_response = read(m_pipefd[0], buf, 15);
+
+ // with linux automake, warnings are treated as errors by default
+ if (read_response < 0)
+ {
+ // todo: handle read response
+ }
+
+ }
+#else
+ retval = select(nfds,
+ SELECT_TYPE_ARG234 &rfds,
+ SELECT_TYPE_ARG234 NULL,
+ SELECT_TYPE_ARG234 NULL,
+ SELECT_TYPE_ARG5 TIMEOUT_DELAY);
+ if (FD_SET(m_pipefd[0], &rfds)) {
+ read(m_pipefd[0], buf, 15);
+ }
+#endif
+ remaining-=TIMEOUT_DELAY;
+ }
+
+ {
+ // we're no longer waiting for events
+ Lock lock(&m_mutex);
+ m_waiting = false;
+ }
+
+ Thread::testCancel();
+}
+
+IEventQueueBuffer::Type
+XWindowsEventQueueBuffer::getEvent(Event& event, UInt32& dataID)
+{
+ Lock 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<UInt32>(m_event.xclient.data.l[0]);
+ return kUser;
+ }
+ else {
+ event = Event(Event::kSystem,
+ m_events->getSystemTarget(), &m_event);
+ return kSystem;
+ }
+}
+
+bool
+XWindowsEventQueueBuffer::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<long>(dataID);
+
+ // save the message
+ Lock 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();
+ // Send a character through the round-trip pipe to wake a thread
+ // that is waiting for a ConnectionNumber() socket to be readable.
+ // The flush call can read incoming data from the socket and put
+ // it in Xlib's input buffer. That sneaks it past the other thread.
+ ssize_t write_response = write(m_pipefd[1], "!", 1);
+
+ // with linux automake, warnings are treated as errors by default
+ if (write_response < 0)
+ {
+ // todo: handle read response
+ }
+ }
+
+ return true;
+}
+
+bool
+XWindowsEventQueueBuffer::isEmpty() const
+{
+ Lock lock(&m_mutex);
+ return (XPending(m_display) == 0 );
+}
+
+EventQueueTimer*
+XWindowsEventQueueBuffer::newTimer(double, bool) const
+{
+ return new EventQueueTimer;
+}
+
+void
+XWindowsEventQueueBuffer::deleteTimer(EventQueueTimer* timer) const
+{
+ delete timer;
+}
+
+void
+XWindowsEventQueueBuffer::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/src/lib/platform/XWindowsEventQueueBuffer.h b/src/lib/platform/XWindowsEventQueueBuffer.h
new file mode 100644
index 0000000..07f3b3a
--- /dev/null
+++ b/src/lib/platform/XWindowsEventQueueBuffer.h
@@ -0,0 +1,64 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mt/Mutex.h"
+#include "base/IEventQueueBuffer.h"
+#include "common/stdvector.h"
+
+#if X_DISPLAY_MISSING
+# error X11 is required to build barrier
+#else
+# include <X11/Xlib.h>
+#endif
+
+class IEventQueue;
+
+//! Event queue buffer for X11
+class XWindowsEventQueueBuffer : public IEventQueueBuffer {
+public:
+ XWindowsEventQueueBuffer(Display*, Window, IEventQueue* events);
+ virtual ~XWindowsEventQueueBuffer();
+
+ // IEventQueueBuffer overrides
+ virtual void init() { }
+ virtual void waitForEvent(double timeout);
+ virtual Type getEvent(Event& event, UInt32& dataID);
+ virtual bool addEvent(UInt32 dataID);
+ virtual bool isEmpty() const;
+ virtual EventQueueTimer*
+ newTimer(double duration, bool oneShot) const;
+ virtual void deleteTimer(EventQueueTimer*) const;
+
+private:
+ void flush();
+
+private:
+ typedef std::vector<XEvent> EventList;
+
+ Mutex m_mutex;
+ Display* m_display;
+ Window m_window;
+ Atom m_userEvent;
+ XEvent m_event;
+ EventList m_postedEvents;
+ bool m_waiting;
+ int m_pipefd[2];
+ IEventQueue* m_events;
+};
diff --git a/src/lib/platform/XWindowsKeyState.cpp b/src/lib/platform/XWindowsKeyState.cpp
new file mode 100644
index 0000000..1ca4629
--- /dev/null
+++ b/src/lib/platform/XWindowsKeyState.cpp
@@ -0,0 +1,867 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsKeyState.h"
+
+#include "platform/XWindowsUtil.h"
+#include "base/Log.h"
+#include "base/String.h"
+#include "common/stdmap.h"
+
+#include <cstddef>
+#include <algorithm>
+#if X_DISPLAY_MISSING
+# error X11 is required to build barrier
+#else
+# include <X11/X.h>
+# include <X11/Xutil.h>
+# define XK_MISCELLANY
+# define XK_XKB_KEYS
+# include <X11/keysymdef.h>
+#if HAVE_XKB_EXTENSION
+# include <X11/XKBlib.h>
+#endif
+#endif
+
+static const size_t ModifiersFromXDefaultSize = 32;
+
+XWindowsKeyState::XWindowsKeyState(
+ Display* display, bool useXKB,
+ IEventQueue* events) :
+ KeyState(events),
+ m_display(display),
+ m_modifierFromX(ModifiersFromXDefaultSize)
+{
+ init(display, useXKB);
+}
+
+XWindowsKeyState::XWindowsKeyState(
+ Display* display, bool useXKB,
+ IEventQueue* events, barrier::KeyMap& keyMap) :
+ KeyState(events, keyMap),
+ m_display(display),
+ m_modifierFromX(ModifiersFromXDefaultSize)
+{
+ init(display, useXKB);
+}
+
+XWindowsKeyState::~XWindowsKeyState()
+{
+#if HAVE_XKB_EXTENSION
+ if (m_xkb != NULL) {
+ XkbFreeKeyboard(m_xkb, 0, True);
+ }
+#endif
+}
+
+void
+XWindowsKeyState::init(Display* display, bool useXKB)
+{
+ 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);
+}
+
+void
+XWindowsKeyState::setActiveGroup(SInt32 group)
+{
+ if (group == kGroupPollAndSet) {
+ // we need to set the group to -1 in order for pollActiveGroup() to
+ // actually poll for the group
+ m_group = -1;
+ m_group = pollActiveGroup();
+ }
+ else if (group == kGroupPoll) {
+ m_group = -1;
+ }
+ else {
+ assert(group >= 0);
+ m_group = group;
+ }
+}
+
+void
+XWindowsKeyState::setAutoRepeat(const XKeyboardState& state)
+{
+ m_keyboardState = state;
+}
+
+KeyModifierMask
+XWindowsKeyState::mapModifiersFromX(unsigned int state) const
+{
+ LOG((CLOG_DEBUG2 "mapping state: %i", state));
+ UInt32 offset = 8 * getGroupFromState(state);
+ KeyModifierMask mask = 0;
+ for (int i = 0; i < 8; ++i) {
+ if ((state & (1u << i)) != 0) {
+ LOG((CLOG_DEBUG2 "|= modifier: %i", offset + i));
+ if (offset + i >= m_modifierFromX.size()) {
+ LOG((CLOG_ERR "m_modifierFromX is too small (%d) for the "
+ "requested offset (%d)", m_modifierFromX.size(), offset+i));
+ } else {
+ mask |= m_modifierFromX[offset + i];
+ }
+ }
+ }
+ return mask;
+}
+
+bool
+XWindowsKeyState::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
+XWindowsKeyState::mapKeyToKeycodes(KeyID key, KeycodeList& keycodes) const
+{
+ keycodes.clear();
+ std::pair<KeyToKeyCodeMap::const_iterator,
+ KeyToKeyCodeMap::const_iterator> range =
+ m_keyCodeFromKey.equal_range(key);
+ for (KeyToKeyCodeMap::const_iterator i = range.first;
+ i != range.second; ++i) {
+ keycodes.push_back(i->second);
+ }
+}
+
+bool
+XWindowsKeyState::fakeCtrlAltDel()
+{
+ // pass keys through unchanged
+ return false;
+}
+
+KeyModifierMask
+XWindowsKeyState::pollActiveModifiers() const
+{
+ Window root = DefaultRootWindow(m_display), window;
+ int xRoot, yRoot, xWindow, yWindow;
+ unsigned int state = 0;
+ if (XQueryPointer(m_display, root, &root, &window,
+ &xRoot, &yRoot, &xWindow, &yWindow, &state) == False) {
+ state = 0;
+ }
+ return mapModifiersFromX(state);
+}
+
+SInt32
+XWindowsKeyState::pollActiveGroup() const
+{
+ // fixed condition where any group < -1 would have undetermined behaviour
+ if (m_group >= 0) {
+ return m_group;
+ }
+
+#if HAVE_XKB_EXTENSION
+ if (m_xkb != NULL) {
+ XkbStateRec state;
+ if (XkbGetState(m_display, XkbUseCoreKbd, &state) == Success) {
+ return state.group;
+ }
+ }
+#endif
+ return 0;
+}
+
+void
+XWindowsKeyState::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
+XWindowsKeyState::getKeyMap(barrier::KeyMap& keyMap)
+{
+ // get autorepeat info. we must use the global_auto_repeat told to
+ // us because it may have modified by barrier.
+ 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) {
+ if (XkbGetUpdatedMap(m_display, XkbKeyActionsMask |
+ XkbKeyBehaviorsMask | XkbAllClientInfoMask, m_xkb) == Success) {
+ updateKeysymMapXKB(keyMap);
+ return;
+ }
+ }
+#endif
+ updateKeysymMap(keyMap);
+}
+
+void
+XWindowsKeyState::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 ||
+ (c!=113 && c!=116 && (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) {
+ if (XkbLockGroup(m_display, XkbUseCoreKbd,
+ keystroke.m_data.m_group.m_group) == False) {
+ LOG((CLOG_DEBUG1 "XkbLockGroup request not sent"));
+ }
+ }
+ 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) {
+ if (XkbLockGroup(m_display, XkbUseCoreKbd,
+ getEffectiveGroup(pollActiveGroup(),
+ keystroke.m_data.m_group.m_group)) == False) {
+ LOG((CLOG_DEBUG1 "XkbLockGroup request not sent"));
+ }
+ }
+ else
+#endif
+ {
+ LOG((CLOG_DEBUG1 " ignored"));
+ }
+ }
+ break;
+ }
+ XFlush(m_display);
+}
+
+void
+XWindowsKeyState::updateKeysymMap(barrier::KeyMap& 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.
+ std::fill(m_modifierFromX.begin(), m_modifierFromX.end(), 0);
+ 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<KeyCode, unsigned int> 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
+ barrier::KeyMap::KeyItem item;
+ for (int i = 0; i < numKeycodes; ++i) {
+ KeySym* keysyms = allKeysyms + maxKeysyms * i;
+ KeyCode keycode = static_cast<KeyCode>(i + minKeycode);
+ item.m_button = static_cast<KeyButton>(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 = XWindowsUtil::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 = XWindowsUtil::mapKeySymToKeyID(keysyms[0]);
+ }
+ else {
+ item.m_id = XWindowsUtil::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
+ barrier::KeyMap::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
+XWindowsKeyState::updateKeysymMapXKB(barrier::KeyMap& 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<KeyCode>(i));
+ if (numGroups > maxNumGroups) {
+ maxNumGroups = numGroups;
+ }
+ }
+
+ // prepare map from X modifier to KeyModifierMask
+ std::vector<int> 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.
+ barrier::KeyMap::KeyItem item;
+ for (int i = m_xkb->min_key_code; i <= m_xkb->max_key_code; ++i) {
+ KeyCode keycode = static_cast<KeyCode>(i);
+ item.m_button = static_cast<KeyButton>(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) == True) {
+ 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 =
+ XWindowsUtil::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 = XWindowsUtil::mapKeySymToKeyID(lKeysym);
+ KeyID uKeyID = XWindowsUtil::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 = XWindowsUtil::mapKeySymToKeyID(keysym);
+ keyMap.addKeyEntry(item);
+ if (group == 0) {
+ m_keyCodeFromKey.insert(std::make_pair(item.m_id, keycode));
+ }
+ }
+ }
+ }
+
+ // change all modifier masks to barrier masks from X masks
+ keyMap.foreachKey(&XWindowsKeyState::remapKeyModifiers, this);
+
+ // allow composition across groups
+ keyMap.allowGroupSwitchDuringCompose();
+}
+#endif
+
+void
+XWindowsKeyState::remapKeyModifiers(KeyID id, SInt32 group,
+ barrier::KeyMap::KeyItem& item, void* vself)
+{
+ XWindowsKeyState* self = static_cast<XWindowsKeyState*>(vself);
+ item.m_required =
+ self->mapModifiersFromX(XkbBuildCoreState(item.m_required, group));
+ item.m_sensitive =
+ self->mapModifiersFromX(XkbBuildCoreState(item.m_sensitive, group));
+}
+
+bool
+XWindowsKeyState::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<KeyCode>(i);
+ if (XkbKeyHasActions(m_xkb, keycode) == True) {
+ // 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
+XWindowsKeyState::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
+XWindowsKeyState::getGroupFromState(unsigned int state) const
+{
+#if HAVE_XKB_EXTENSION
+ if (m_xkb != NULL) {
+ return XkbGroupForCoreState(state);
+ }
+#endif
+ return 0;
+}
diff --git a/src/lib/platform/XWindowsKeyState.h b/src/lib/platform/XWindowsKeyState.h
new file mode 100644
index 0000000..f3c0a1e
--- /dev/null
+++ b/src/lib/platform/XWindowsKeyState.h
@@ -0,0 +1,174 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/KeyState.h"
+#include "common/stdmap.h"
+#include "common/stdvector.h"
+
+#if X_DISPLAY_MISSING
+# error X11 is required to build barrier
+#else
+# include <X11/Xlib.h>
+# if HAVE_X11_EXTENSIONS_XTEST_H
+# include <X11/extensions/XTest.h>
+# else
+# error The XTest extension is required to build barrier
+# endif
+# if HAVE_XKB_EXTENSION
+# include <X11/extensions/XKBstr.h>
+# endif
+#endif
+
+class IEventQueue;
+
+//! X Windows key state
+/*!
+A key state for X Windows.
+*/
+class XWindowsKeyState : public KeyState {
+public:
+ typedef std::vector<int> KeycodeList;
+ enum {
+ kGroupPoll = -1,
+ kGroupPollAndSet = -2
+ };
+
+ XWindowsKeyState(Display*, bool useXKB, IEventQueue* events);
+ XWindowsKeyState(Display*, bool useXKB,
+ IEventQueue* events, barrier::KeyMap& keyMap);
+ ~XWindowsKeyState();
+
+ //! @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 barrier mask
+ /*!
+ Returns the barrier modifier mask corresponding to the X modifier
+ mask in \p state.
+ */
+ KeyModifierMask mapModifiersFromX(unsigned int state) const;
+
+ //! Convert barrier modifier mask to X mask
+ /*!
+ Converts the barrier 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 barrier key to all corresponding X keycodes
+ /*!
+ Converts the barrier key \p key to all of the keycodes that map to
+ that key.
+ */
+ void mapKeyToKeycodes(KeyID key,
+ KeycodeList& keycodes) const;
+
+ //@}
+
+ // IKeyState overrides
+ virtual bool fakeCtrlAltDel();
+ virtual KeyModifierMask
+ pollActiveModifiers() const;
+ virtual SInt32 pollActiveGroup() const;
+ virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const;
+
+protected:
+ // KeyState overrides
+ virtual void getKeyMap(barrier::KeyMap& keyMap);
+ virtual void fakeKey(const Keystroke& keystroke);
+
+private:
+ void init(Display* display, bool useXKB);
+ void updateKeysymMap(barrier::KeyMap&);
+ void updateKeysymMapXKB(barrier::KeyMap&);
+ bool hasModifiersXKB() const;
+ int getEffectiveGroup(KeyCode, int group) const;
+ UInt32 getGroupFromState(unsigned int state) const;
+
+ static void remapKeyModifiers(KeyID, SInt32,
+ barrier::KeyMap::KeyItem&, void*);
+
+private:
+ struct XKBModifierInfo {
+ public:
+ unsigned char m_level;
+ UInt32 m_mask;
+ bool m_lock;
+ };
+
+#ifdef TEST_ENV
+public: // yuck
+#endif
+ typedef std::vector<KeyModifierMask> KeyModifierMaskList;
+
+private:
+ typedef std::map<KeyModifierMask, unsigned int> KeyModifierToXMask;
+ typedef std::multimap<KeyID, KeyCode> KeyToKeyCodeMap;
+ typedef std::map<KeyCode, unsigned int> NonXKBModifierMap;
+ typedef std::map<UInt32, XKBModifierInfo> 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 barrier modifier (mask) mapping
+ KeyModifierMaskList m_modifierFromX;
+
+ // barrier 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;
+
+#ifdef TEST_ENV
+public:
+ SInt32 group() const { return m_group; }
+ void group(const SInt32& group) { m_group = group; }
+ KeyModifierMaskList modifierFromX() const { return m_modifierFromX; }
+#endif
+};
diff --git a/src/lib/platform/XWindowsScreen.cpp b/src/lib/platform/XWindowsScreen.cpp
new file mode 100644
index 0000000..581c911
--- /dev/null
+++ b/src/lib/platform/XWindowsScreen.cpp
@@ -0,0 +1,2096 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsScreen.h"
+
+#include "platform/XWindowsClipboard.h"
+#include "platform/XWindowsEventQueueBuffer.h"
+#include "platform/XWindowsKeyState.h"
+#include "platform/XWindowsScreenSaver.h"
+#include "platform/XWindowsUtil.h"
+#include "barrier/Clipboard.h"
+#include "barrier/KeyMap.h"
+#include "barrier/XScreen.h"
+#include "arch/XArch.h"
+#include "arch/Arch.h"
+#include "base/Log.h"
+#include "base/Stopwatch.h"
+#include "base/String.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+#include <cstring>
+#include <cstdlib>
+#include <algorithm>
+#if X_DISPLAY_MISSING
+# error X11 is required to build barrier
+#else
+# include <X11/X.h>
+# include <X11/Xutil.h>
+# define XK_MISCELLANY
+# define XK_XKB_KEYS
+# include <X11/keysymdef.h>
+# if HAVE_X11_EXTENSIONS_DPMS_H
+ extern "C" {
+# include <X11/extensions/dpms.h>
+ }
+# endif
+# if HAVE_X11_EXTENSIONS_XTEST_H
+# include <X11/extensions/XTest.h>
+# else
+# error The XTest extension is required to build barrier
+# endif
+# if HAVE_X11_EXTENSIONS_XINERAMA_H
+ // Xinerama.h may lack extern "C" for inclusion by C++
+ extern "C" {
+# include <X11/extensions/Xinerama.h>
+ }
+# endif
+# if HAVE_X11_EXTENSIONS_XRANDR_H
+# include <X11/extensions/Xrandr.h>
+# endif
+# if HAVE_XKB_EXTENSION
+# include <X11/XKBlib.h>
+# endif
+# ifdef HAVE_XI2
+# include <X11/extensions/XInput2.h>
+# endif
+#endif
+
+static int xi_opcode;
+
+//
+// XWindowsScreen
+//
+
+// NOTE -- the X display is shared among several objects but is owned
+// by the XWindowsScreen. 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.
+
+XWindowsScreen* XWindowsScreen::s_screen = NULL;
+
+XWindowsScreen::XWindowsScreen(
+ const char* displayName,
+ bool isPrimary,
+ bool disableXInitThreads,
+ int mouseScrollDelta,
+ IEventQueue* events) :
+ m_isPrimary(isPrimary),
+ m_mouseScrollDelta(mouseScrollDelta),
+ 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_preserveFocus(false),
+ m_xkb(false),
+ m_xi2detected(false),
+ m_xrandr(false),
+ m_events(events),
+ PlatformScreen(events)
+{
+ assert(s_screen == NULL);
+
+ if (mouseScrollDelta==0) m_mouseScrollDelta=120;
+ s_screen = this;
+
+ if (!disableXInitThreads) {
+ // initializes Xlib support for concurrent threads.
+ if (XInitThreads() == 0)
+ throw XArch("XInitThreads() returned zero");
+ } else {
+ LOG((CLOG_DEBUG "skipping XInitThreads()"));
+ }
+
+ // set the X I/O error handler so we catch the display disconnecting
+ XSetIOErrorHandler(&XWindowsScreen::ioErrorHandler);
+
+ try {
+ m_display = openDisplay(displayName);
+ m_root = DefaultRootWindow(m_display);
+ saveShape();
+ m_window = openWindow();
+ m_screensaver = new XWindowsScreenSaver(m_display,
+ m_window, getEventTarget(), events);
+ m_keyState = new XWindowsKeyState(m_display, m_xkb, events, m_keyMap);
+ 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) {
+#ifdef HAVE_XI2
+ m_xi2detected = detectXI2();
+ if (m_xi2detected) {
+ selectXIRawMotion();
+ } else
+#endif
+ {
+ // 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 XWindowsClipboard(m_display, m_window, id);
+ }
+
+ // install event handlers
+ m_events->adoptHandler(Event::kSystem, m_events->getSystemTarget(),
+ new TMethodEventJob<XWindowsScreen>(this,
+ &XWindowsScreen::handleSystemEvent));
+
+ // install the platform event queue
+ m_events->adoptBuffer(new XWindowsEventQueueBuffer(
+ m_display, m_window, m_events));
+}
+
+XWindowsScreen::~XWindowsScreen()
+{
+ assert(s_screen != NULL);
+ assert(m_display != NULL);
+
+ m_events->adoptBuffer(NULL);
+ m_events->removeHandler(Event::kSystem, m_events->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
+XWindowsScreen::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
+XWindowsScreen::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
+XWindowsScreen::enter()
+{
+ screensaver(false);
+
+ // 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
+ XWindowsUtil::ErrorLock lock(m_display);
+ XSetInputFocus(m_display, m_lastFocus, m_lastFocusRevert, CurrentTime);
+ }
+
+ #if HAVE_X11_EXTENSIONS_DPMS_H
+ // Force the DPMS to turn screen back on since we don't
+ // actually cause physical hardware input to trigger it
+ int dummy;
+ CARD16 powerlevel;
+ BOOL enabled;
+ if (DPMSQueryExtension(m_display, &dummy, &dummy) &&
+ DPMSCapable(m_display) &&
+ DPMSInfo(m_display, &powerlevel, &enabled))
+ {
+ if (enabled && powerlevel != DPMSModeOn)
+ DPMSForceLevel(m_display, DPMSModeOn);
+ }
+ #endif
+
+ // 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
+XWindowsScreen::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
+ if (m_isPrimary || !m_preserveFocus) {
+ 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
+XWindowsScreen::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 = XWindowsUtil::getCurrentTime(
+ m_display, m_clipboard[id]->getWindow());
+
+ if (clipboard != NULL) {
+ // save clipboard data
+ return Clipboard::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
+XWindowsScreen::checkClipboards()
+{
+ // do nothing, we're always up to date
+}
+
+void
+XWindowsScreen::openScreensaver(bool notify)
+{
+ m_screensaverNotify = notify;
+ if (!m_screensaverNotify) {
+ m_screensaver->disable();
+ }
+}
+
+void
+XWindowsScreen::closeScreensaver()
+{
+ if (!m_screensaverNotify) {
+ m_screensaver->enable();
+ }
+}
+
+void
+XWindowsScreen::screensaver(bool activate)
+{
+ if (activate) {
+ m_screensaver->activate();
+ }
+ else {
+ m_screensaver->deactivate();
+ }
+}
+
+void
+XWindowsScreen::resetOptions()
+{
+ m_xtestIsXineramaUnaware = true;
+ m_preserveFocus = false;
+}
+
+void
+XWindowsScreen::setOptions(const OptionsList& 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"));
+ }
+ else if (options[i] == kOptionScreenPreserveFocus) {
+ m_preserveFocus = (options[i + 1] != 0);
+ LOG((CLOG_DEBUG1 "Preserve Focus = %s", m_preserveFocus ? "true" : "false"));
+ }
+ }
+}
+
+void
+XWindowsScreen::setSequenceNumber(UInt32 seqNum)
+{
+ m_sequenceNumber = seqNum;
+}
+
+bool
+XWindowsScreen::isPrimary() const
+{
+ return m_isPrimary;
+}
+
+void*
+XWindowsScreen::getEventTarget() const
+{
+ return const_cast<XWindowsScreen*>(this);
+}
+
+bool
+XWindowsScreen::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 = XWindowsUtil::getCurrentTime(
+ m_display, m_clipboard[id]->getWindow());
+
+ // copy the clipboard
+ return Clipboard::copy(clipboard, m_clipboard[id], timestamp);
+}
+
+void
+XWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
+{
+ x = m_x;
+ y = m_y;
+ w = m_w;
+ h = m_h;
+}
+
+void
+XWindowsScreen::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
+XWindowsScreen::reconfigure(UInt32)
+{
+ // do nothing
+}
+
+void
+XWindowsScreen::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
+XWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask)
+{
+ // only allow certain modifiers
+ if ((mask & ~(KeyModifierShift | KeyModifierControl |
+ KeyModifierAlt | KeyModifierSuper)) != 0) {
+ LOG((CLOG_DEBUG "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_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask));
+ return 0;
+ }
+ XWindowsKeyState::KeycodeList keycodes;
+ m_keyState->mapKeyToKeycodes(key, keycodes);
+ if (key != kKeyNone && keycodes.empty()) {
+ // can't map key
+ LOG((CLOG_DEBUG "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;
+ {
+ XWindowsUtil::ErrorLock 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[HotKeyItem(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 (XWindowsKeyState::KeycodeList::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[HotKeyItem(*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(HotKeyItem(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)", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask));
+ return 0;
+ }
+
+ LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask, id));
+ return id;
+}
+
+void
+XWindowsScreen::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;
+ {
+ XWindowsUtil::ErrorLock 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(HotKeyItem(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
+XWindowsScreen::fakeInputBegin()
+{
+ // FIXME -- not implemented
+}
+
+void
+XWindowsScreen::fakeInputEnd()
+{
+ // FIXME -- not implemented
+}
+
+SInt32
+XWindowsScreen::getJumpZoneSize() const
+{
+ return 1;
+}
+
+bool
+XWindowsScreen::isAnyMouseButtonDown(UInt32& buttonID) 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
+XWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const
+{
+ x = m_xCenter;
+ y = m_yCenter;
+}
+
+void
+XWindowsScreen::fakeMouseButton(ButtonID button, bool press)
+{
+ const unsigned int xButton = mapButtonToX(button);
+ if (xButton > 0 && xButton < 11) {
+ XTestFakeButtonEvent(m_display, xButton,
+ press ? True : False, CurrentTime);
+ XFlush(m_display);
+ }
+}
+
+void
+XWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y)
+{
+ 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
+XWindowsScreen::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
+XWindowsScreen::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<ButtonID>(
+ (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;
+ }
+
+ if (yDelta < m_mouseScrollDelta) {
+ LOG((CLOG_WARN "Wheel scroll delta (%d) smaller than threshold (%d)", yDelta, m_mouseScrollDelta));
+ }
+
+ // send as many clicks as necessary
+ for (; yDelta >= m_mouseScrollDelta; yDelta -= m_mouseScrollDelta) {
+ XTestFakeButtonEvent(m_display, xButton, True, CurrentTime);
+ XTestFakeButtonEvent(m_display, xButton, False, CurrentTime);
+ }
+ XFlush(m_display);
+}
+
+Display*
+XWindowsScreen::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
+
+#if HAVE_X11_EXTENSIONS_XRANDR_H
+ // query for XRandR extension
+ int dummyError;
+ m_xrandr = XRRQueryExtension(display, &m_xrandrEventBase, &dummyError);
+ if (m_xrandr) {
+ // enable XRRScreenChangeNotifyEvent
+ XRRSelectInput(display, DefaultRootWindow(display), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask);
+ }
+#endif
+
+ return display;
+}
+
+void
+XWindowsScreen::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 barrier 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
+XWindowsScreen::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
+XWindowsScreen::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. barrier 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
+XWindowsScreen::sendEvent(Event::Type type, void* data)
+{
+ m_events->addEvent(Event(type, getEventTarget(), data));
+}
+
+void
+XWindowsScreen::sendClipboardEvent(Event::Type type, ClipboardID id)
+{
+ ClipboardInfo* info = (ClipboardInfo*)malloc(sizeof(ClipboardInfo));
+ info->m_id = id;
+ info->m_sequenceNumber = m_sequenceNumber;
+ sendEvent(type, info);
+}
+
+IKeyState*
+XWindowsScreen::getKeyState() const
+{
+ return m_keyState;
+}
+
+Bool
+XWindowsScreen::findKeyEvent(Display*, XEvent* xevent, XPointer arg)
+{
+ KeyEventFilter* filter = reinterpret_cast<KeyEventFilter*>(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
+XWindowsScreen::handleSystemEvent(const Event& event, void*)
+{
+ XEvent* xevent = static_cast<XEvent*>(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.
+ KeyEventFilter 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,
+ &XWindowsScreen::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, DefaultRootWindow(m_display))) {
+ 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;
+ }
+
+#ifdef HAVE_XI2
+ if (m_xi2detected) {
+ // Process RawMotion
+ XGenericEventCookie *cookie = (XGenericEventCookie*)&xevent->xcookie;
+ if (XGetEventData(m_display, cookie) &&
+ cookie->type == GenericEvent &&
+ cookie->extension == xi_opcode) {
+ if (cookie->evtype == XI_RawMotion) {
+ // Get current pointer's position
+ Window root, child;
+ XMotionEvent xmotion;
+ xmotion.type = MotionNotify;
+ xmotion.send_event = False; // Raw motion
+ xmotion.display = m_display;
+ xmotion.window = m_window;
+ /* xmotion's time, state and is_hint are not used */
+ unsigned int msk;
+ xmotion.same_screen = XQueryPointer(
+ m_display, m_root, &xmotion.root, &xmotion.subwindow,
+ &xmotion.x_root,
+ &xmotion.y_root,
+ &xmotion.x,
+ &xmotion.y,
+ &msk);
+ onMouseMove(xmotion);
+ XFreeEventData(m_display, cookie);
+ return;
+ }
+ XFreeEventData(m_display, cookie);
+ }
+ }
+#endif
+
+ // handle the event ourself
+ switch (xevent->type) {
+ case CreateNotify:
+ if (m_isPrimary && !m_xi2detected) {
+ // 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) {
+ m_clipboard[id]->lost(xevent->xselectionclear.time);
+ sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), 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<XkbEvent*>(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
+
+#if HAVE_X11_EXTENSIONS_XRANDR_H
+ if (m_xrandr) {
+ if (xevent->type == m_xrandrEventBase + RRScreenChangeNotify ||
+ (xevent->type == m_xrandrEventBase + RRNotify &&
+ reinterpret_cast<XRRNotifyEvent *>(xevent)->subtype == RRNotify_CrtcChange)) {
+ LOG((CLOG_INFO "XRRScreenChangeNotifyEvent or RRNotify_CrtcChange received"));
+
+ // we're required to call back into XLib so XLib can update its internal state
+ XRRUpdateConfiguration(xevent);
+
+ // requery/recalculate the screen shape
+ saveShape();
+
+ // we need to resize m_window, otherwise we'll get a weird problem where moving
+ // off the server onto the client causes the pointer to warp to the
+ // center of the server (so you can't move the pointer off the server)
+ if (m_isPrimary) {
+ XMoveWindow(m_display, m_window, m_x, m_y);
+ XResizeWindow(m_display, m_window, m_w, m_h);
+ }
+
+ sendEvent(m_events->forIScreen().shapeChanged());
+ }
+ }
+#endif
+
+ break;
+ }
+}
+
+void
+XWindowsScreen::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<KeyButton>(xkey.keycode);
+ if (keycode == 0) {
+ isFake = true;
+ keycode = static_cast<KeyButton>(m_lastKeycode);
+ if (keycode == 0) {
+ // no keycode
+ LOG((CLOG_DEBUG1 "event: KeyPress 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);
+ }
+ }
+ else {
+ LOG((CLOG_DEBUG1 "can't map keycode to key id"));
+ }
+}
+
+void
+XWindowsScreen::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<KeyButton>(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
+XWindowsScreen::onHotKey(XKeyEvent& xkey, bool isRepeat)
+{
+ // find the hot key id
+ HotKeyToIDMap::const_iterator i =
+ m_hotKeyToIDMap.find(HotKeyItem(xkey.keycode, xkey.state));
+ if (i == m_hotKeyToIDMap.end()) {
+ return false;
+ }
+
+ // find what kind of event
+ Event::Type type;
+ if (xkey.type == KeyPress) {
+ type = m_events->forIPrimaryScreen().hotKeyDown();
+ }
+ else if (xkey.type == KeyRelease) {
+ type = m_events->forIPrimaryScreen().hotKeyUp();
+ }
+ else {
+ return false;
+ }
+
+ // generate event (ignore key repeats)
+ if (!isRepeat) {
+ m_events->addEvent(Event(type, getEventTarget(),
+ HotKeyInfo::alloc(i->second)));
+ }
+ return true;
+}
+
+void
+XWindowsScreen::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(m_events->forIPrimaryScreen().buttonDown(), ButtonInfo::alloc(button, mask));
+ }
+}
+
+void
+XWindowsScreen::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(m_events->forIPrimaryScreen().buttonUp(), ButtonInfo::alloc(button, mask));
+ }
+ else if (xbutton.button == 4) {
+ // wheel forward (away from user)
+ sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(0, 120));
+ }
+ else if (xbutton.button == 5) {
+ // wheel backward (toward user)
+ sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(0, -120));
+ }
+ // XXX -- support x-axis scrolling
+}
+
+void
+XWindowsScreen::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;
+ char cntr = 0;
+ do {
+ XMaskEvent(m_display, PointerMotionMask, &xevent);
+ if (cntr++ > 10) {
+ LOG((CLOG_WARN "too many discarded events! %d", cntr));
+ break;
+ }
+ } while (!xevent.xany.send_event);
+ cntr = 0;
+ }
+ else if (m_isOnScreen) {
+ // motion on primary screen
+ sendEvent(m_events->forIPrimaryScreen().motionOnPrimary(),
+ MotionInfo::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(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(x, y));
+ }
+ }
+}
+
+Cursor
+XWindowsScreen::createBlankCursor() const
+{
+ // this seems just a bit more complicated than really necessary
+
+ // get the closet cursor size to 1x1
+ unsigned int w = 0, h = 0;
+ XQueryBestCursor(m_display, m_root, 1, 1, &w, &h);
+ w = std::max(1u, w);
+ h = std::max(1u, 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
+XWindowsScreen::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
+XWindowsScreen::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
+XWindowsScreen::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
+XWindowsScreen::onError()
+{
+ // prevent further access to the X display
+ m_events->adoptBuffer(NULL);
+ m_screensaver->destroy();
+ m_screensaver = NULL;
+ m_display = NULL;
+
+ // notify of failure
+ sendEvent(m_events->forIScreen().error(), 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
+XWindowsScreen::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
+XWindowsScreen::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.
+ XWindowsUtil::ErrorLock lock(m_display);
+
+ // adjust event masks
+ doSelectEvents(w);
+}
+
+void
+XWindowsScreen::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.
+
+ // we don't want to adjust our grab window
+ if (w == m_window) {
+ return;
+ }
+
+ // X11 has a design flaw. If *no* client selected PointerMotionMask for
+ // a window, motion events will be delivered to that window's parent.
+ // If *any* client, not necessarily the owner, selects PointerMotionMask
+ // on such a window, X will stop propagating motion events to its
+ // parent. This breaks applications that rely on event propagation
+ // behavior.
+ //
+ // Avoid selecting PointerMotionMask unless some other client selected
+ // it already.
+ long mask = SubstructureNotifyMask;
+ XWindowAttributes attr;
+ XGetWindowAttributes(m_display, w, &attr);
+ if ((attr.all_event_masks & PointerMotionMask) == PointerMotionMask) {
+ mask |= PointerMotionMask;
+ }
+
+ // 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, mask);
+
+ // 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
+XWindowsScreen::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);
+ }
+
+ LOG((CLOG_DEBUG2 "mapped code=%d to keysym=0x%04x", event->keycode, keysym));
+
+ // convert key
+ KeyID result = XWindowsUtil::mapKeySymToKeyID(keysym);
+ LOG((CLOG_DEBUG2 "mapped keysym=0x%04x to keyID=%d", keysym, result));
+ return result;
+}
+
+ButtonID
+XWindowsScreen::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<ButtonID>(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<ButtonID>(button - 2);
+ }
+
+ // unknown button
+ else {
+ return kButtonNone;
+ }
+}
+
+unsigned int
+XWindowsScreen::mapButtonToX(ButtonID id) const
+{
+ // map button -1 to button 4 (+wheel)
+ if (id == static_cast<ButtonID>(-1)) {
+ id = 4;
+ }
+
+ // map button -2 to button 5 (-wheel)
+ else if (id == static_cast<ButtonID>(-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<unsigned int>(id);
+}
+
+void
+XWindowsScreen::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
+XWindowsScreen::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
+XWindowsScreen::grabMouseAndKeyboard()
+{
+ unsigned int event_mask = ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask;
+
+ // 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;
+ Stopwatch 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 --- use event_mask to get EnterNotify, LeaveNotify events
+ result = XGrabPointer(m_display, m_window, False, event_mask,
+ 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
+XWindowsScreen::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();
+}
+
+
+//
+// XWindowsScreen::HotKeyItem
+//
+
+XWindowsScreen::HotKeyItem::HotKeyItem(int keycode, unsigned int mask) :
+ m_keycode(keycode),
+ m_mask(mask)
+{
+ // do nothing
+}
+
+bool
+XWindowsScreen::HotKeyItem::operator<(const HotKeyItem& x) const
+{
+ return (m_keycode < x.m_keycode ||
+ (m_keycode == x.m_keycode && m_mask < x.m_mask));
+}
+
+bool
+XWindowsScreen::detectXI2()
+{
+ int event, error;
+ return XQueryExtension(m_display,
+ "XInputExtension", &xi_opcode, &event, &error);
+}
+
+#ifdef HAVE_XI2
+void
+XWindowsScreen::selectXIRawMotion()
+{
+ XIEventMask mask;
+
+ mask.deviceid = XIAllDevices;
+ mask.mask_len = XIMaskLen(XI_RawMotion);
+ mask.mask = (unsigned char*)calloc(mask.mask_len, sizeof(char));
+ mask.deviceid = XIAllMasterDevices;
+ memset(mask.mask, 0, 2);
+ XISetMask(mask.mask, XI_RawKeyRelease);
+ XISetMask(mask.mask, XI_RawMotion);
+ XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1);
+ free(mask.mask);
+}
+#endif
diff --git a/src/lib/platform/XWindowsScreen.h b/src/lib/platform/XWindowsScreen.h
new file mode 100644
index 0000000..35f9368
--- /dev/null
+++ b/src/lib/platform/XWindowsScreen.h
@@ -0,0 +1,252 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/PlatformScreen.h"
+#include "barrier/KeyMap.h"
+#include "common/stdset.h"
+#include "common/stdvector.h"
+
+#if X_DISPLAY_MISSING
+# error X11 is required to build barrier
+#else
+# include <X11/Xlib.h>
+#endif
+
+class XWindowsClipboard;
+class XWindowsKeyState;
+class XWindowsScreenSaver;
+
+//! Implementation of IPlatformScreen for X11
+class XWindowsScreen : public PlatformScreen {
+public:
+ XWindowsScreen(const char* displayName, bool isPrimary,
+ bool disableXInitThreads, int mouseScrollDelta,
+ IEventQueue* events);
+ virtual ~XWindowsScreen();
+
+ //! @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(UInt32& buttonID) const;
+ virtual void getCursorCenter(SInt32& x, SInt32& y) const;
+
+ // ISecondaryScreen overrides
+ virtual void fakeMouseButton(ButtonID id, bool press);
+ virtual void fakeMouseMove(SInt32 x, SInt32 y);
+ 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 OptionsList& options);
+ virtual void setSequenceNumber(UInt32);
+ virtual bool isPrimary() const;
+
+protected:
+ // IPlatformScreen overrides
+ virtual void handleSystemEvent(const Event&, void*);
+ virtual void updateButtons();
+ virtual IKeyState* getKeyState() const;
+
+private:
+ // event sending
+ void sendEvent(Event::Type, void* = NULL);
+ void sendClipboardEvent(Event::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 KeyEventFilter {
+ 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&);
+
+ bool detectXI2();
+#ifdef HAVE_XI2
+ void selectXIRawMotion();
+#endif
+ 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 HotKeyItem {
+ public:
+ HotKeyItem(int, unsigned int);
+
+ bool operator<(const HotKeyItem&) const;
+
+ private:
+ int m_keycode;
+ unsigned int m_mask;
+ };
+ typedef std::set<bool> FilteredKeycodes;
+ typedef std::vector<std::pair<int, unsigned int> > HotKeyList;
+ typedef std::map<UInt32, HotKeyList> HotKeyMap;
+ typedef std::vector<UInt32> HotKeyIDList;
+ typedef std::map<HotKeyItem, UInt32> HotKeyToIDMap;
+
+ // true if screen is being used as a primary screen, false otherwise
+ bool m_isPrimary;
+ int m_mouseScrollDelta;
+
+ 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
+ XWindowsKeyState* 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;
+ FilteredKeycodes m_filtered;
+
+ // clipboards
+ XWindowsClipboard* m_clipboard[kClipboardEnd];
+ UInt32 m_sequenceNumber;
+
+ // screen saver stuff
+ XWindowsScreenSaver* m_screensaver;
+ bool m_screensaverNotify;
+
+ // logical to physical button mapping. m_buttons[i] gives the
+ // physical button for logical button i+1.
+ std::vector<unsigned char> 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;
+
+ // stuff to work around lost focus issues on certain systems
+ // (ie: a MythTV front-end).
+ bool m_preserveFocus;
+
+ // XKB extension stuff
+ bool m_xkb;
+ int m_xkbEventBase;
+
+ bool m_xi2detected;
+
+ // XRandR extension stuff
+ bool m_xrandr;
+ int m_xrandrEventBase;
+
+ IEventQueue* m_events;
+ barrier::KeyMap m_keyMap;
+
+ // pointer to (singleton) screen. this is only needed by
+ // ioErrorHandler().
+ static XWindowsScreen* s_screen;
+};
diff --git a/src/lib/platform/XWindowsScreenSaver.cpp b/src/lib/platform/XWindowsScreenSaver.cpp
new file mode 100644
index 0000000..bc457f9
--- /dev/null
+++ b/src/lib/platform/XWindowsScreenSaver.cpp
@@ -0,0 +1,605 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsScreenSaver.h"
+
+#include "platform/XWindowsUtil.h"
+#include "barrier/IPlatformScreen.h"
+#include "base/Log.h"
+#include "base/Event.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+#include <X11/Xatom.h>
+#if HAVE_X11_EXTENSIONS_XTEST_H
+# include <X11/extensions/XTest.h>
+#else
+# error The XTest extension is required to build barrier
+#endif
+#if HAVE_X11_EXTENSIONS_DPMS_H
+extern "C" {
+# include <X11/Xmd.h>
+# include <X11/extensions/dpms.h>
+# 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
+
+//
+// XWindowsScreenSaver
+//
+
+XWindowsScreenSaver::XWindowsScreenSaver(
+ Display* display, Window window, void* eventTarget, IEventQueue* events) :
+ 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),
+ m_events(events)
+{
+ // 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;
+ {
+ XWindowsUtil::ErrorLock 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
+ m_events->adoptHandler(Event::kTimer, this,
+ new TMethodEventJob<XWindowsScreenSaver>(this,
+ &XWindowsScreenSaver::handleDisableTimer));
+}
+
+XWindowsScreenSaver::~XWindowsScreenSaver()
+{
+ // done with disable job
+ if (m_disableTimer != NULL) {
+ m_events->deleteTimer(m_disableTimer);
+ }
+ m_events->removeHandler(Event::kTimer, this);
+
+ if (m_display != NULL) {
+ enableDPMS(m_dpmsEnabled);
+ XSetScreenSaver(m_display, m_timeout, m_interval,
+ m_preferBlanking, m_allowExposures);
+ clearWatchForXScreenSaver();
+ XWindowsUtil::ErrorLock lock(m_display);
+ XSelectInput(m_display, DefaultRootWindow(m_display), m_rootEventMask);
+ }
+}
+
+void
+XWindowsScreenSaver::destroy()
+{
+ m_display = NULL;
+ delete this;
+}
+
+bool
+XWindowsScreenSaver::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
+XWindowsScreenSaver::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
+XWindowsScreenSaver::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
+XWindowsScreenSaver::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
+XWindowsScreenSaver::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
+XWindowsScreenSaver::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
+XWindowsScreenSaver::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
+XWindowsScreenSaver::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;
+ {
+ XWindowsUtil::ErrorLock 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
+XWindowsScreenSaver::isXScreenSaver(Window w) const
+{
+ // check for m_atomScreenSaverVersion string property
+ Atom type;
+ return (XWindowsUtil::getWindowProperty(m_display, w,
+ m_atomScreenSaverVersion,
+ NULL, &type, NULL, False) &&
+ type == XA_STRING);
+}
+
+void
+XWindowsScreenSaver::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) {
+ m_events->addEvent(Event(
+ m_events->forIPrimaryScreen().screensaverActivated(),
+ m_eventTarget));
+ }
+ else {
+ m_events->addEvent(Event(
+ m_events->forIPrimaryScreen().screensaverDeactivated(),
+ m_eventTarget));
+ }
+ }
+}
+
+void
+XWindowsScreenSaver::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<long>(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;
+ {
+ XWindowsUtil::ErrorLock lock(m_display, &error);
+ XSendEvent(m_display, m_xscreensaver, False, 0, &event);
+ }
+ if (error) {
+ findXScreenSaver();
+ }
+}
+
+void
+XWindowsScreenSaver::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
+XWindowsScreenSaver::clearWatchForXScreenSaver()
+{
+ // stop watching all windows
+ XWindowsUtil::ErrorLock lock(m_display);
+ for (WatchList::iterator index = m_watchWindows.begin();
+ index != m_watchWindows.end(); ++index) {
+ XSelectInput(m_display, index->first, index->second);
+ }
+ m_watchWindows.clear();
+}
+
+void
+XWindowsScreenSaver::addWatchXScreenSaver(Window window)
+{
+ // get window attributes
+ bool error = false;
+ XWindowAttributes attr;
+ {
+ XWindowsUtil::ErrorLock 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;
+ {
+ XWindowsUtil::ErrorLock 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
+XWindowsScreenSaver::updateDisableTimer()
+{
+ if (m_disabled && !m_suppressDisable && m_disableTimer == NULL) {
+ // 5 seconds should be plenty often to suppress the screen saver
+ m_disableTimer = m_events->newTimer(5.0, this);
+ }
+ else if ((!m_disabled || m_suppressDisable) && m_disableTimer != NULL) {
+ m_events->deleteTimer(m_disableTimer);
+ m_disableTimer = NULL;
+ }
+}
+
+void
+XWindowsScreenSaver::handleDisableTimer(const Event&, 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;
+
+ XWindowsUtil::ErrorLock lock(m_display);
+ XSendEvent(m_display, m_xscreensaver, False, 0, &event);
+
+ m_disablePos = 20 - m_disablePos;
+ }
+}
+
+void
+XWindowsScreenSaver::activateDPMS(bool activate)
+{
+#if HAVE_X11_EXTENSIONS_DPMS_H
+ if (m_dpms) {
+ // DPMSForceLevel will generate a BadMatch if DPMS is disabled
+ XWindowsUtil::ErrorLock lock(m_display);
+ DPMSForceLevel(m_display, activate ? DPMSModeStandby : DPMSModeOn);
+ }
+#endif
+}
+
+void
+XWindowsScreenSaver::enableDPMS(bool enable)
+{
+#if HAVE_X11_EXTENSIONS_DPMS_H
+ if (m_dpms) {
+ if (enable) {
+ DPMSEnable(m_display);
+ }
+ else {
+ DPMSDisable(m_display);
+ }
+ }
+#endif
+}
+
+bool
+XWindowsScreenSaver::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
+XWindowsScreenSaver::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/src/lib/platform/XWindowsScreenSaver.h b/src/lib/platform/XWindowsScreenSaver.h
new file mode 100644
index 0000000..db85f41
--- /dev/null
+++ b/src/lib/platform/XWindowsScreenSaver.h
@@ -0,0 +1,169 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/IScreenSaver.h"
+#include "base/IEventQueue.h"
+#include "common/stdmap.h"
+
+#if X_DISPLAY_MISSING
+# error X11 is required to build barrier
+#else
+# include <X11/Xlib.h>
+#endif
+
+class Event;
+class EventQueueTimer;
+
+//! X11 screen saver implementation
+class XWindowsScreenSaver : public IScreenSaver {
+public:
+ XWindowsScreenSaver(Display*, Window, void* eventTarget, IEventQueue* events);
+ virtual ~XWindowsScreenSaver();
+
+ //! @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 Event&, 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<Window, long> WatchList;
+
+ // 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
+ WatchList 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)
+ EventQueueTimer* 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;
+
+ IEventQueue* m_events;
+};
diff --git a/src/lib/platform/XWindowsUtil.cpp b/src/lib/platform/XWindowsUtil.cpp
new file mode 100644
index 0000000..65448e8
--- /dev/null
+++ b/src/lib/platform/XWindowsUtil.cpp
@@ -0,0 +1,1790 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/XWindowsUtil.h"
+
+#include "barrier/key_types.h"
+#include "mt/Thread.h"
+#include "base/Log.h"
+#include "base/String.h"
+
+#include <X11/Xatom.h>
+#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 <X11/keysym.h>
+
+#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 <http://www.cl.cam.ac.uk/~mgk25/>,
+ * University of Cambridge, April 2001
+ *
+ * Special thanks to Richard Verhoeven <river@win.tue.nl> 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, kKeyBrightnessUp, kKeyBrightnessDown, 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, kKeyMissionControl, kKeyLaunchpad, 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
+};
+
+
+//
+// XWindowsUtil
+//
+
+XWindowsUtil::KeySymMap XWindowsUtil::s_keySymToUCS4;
+
+bool
+XWindowsUtil::getWindowProperty(Display* display, Window window,
+ Atom property, String* data, Atom* type,
+ SInt32* format, bool deleteProperty)
+{
+ assert(display != NULL);
+
+ Atom actualType;
+ int actualDatumSize;
+
+ // ignore errors. XGetWindowProperty() will report failure.
+ XWindowsUtil::ErrorLock 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<SInt32>(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
+XWindowsUtil::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 = static_cast<const unsigned char*>(vdata);
+ UInt32 datumSize = static_cast<UInt32>(format / 8);
+ // format 32 on 64bit systems is 8 bytes not 4.
+ if (format == 32) {
+ datumSize = sizeof(Atom);
+ }
+
+ // save errors
+ bool error = false;
+ XWindowsUtil::ErrorLock 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
+XWindowsUtil::getCurrentTime(Display* display, Window window)
+{
+ XLockDisplay(display);
+ // 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
+ PropertyNotifyPredicateInfo filter;
+ filter.m_window = window;
+ filter.m_property = atom;
+
+ // wait for reply
+ XEvent xevent;
+ XIfEvent(display, &xevent, &XWindowsUtil::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);
+ XUnlockDisplay(display);
+
+ return xevent.xproperty.time;
+}
+
+KeyID
+XWindowsUtil::mapKeySymToKeyID(KeySym k)
+{
+ initKeyMaps();
+
+ switch (k & 0xffffff00) {
+ case 0x0000:
+ // Latin-1
+ return static_cast<KeyID>(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;
+
+#ifdef XK_ISO_Level5_Shift
+ case XK_ISO_Level5_Shift:
+ return XK_ISO_Level5_Shift; //FIXME: there is no "usual" key for this...
+#endif
+
+ 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<KeyID>(k - 0xff00 + 0xef00);
+
+ case 0x1008ff00:
+ // "Internet" keys
+ return s_map1008FF[k & 0xff];
+
+ default: {
+ // lookup character in table
+ KeySymMap::const_iterator index = s_keySymToUCS4.find(k);
+ if (index != s_keySymToUCS4.end()) {
+ return static_cast<KeyID>(index->second);
+ }
+
+ // unknown character
+ return kKeyNone;
+ }
+ }
+}
+
+UInt32
+XWindowsUtil::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;
+
+#ifdef XK_ISO_Level5_Shift
+ case XK_ISO_Level5_Shift:
+ return kKeyModifierBitLevel5Lock;
+#endif
+
+ case XK_Caps_Lock:
+ return kKeyModifierBitCapsLock;
+
+ case XK_Num_Lock:
+ return kKeyModifierBitNumLock;
+
+ case XK_Scroll_Lock:
+ return kKeyModifierBitScrollLock;
+
+ default:
+ return kKeyModifierBitNone;
+ }
+}
+
+String
+XWindowsUtil::atomToString(Display* display, Atom atom)
+{
+ if (atom == 0) {
+ return "None";
+ }
+
+ bool error = false;
+ XWindowsUtil::ErrorLock lock(display, &error);
+ char* name = XGetAtomName(display, atom);
+ if (error) {
+ return barrier::string::sprintf("<UNKNOWN> (%d)", (int)atom);
+ }
+ else {
+ String msg = barrier::string::sprintf("%s (%d)", name, (int)atom);
+ XFree(name);
+ return msg;
+ }
+}
+
+String
+XWindowsUtil::atomsToString(Display* display, const Atom* atom, UInt32 num)
+{
+ char** names = new char*[num];
+ bool error = false;
+ XWindowsUtil::ErrorLock lock(display, &error);
+ XGetAtomNames(display, const_cast<Atom*>(atom), (int)num, names);
+ String msg;
+ if (error) {
+ for (UInt32 i = 0; i < num; ++i) {
+ msg += barrier::string::sprintf("<UNKNOWN> (%d), ", (int)atom[i]);
+ }
+ }
+ else {
+ for (UInt32 i = 0; i < num; ++i) {
+ msg += barrier::string::sprintf("%s (%d), ", names[i], (int)atom[i]);
+ XFree(names[i]);
+ }
+ }
+ delete[] names;
+ if (msg.size() > 2) {
+ msg.erase(msg.size() - 2);
+ }
+ return msg;
+}
+
+void
+XWindowsUtil::convertAtomProperty(String& 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<char*>(&zero), sizeof(zero));
+ }
+}
+
+void
+XWindowsUtil::appendAtomData(String& data, Atom atom)
+{
+ data.append(reinterpret_cast<char*>(&atom), sizeof(Atom));
+}
+
+void
+XWindowsUtil::replaceAtomData(String& data, UInt32 index, Atom atom)
+{
+ data.replace(index * sizeof(Atom), sizeof(Atom),
+ reinterpret_cast<const char*>(&atom),
+ sizeof(Atom));
+}
+
+void
+XWindowsUtil::appendTimeData(String& data, Time time)
+{
+ data.append(reinterpret_cast<char*>(&time), sizeof(Time));
+}
+
+Bool
+XWindowsUtil::propertyNotifyPredicate(Display*, XEvent* xevent, XPointer arg)
+{
+ PropertyNotifyPredicateInfo* filter =
+ reinterpret_cast<PropertyNotifyPredicateInfo*>(arg);
+ return (xevent->type == PropertyNotify &&
+ xevent->xproperty.window == filter->m_window &&
+ xevent->xproperty.atom == filter->m_property &&
+ xevent->xproperty.state == PropertyNewValue) ? True : False;
+}
+
+void
+XWindowsUtil::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;
+ }
+ }
+}
+
+
+//
+// XWindowsUtil::ErrorLock
+//
+
+XWindowsUtil::ErrorLock* XWindowsUtil::ErrorLock::s_top = NULL;
+
+XWindowsUtil::ErrorLock::ErrorLock(Display* display) :
+ m_display(display)
+{
+ install(&XWindowsUtil::ErrorLock::ignoreHandler, NULL);
+}
+
+XWindowsUtil::ErrorLock::ErrorLock(Display* display, bool* flag) :
+ m_display(display)
+{
+ install(&XWindowsUtil::ErrorLock::saveHandler, flag);
+}
+
+XWindowsUtil::ErrorLock::ErrorLock(Display* display,
+ ErrorHandler handler, void* data) :
+ m_display(display)
+{
+ install(handler, data);
+}
+
+XWindowsUtil::ErrorLock::~ErrorLock()
+{
+ // 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
+XWindowsUtil::ErrorLock::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(
+ &XWindowsUtil::ErrorLock::internalHandler);
+ m_next = s_top;
+ s_top = this;
+}
+
+int
+XWindowsUtil::ErrorLock::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
+XWindowsUtil::ErrorLock::ignoreHandler(Display*, XErrorEvent* e, void*)
+{
+ LOG((CLOG_DEBUG1 "ignoring X error: %d", e->error_code));
+}
+
+void
+XWindowsUtil::ErrorLock::saveHandler(Display* display, XErrorEvent* e, void* flag)
+{
+ char errtxt[1024];
+ XGetErrorText(display, e->error_code, errtxt, 1023);
+ LOG((CLOG_DEBUG1 "flagging X error: %d - %.1023s", e->error_code, errtxt));
+ *static_cast<bool*>(flag) = true;
+}
diff --git a/src/lib/platform/XWindowsUtil.h b/src/lib/platform/XWindowsUtil.h
new file mode 100644
index 0000000..4df888f
--- /dev/null
+++ b/src/lib/platform/XWindowsUtil.h
@@ -0,0 +1,187 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+#include "base/EventTypes.h"
+#include "common/stdmap.h"
+#include "common/stdvector.h"
+
+#if X_DISPLAY_MISSING
+# error X11 is required to build barrier
+#else
+# include <X11/Xlib.h>
+#endif
+
+//! X11 utility functions
+class XWindowsUtil {
+public:
+ typedef std::vector<KeySym> 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,
+ String* 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 String 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 String 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(String& 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(String& 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(String& 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(String& 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.
+
+ ErrorLock() ignores errors
+ ErrorLock(bool* flag) sets *flag to true if any error occurs
+ */
+ class ErrorLock {
+ public:
+ //! Error handler type
+ typedef void (*ErrorHandler)(Display*, XErrorEvent*, void* userData);
+
+ /*!
+ Ignore X11 errors.
+ */
+ ErrorLock(Display*);
+
+ /*!
+ Set \c *errorFlag if any error occurs.
+ */
+ ErrorLock(Display*, bool* errorFlag);
+
+ /*!
+ Call \c handler on each error.
+ */
+ ErrorLock(Display*, ErrorHandler handler, void* userData);
+
+ ~ErrorLock();
+
+ 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;
+ ErrorLock* m_next;
+ static ErrorLock* s_top;
+ };
+
+private:
+ class PropertyNotifyPredicateInfo {
+ public:
+ Window m_window;
+ Atom m_property;
+ };
+
+ static Bool propertyNotifyPredicate(Display*,
+ XEvent* xevent, XPointer arg);
+
+ static void initKeyMaps();
+
+private:
+ typedef std::map<KeySym, UInt32> KeySymMap;
+
+ static KeySymMap s_keySymToUCS4;
+};
diff --git a/src/lib/platform/synwinhk.h b/src/lib/platform/synwinhk.h
new file mode 100644
index 0000000..4b2d8e3
--- /dev/null
+++ b/src/lib/platform/synwinhk.h
@@ -0,0 +1,66 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2018 Debauchee Open Source Group
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/EventTypes.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+#if defined(synwinhk_EXPORTS)
+#define CBARRIERHOOK_API __declspec(dllexport)
+#else
+#define CBARRIERHOOK_API __declspec(dllimport)
+#endif
+
+#define BARRIER_MSG_MARK WM_APP + 0x0011 // mark id; <unused>
+#define BARRIER_MSG_KEY WM_APP + 0x0012 // vk code; key data
+#define BARRIER_MSG_MOUSE_BUTTON WM_APP + 0x0013 // button msg; <unused>
+#define BARRIER_MSG_MOUSE_WHEEL WM_APP + 0x0014 // delta; <unused>
+#define BARRIER_MSG_MOUSE_MOVE WM_APP + 0x0015 // x; y
+#define BARRIER_MSG_POST_WARP WM_APP + 0x0016 // <unused>; <unused>
+#define BARRIER_MSG_PRE_WARP WM_APP + 0x0017 // x; y
+#define BARRIER_MSG_SCREEN_SAVER WM_APP + 0x0018 // activated; <unused>
+#define BARRIER_MSG_DEBUG WM_APP + 0x0019 // data, data
+#define BARRIER_MSG_INPUT_FIRST BARRIER_MSG_KEY
+#define BARRIER_MSG_INPUT_LAST BARRIER_MSG_PRE_WARP
+#define BARRIER_HOOK_LAST_MSG BARRIER_MSG_DEBUG
+
+#define BARRIER_HOOK_FAKE_INPUT_VIRTUAL_KEY VK_CANCEL
+#define BARRIER_HOOK_FAKE_INPUT_SCANCODE 0
+
+extern "C" {
+
+enum EHookMode {
+ kHOOK_DISABLE,
+ kHOOK_WATCH_JUMP_ZONE,
+ kHOOK_RELAY_EVENTS
+};
+
+/* REMOVED ImmuneKeys for migration of synwinhk out of DLL
+
+typedef void (*SetImmuneKeysFunc)(const DWORD*, std::size_t);
+
+// do not call setImmuneKeys() while the hooks are active!
+CBARRIERHOOK_API void setImmuneKeys(const DWORD *list, std::size_t size);
+
+*/
+
+}
diff --git a/src/lib/server/BaseClientProxy.cpp b/src/lib/server/BaseClientProxy.cpp
new file mode 100644
index 0000000..b9c5339
--- /dev/null
+++ b/src/lib/server/BaseClientProxy.cpp
@@ -0,0 +1,56 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/BaseClientProxy.h"
+
+//
+// BaseClientProxy
+//
+
+BaseClientProxy::BaseClientProxy(const String& name) :
+ m_name(name),
+ m_x(0),
+ m_y(0)
+{
+ // do nothing
+}
+
+BaseClientProxy::~BaseClientProxy()
+{
+ // do nothing
+}
+
+void
+BaseClientProxy::setJumpCursorPos(SInt32 x, SInt32 y)
+{
+ m_x = x;
+ m_y = y;
+}
+
+void
+BaseClientProxy::getJumpCursorPos(SInt32& x, SInt32& y) const
+{
+ x = m_x;
+ y = m_y;
+}
+
+String
+BaseClientProxy::getName() const
+{
+ return m_name;
+}
diff --git a/src/lib/server/BaseClientProxy.h b/src/lib/server/BaseClientProxy.h
new file mode 100644
index 0000000..c7c23ff
--- /dev/null
+++ b/src/lib/server/BaseClientProxy.h
@@ -0,0 +1,99 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/IClient.h"
+#include "base/String.h"
+
+namespace barrier { class IStream; }
+
+//! Generic proxy for client or primary
+class BaseClientProxy : public IClient {
+public:
+ /*!
+ \c name is the name of the client.
+ */
+ BaseClientProxy(const String& name);
+ ~BaseClientProxy();
+
+ //! @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;
+
+ //! Get cursor position
+ /*!
+ Return if this proxy is for client or primary.
+ */
+ virtual bool isPrimary() const { return false; }
+
+ //@}
+
+ // 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 OptionsList& options) = 0;
+ virtual void sendDragInfo(UInt32 fileCount, const char* info,
+ size_t size) = 0;
+ virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize) = 0;
+ virtual String getName() const;
+ virtual barrier::IStream*
+ getStream() const = 0;
+
+private:
+ String m_name;
+ SInt32 m_x, m_y;
+};
diff --git a/src/lib/server/CMakeLists.txt b/src/lib/server/CMakeLists.txt
new file mode 100644
index 0000000..5242d6d
--- /dev/null
+++ b/src/lib/server/CMakeLists.txt
@@ -0,0 +1,30 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+file(GLOB headers "*.h")
+file(GLOB sources "*.cpp")
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+add_library(server STATIC ${sources})
+
+target_link_libraries(server)
+
+if (UNIX)
+ target_link_libraries(server synlib)
+endif()
diff --git a/src/lib/server/ClientListener.cpp b/src/lib/server/ClientListener.cpp
new file mode 100644
index 0000000..00067ba
--- /dev/null
+++ b/src/lib/server/ClientListener.cpp
@@ -0,0 +1,256 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/ClientListener.h"
+
+#include "server/ClientProxy.h"
+#include "server/ClientProxyUnknown.h"
+#include "barrier/PacketStreamFilter.h"
+#include "net/IDataSocket.h"
+#include "net/IListenSocket.h"
+#include "net/ISocketFactory.h"
+#include "net/XSocket.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+//
+// ClientListener
+//
+
+ClientListener::ClientListener(const NetworkAddress& address,
+ ISocketFactory* socketFactory,
+ IEventQueue* events,
+ bool enableCrypto) :
+ m_socketFactory(socketFactory),
+ m_server(NULL),
+ m_events(events),
+ m_useSecureNetwork(enableCrypto)
+{
+ assert(m_socketFactory != NULL);
+
+ try {
+ m_listen = m_socketFactory->createListen(
+ ARCH->getAddrFamily(address.getAddress()),
+ m_useSecureNetwork);
+
+ // setup event handler
+ m_events->adoptHandler(m_events->forIListenSocket().connecting(),
+ m_listen,
+ new TMethodEventJob<ClientListener>(this,
+ &ClientListener::handleClientConnecting));
+
+ // bind listen address
+ LOG((CLOG_DEBUG1 "binding listen socket"));
+ m_listen->bind(address);
+ }
+ catch (XSocketAddressInUse&) {
+ cleanupListenSocket();
+ delete m_socketFactory;
+ throw;
+ }
+ catch (XBase&) {
+ cleanupListenSocket();
+ delete m_socketFactory;
+ throw;
+ }
+ LOG((CLOG_DEBUG1 "listening for clients"));
+}
+
+ClientListener::~ClientListener()
+{
+ LOG((CLOG_DEBUG1 "stop listening for clients"));
+
+ // discard already connected clients
+ for (NewClients::iterator index = m_newClients.begin();
+ index != m_newClients.end(); ++index) {
+ ClientProxyUnknown* client = *index;
+ m_events->removeHandler(
+ m_events->forClientProxyUnknown().success(), client);
+ m_events->removeHandler(
+ m_events->forClientProxyUnknown().failure(), client);
+ m_events->removeHandler(
+ m_events->forClientProxy().disconnected(), client);
+ delete client;
+ }
+
+ // discard waiting clients
+ ClientProxy* client = getNextClient();
+ while (client != NULL) {
+ delete client;
+ client = getNextClient();
+ }
+
+ m_events->removeHandler(m_events->forIListenSocket().connecting(), m_listen);
+ cleanupListenSocket();
+ cleanupClientSockets();
+ delete m_socketFactory;
+}
+
+void
+ClientListener::setServer(Server* server)
+{
+ assert(server != NULL);
+ m_server = server;
+}
+
+ClientProxy*
+ClientListener::getNextClient()
+{
+ ClientProxy* client = NULL;
+ if (!m_waitingClients.empty()) {
+ client = m_waitingClients.front();
+ m_waitingClients.pop_front();
+ m_events->removeHandler(m_events->forClientProxy().disconnected(), client);
+ }
+ return client;
+}
+
+void
+ClientListener::handleClientConnecting(const Event&, void*)
+{
+ // accept client connection
+ IDataSocket* socket = m_listen->accept();
+
+ if (socket == NULL) {
+ return;
+ }
+
+ m_clientSockets.insert(socket);
+
+ m_events->adoptHandler(m_events->forClientListener().accepted(),
+ socket->getEventTarget(),
+ new TMethodEventJob<ClientListener>(this,
+ &ClientListener::handleClientAccepted, socket));
+
+ // When using non SSL, server accepts clients immediately, while SSL
+ // has to call secure accept which may require retry
+ if (!m_useSecureNetwork) {
+ m_events->addEvent(Event(m_events->forClientListener().accepted(),
+ socket->getEventTarget()));
+ }
+}
+
+void
+ClientListener::handleClientAccepted(const Event&, void* vsocket)
+{
+ LOG((CLOG_NOTE "accepted client connection"));
+
+ IDataSocket* socket = static_cast<IDataSocket*>(vsocket);
+
+ // filter socket messages, including a packetizing filter
+ barrier::IStream* stream = new PacketStreamFilter(m_events, socket, false);
+ assert(m_server != NULL);
+
+ // create proxy for unknown client
+ ClientProxyUnknown* client = new ClientProxyUnknown(stream, 30.0, m_server, m_events);
+
+ m_newClients.insert(client);
+
+ // watch for events from unknown client
+ m_events->adoptHandler(m_events->forClientProxyUnknown().success(),
+ client,
+ new TMethodEventJob<ClientListener>(this,
+ &ClientListener::handleUnknownClient, client));
+ m_events->adoptHandler(m_events->forClientProxyUnknown().failure(),
+ client,
+ new TMethodEventJob<ClientListener>(this,
+ &ClientListener::handleUnknownClient, client));
+}
+
+void
+ClientListener::handleUnknownClient(const Event&, void* vclient)
+{
+ ClientProxyUnknown* unknownClient =
+ static_cast<ClientProxyUnknown*>(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
+ ClientProxy* client = unknownClient->orphanClientProxy();
+ bool handshakeOk = true;
+ if (client != NULL) {
+ // handshake was successful
+ m_waitingClients.push_back(client);
+ m_events->addEvent(Event(m_events->forClientListener().connected(),
+ this));
+
+ // watch for client to disconnect while it's in our queue
+ m_events->adoptHandler(m_events->forClientProxy().disconnected(), client,
+ new TMethodEventJob<ClientListener>(this,
+ &ClientListener::handleClientDisconnected,
+ client));
+ }
+ else {
+ handshakeOk = false;
+ }
+
+ // now finished with unknown client
+ m_events->removeHandler(m_events->forClientProxyUnknown().success(), client);
+ m_events->removeHandler(m_events->forClientProxyUnknown().failure(), client);
+ m_newClients.erase(unknownClient);
+ PacketStreamFilter* streamFileter = dynamic_cast<PacketStreamFilter*>(unknownClient->getStream());
+ IDataSocket* socket = NULL;
+ if (streamFileter != NULL) {
+ socket = dynamic_cast<IDataSocket*>(streamFileter->getStream());
+ }
+
+ delete unknownClient;
+}
+
+void
+ClientListener::handleClientDisconnected(const Event&, void* vclient)
+{
+ ClientProxy* client = static_cast<ClientProxy*>(vclient);
+
+ // find client in waiting clients queue
+ for (WaitingClients::iterator i = m_waitingClients.begin(),
+ n = m_waitingClients.end(); i != n; ++i) {
+ if (*i == client) {
+ m_waitingClients.erase(i);
+ m_events->removeHandler(m_events->forClientProxy().disconnected(),
+ client);
+
+ // pull out the socket before deleting the client so
+ // we know which socket we no longer need
+ IDataSocket* socket = static_cast<IDataSocket*>(client->getStream());
+ delete client;
+ m_clientSockets.erase(socket);
+ delete socket;
+
+ break;
+ }
+ }
+}
+
+void
+ClientListener::cleanupListenSocket()
+{
+ delete m_listen;
+}
+
+void
+ClientListener::cleanupClientSockets()
+{
+ ClientSockets::iterator it;
+ for (it = m_clientSockets.begin(); it != m_clientSockets.end(); it++) {
+ delete *it;
+ }
+ m_clientSockets.clear();
+}
diff --git a/src/lib/server/ClientListener.h b/src/lib/server/ClientListener.h
new file mode 100644
index 0000000..b02cbb1
--- /dev/null
+++ b/src/lib/server/ClientListener.h
@@ -0,0 +1,91 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/Config.h"
+#include "base/EventTypes.h"
+#include "base/Event.h"
+#include "common/stddeque.h"
+#include "common/stdset.h"
+
+class ClientProxy;
+class ClientProxyUnknown;
+class NetworkAddress;
+class IListenSocket;
+class ISocketFactory;
+class Server;
+class IEventQueue;
+class IDataSocket;
+
+class ClientListener {
+public:
+ // The factories are adopted.
+ ClientListener(const NetworkAddress&,
+ ISocketFactory*,
+ IEventQueue* events,
+ bool enableCrypto);
+ ~ClientListener();
+
+ //! @name manipulators
+ //@{
+
+ void setServer(Server* server);
+
+ //@}
+
+ //! @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.
+ */
+ ClientProxy* getNextClient();
+
+ //! Get server which owns this listener
+ Server* getServer() { return m_server; }
+
+ //@}
+
+private:
+ // client connection event handlers
+ void handleClientConnecting(const Event&, void*);
+ void handleClientAccepted(const Event&, void*);
+ void handleUnknownClient(const Event&, void*);
+ void handleClientDisconnected(const Event&, void*);
+
+ void cleanupListenSocket();
+ void cleanupClientSockets();
+
+private:
+ typedef std::set<ClientProxyUnknown*> NewClients;
+ typedef std::deque<ClientProxy*> WaitingClients;
+ typedef std::set<IDataSocket*> ClientSockets;
+
+ IListenSocket* m_listen;
+ ISocketFactory* m_socketFactory;
+ NewClients m_newClients;
+ WaitingClients m_waitingClients;
+ Server* m_server;
+ IEventQueue* m_events;
+ bool m_useSecureNetwork;
+ ClientSockets m_clientSockets;
+};
diff --git a/src/lib/server/ClientProxy.cpp b/src/lib/server/ClientProxy.cpp
new file mode 100644
index 0000000..5a28248
--- /dev/null
+++ b/src/lib/server/ClientProxy.cpp
@@ -0,0 +1,61 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/ClientProxy.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "io/IStream.h"
+#include "base/Log.h"
+#include "base/EventQueue.h"
+
+//
+// ClientProxy
+//
+
+ClientProxy::ClientProxy(const String& name, barrier::IStream* stream) :
+ BaseClientProxy(name),
+ m_stream(stream)
+{
+}
+
+ClientProxy::~ClientProxy()
+{
+ delete m_stream;
+}
+
+void
+ClientProxy::close(const char* msg)
+{
+ LOG((CLOG_DEBUG1 "send close \"%s\" to \"%s\"", msg, getName().c_str()));
+ ProtocolUtil::writef(getStream(), msg);
+
+ // force the close to be sent before we return
+ getStream()->flush();
+}
+
+barrier::IStream*
+ClientProxy::getStream() const
+{
+ return m_stream;
+}
+
+void*
+ClientProxy::getEventTarget() const
+{
+ return static_cast<IScreen*>(const_cast<ClientProxy*>(this));
+}
diff --git a/src/lib/server/ClientProxy.h b/src/lib/server/ClientProxy.h
new file mode 100644
index 0000000..726ded4
--- /dev/null
+++ b/src/lib/server/ClientProxy.h
@@ -0,0 +1,91 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/BaseClientProxy.h"
+#include "base/Event.h"
+#include "base/String.h"
+#include "base/EventTypes.h"
+
+namespace barrier { class IStream; }
+
+//! Generic proxy for client
+class ClientProxy : public BaseClientProxy {
+public:
+ /*!
+ \c name is the name of the client.
+ */
+ ClientProxy(const String& name, barrier::IStream* adoptedStream);
+ ~ClientProxy();
+
+ //! @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 original stream passed to the c'tor.
+ */
+ barrier::IStream* getStream() const;
+
+ //@}
+
+ // 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 OptionsList& options) = 0;
+ virtual void sendDragInfo(UInt32 fileCount, const char* info,
+ size_t size) = 0;
+ virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize) = 0;
+
+private:
+ barrier::IStream* m_stream;
+};
diff --git a/src/lib/server/ClientProxy1_0.cpp b/src/lib/server/ClientProxy1_0.cpp
new file mode 100644
index 0000000..ee805c6
--- /dev/null
+++ b/src/lib/server/ClientProxy1_0.cpp
@@ -0,0 +1,484 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/ClientProxy1_0.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "barrier/XBarrier.h"
+#include "io/IStream.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+#include <cstring>
+
+//
+// ClientProxy1_0
+//
+
+ClientProxy1_0::ClientProxy1_0(const String& name, barrier::IStream* stream, IEventQueue* events) :
+ ClientProxy(name, stream),
+ m_heartbeatTimer(NULL),
+ m_parser(&ClientProxy1_0::parseHandshakeMessage),
+ m_events(events)
+{
+ // install event handlers
+ m_events->adoptHandler(m_events->forIStream().inputReady(),
+ stream->getEventTarget(),
+ new TMethodEventJob<ClientProxy1_0>(this,
+ &ClientProxy1_0::handleData, NULL));
+ m_events->adoptHandler(m_events->forIStream().outputError(),
+ stream->getEventTarget(),
+ new TMethodEventJob<ClientProxy1_0>(this,
+ &ClientProxy1_0::handleWriteError, NULL));
+ m_events->adoptHandler(m_events->forIStream().inputShutdown(),
+ stream->getEventTarget(),
+ new TMethodEventJob<ClientProxy1_0>(this,
+ &ClientProxy1_0::handleDisconnect, NULL));
+ m_events->adoptHandler(m_events->forIStream().outputShutdown(),
+ stream->getEventTarget(),
+ new TMethodEventJob<ClientProxy1_0>(this,
+ &ClientProxy1_0::handleWriteError, NULL));
+ m_events->adoptHandler(Event::kTimer, this,
+ new TMethodEventJob<ClientProxy1_0>(this,
+ &ClientProxy1_0::handleFlatline, NULL));
+
+ setHeartbeatRate(kHeartRate, kHeartRate * kHeartBeatsUntilDeath);
+
+ LOG((CLOG_DEBUG1 "querying client \"%s\" info", getName().c_str()));
+ ProtocolUtil::writef(getStream(), kMsgQInfo);
+}
+
+ClientProxy1_0::~ClientProxy1_0()
+{
+ removeHandlers();
+}
+
+void
+ClientProxy1_0::disconnect()
+{
+ removeHandlers();
+ getStream()->close();
+ m_events->addEvent(Event(m_events->forClientProxy().disconnected(), getEventTarget()));
+}
+
+void
+ClientProxy1_0::removeHandlers()
+{
+ // uninstall event handlers
+ m_events->removeHandler(m_events->forIStream().inputReady(),
+ getStream()->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().outputError(),
+ getStream()->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().inputShutdown(),
+ getStream()->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().outputShutdown(),
+ getStream()->getEventTarget());
+ m_events->removeHandler(Event::kTimer, this);
+
+ // remove timer
+ removeHeartbeatTimer();
+}
+
+void
+ClientProxy1_0::addHeartbeatTimer()
+{
+ if (m_heartbeatAlarm > 0.0) {
+ m_heartbeatTimer = m_events->newOneShotTimer(m_heartbeatAlarm, this);
+ }
+}
+
+void
+ClientProxy1_0::removeHeartbeatTimer()
+{
+ if (m_heartbeatTimer != NULL) {
+ m_events->deleteTimer(m_heartbeatTimer);
+ m_heartbeatTimer = NULL;
+ }
+}
+
+void
+ClientProxy1_0::resetHeartbeatTimer()
+{
+ // reset the alarm
+ removeHeartbeatTimer();
+ addHeartbeatTimer();
+}
+
+void
+ClientProxy1_0::resetHeartbeatRate()
+{
+ setHeartbeatRate(kHeartRate, kHeartRate * kHeartBeatsUntilDeath);
+}
+
+void
+ClientProxy1_0::setHeartbeatRate(double, double alarm)
+{
+ m_heartbeatAlarm = alarm;
+}
+
+void
+ClientProxy1_0::handleData(const Event&, 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
+ClientProxy1_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 = &ClientProxy1_0::parseMessage;
+ if (recvInfo()) {
+ m_events->addEvent(Event(m_events->forClientProxy().ready(), getEventTarget()));
+ addHeartbeatTimer();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+ClientProxy1_0::parseMessage(const UInt8* code)
+{
+ if (memcmp(code, kMsgDInfo, 4) == 0) {
+ if (recvInfo()) {
+ m_events->addEvent(
+ Event(m_events->forIScreen().shapeChanged(), 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
+ClientProxy1_0::handleDisconnect(const Event&, void*)
+{
+ LOG((CLOG_NOTE "client \"%s\" has disconnected", getName().c_str()));
+ disconnect();
+}
+
+void
+ClientProxy1_0::handleWriteError(const Event&, void*)
+{
+ LOG((CLOG_WARN "error writing to client \"%s\"", getName().c_str()));
+ disconnect();
+}
+
+void
+ClientProxy1_0::handleFlatline(const Event&, void*)
+{
+ // didn't get a heartbeat fast enough. assume client is dead.
+ LOG((CLOG_NOTE "client \"%s\" is dead", getName().c_str()));
+ disconnect();
+}
+
+bool
+ClientProxy1_0::getClipboard(ClipboardID id, IClipboard* clipboard) const
+{
+ Clipboard::copy(clipboard, &m_clipboard[id].m_clipboard);
+ return true;
+}
+
+void
+ClientProxy1_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
+ClientProxy1_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
+ClientProxy1_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));
+ ProtocolUtil::writef(getStream(), kMsgCEnter,
+ xAbs, yAbs, seqNum, mask);
+}
+
+bool
+ClientProxy1_0::leave()
+{
+ LOG((CLOG_DEBUG1 "send leave to \"%s\"", getName().c_str()));
+ ProtocolUtil::writef(getStream(), kMsgCLeave);
+
+ // we can never prevent the user from leaving
+ return true;
+}
+
+void
+ClientProxy1_0::setClipboard(ClipboardID id, const IClipboard* clipboard)
+{
+ // ignore -- deprecated in protocol 1.0
+}
+
+void
+ClientProxy1_0::grabClipboard(ClipboardID id)
+{
+ LOG((CLOG_DEBUG "send grab clipboard %d to \"%s\"", id, getName().c_str()));
+ ProtocolUtil::writef(getStream(), kMsgCClipboard, id, 0);
+
+ // this clipboard is now dirty
+ m_clipboard[id].m_dirty = true;
+}
+
+void
+ClientProxy1_0::setClipboardDirty(ClipboardID id, bool dirty)
+{
+ m_clipboard[id].m_dirty = dirty;
+}
+
+void
+ClientProxy1_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));
+ ProtocolUtil::writef(getStream(), kMsgDKeyDown1_0, key, mask);
+}
+
+void
+ClientProxy1_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));
+ ProtocolUtil::writef(getStream(), kMsgDKeyRepeat1_0, key, mask, count);
+}
+
+void
+ClientProxy1_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));
+ ProtocolUtil::writef(getStream(), kMsgDKeyUp1_0, key, mask);
+}
+
+void
+ClientProxy1_0::mouseDown(ButtonID button)
+{
+ LOG((CLOG_DEBUG1 "send mouse down to \"%s\" id=%d", getName().c_str(), button));
+ ProtocolUtil::writef(getStream(), kMsgDMouseDown, button);
+}
+
+void
+ClientProxy1_0::mouseUp(ButtonID button)
+{
+ LOG((CLOG_DEBUG1 "send mouse up to \"%s\" id=%d", getName().c_str(), button));
+ ProtocolUtil::writef(getStream(), kMsgDMouseUp, button);
+}
+
+void
+ClientProxy1_0::mouseMove(SInt32 xAbs, SInt32 yAbs)
+{
+ LOG((CLOG_DEBUG2 "send mouse move to \"%s\" %d,%d", getName().c_str(), xAbs, yAbs));
+ ProtocolUtil::writef(getStream(), kMsgDMouseMove, xAbs, yAbs);
+}
+
+void
+ClientProxy1_0::mouseRelativeMove(SInt32, SInt32)
+{
+ // ignore -- not supported in protocol 1.0
+}
+
+void
+ClientProxy1_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));
+ ProtocolUtil::writef(getStream(), kMsgDMouseWheel1_0, yDelta);
+}
+
+void
+ClientProxy1_0::sendDragInfo(UInt32 fileCount, const char* info, size_t size)
+{
+ // ignore -- not supported in protocol 1.0
+ LOG((CLOG_DEBUG "draggingInfoSending not supported"));
+}
+
+void
+ClientProxy1_0::fileChunkSending(UInt8 mark, char* data, size_t dataSize)
+{
+ // ignore -- not supported in protocol 1.0
+ LOG((CLOG_DEBUG "fileChunkSending not supported"));
+}
+
+void
+ClientProxy1_0::screensaver(bool on)
+{
+ LOG((CLOG_DEBUG1 "send screen saver to \"%s\" on=%d", getName().c_str(), on ? 1 : 0));
+ ProtocolUtil::writef(getStream(), kMsgCScreenSaver, on ? 1 : 0);
+}
+
+void
+ClientProxy1_0::resetOptions()
+{
+ LOG((CLOG_DEBUG1 "send reset options to \"%s\"", getName().c_str()));
+ ProtocolUtil::writef(getStream(), kMsgCResetOptions);
+
+ // reset heart rate and death
+ resetHeartbeatRate();
+ removeHeartbeatTimer();
+ addHeartbeatTimer();
+}
+
+void
+ClientProxy1_0::setOptions(const OptionsList& options)
+{
+ LOG((CLOG_DEBUG1 "send set options to \"%s\" size=%d", getName().c_str(), options.size()));
+ ProtocolUtil::writef(getStream(), kMsgDSetOptions, &options);
+
+ // check options
+ for (UInt32 i = 0, n = (UInt32)options.size(); i < n; i += 2) {
+ if (options[i] == kOptionHeartbeat) {
+ double rate = 1.0e-3 * static_cast<double>(options[i + 1]);
+ if (rate <= 0.0) {
+ rate = -1.0;
+ }
+ setHeartbeatRate(rate, rate * kHeartBeatsUntilDeath);
+ removeHeartbeatTimer();
+ addHeartbeatTimer();
+ }
+ }
+}
+
+bool
+ClientProxy1_0::recvInfo()
+{
+ // parse the message
+ SInt16 x, y, w, h, dummy1, mx, my;
+ if (!ProtocolUtil::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()));
+ ProtocolUtil::writef(getStream(), kMsgCInfoAck);
+ return true;
+}
+
+bool
+ClientProxy1_0::recvClipboard()
+{
+ // deprecated in protocol 1.0
+ return false;
+}
+
+bool
+ClientProxy1_0::recvGrabClipboard()
+{
+ // parse message
+ ClipboardID id;
+ UInt32 seqNum;
+ if (!ProtocolUtil::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
+ ClipboardInfo* info = new ClipboardInfo;
+ info->m_id = id;
+ info->m_sequenceNumber = seqNum;
+ m_events->addEvent(Event(m_events->forClipboard().clipboardGrabbed(),
+ getEventTarget(), info));
+
+ return true;
+}
+
+//
+// ClientProxy1_0::ClientClipboard
+//
+
+ClientProxy1_0::ClientClipboard::ClientClipboard() :
+ m_clipboard(),
+ m_sequenceNumber(0),
+ m_dirty(true)
+{
+ // do nothing
+}
diff --git a/src/lib/server/ClientProxy1_0.h b/src/lib/server/ClientProxy1_0.h
new file mode 100644
index 0000000..0720232
--- /dev/null
+++ b/src/lib/server/ClientProxy1_0.h
@@ -0,0 +1,107 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/ClientProxy.h"
+#include "barrier/Clipboard.h"
+#include "barrier/protocol_types.h"
+
+class Event;
+class EventQueueTimer;
+class IEventQueue;
+
+//! Proxy for client implementing protocol version 1.0
+class ClientProxy1_0 : public ClientProxy {
+public:
+ ClientProxy1_0(const String& name, barrier::IStream* adoptedStream, IEventQueue* events);
+ ~ClientProxy1_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 OptionsList& options);
+ virtual void sendDragInfo(UInt32 fileCount, const char* info, size_t size);
+ virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize);
+
+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();
+ virtual bool recvClipboard();
+private:
+ void disconnect();
+ void removeHandlers();
+
+ void handleData(const Event&, void*);
+ void handleDisconnect(const Event&, void*);
+ void handleWriteError(const Event&, void*);
+ void handleFlatline(const Event&, void*);
+
+ bool recvInfo();
+ bool recvGrabClipboard();
+
+protected:
+ struct ClientClipboard {
+ public:
+ ClientClipboard();
+
+ public:
+ Clipboard m_clipboard;
+ UInt32 m_sequenceNumber;
+ bool m_dirty;
+ };
+
+ ClientClipboard m_clipboard[kClipboardEnd];
+
+private:
+ typedef bool (ClientProxy1_0::*MessageParser)(const UInt8*);
+
+ ClientInfo m_info;
+ double m_heartbeatAlarm;
+ EventQueueTimer* m_heartbeatTimer;
+ MessageParser m_parser;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/ClientProxy1_1.cpp b/src/lib/server/ClientProxy1_1.cpp
new file mode 100644
index 0000000..b7eb4c4
--- /dev/null
+++ b/src/lib/server/ClientProxy1_1.cpp
@@ -0,0 +1,61 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/ClientProxy1_1.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "base/Log.h"
+
+#include <cstring>
+
+//
+// ClientProxy1_1
+//
+
+ClientProxy1_1::ClientProxy1_1(const String& name, barrier::IStream* stream, IEventQueue* events) :
+ ClientProxy1_0(name, stream, events)
+{
+ // do nothing
+}
+
+ClientProxy1_1::~ClientProxy1_1()
+{
+ // do nothing
+}
+
+void
+ClientProxy1_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));
+ ProtocolUtil::writef(getStream(), kMsgDKeyDown, key, mask, button);
+}
+
+void
+ClientProxy1_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));
+ ProtocolUtil::writef(getStream(), kMsgDKeyRepeat, key, mask, count, button);
+}
+
+void
+ClientProxy1_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));
+ ProtocolUtil::writef(getStream(), kMsgDKeyUp, key, mask, button);
+}
diff --git a/src/lib/server/ClientProxy1_1.h b/src/lib/server/ClientProxy1_1.h
new file mode 100644
index 0000000..cdb674d
--- /dev/null
+++ b/src/lib/server/ClientProxy1_1.h
@@ -0,0 +1,34 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/ClientProxy1_0.h"
+
+//! Proxy for client implementing protocol version 1.1
+class ClientProxy1_1 : public ClientProxy1_0 {
+public:
+ ClientProxy1_1(const String& name, barrier::IStream* adoptedStream, IEventQueue* events);
+ ~ClientProxy1_1();
+
+ // IClient overrides
+ virtual void keyDown(KeyID, KeyModifierMask, KeyButton);
+ virtual void keyRepeat(KeyID, KeyModifierMask,
+ SInt32 count, KeyButton);
+ virtual void keyUp(KeyID, KeyModifierMask, KeyButton);
+};
diff --git a/src/lib/server/ClientProxy1_2.cpp b/src/lib/server/ClientProxy1_2.cpp
new file mode 100644
index 0000000..2dd13cf
--- /dev/null
+++ b/src/lib/server/ClientProxy1_2.cpp
@@ -0,0 +1,44 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/ClientProxy1_2.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "base/Log.h"
+
+//
+// ClientProxy1_1
+//
+
+ClientProxy1_2::ClientProxy1_2(const String& name, barrier::IStream* stream, IEventQueue* events) :
+ ClientProxy1_1(name, stream, events)
+{
+ // do nothing
+}
+
+ClientProxy1_2::~ClientProxy1_2()
+{
+ // do nothing
+}
+
+void
+ClientProxy1_2::mouseRelativeMove(SInt32 xRel, SInt32 yRel)
+{
+ LOG((CLOG_DEBUG2 "send mouse relative move to \"%s\" %d,%d", getName().c_str(), xRel, yRel));
+ ProtocolUtil::writef(getStream(), kMsgDMouseRelMove, xRel, yRel);
+}
diff --git a/src/lib/server/ClientProxy1_2.h b/src/lib/server/ClientProxy1_2.h
new file mode 100644
index 0000000..f6ffe94
--- /dev/null
+++ b/src/lib/server/ClientProxy1_2.h
@@ -0,0 +1,33 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/ClientProxy1_1.h"
+
+class IEventQueue;
+
+//! Proxy for client implementing protocol version 1.2
+class ClientProxy1_2 : public ClientProxy1_1 {
+public:
+ ClientProxy1_2(const String& name, barrier::IStream* adoptedStream, IEventQueue* events);
+ ~ClientProxy1_2();
+
+ // IClient overrides
+ virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel);
+};
diff --git a/src/lib/server/ClientProxy1_3.cpp b/src/lib/server/ClientProxy1_3.cpp
new file mode 100644
index 0000000..34ea0c8
--- /dev/null
+++ b/src/lib/server/ClientProxy1_3.cpp
@@ -0,0 +1,129 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/ClientProxy1_3.h"
+
+#include "barrier/ProtocolUtil.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+#include <cstring>
+#include <memory>
+
+//
+// ClientProxy1_3
+//
+
+ClientProxy1_3::ClientProxy1_3(const String& name, barrier::IStream* stream, IEventQueue* events) :
+ ClientProxy1_2(name, stream, events),
+ m_keepAliveRate(kKeepAliveRate),
+ m_keepAliveTimer(NULL),
+ m_events(events)
+{
+ setHeartbeatRate(kKeepAliveRate, kKeepAliveRate * kKeepAlivesUntilDeath);
+}
+
+ClientProxy1_3::~ClientProxy1_3()
+{
+ // cannot do this in superclass or our override wouldn't get called
+ removeHeartbeatTimer();
+}
+
+void
+ClientProxy1_3::mouseWheel(SInt32 xDelta, SInt32 yDelta)
+{
+ LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d,%+d", getName().c_str(), xDelta, yDelta));
+ ProtocolUtil::writef(getStream(), kMsgDMouseWheel, xDelta, yDelta);
+}
+
+bool
+ClientProxy1_3::parseMessage(const UInt8* code)
+{
+ // process message
+ if (memcmp(code, kMsgCKeepAlive, 4) == 0) {
+ // reset alarm
+ resetHeartbeatTimer();
+ return true;
+ }
+ else {
+ return ClientProxy1_2::parseMessage(code);
+ }
+}
+
+void
+ClientProxy1_3::resetHeartbeatRate()
+{
+ setHeartbeatRate(kKeepAliveRate, kKeepAliveRate * kKeepAlivesUntilDeath);
+}
+
+void
+ClientProxy1_3::setHeartbeatRate(double rate, double)
+{
+ m_keepAliveRate = rate;
+ ClientProxy1_2::setHeartbeatRate(rate, rate * kKeepAlivesUntilDeath);
+}
+
+void
+ClientProxy1_3::resetHeartbeatTimer()
+{
+ // reset the alarm but not the keep alive timer
+ ClientProxy1_2::removeHeartbeatTimer();
+ ClientProxy1_2::addHeartbeatTimer();
+}
+
+void
+ClientProxy1_3::addHeartbeatTimer()
+{
+ // create and install a timer to periodically send keep alives
+ if (m_keepAliveRate > 0.0) {
+ m_keepAliveTimer = m_events->newTimer(m_keepAliveRate, NULL);
+ m_events->adoptHandler(Event::kTimer, m_keepAliveTimer,
+ new TMethodEventJob<ClientProxy1_3>(this,
+ &ClientProxy1_3::handleKeepAlive, NULL));
+ }
+
+ // superclass does the alarm
+ ClientProxy1_2::addHeartbeatTimer();
+}
+
+void
+ClientProxy1_3::removeHeartbeatTimer()
+{
+ // remove the timer that sends keep alives periodically
+ if (m_keepAliveTimer != NULL) {
+ m_events->removeHandler(Event::kTimer, m_keepAliveTimer);
+ m_events->deleteTimer(m_keepAliveTimer);
+ m_keepAliveTimer = NULL;
+ }
+
+ // superclass does the alarm
+ ClientProxy1_2::removeHeartbeatTimer();
+}
+
+void
+ClientProxy1_3::handleKeepAlive(const Event&, void*)
+{
+ keepAlive();
+}
+
+void
+ClientProxy1_3::keepAlive()
+{
+ ProtocolUtil::writef(getStream(), kMsgCKeepAlive);
+}
diff --git a/src/lib/server/ClientProxy1_3.h b/src/lib/server/ClientProxy1_3.h
new file mode 100644
index 0000000..ff2ed0a
--- /dev/null
+++ b/src/lib/server/ClientProxy1_3.h
@@ -0,0 +1,48 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/ClientProxy1_2.h"
+
+//! Proxy for client implementing protocol version 1.3
+class ClientProxy1_3 : public ClientProxy1_2 {
+public:
+ ClientProxy1_3(const String& name, barrier::IStream* adoptedStream, IEventQueue* events);
+ ~ClientProxy1_3();
+
+ // IClient overrides
+ virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta);
+
+ void handleKeepAlive(const Event&, void*);
+
+protected:
+ // ClientProxy 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();
+ virtual void keepAlive();
+
+private:
+ double m_keepAliveRate;
+ EventQueueTimer* m_keepAliveTimer;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/ClientProxy1_4.cpp b/src/lib/server/ClientProxy1_4.cpp
new file mode 100644
index 0000000..43c708d
--- /dev/null
+++ b/src/lib/server/ClientProxy1_4.cpp
@@ -0,0 +1,66 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2011 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/ClientProxy1_4.h"
+
+#include "server/Server.h"
+#include "barrier/ProtocolUtil.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+#include <cstring>
+#include <memory>
+
+//
+// ClientProxy1_4
+//
+
+ClientProxy1_4::ClientProxy1_4(const String& name, barrier::IStream* stream, Server* server, IEventQueue* events) :
+ ClientProxy1_3(name, stream, events), m_server(server)
+{
+ assert(m_server != NULL);
+}
+
+ClientProxy1_4::~ClientProxy1_4()
+{
+}
+
+void
+ClientProxy1_4::keyDown(KeyID key, KeyModifierMask mask, KeyButton button)
+{
+ ClientProxy1_3::keyDown(key, mask, button);
+}
+
+void
+ClientProxy1_4::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button)
+{
+ ClientProxy1_3::keyRepeat(key, mask, count, button);
+}
+
+void
+ClientProxy1_4::keyUp(KeyID key, KeyModifierMask mask, KeyButton button)
+{
+ ClientProxy1_3::keyUp(key, mask, button);
+}
+
+void
+ClientProxy1_4::keepAlive()
+{
+ ClientProxy1_3::keepAlive();
+}
diff --git a/src/lib/server/ClientProxy1_4.h b/src/lib/server/ClientProxy1_4.h
new file mode 100644
index 0000000..6c55965
--- /dev/null
+++ b/src/lib/server/ClientProxy1_4.h
@@ -0,0 +1,46 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2011 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/ClientProxy1_3.h"
+
+class Server;
+
+//! Proxy for client implementing protocol version 1.4
+class ClientProxy1_4 : public ClientProxy1_3 {
+public:
+ ClientProxy1_4(const String& name, barrier::IStream* adoptedStream, Server* server, IEventQueue* events);
+ ~ClientProxy1_4();
+
+ //! @name accessors
+ //@{
+
+ //! get server pointer
+ Server* getServer() { return m_server; }
+
+ //@}
+
+ // IClient overrides
+ virtual void keyDown(KeyID key, KeyModifierMask mask, KeyButton button);
+ virtual void keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button);
+ virtual void keyUp(KeyID key, KeyModifierMask mask, KeyButton button);
+ virtual void keepAlive();
+
+ Server* m_server;
+};
diff --git a/src/lib/server/ClientProxy1_5.cpp b/src/lib/server/ClientProxy1_5.cpp
new file mode 100644
index 0000000..43fd0b7
--- /dev/null
+++ b/src/lib/server/ClientProxy1_5.cpp
@@ -0,0 +1,110 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/ClientProxy1_5.h"
+
+#include "server/Server.h"
+#include "barrier/FileChunk.h"
+#include "barrier/StreamChunker.h"
+#include "barrier/ProtocolUtil.h"
+#include "io/IStream.h"
+#include "base/TMethodEventJob.h"
+#include "base/Log.h"
+
+#include <sstream>
+
+//
+// ClientProxy1_5
+//
+
+ClientProxy1_5::ClientProxy1_5(const String& name, barrier::IStream* stream, Server* server, IEventQueue* events) :
+ ClientProxy1_4(name, stream, server, events),
+ m_events(events)
+{
+
+ m_events->adoptHandler(m_events->forFile().keepAlive(),
+ this,
+ new TMethodEventJob<ClientProxy1_3>(this,
+ &ClientProxy1_3::handleKeepAlive, NULL));
+}
+
+ClientProxy1_5::~ClientProxy1_5()
+{
+ m_events->removeHandler(m_events->forFile().keepAlive(), this);
+}
+
+void
+ClientProxy1_5::sendDragInfo(UInt32 fileCount, const char* info, size_t size)
+{
+ String data(info, size);
+
+ ProtocolUtil::writef(getStream(), kMsgDDragInfo, fileCount, &data);
+}
+
+void
+ClientProxy1_5::fileChunkSending(UInt8 mark, char* data, size_t dataSize)
+{
+ FileChunk::send(getStream(), mark, data, dataSize);
+}
+
+bool
+ClientProxy1_5::parseMessage(const UInt8* code)
+{
+ if (memcmp(code, kMsgDFileTransfer, 4) == 0) {
+ fileChunkReceived();
+ }
+ else if (memcmp(code, kMsgDDragInfo, 4) == 0) {
+ dragInfoReceived();
+ }
+ else {
+ return ClientProxy1_4::parseMessage(code);
+ }
+
+ return true;
+}
+
+void
+ClientProxy1_5::fileChunkReceived()
+{
+ Server* server = getServer();
+ int result = FileChunk::assemble(
+ getStream(),
+ server->getReceivedFileData(),
+ server->getExpectedFileSize());
+
+
+ if (result == kFinish) {
+ m_events->addEvent(Event(m_events->forFile().fileRecieveCompleted(), server));
+ }
+ else if (result == kStart) {
+ if (server->getFakeDragFileList().size() > 0) {
+ String filename = server->getFakeDragFileList().at(0).getFilename();
+ LOG((CLOG_DEBUG "start receiving %s", filename.c_str()));
+ }
+ }
+}
+
+void
+ClientProxy1_5::dragInfoReceived()
+{
+ // parse
+ UInt32 fileNum = 0;
+ String content;
+ ProtocolUtil::readf(getStream(), kMsgDDragInfo + 4, &fileNum, &content);
+
+ m_server->dragInfoReceived(fileNum, content);
+}
diff --git a/src/lib/server/ClientProxy1_5.h b/src/lib/server/ClientProxy1_5.h
new file mode 100644
index 0000000..776de03
--- /dev/null
+++ b/src/lib/server/ClientProxy1_5.h
@@ -0,0 +1,41 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2013-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/ClientProxy1_4.h"
+#include "base/Stopwatch.h"
+#include "common/stdvector.h"
+
+class Server;
+class IEventQueue;
+
+//! Proxy for client implementing protocol version 1.5
+class ClientProxy1_5 : public ClientProxy1_4 {
+public:
+ ClientProxy1_5(const String& name, barrier::IStream* adoptedStream, Server* server, IEventQueue* events);
+ ~ClientProxy1_5();
+
+ virtual void sendDragInfo(UInt32 fileCount, const char* info, size_t size);
+ virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize);
+ virtual bool parseMessage(const UInt8* code);
+ void fileChunkReceived();
+ void dragInfoReceived();
+
+private:
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/ClientProxy1_6.cpp b/src/lib/server/ClientProxy1_6.cpp
new file mode 100644
index 0000000..a0d2621
--- /dev/null
+++ b/src/lib/server/ClientProxy1_6.cpp
@@ -0,0 +1,100 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/ClientProxy1_6.h"
+
+#include "server/Server.h"
+#include "barrier/ProtocolUtil.h"
+#include "barrier/StreamChunker.h"
+#include "barrier/ClipboardChunk.h"
+#include "io/IStream.h"
+#include "base/TMethodEventJob.h"
+#include "base/Log.h"
+
+//
+// ClientProxy1_6
+//
+
+ClientProxy1_6::ClientProxy1_6(const String& name, barrier::IStream* stream, Server* server, IEventQueue* events) :
+ ClientProxy1_5(name, stream, server, events),
+ m_events(events)
+{
+ m_events->adoptHandler(m_events->forClipboard().clipboardSending(),
+ this,
+ new TMethodEventJob<ClientProxy1_6>(this,
+ &ClientProxy1_6::handleClipboardSendingEvent));
+}
+
+ClientProxy1_6::~ClientProxy1_6()
+{
+}
+
+void
+ClientProxy1_6::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;
+ Clipboard::copy(&m_clipboard[id].m_clipboard, clipboard);
+
+ String data = m_clipboard[id].m_clipboard.marshall();
+
+ size_t size = data.size();
+ LOG((CLOG_DEBUG "sending clipboard %d to \"%s\"", id, getName().c_str()));
+
+ StreamChunker::sendClipboard(data, size, id, 0, m_events, this);
+ }
+}
+
+void
+ClientProxy1_6::handleClipboardSendingEvent(const Event& event, void*)
+{
+ ClipboardChunk::send(getStream(), event.getData());
+}
+
+bool
+ClientProxy1_6::recvClipboard()
+{
+ // parse message
+ static String dataCached;
+ ClipboardID id;
+ UInt32 seq;
+
+ int r = ClipboardChunk::assemble(getStream(), dataCached, id, seq);
+
+ if (r == kStart) {
+ size_t size = ClipboardChunk::getExpectedSize();
+ LOG((CLOG_DEBUG "receiving clipboard %d size=%d", id, size));
+ }
+ else if (r == kFinish) {
+ LOG((CLOG_DEBUG "received client \"%s\" clipboard %d seqnum=%d, size=%d",
+ getName().c_str(), id, seq, dataCached.size()));
+ // save clipboard
+ m_clipboard[id].m_clipboard.unmarshall(dataCached, 0);
+ m_clipboard[id].m_sequenceNumber = seq;
+
+ // notify
+ ClipboardInfo* info = new ClipboardInfo;
+ info->m_id = id;
+ info->m_sequenceNumber = seq;
+ m_events->addEvent(Event(m_events->forClipboard().clipboardChanged(),
+ getEventTarget(), info));
+ }
+
+ return true;
+}
diff --git a/src/lib/server/ClientProxy1_6.h b/src/lib/server/ClientProxy1_6.h
new file mode 100644
index 0000000..838cb02
--- /dev/null
+++ b/src/lib/server/ClientProxy1_6.h
@@ -0,0 +1,39 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/ClientProxy1_5.h"
+
+class Server;
+class IEventQueue;
+
+//! Proxy for client implementing protocol version 1.6
+class ClientProxy1_6 : public ClientProxy1_5 {
+public:
+ ClientProxy1_6(const String& name, barrier::IStream* adoptedStream, Server* server, IEventQueue* events);
+ ~ClientProxy1_6();
+
+ virtual void setClipboard(ClipboardID id, const IClipboard* clipboard);
+ virtual bool recvClipboard();
+
+private:
+ void handleClipboardSendingEvent(const Event&, void*);
+
+private:
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/ClientProxyUnknown.cpp b/src/lib/server/ClientProxyUnknown.cpp
new file mode 100644
index 0000000..f929108
--- /dev/null
+++ b/src/lib/server/ClientProxyUnknown.cpp
@@ -0,0 +1,295 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/ClientProxyUnknown.h"
+
+#include "server/Server.h"
+#include "server/ClientProxy1_0.h"
+#include "server/ClientProxy1_1.h"
+#include "server/ClientProxy1_2.h"
+#include "server/ClientProxy1_3.h"
+#include "server/ClientProxy1_4.h"
+#include "server/ClientProxy1_5.h"
+#include "server/ClientProxy1_6.h"
+#include "barrier/protocol_types.h"
+#include "barrier/ProtocolUtil.h"
+#include "barrier/XBarrier.h"
+#include "io/IStream.h"
+#include "io/XIO.h"
+#include "base/Log.h"
+#include "base/String.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+
+//
+// ClientProxyUnknown
+//
+
+ClientProxyUnknown::ClientProxyUnknown(barrier::IStream* stream, double timeout, Server* server, IEventQueue* events) :
+ m_stream(stream),
+ m_proxy(NULL),
+ m_ready(false),
+ m_server(server),
+ m_events(events)
+{
+ assert(m_server != NULL);
+
+ m_events->adoptHandler(Event::kTimer, this,
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleTimeout, NULL));
+ m_timer = m_events->newOneShotTimer(timeout, this);
+ addStreamHandlers();
+
+ LOG((CLOG_DEBUG1 "saying hello"));
+ ProtocolUtil::writef(m_stream, kMsgHello,
+ kProtocolMajorVersion,
+ kProtocolMinorVersion);
+}
+
+ClientProxyUnknown::~ClientProxyUnknown()
+{
+ removeHandlers();
+ removeTimer();
+ delete m_stream;
+ delete m_proxy;
+}
+
+ClientProxy*
+ClientProxyUnknown::orphanClientProxy()
+{
+ if (m_ready) {
+ removeHandlers();
+ ClientProxy* proxy = m_proxy;
+ m_proxy = NULL;
+ return proxy;
+ }
+ else {
+ return NULL;
+ }
+}
+
+void
+ClientProxyUnknown::sendSuccess()
+{
+ m_ready = true;
+ removeTimer();
+ m_events->addEvent(Event(m_events->forClientProxyUnknown().success(), this));
+}
+
+void
+ClientProxyUnknown::sendFailure()
+{
+ delete m_proxy;
+ m_proxy = NULL;
+ m_ready = false;
+ removeHandlers();
+ removeTimer();
+ m_events->addEvent(Event(m_events->forClientProxyUnknown().failure(), this));
+}
+
+void
+ClientProxyUnknown::addStreamHandlers()
+{
+ assert(m_stream != NULL);
+
+ m_events->adoptHandler(m_events->forIStream().inputReady(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleData));
+ m_events->adoptHandler(m_events->forIStream().outputError(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleWriteError));
+ m_events->adoptHandler(m_events->forIStream().inputShutdown(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleDisconnect));
+ m_events->adoptHandler(m_events->forIStream().outputShutdown(),
+ m_stream->getEventTarget(),
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleWriteError));
+}
+
+void
+ClientProxyUnknown::addProxyHandlers()
+{
+ assert(m_proxy != NULL);
+
+ m_events->adoptHandler(m_events->forClientProxy().ready(),
+ m_proxy,
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleReady));
+ m_events->adoptHandler(m_events->forClientProxy().disconnected(),
+ m_proxy,
+ new TMethodEventJob<ClientProxyUnknown>(this,
+ &ClientProxyUnknown::handleDisconnect));
+}
+
+void
+ClientProxyUnknown::removeHandlers()
+{
+ if (m_stream != NULL) {
+ m_events->removeHandler(m_events->forIStream().inputReady(),
+ m_stream->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().outputError(),
+ m_stream->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().inputShutdown(),
+ m_stream->getEventTarget());
+ m_events->removeHandler(m_events->forIStream().outputShutdown(),
+ m_stream->getEventTarget());
+ }
+ if (m_proxy != NULL) {
+ m_events->removeHandler(m_events->forClientProxy().ready(),
+ m_proxy);
+ m_events->removeHandler(m_events->forClientProxy().disconnected(),
+ m_proxy);
+ }
+}
+
+void
+ClientProxyUnknown::removeTimer()
+{
+ if (m_timer != NULL) {
+ m_events->deleteTimer(m_timer);
+ m_events->removeHandler(Event::kTimer, this);
+ m_timer = NULL;
+ }
+}
+
+void
+ClientProxyUnknown::handleData(const Event&, void*)
+{
+ LOG((CLOG_DEBUG1 "parsing hello reply"));
+
+ String name("<unknown>");
+ 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 (!ProtocolUtil::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 ClientProxy1_0(name, m_stream, m_events);
+ break;
+
+ case 1:
+ m_proxy = new ClientProxy1_1(name, m_stream, m_events);
+ break;
+
+ case 2:
+ m_proxy = new ClientProxy1_2(name, m_stream, m_events);
+ break;
+
+ case 3:
+ m_proxy = new ClientProxy1_3(name, m_stream, m_events);
+ break;
+
+ case 4:
+ m_proxy = new ClientProxy1_4(name, m_stream, m_server, m_events);
+ break;
+
+ case 5:
+ m_proxy = new ClientProxy1_5(name, m_stream, m_server, m_events);
+ break;
+
+ case 6:
+ m_proxy = new ClientProxy1_6(name, m_stream, m_server, m_events);
+ 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()));
+ ProtocolUtil::writef(m_stream,
+ kMsgEIncompatible,
+ kProtocolMajorVersion, kProtocolMinorVersion);
+ }
+ catch (XBadClient&) {
+ // client not behaving
+ LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str()));
+ ProtocolUtil::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
+ClientProxyUnknown::handleWriteError(const Event&, void*)
+{
+ LOG((CLOG_NOTE "error communicating with new client"));
+ sendFailure();
+}
+
+void
+ClientProxyUnknown::handleTimeout(const Event&, void*)
+{
+ LOG((CLOG_NOTE "new client is unresponsive"));
+ sendFailure();
+}
+
+void
+ClientProxyUnknown::handleDisconnect(const Event&, void*)
+{
+ LOG((CLOG_NOTE "new client disconnected"));
+ sendFailure();
+}
+
+void
+ClientProxyUnknown::handleReady(const Event&, void*)
+{
+ sendSuccess();
+}
diff --git a/src/lib/server/ClientProxyUnknown.h b/src/lib/server/ClientProxyUnknown.h
new file mode 100644
index 0000000..5d59402
--- /dev/null
+++ b/src/lib/server/ClientProxyUnknown.h
@@ -0,0 +1,71 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "base/Event.h"
+#include "base/EventTypes.h"
+
+class ClientProxy;
+class EventQueueTimer;
+namespace barrier { class IStream; }
+class Server;
+class IEventQueue;
+
+class ClientProxyUnknown {
+public:
+ ClientProxyUnknown(barrier::IStream* stream, double timeout, Server* server, IEventQueue* events);
+ ~ClientProxyUnknown();
+
+ //! @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.
+ */
+ ClientProxy* orphanClientProxy();
+
+ //! Get the stream
+ barrier::IStream* getStream() { return m_stream; }
+
+ //@}
+
+private:
+ void sendSuccess();
+ void sendFailure();
+ void addStreamHandlers();
+ void addProxyHandlers();
+ void removeHandlers();
+ void removeTimer();
+ void handleData(const Event&, void*);
+ void handleWriteError(const Event&, void*);
+ void handleTimeout(const Event&, void*);
+ void handleDisconnect(const Event&, void*);
+ void handleReady(const Event&, void*);
+
+private:
+ barrier::IStream* m_stream;
+ EventQueueTimer* m_timer;
+ ClientProxy* m_proxy;
+ bool m_ready;
+ Server* m_server;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/Config.cpp b/src/lib/server/Config.cpp
new file mode 100644
index 0000000..3cf60a5
--- /dev/null
+++ b/src/lib/server/Config.cpp
@@ -0,0 +1,2335 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/Config.h"
+
+#include "server/Server.h"
+#include "barrier/KeyMap.h"
+#include "barrier/key_types.h"
+#include "net/XSocket.h"
+#include "base/IEventQueue.h"
+#include "common/stdistream.h"
+#include "common/stdostream.h"
+
+#include <cstdlib>
+
+using namespace barrier::string;
+
+//
+// Config
+//
+
+Config::Config(IEventQueue* events) :
+ m_inputFilter(events),
+ m_hasLockToScreenAction(false),
+ m_events(events)
+{
+ // do nothing
+}
+
+Config::~Config()
+{
+ // do nothing
+}
+
+bool
+Config::addScreen(const String& 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, Cell()));
+
+ // add name
+ m_nameToCanonicalName.insert(std::make_pair(name, name));
+
+ return true;
+}
+
+bool
+Config::renameScreen(const String& oldName,
+ const String& newName)
+{
+ // get canonical name and find cell
+ String oldCanonical = getCanonicalName(oldName);
+ CellMap::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 (!CaselessCmp::equal(oldName, newName) &&
+ m_nameToCanonicalName.find(newName) != m_nameToCanonicalName.end()) {
+ return false;
+ }
+
+ // update cell
+ Cell 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
+ Name oldNameObj(this, oldName);
+ for (index = m_map.begin(); index != m_map.end(); ++index) {
+ index->second.rename(oldNameObj, newName);
+ }
+
+ // update alias targets
+ if (CaselessCmp::equal(oldName, oldCanonical)) {
+ for (NameMap::iterator iter = m_nameToCanonicalName.begin();
+ iter != m_nameToCanonicalName.end(); ++iter) {
+ if (CaselessCmp::equal(
+ iter->second, oldCanonical)) {
+ iter->second = newName;
+ }
+ }
+ }
+
+ return true;
+}
+
+void
+Config::removeScreen(const String& name)
+{
+ // get canonical name and find cell
+ String canonical = getCanonicalName(name);
+ CellMap::iterator index = m_map.find(canonical);
+ if (index == m_map.end()) {
+ return;
+ }
+
+ // remove from map
+ m_map.erase(index);
+
+ // disconnect
+ Name nameObj(this, name);
+ for (index = m_map.begin(); index != m_map.end(); ++index) {
+ index->second.remove(nameObj);
+ }
+
+ // remove aliases (and canonical name)
+ for (NameMap::iterator iter = m_nameToCanonicalName.begin();
+ iter != m_nameToCanonicalName.end(); ) {
+ if (iter->second == canonical) {
+ m_nameToCanonicalName.erase(iter++);
+ }
+ else {
+ ++index;
+ }
+ }
+}
+
+void
+Config::removeAllScreens()
+{
+ m_map.clear();
+ m_nameToCanonicalName.clear();
+}
+
+bool
+Config::addAlias(const String& canonical, const String& 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
+Config::removeAlias(const String& alias)
+{
+ // must not be a canonical name
+ if (m_map.find(alias) != m_map.end()) {
+ return false;
+ }
+
+ // find alias
+ NameMap::iterator index = m_nameToCanonicalName.find(alias);
+ if (index == m_nameToCanonicalName.end()) {
+ return false;
+ }
+
+ // remove alias
+ m_nameToCanonicalName.erase(index);
+
+ return true;
+}
+
+bool
+Config::removeAliases(const String& canonical)
+{
+ // must be a canonical name
+ if (m_map.find(canonical) == m_map.end()) {
+ return false;
+ }
+
+ // find and removing matching aliases
+ for (NameMap::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
+Config::removeAllAliases()
+{
+ // remove all names
+ m_nameToCanonicalName.clear();
+
+ // put the canonical names back in
+ for (CellMap::iterator index = m_map.begin();
+ index != m_map.end(); ++index) {
+ m_nameToCanonicalName.insert(
+ std::make_pair(index->first, index->first));
+ }
+}
+
+bool
+Config::connect(const String& srcName,
+ EDirection srcSide,
+ float srcStart, float srcEnd,
+ const String& dstName,
+ float dstStart, float dstEnd)
+{
+ assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
+
+ // find source cell
+ CellMap::iterator index = m_map.find(getCanonicalName(srcName));
+ if (index == m_map.end()) {
+ return false;
+ }
+
+ // add link
+ CellEdge srcEdge(srcSide, Interval(srcStart, srcEnd));
+ CellEdge dstEdge(dstName, srcSide, Interval(dstStart, dstEnd));
+ return index->second.add(srcEdge, dstEdge);
+}
+
+bool
+Config::disconnect(const String& srcName, EDirection srcSide)
+{
+ assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
+
+ // find source cell
+ CellMap::iterator index = m_map.find(srcName);
+ if (index == m_map.end()) {
+ return false;
+ }
+
+ // disconnect side
+ index->second.remove(srcSide);
+
+ return true;
+}
+
+bool
+Config::disconnect(const String& srcName, EDirection srcSide, float position)
+{
+ assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
+
+ // find source cell
+ CellMap::iterator index = m_map.find(srcName);
+ if (index == m_map.end()) {
+ return false;
+ }
+
+ // disconnect side
+ index->second.remove(srcSide, position);
+
+ return true;
+}
+
+void
+Config::setBarrierAddress(const NetworkAddress& addr)
+{
+ m_barrierAddress = addr;
+}
+
+bool
+Config::addOption(const String& name, OptionID option, OptionValue value)
+{
+ // find options
+ ScreenOptions* options = NULL;
+ if (name.empty()) {
+ options = &m_globalOptions;
+ }
+ else {
+ CellMap::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
+Config::removeOption(const String& name, OptionID option)
+{
+ // find options
+ ScreenOptions* options = NULL;
+ if (name.empty()) {
+ options = &m_globalOptions;
+ }
+ else {
+ CellMap::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
+Config::removeOptions(const String& name)
+{
+ // find options
+ ScreenOptions* options = NULL;
+ if (name.empty()) {
+ options = &m_globalOptions;
+ }
+ else {
+ CellMap::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
+Config::isValidScreenName(const String& 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
+ String::size_type b = 0;
+ for (;;) {
+ // accept trailing .
+ if (b == name.size()) {
+ break;
+ }
+
+ // find end of part
+ String::size_type e = name.find('.', b);
+ if (e == String::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 (String::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;
+}
+
+Config::const_iterator
+Config::begin() const
+{
+ return const_iterator(m_map.begin());
+}
+
+Config::const_iterator
+Config::end() const
+{
+ return const_iterator(m_map.end());
+}
+
+Config::all_const_iterator
+Config::beginAll() const
+{
+ return m_nameToCanonicalName.begin();
+}
+
+Config::all_const_iterator
+Config::endAll() const
+{
+ return m_nameToCanonicalName.end();
+}
+
+bool
+Config::isScreen(const String& name) const
+{
+ return (m_nameToCanonicalName.count(name) > 0);
+}
+
+bool
+Config::isCanonicalName(const String& name) const
+{
+ return (!name.empty() &&
+ CaselessCmp::equal(getCanonicalName(name), name));
+}
+
+String
+Config::getCanonicalName(const String& name) const
+{
+ NameMap::const_iterator index = m_nameToCanonicalName.find(name);
+ if (index == m_nameToCanonicalName.end()) {
+ return String();
+ }
+ else {
+ return index->second;
+ }
+}
+
+String
+Config::getNeighbor(const String& srcName, EDirection srcSide,
+ float position, float* positionOut) const
+{
+ assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
+
+ // find source cell
+ CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
+ if (index == m_map.end()) {
+ return String();
+ }
+
+ // find edge
+ const CellEdge* 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
+Config::hasNeighbor(const String& srcName, EDirection srcSide) const
+{
+ return hasNeighbor(srcName, srcSide, 0.0f, 1.0f);
+}
+
+bool
+Config::hasNeighbor(const String& srcName, EDirection srcSide,
+ float start, float end) const
+{
+ assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
+
+ // find source cell
+ CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
+ if (index == m_map.end()) {
+ return false;
+ }
+
+ return index->second.overlaps(CellEdge(srcSide, Interval(start, end)));
+}
+
+Config::link_const_iterator
+Config::beginNeighbor(const String& srcName) const
+{
+ CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
+ assert(index != m_map.end());
+ return index->second.begin();
+}
+
+Config::link_const_iterator
+Config::endNeighbor(const String& srcName) const
+{
+ CellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
+ assert(index != m_map.end());
+ return index->second.end();
+}
+
+const NetworkAddress&
+Config::getBarrierAddress() const
+{
+ return m_barrierAddress;
+}
+
+const Config::ScreenOptions*
+Config::getOptions(const String& name) const
+{
+ // find options
+ const ScreenOptions* options = NULL;
+ if (name.empty()) {
+ options = &m_globalOptions;
+ }
+ else {
+ CellMap::const_iterator index = m_map.find(name);
+ if (index != m_map.end()) {
+ options = &index->second.m_options;
+ }
+ }
+
+ // return options
+ return options;
+}
+
+bool
+Config::hasLockToScreenAction() const
+{
+ return m_hasLockToScreenAction;
+}
+
+bool
+Config::operator==(const Config& x) const
+{
+ if (m_barrierAddress != x.m_barrierAddress) {
+ 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 (CellMap::const_iterator index1 = m_map.begin(),
+ index2 = x.m_map.begin();
+ index1 != m_map.end(); ++index1, ++index2) {
+ // compare names
+ if (!CaselessCmp::equal(index1->first, index2->first)) {
+ return false;
+ }
+
+ // compare cells
+ if (index1->second != index2->second) {
+ return false;
+ }
+ }
+
+ for (NameMap::const_iterator index1 = m_nameToCanonicalName.begin(),
+ index2 = x.m_nameToCanonicalName.begin();
+ index1 != m_nameToCanonicalName.end();
+ ++index1, ++index2) {
+ if (!CaselessCmp::equal(index1->first, index2->first) ||
+ !CaselessCmp::equal(index1->second, index2->second)) {
+ return false;
+ }
+ }
+
+ // compare input filters
+ if (m_inputFilter != x.m_inputFilter) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+Config::operator!=(const Config& x) const
+{
+ return !operator==(x);
+}
+
+void
+Config::read(ConfigReadContext& context)
+{
+ Config tmp(m_events);
+ while (context.getStream()) {
+ tmp.readSection(context);
+ }
+ *this = tmp;
+}
+
+const char*
+Config::dirName(EDirection dir)
+{
+ static const char* s_name[] = { "left", "right", "up", "down" };
+
+ assert(dir >= kFirstDirection && dir <= kLastDirection);
+
+ return s_name[dir - kFirstDirection];
+}
+
+InputFilter*
+Config::getInputFilter()
+{
+ return &m_inputFilter;
+}
+
+String
+Config::formatInterval(const Interval& x)
+{
+ if (x.first == 0.0f && x.second == 1.0f) {
+ return "";
+ }
+ return barrier::string::sprintf("(%d,%d)", (int)(x.first * 100.0f + 0.5f),
+ (int)(x.second * 100.0f + 0.5f));
+}
+
+void
+Config::readSection(ConfigReadContext& 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";
+
+ String 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
+ String::size_type i = line.find_first_not_of(" \t", sizeof(s_section) - 1);
+ if (i == String::npos) {
+ throw XConfigRead(s, "section name is missing");
+ }
+ String name = line.substr(i);
+ i = name.find_first_of(" \t");
+ if (i != String::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
+Config::readSectionOptions(ConfigReadContext& s)
+{
+ String line;
+ while (s.readLine(line)) {
+ // check for end of section
+ if (line == "end") {
+ return;
+ }
+
+ // parse argument: `nameAndArgs = [values][;[values]]'
+ // nameAndArgs := <name>[(arg[,...])]
+ // values := valueAndArgs[,valueAndArgs]...
+ // valueAndArgs := <value>[(arg[,...])]
+ String::size_type i = 0;
+ String name, value;
+ ConfigReadContext::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_barrierAddress = NetworkAddress(value, kDefaultPort);
+ m_barrierAddress.resolve();
+ }
+ catch (XSocketAddress& e) {
+ throw XConfigRead(s,
+ String("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 == "switchNeedsShift") {
+ addOption("", kOptionScreenSwitchNeedsShift, s.parseBoolean(value));
+ }
+ else if (name == "switchNeedsControl") {
+ addOption("", kOptionScreenSwitchNeedsControl, s.parseBoolean(value));
+ }
+ else if (name == "switchNeedsAlt") {
+ addOption("", kOptionScreenSwitchNeedsAlt, s.parseBoolean(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 if (name == "clipboardSharing") {
+ addOption("", kOptionClipboardSharing, 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
+ InputFilter::Rule 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 == String::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
+Config::readSectionScreens(ConfigReadContext& s)
+{
+ String line;
+ String 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: `<name>=<value>'
+ String::size_type i = line.find_first_of(" \t=");
+ if (i == 0) {
+ throw XConfigRead(s, "missing argument name");
+ }
+ if (i == String::npos) {
+ throw XConfigRead(s, "missing =");
+ }
+ String name = line.substr(0, i);
+ i = line.find_first_not_of(" \t", i);
+ if (i == String::npos || line[i] != '=') {
+ throw XConfigRead(s, "missing =");
+ }
+ i = line.find_first_not_of(" \t", i + 1);
+ String value;
+ if (i != String::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 == "altgr") {
+ addOption(screen, kOptionModifierMapForAltGr,
+ 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 if (name == "preserveFocus") {
+ addOption(screen, kOptionScreenPreserveFocus,
+ s.parseBoolean(value));
+ }
+ else {
+ // unknown argument
+ throw XConfigRead(s, "unknown argument \"%{1}\"", name);
+ }
+ }
+ }
+ throw XConfigRead(s, "unexpected end of screens section");
+}
+
+void
+Config::readSectionLinks(ConfigReadContext& s)
+{
+ String line;
+ String 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: `<name>[(<s0>,<e0>)]=<value>[(<s1>,<e1>)]'
+ // 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).
+ String::size_type i = 0;
+ String side, dstScreen, srcArgString, dstArgString;
+ ConfigReadContext::ArgList srcArgs, dstArgs;
+ s.parseNameWithArgs("link", line, "=", i, side, srcArgs);
+ ++i;
+ s.parseNameWithArgs("screen", line, "", i, dstScreen, dstArgs);
+ Interval srcInterval(s.parseInterval(srcArgs));
+ Interval 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
+Config::readSectionAliases(ConfigReadContext& s)
+{
+ String line;
+ String 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");
+}
+
+
+InputFilter::Condition*
+Config::parseCondition(ConfigReadContext& s,
+ const String& name, const std::vector<String>& args)
+{
+ if (name == "keystroke") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for condition: keystroke(modifiers+key)");
+ }
+
+ IPlatformScreen::KeyInfo* keyInfo = s.parseKeystroke(args[0]);
+
+ return new InputFilter::KeystrokeCondition(m_events, keyInfo);
+ }
+
+ if (name == "mousebutton") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for condition: mousebutton(modifiers+button)");
+ }
+
+ IPlatformScreen::ButtonInfo* mouseInfo = s.parseMouse(args[0]);
+
+ return new InputFilter::MouseButtonCondition(m_events, mouseInfo);
+ }
+
+ if (name == "connect") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for condition: connect([screen])");
+ }
+
+ String 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 InputFilter::ScreenConnectedCondition(m_events, screen);
+ }
+
+ throw XConfigRead(s, "unknown argument \"%{1}\"", name);
+}
+
+void
+Config::parseAction(ConfigReadContext& s,
+ const String& name, const std::vector<String>& args,
+ InputFilter::Rule& rule, bool activate)
+{
+ InputFilter::Action* 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::KeyInfo* keyInfo;
+ if (args.size() == 1) {
+ keyInfo = s.parseKeystroke(args[0]);
+ }
+ else {
+ std::set<String> screens;
+ parseScreens(s, args[1], screens);
+ keyInfo = s.parseKeystroke(args[0], screens);
+ }
+
+ if (name == "keystroke") {
+ IPlatformScreen::KeyInfo* keyInfo2 =
+ IKeyState::KeyInfo::alloc(*keyInfo);
+ action = new InputFilter::KeystrokeAction(m_events, keyInfo2, true);
+ rule.adoptAction(action, true);
+ action = new InputFilter::KeystrokeAction(m_events, keyInfo, false);
+ activate = false;
+ }
+ else if (name == "keyDown") {
+ action = new InputFilter::KeystrokeAction(m_events, keyInfo, true);
+ }
+ else {
+ action = new InputFilter::KeystrokeAction(m_events, keyInfo, false);
+ }
+ }
+
+ else if (name == "mousebutton" ||
+ name == "mouseDown" || name == "mouseUp") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for action: mousebutton(modifiers+button)");
+ }
+
+ IPlatformScreen::ButtonInfo* mouseInfo = s.parseMouse(args[0]);
+
+ if (name == "mousebutton") {
+ IPlatformScreen::ButtonInfo* mouseInfo2 =
+ IPlatformScreen::ButtonInfo::alloc(*mouseInfo);
+ action = new InputFilter::MouseButtonAction(m_events, mouseInfo2, true);
+ rule.adoptAction(action, true);
+ action = new InputFilter::MouseButtonAction(m_events, mouseInfo, false);
+ activate = false;
+ }
+ else if (name == "mouseDown") {
+ action = new InputFilter::MouseButtonAction(m_events, mouseInfo, true);
+ }
+ else {
+ action = new InputFilter::MouseButtonAction(m_events, 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 InputFilter::ModifierAction(mask, ~mask);
+ }
+*/
+
+ else if (name == "switchToScreen") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for action: switchToScreen(name)");
+ }
+
+ String screen = args[0];
+ if (isScreen(screen)) {
+ screen = getCanonicalName(screen);
+ }
+ else if (!screen.empty()) {
+ throw XConfigRead(s, "unknown screen name in switchToScreen");
+ }
+
+ action = new InputFilter::SwitchToScreenAction(m_events, screen);
+ }
+
+ else if (name == "switchInDirection") {
+ if (args.size() != 1) {
+ throw XConfigRead(s, "syntax for action: switchInDirection(<left|right|up|down>)");
+ }
+
+ 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 InputFilter::SwitchInDirectionAction(m_events, direction);
+ }
+
+ else if (name == "lockCursorToScreen") {
+ if (args.size() > 1) {
+ throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])");
+ }
+
+ InputFilter::LockCursorToScreenAction::Mode mode =
+ InputFilter::LockCursorToScreenAction::kToggle;
+ if (args.size() == 1) {
+ if (args[0] == "off") {
+ mode = InputFilter::LockCursorToScreenAction::kOff;
+ }
+ else if (args[0] == "on") {
+ mode = InputFilter::LockCursorToScreenAction::kOn;
+ }
+ else if (args[0] == "toggle") {
+ mode = InputFilter::LockCursorToScreenAction::kToggle;
+ }
+ else {
+ throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])");
+ }
+ }
+
+ if (mode != InputFilter::LockCursorToScreenAction::kOff) {
+ m_hasLockToScreenAction = true;
+ }
+
+ action = new InputFilter::LockCursorToScreenAction(m_events, mode);
+ }
+
+ else if (name == "keyboardBroadcast") {
+ if (args.size() > 2) {
+ throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])");
+ }
+
+ InputFilter::KeyboardBroadcastAction::Mode mode =
+ InputFilter::KeyboardBroadcastAction::kToggle;
+ if (args.size() >= 1) {
+ if (args[0] == "off") {
+ mode = InputFilter::KeyboardBroadcastAction::kOff;
+ }
+ else if (args[0] == "on") {
+ mode = InputFilter::KeyboardBroadcastAction::kOn;
+ }
+ else if (args[0] == "toggle") {
+ mode = InputFilter::KeyboardBroadcastAction::kToggle;
+ }
+ else {
+ throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])");
+ }
+ }
+
+ std::set<String> screens;
+ if (args.size() >= 2) {
+ parseScreens(s, args[1], screens);
+ }
+
+ action = new InputFilter::KeyboardBroadcastAction(m_events, mode, screens);
+ }
+
+ else {
+ throw XConfigRead(s, "unknown action argument \"%{1}\"", name);
+ }
+
+ rule.adoptAction(action, activate);
+}
+
+void
+Config::parseScreens(ConfigReadContext& c,
+ const String& s, std::set<String>& screens) const
+{
+ screens.clear();
+
+ String::size_type i = 0;
+ while (i < s.size()) {
+ // find end of next screen name
+ String::size_type j = s.find(':', i);
+ if (j == String::npos) {
+ j = s.size();
+ }
+
+ // extract name
+ String 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()) {
+ String name = getCanonicalName(rawName);
+ if (name.empty()) {
+ throw XConfigRead(c, "unknown screen name \"%{1}\"", rawName);
+ }
+ screens.insert(name);
+ }
+
+ // next
+ i = j + 1;
+ }
+}
+
+const char*
+Config::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 == kOptionModifierMapForAltGr) {
+ return "altgr";
+ }
+ 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 == kOptionScreenSwitchNeedsShift) {
+ return "switchNeedsShift";
+ }
+ if (id == kOptionScreenSwitchNeedsControl) {
+ return "switchNeedsControl";
+ }
+ if (id == kOptionScreenSwitchNeedsAlt) {
+ return "switchNeedsAlt";
+ }
+ if (id == kOptionScreenSaverSync) {
+ return "screenSaverSync";
+ }
+ if (id == kOptionXTestXineramaUnaware) {
+ return "xtestIsXineramaUnaware";
+ }
+ if (id == kOptionRelativeMouseMoves) {
+ return "relativeMouseMoves";
+ }
+ if (id == kOptionWin32KeepForeground) {
+ return "win32KeepForeground";
+ }
+ if (id == kOptionScreenPreserveFocus) {
+ return "preserveFocus";
+ }
+ if (id == kOptionClipboardSharing) {
+ return "clipboardSharing";
+ }
+ return NULL;
+}
+
+String
+Config::getOptionValue(OptionID id, OptionValue value)
+{
+ if (id == kOptionHalfDuplexCapsLock ||
+ id == kOptionHalfDuplexNumLock ||
+ id == kOptionHalfDuplexScrollLock ||
+ id == kOptionScreenSwitchNeedsShift ||
+ id == kOptionScreenSwitchNeedsControl ||
+ id == kOptionScreenSwitchNeedsAlt ||
+ id == kOptionScreenSaverSync ||
+ id == kOptionXTestXineramaUnaware ||
+ id == kOptionRelativeMouseMoves ||
+ id == kOptionWin32KeepForeground ||
+ id == kOptionScreenPreserveFocus ||
+ id == kOptionClipboardSharing) {
+ return (value != 0) ? "true" : "false";
+ }
+ if (id == kOptionModifierMapForShift ||
+ id == kOptionModifierMapForControl ||
+ id == kOptionModifierMapForAlt ||
+ id == kOptionModifierMapForAltGr ||
+ id == kOptionModifierMapForMeta ||
+ id == kOptionModifierMapForSuper) {
+ switch (value) {
+ case kKeyModifierIDShift:
+ return "shift";
+
+ case kKeyModifierIDControl:
+ return "ctrl";
+
+ case kKeyModifierIDAlt:
+ return "alt";
+
+ case kKeyModifierIDAltGr:
+ return "altgr";
+
+ case kKeyModifierIDMeta:
+ return "meta";
+
+ case kKeyModifierIDSuper:
+ return "super";
+
+ default:
+ return "none";
+ }
+ }
+ if (id == kOptionHeartbeat ||
+ id == kOptionScreenSwitchCornerSize ||
+ id == kOptionScreenSwitchDelay ||
+ id == kOptionScreenSwitchTwoTap) {
+ return barrier::string::sprintf("%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 "";
+}
+
+
+//
+// Config::Name
+//
+
+Config::Name::Name(Config* config, const String& name) :
+ m_config(config),
+ m_name(config->getCanonicalName(name))
+{
+ // do nothing
+}
+
+bool
+Config::Name::operator==(const String& name) const
+{
+ String canonical = m_config->getCanonicalName(name);
+ return CaselessCmp::equal(canonical, m_name);
+}
+
+
+//
+// Config::CellEdge
+//
+
+Config::CellEdge::CellEdge(EDirection side, float position)
+{
+ init("", side, Interval(position, position));
+}
+
+Config::CellEdge::CellEdge(EDirection side, const Interval& interval)
+{
+ assert(interval.first >= 0.0f);
+ assert(interval.second <= 1.0f);
+ assert(interval.first < interval.second);
+
+ init("", side, interval);
+}
+
+Config::CellEdge::CellEdge(const String& name,
+ EDirection side, const Interval& interval)
+{
+ assert(interval.first >= 0.0f);
+ assert(interval.second <= 1.0f);
+ assert(interval.first < interval.second);
+
+ init(name, side, interval);
+}
+
+Config::CellEdge::~CellEdge()
+{
+ // do nothing
+}
+
+void
+Config::CellEdge::init(const String& name, EDirection side,
+ const Interval& interval)
+{
+ assert(side != kNoDirection);
+
+ m_name = name;
+ m_side = side;
+ m_interval = interval;
+}
+
+Config::Interval
+Config::CellEdge::getInterval() const
+{
+ return m_interval;
+}
+
+void
+Config::CellEdge::setName(const String& newName)
+{
+ m_name = newName;
+}
+
+String
+Config::CellEdge::getName() const
+{
+ return m_name;
+}
+
+EDirection
+Config::CellEdge::getSide() const
+{
+ return m_side;
+}
+
+bool
+Config::CellEdge::overlaps(const CellEdge& edge) const
+{
+ const Interval& x = m_interval;
+ const Interval& 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
+Config::CellEdge::isInside(float x) const
+{
+ return (x >= m_interval.first && x < m_interval.second);
+}
+
+float
+Config::CellEdge::transform(float x) const
+{
+ return (x - m_interval.first) / (m_interval.second - m_interval.first);
+}
+
+
+float
+Config::CellEdge::inverseTransform(float x) const
+{
+ return x * (m_interval.second - m_interval.first) + m_interval.first;
+}
+
+bool
+Config::CellEdge::operator<(const CellEdge& o) const
+{
+ if (static_cast<int>(m_side) < static_cast<int>(o.m_side)) {
+ return true;
+ }
+ else if (static_cast<int>(m_side) > static_cast<int>(o.m_side)) {
+ return false;
+ }
+
+ return (m_interval.first < o.m_interval.first);
+}
+
+bool
+Config::CellEdge::operator==(const CellEdge& x) const
+{
+ return (m_side == x.m_side && m_interval == x.m_interval);
+}
+
+bool
+Config::CellEdge::operator!=(const CellEdge& x) const
+{
+ return !operator==(x);
+}
+
+
+//
+// Config::Cell
+//
+
+bool
+Config::Cell::add(const CellEdge& src, const CellEdge& 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
+Config::Cell::remove(EDirection side)
+{
+ for (EdgeLinks::iterator j = m_neighbors.begin();
+ j != m_neighbors.end(); ) {
+ if (j->first.getSide() == side) {
+ m_neighbors.erase(j++);
+ }
+ else {
+ ++j;
+ }
+ }
+}
+
+void
+Config::Cell::remove(EDirection side, float position)
+{
+ for (EdgeLinks::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
+Config::Cell::remove(const Name& name)
+{
+ for (EdgeLinks::iterator j = m_neighbors.begin();
+ j != m_neighbors.end(); ) {
+ if (name == j->second.getName()) {
+ m_neighbors.erase(j++);
+ }
+ else {
+ ++j;
+ }
+ }
+}
+
+void
+Config::Cell::rename(const Name& oldName, const String& newName)
+{
+ for (EdgeLinks::iterator j = m_neighbors.begin();
+ j != m_neighbors.end(); ++j) {
+ if (oldName == j->second.getName()) {
+ j->second.setName(newName);
+ }
+ }
+}
+
+bool
+Config::Cell::hasEdge(const CellEdge& edge) const
+{
+ EdgeLinks::const_iterator i = m_neighbors.find(edge);
+ return (i != m_neighbors.end() && i->first == edge);
+}
+
+bool
+Config::Cell::overlaps(const CellEdge& edge) const
+{
+ EdgeLinks::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
+Config::Cell::getLink(EDirection side, float position,
+ const CellEdge*& src, const CellEdge*& dst) const
+{
+ CellEdge edge(side, position);
+ EdgeLinks::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
+Config::Cell::operator==(const Cell& 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 (EdgeLinks::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 (!CaselessCmp::equal(index1->second.getName(),
+ index2->second.getName())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+Config::Cell::operator!=(const Cell& x) const
+{
+ return !operator==(x);
+}
+
+Config::Cell::const_iterator
+Config::Cell::begin() const
+{
+ return m_neighbors.begin();
+}
+
+Config::Cell::const_iterator
+Config::Cell::end() const
+{
+ return m_neighbors.end();
+}
+
+
+//
+// Config I/O
+//
+
+std::istream&
+operator>>(std::istream& s, Config& config)
+{
+ ConfigReadContext context(s);
+ config.read(context);
+ return s;
+}
+
+std::ostream&
+operator<<(std::ostream& s, const Config& config)
+{
+ // screens section
+ s << "section: screens" << std::endl;
+ for (Config::const_iterator screen = config.begin();
+ screen != config.end(); ++screen) {
+ s << "\t" << screen->c_str() << ":" << std::endl;
+ const Config::ScreenOptions* options = config.getOptions(*screen);
+ if (options != NULL && options->size() > 0) {
+ for (Config::ScreenOptions::const_iterator
+ option = options->begin();
+ option != options->end(); ++option) {
+ const char* name = Config::getOptionName(option->first);
+ String value = Config::getOptionValue(option->first,
+ option->second);
+ if (name != NULL && !value.empty()) {
+ s << "\t\t" << name << " = " << value << std::endl;
+ }
+ }
+ }
+ }
+ s << "end" << std::endl;
+
+ // links section
+ String neighbor;
+ s << "section: links" << std::endl;
+ for (Config::const_iterator screen = config.begin();
+ screen != config.end(); ++screen) {
+ s << "\t" << screen->c_str() << ":" << std::endl;
+
+ for (Config::link_const_iterator
+ link = config.beginNeighbor(*screen),
+ nend = config.endNeighbor(*screen); link != nend; ++link) {
+ s << "\t\t" << Config::dirName(link->first.getSide()) <<
+ Config::formatInterval(link->first.getInterval()) <<
+ " = " << link->second.getName().c_str() <<
+ Config::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<String, String,
+ CaselessCmp> CMNameMap;
+ CMNameMap aliases;
+ for (Config::NameMap::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
+ String 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 Config::ScreenOptions* options = config.getOptions("");
+ if (options != NULL && options->size() > 0) {
+ for (Config::ScreenOptions::const_iterator
+ option = options->begin();
+ option != options->end(); ++option) {
+ const char* name = Config::getOptionName(option->first);
+ String value = Config::getOptionValue(option->first,
+ option->second);
+ if (name != NULL && !value.empty()) {
+ s << "\t" << name << " = " << value << std::endl;
+ }
+ }
+ }
+ if (config.m_barrierAddress.isValid()) {
+ s << "\taddress = " <<
+ config.m_barrierAddress.getHostname().c_str() << std::endl;
+ }
+ s << config.m_inputFilter.format("\t");
+ s << "end" << std::endl;
+
+ return s;
+}
+
+
+//
+// ConfigReadContext
+//
+
+ConfigReadContext::ConfigReadContext(std::istream& s, SInt32 firstLine) :
+ m_stream(s),
+ m_line(firstLine - 1)
+{
+ // do nothing
+}
+
+ConfigReadContext::~ConfigReadContext()
+{
+ // do nothing
+}
+
+bool
+ConfigReadContext::readLine(String& line)
+{
+ ++m_line;
+ while (std::getline(m_stream, line)) {
+ // strip leading whitespace
+ String::size_type i = line.find_first_not_of(" \t");
+ if (i != String::npos) {
+ line.erase(0, i);
+ }
+
+ // strip comments and then trailing whitespace
+ i = line.find('#');
+ if (i != String::npos) {
+ line.erase(i);
+ }
+ i = line.find_last_not_of(" \r\t");
+ if (i != String::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}",
+ barrier::string::sprintf("%#2x", line[i]));
+ }
+ }
+
+ return true;
+ }
+
+ // next line
+ ++m_line;
+ }
+ return false;
+}
+
+UInt32
+ConfigReadContext::getLineNumber() const
+{
+ return m_line;
+}
+
+bool
+ConfigReadContext::operator!() const
+{
+ return !m_stream;
+}
+
+OptionValue
+ConfigReadContext::parseBoolean(const String& arg) const
+{
+ if (CaselessCmp::equal(arg, "true")) {
+ return static_cast<OptionValue>(true);
+ }
+ if (CaselessCmp::equal(arg, "false")) {
+ return static_cast<OptionValue>(false);
+ }
+ throw XConfigRead(*this, "invalid boolean argument \"%{1}\"", arg);
+}
+
+OptionValue
+ConfigReadContext::parseInt(const String& 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<OptionValue>(tmp);
+ if (value != tmp) {
+ // out of range
+ throw XConfigRead(*this, "integer argument \"%{1}\" out of range", arg);
+ }
+ return value;
+}
+
+OptionValue
+ConfigReadContext::parseModifierKey(const String& arg) const
+{
+ if (CaselessCmp::equal(arg, "shift")) {
+ return static_cast<OptionValue>(kKeyModifierIDShift);
+ }
+ if (CaselessCmp::equal(arg, "ctrl")) {
+ return static_cast<OptionValue>(kKeyModifierIDControl);
+ }
+ if (CaselessCmp::equal(arg, "alt")) {
+ return static_cast<OptionValue>(kKeyModifierIDAlt);
+ }
+ if (CaselessCmp::equal(arg, "altgr")) {
+ return static_cast<OptionValue>(kKeyModifierIDAltGr);
+ }
+ if (CaselessCmp::equal(arg, "meta")) {
+ return static_cast<OptionValue>(kKeyModifierIDMeta);
+ }
+ if (CaselessCmp::equal(arg, "super")) {
+ return static_cast<OptionValue>(kKeyModifierIDSuper);
+ }
+ if (CaselessCmp::equal(arg, "none")) {
+ return static_cast<OptionValue>(kKeyModifierIDNull);
+ }
+ throw XConfigRead(*this, "invalid argument \"%{1}\"", arg);
+}
+
+OptionValue
+ConfigReadContext::parseCorner(const String& arg) const
+{
+ if (CaselessCmp::equal(arg, "left")) {
+ return kTopLeftMask | kBottomLeftMask;
+ }
+ else if (CaselessCmp::equal(arg, "right")) {
+ return kTopRightMask | kBottomRightMask;
+ }
+ else if (CaselessCmp::equal(arg, "top")) {
+ return kTopLeftMask | kTopRightMask;
+ }
+ else if (CaselessCmp::equal(arg, "bottom")) {
+ return kBottomLeftMask | kBottomRightMask;
+ }
+ else if (CaselessCmp::equal(arg, "top-left")) {
+ return kTopLeftMask;
+ }
+ else if (CaselessCmp::equal(arg, "top-right")) {
+ return kTopRightMask;
+ }
+ else if (CaselessCmp::equal(arg, "bottom-left")) {
+ return kBottomLeftMask;
+ }
+ else if (CaselessCmp::equal(arg, "bottom-right")) {
+ return kBottomRightMask;
+ }
+ else if (CaselessCmp::equal(arg, "none")) {
+ return kNoCornerMask;
+ }
+ else if (CaselessCmp::equal(arg, "all")) {
+ return kAllCornersMask;
+ }
+ throw XConfigRead(*this, "invalid argument \"%{1}\"", arg);
+}
+
+OptionValue
+ConfigReadContext::parseCorners(const String& args) const
+{
+ // find first token
+ String::size_type i = args.find_first_not_of(" \t", 0);
+ if (i == String::npos) {
+ throw XConfigRead(*this, "missing corner argument");
+ }
+ String::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 != String::npos) {
+ // parse +/-
+ bool add;
+ if (args[i] == '-') {
+ add = false;
+ }
+ else if (args[i] == '+') {
+ add = true;
+ }
+ else {
+ throw XConfigRead(*this,
+ "invalid corner operator \"%{1}\"",
+ String(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 == String::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;
+}
+
+Config::Interval
+ConfigReadContext::parseInterval(const ArgList& args) const
+{
+ if (args.size() == 0) {
+ return Config::Interval(0.0f, 1.0f);
+ }
+ if (args.size() != 2 || args[0].empty() || args[1].empty()) {
+ throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
+ }
+
+ char* end;
+ double startValue = strtod(args[0].c_str(), &end);
+ if (end[0] != '\0') {
+ throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args));
+ }
+ double endValue = strtod(args[1].c_str(), &end);
+ 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 Config::Interval(startValue / 100.0f, endValue / 100.0f);
+}
+
+void
+ConfigReadContext::parseNameWithArgs(
+ const String& type, const String& line,
+ const String& delim, String::size_type& index,
+ String& name, ArgList& args) const
+{
+ // skip leading whitespace
+ String::size_type i = line.find_first_not_of(" \t", index);
+ if (i == String::npos) {
+ throw XConfigRead(*this, String("missing ") + type);
+ }
+
+ // find end of name
+ String::size_type j = line.find_first_of(" \t(" + delim, i);
+ if (j == String::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') == String::npos);
+
+ // skip whitespace
+ i = line.find_first_not_of(" \t", j);
+ if (i == String::npos && needDelim) {
+ // expected delimiter but didn't find it
+ throw XConfigRead(*this, String("missing ") + delim[0]);
+ }
+ if (i == String::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 != String::npos) {
+ // extract arg
+ String arg(line.substr(i, j - i));
+ i = j;
+
+ // trim whitespace
+ j = arg.find_first_not_of(" \t");
+ if (j != String::npos) {
+ arg.erase(0, j);
+ }
+ j = arg.find_last_not_of(" \t");
+ if (j != String::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 == String::npos) {
+ // expected )
+ throw XConfigRead(*this, "missing )");
+ }
+
+ // eat ')'
+ ++i;
+
+ // skip whitespace
+ j = line.find_first_not_of(" \t", i);
+ if (j == String::npos && needDelim) {
+ // expected delimiter but didn't find it
+ throw XConfigRead(*this, String("missing ") + delim[0]);
+ }
+
+ // verify delimiter
+ if (needDelim && delim.find(line[j]) == String::npos) {
+ throw XConfigRead(*this, String("expected ") + delim[0]);
+ }
+
+ if (j == String::npos) {
+ j = line.length();
+ }
+
+ index = j;
+ return;
+}
+
+IPlatformScreen::KeyInfo*
+ConfigReadContext::parseKeystroke(const String& keystroke) const
+{
+ return parseKeystroke(keystroke, std::set<String>());
+}
+
+IPlatformScreen::KeyInfo*
+ConfigReadContext::parseKeystroke(const String& keystroke,
+ const std::set<String>& screens) const
+{
+ String s = keystroke;
+
+ KeyModifierMask mask;
+ if (!barrier::KeyMap::parseModifiers(s, mask)) {
+ throw XConfigRead(*this, "unable to parse key modifiers");
+ }
+
+ KeyID key;
+ if (!barrier::KeyMap::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::KeyInfo::alloc(key, mask, 0, 0, screens);
+}
+
+IPlatformScreen::ButtonInfo*
+ConfigReadContext::parseMouse(const String& mouse) const
+{
+ String s = mouse;
+
+ KeyModifierMask mask;
+ if (!barrier::KeyMap::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::ButtonInfo::alloc(button, mask);
+}
+
+KeyModifierMask
+ConfigReadContext::parseModifier(const String& modifiers) const
+{
+ String s = modifiers;
+
+ KeyModifierMask mask;
+ if (!barrier::KeyMap::parseModifiers(s, mask)) {
+ throw XConfigRead(*this, "unable to parse modifiers");
+ }
+
+ if (mask == 0) {
+ throw XConfigRead(*this, "no modifiers specified");
+ }
+
+ return mask;
+}
+
+String
+ConfigReadContext::concatArgs(const ArgList& args)
+{
+ String s("(");
+ for (size_t i = 0; i < args.size(); ++i) {
+ if (i != 0) {
+ s += ",";
+ }
+ s += args[i];
+ }
+ s += ")";
+ return s;
+}
+
+
+//
+// Config I/O exceptions
+//
+
+XConfigRead::XConfigRead(const ConfigReadContext& context,
+ const String& error) :
+ m_error(barrier::string::sprintf("line %d: %s",
+ context.getLineNumber(), error.c_str()))
+{
+ // do nothing
+}
+
+XConfigRead::XConfigRead(const ConfigReadContext& context,
+ const char* errorFmt, const String& arg) :
+ m_error(barrier::string::sprintf("line %d: ", context.getLineNumber()) +
+ barrier::string::format(errorFmt, arg.c_str()))
+{
+ // do nothing
+}
+
+XConfigRead::~XConfigRead() _NOEXCEPT
+{
+ // do nothing
+}
+
+String
+XConfigRead::getWhat() const throw()
+{
+ return format("XConfigRead", "read error: %{1}", m_error.c_str());
+}
diff --git a/src/lib/server/Config.h b/src/lib/server/Config.h
new file mode 100644
index 0000000..69b01c4
--- /dev/null
+++ b/src/lib/server/Config.h
@@ -0,0 +1,549 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/InputFilter.h"
+#include "barrier/option_types.h"
+#include "barrier/protocol_types.h"
+#include "barrier/IPlatformScreen.h"
+#include "net/NetworkAddress.h"
+#include "base/String.h"
+#include "base/XBase.h"
+#include "common/stdmap.h"
+#include "common/stdset.h"
+
+#include <iosfwd>
+
+class Config;
+class ConfigReadContext;
+class IEventQueue;
+
+namespace std {
+template <>
+struct iterator_traits<Config> {
+ typedef String value_type;
+ typedef ptrdiff_t difference_type;
+ typedef bidirectional_iterator_tag iterator_category;
+ typedef String* pointer;
+ typedef String& 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 Config {
+public:
+ typedef std::map<OptionID, OptionValue> ScreenOptions;
+ typedef std::pair<float, float> Interval;
+
+ class CellEdge {
+ public:
+ CellEdge(EDirection side, float position);
+ CellEdge(EDirection side, const Interval&);
+ CellEdge(const String& name, EDirection side, const Interval&);
+ ~CellEdge();
+
+ Interval getInterval() const;
+ void setName(const String& newName);
+ String getName() const;
+ EDirection getSide() const;
+ bool overlaps(const CellEdge&) 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 CellEdge&) const;
+
+ // compares side and interval
+ bool operator==(const CellEdge&) const;
+ bool operator!=(const CellEdge&) const;
+
+ private:
+ void init(const String& name, EDirection side,
+ const Interval&);
+
+ private:
+ String m_name;
+ EDirection m_side;
+ Interval m_interval;
+ };
+
+private:
+ class Name {
+ public:
+ Name(Config*, const String& name);
+
+ bool operator==(const String& name) const;
+
+ private:
+ Config* m_config;
+ String m_name;
+ };
+
+ class Cell {
+ private:
+ typedef std::map<CellEdge, CellEdge> EdgeLinks;
+
+ public:
+ typedef EdgeLinks::const_iterator const_iterator;
+
+ bool add(const CellEdge& src, const CellEdge& dst);
+ void remove(EDirection side);
+ void remove(EDirection side, float position);
+ void remove(const Name& destinationName);
+ void rename(const Name& oldName, const String& newName);
+
+ bool hasEdge(const CellEdge&) const;
+ bool overlaps(const CellEdge&) const;
+
+ bool getLink(EDirection side, float position,
+ const CellEdge*& src, const CellEdge*& dst) const;
+
+ bool operator==(const Cell&) const;
+ bool operator!=(const Cell&) const;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ private:
+ EdgeLinks m_neighbors;
+
+ public:
+ ScreenOptions m_options;
+ };
+ typedef std::map<String, Cell, barrier::string::CaselessCmp> CellMap;
+ typedef std::map<String, String, barrier::string::CaselessCmp> NameMap;
+
+public:
+ typedef Cell::const_iterator link_const_iterator;
+ typedef CellMap::const_iterator internal_const_iterator;
+ typedef NameMap::const_iterator all_const_iterator;
+ class const_iterator : std::iterator_traits<Config> {
+ 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;
+ }
+ String operator*() { return m_i->first; }
+ const String* 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;
+ };
+
+ Config(IEventQueue* events);
+ virtual ~Config();
+
+#ifdef TEST_ENV
+ Config() : m_inputFilter(NULL) { }
+#endif
+
+ //! @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 String& name);
+
+ //! Rename screen
+ /*!
+ Renames a screen. All references to the name are updated.
+ Returns true iff successful.
+ */
+ bool renameScreen(const String& oldName,
+ const String& 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 String& 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 String& canonical,
+ const String& 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 String& 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 String& 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 String& srcName,
+ EDirection srcSide,
+ float srcStart, float srcEnd,
+ const String& 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 String& 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 String& srcName,
+ EDirection srcSide, float position);
+
+ //! Set server address
+ /*!
+ Set the barrier listen addresses. There is no default address so
+ this must be called to run a server using this configuration.
+ */
+ void setBarrierAddress(const NetworkAddress&);
+
+ //! 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 String& 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 String& 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 String& name);
+
+ //! Get the hot key input filter
+ /*!
+ Returns the hot key input filter. Clients can modify hotkeys using
+ that object.
+ */
+ virtual InputFilter*
+ getInputFilter();
+
+ //@}
+ //! @name accessors
+ //@{
+
+ //! Test screen name validity
+ /*!
+ Returns true iff \c name is a valid screen name.
+ */
+ bool isValidScreenName(const String& 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.
+ */
+ virtual bool isScreen(const String& name) const;
+
+ //! Test for canonical screen name
+ /*!
+ Returns true iff \c name is the canonical name of a screen.
+ */
+ bool isCanonicalName(const String& 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.
+ */
+ String getCanonicalName(const String& 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.
+ */
+ String getNeighbor(const String&, 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 String&, 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 String&, EDirection,
+ float start, float end) const;
+
+ //! Get beginning neighbor iterator
+ link_const_iterator beginNeighbor(const String&) const;
+ //! Get ending neighbor iterator
+ link_const_iterator endNeighbor(const String&) const;
+
+ //! Get the server address
+ const NetworkAddress&
+ getBarrierAddress() 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 ScreenOptions*
+ getOptions(const String& 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 Config&) const;
+ //! Compare configurations
+ bool operator!=(const Config&) const;
+
+ //! Read configuration
+ /*!
+ Reads a configuration from a context. Throws XConfigRead on error
+ and context is unchanged.
+ */
+ void read(ConfigReadContext& context);
+
+ //! Read configuration
+ /*!
+ Reads a configuration from a stream. Throws XConfigRead on error.
+ */
+ friend std::istream&
+ operator>>(std::istream&, Config&);
+
+ //! Write configuration
+ /*!
+ Writes a configuration to a stream.
+ */
+ friend std::ostream&
+ operator<<(std::ostream&, const Config&);
+
+ //! 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 String formatInterval(const Interval&);
+
+ //@}
+
+private:
+ void readSection(ConfigReadContext&);
+ void readSectionOptions(ConfigReadContext&);
+ void readSectionScreens(ConfigReadContext&);
+ void readSectionLinks(ConfigReadContext&);
+ void readSectionAliases(ConfigReadContext&);
+
+ InputFilter::Condition*
+ parseCondition(ConfigReadContext&,
+ const String& condition,
+ const std::vector<String>& args);
+ void parseAction(ConfigReadContext&,
+ const String& action,
+ const std::vector<String>& args,
+ InputFilter::Rule&, bool activate);
+
+ void parseScreens(ConfigReadContext&, const String&,
+ std::set<String>& screens) const;
+ static const char* getOptionName(OptionID);
+ static String getOptionValue(OptionID, OptionValue);
+
+private:
+ CellMap m_map;
+ NameMap m_nameToCanonicalName;
+ NetworkAddress m_barrierAddress;
+ ScreenOptions m_globalOptions;
+ InputFilter m_inputFilter;
+ bool m_hasLockToScreenAction;
+ IEventQueue* m_events;
+};
+
+//! Configuration read context
+/*!
+Maintains a context when reading a configuration from a stream.
+*/
+class ConfigReadContext {
+public:
+ typedef std::vector<String> ArgList;
+
+ ConfigReadContext(std::istream&, SInt32 firstLine = 1);
+ ~ConfigReadContext();
+
+ bool readLine(String&);
+ UInt32 getLineNumber() const;
+
+ bool operator!() const;
+
+ OptionValue parseBoolean(const String&) const;
+ OptionValue parseInt(const String&) const;
+ OptionValue parseModifierKey(const String&) const;
+ OptionValue parseCorner(const String&) const;
+ OptionValue parseCorners(const String&) const;
+ Config::Interval
+ parseInterval(const ArgList& args) const;
+ void parseNameWithArgs(
+ const String& type, const String& line,
+ const String& delim, String::size_type& index,
+ String& name, ArgList& args) const;
+ IPlatformScreen::KeyInfo*
+ parseKeystroke(const String& keystroke) const;
+ IPlatformScreen::KeyInfo*
+ parseKeystroke(const String& keystroke,
+ const std::set<String>& screens) const;
+ IPlatformScreen::ButtonInfo*
+ parseMouse(const String& mouse) const;
+ KeyModifierMask parseModifier(const String& modifiers) const;
+ std::istream& getStream() const { return m_stream; };
+
+private:
+ // not implemented
+ ConfigReadContext& operator=(const ConfigReadContext&);
+
+ static String 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 ConfigReadContext& context, const String&);
+ XConfigRead(const ConfigReadContext& context,
+ const char* errorFmt, const String& arg);
+ virtual ~XConfigRead() _NOEXCEPT;
+
+protected:
+ // XBase overrides
+ virtual String getWhat() const throw();
+
+private:
+ String m_error;
+};
diff --git a/src/lib/server/InputFilter.cpp b/src/lib/server/InputFilter.cpp
new file mode 100644
index 0000000..9e73f45
--- /dev/null
+++ b/src/lib/server/InputFilter.cpp
@@ -0,0 +1,1090 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/InputFilter.h"
+#include "server/Server.h"
+#include "server/PrimaryClient.h"
+#include "barrier/KeyMap.h"
+#include "base/EventQueue.h"
+#include "base/Log.h"
+#include "base/TMethodEventJob.h"
+
+#include <cstdlib>
+#include <cstring>
+
+// -----------------------------------------------------------------------------
+// Input Filter Condition Classes
+// -----------------------------------------------------------------------------
+InputFilter::Condition::Condition()
+{
+ // do nothing
+}
+
+InputFilter::Condition::~Condition()
+{
+ // do nothing
+}
+
+void
+InputFilter::Condition::enablePrimary(PrimaryClient*)
+{
+ // do nothing
+}
+
+void
+InputFilter::Condition::disablePrimary(PrimaryClient*)
+{
+ // do nothing
+}
+
+InputFilter::KeystrokeCondition::KeystrokeCondition(
+ IEventQueue* events, IPlatformScreen::KeyInfo* info) :
+ m_id(0),
+ m_key(info->m_key),
+ m_mask(info->m_mask),
+ m_events(events)
+{
+ free(info);
+}
+
+InputFilter::KeystrokeCondition::KeystrokeCondition(
+ IEventQueue* events, KeyID key, KeyModifierMask mask) :
+ m_id(0),
+ m_key(key),
+ m_mask(mask),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::KeystrokeCondition::~KeystrokeCondition()
+{
+ // do nothing
+}
+
+KeyID
+InputFilter::KeystrokeCondition::getKey() const
+{
+ return m_key;
+}
+
+KeyModifierMask
+InputFilter::KeystrokeCondition::getMask() const
+{
+ return m_mask;
+}
+
+InputFilter::Condition*
+InputFilter::KeystrokeCondition::clone() const
+{
+ return new KeystrokeCondition(m_events, m_key, m_mask);
+}
+
+String
+InputFilter::KeystrokeCondition::format() const
+{
+ return barrier::string::sprintf("keystroke(%s)",
+ barrier::KeyMap::formatKey(m_key, m_mask).c_str());
+}
+
+InputFilter::EFilterStatus
+InputFilter::KeystrokeCondition::match(const Event& event)
+{
+ EFilterStatus status;
+
+ // check for hotkey events
+ Event::Type type = event.getType();
+ if (type == m_events->forIPrimaryScreen().hotKeyDown()) {
+ status = kActivate;
+ }
+ else if (type == m_events->forIPrimaryScreen().hotKeyUp()) {
+ status = kDeactivate;
+ }
+ else {
+ return kNoMatch;
+ }
+
+ // check if it's our hotkey
+ IPrimaryScreen::HotKeyInfo* kinfo =
+ static_cast<IPlatformScreen::HotKeyInfo*>(event.getData());
+ if (kinfo->m_id != m_id) {
+ return kNoMatch;
+ }
+
+ return status;
+}
+
+void
+InputFilter::KeystrokeCondition::enablePrimary(PrimaryClient* primary)
+{
+ m_id = primary->registerHotKey(m_key, m_mask);
+}
+
+void
+InputFilter::KeystrokeCondition::disablePrimary(PrimaryClient* primary)
+{
+ primary->unregisterHotKey(m_id);
+ m_id = 0;
+}
+
+InputFilter::MouseButtonCondition::MouseButtonCondition(
+ IEventQueue* events, IPlatformScreen::ButtonInfo* info) :
+ m_button(info->m_button),
+ m_mask(info->m_mask),
+ m_events(events)
+{
+ free(info);
+}
+
+InputFilter::MouseButtonCondition::MouseButtonCondition(
+ IEventQueue* events, ButtonID button, KeyModifierMask mask) :
+ m_button(button),
+ m_mask(mask),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::MouseButtonCondition::~MouseButtonCondition()
+{
+ // do nothing
+}
+
+ButtonID
+InputFilter::MouseButtonCondition::getButton() const
+{
+ return m_button;
+}
+
+KeyModifierMask
+InputFilter::MouseButtonCondition::getMask() const
+{
+ return m_mask;
+}
+
+InputFilter::Condition*
+InputFilter::MouseButtonCondition::clone() const
+{
+ return new MouseButtonCondition(m_events, m_button, m_mask);
+}
+
+String
+InputFilter::MouseButtonCondition::format() const
+{
+ String key = barrier::KeyMap::formatKey(kKeyNone, m_mask);
+ if (!key.empty()) {
+ key += "+";
+ }
+ return barrier::string::sprintf("mousebutton(%s%d)", key.c_str(), m_button);
+}
+
+InputFilter::EFilterStatus
+InputFilter::MouseButtonCondition::match(const Event& event)
+{
+ static const KeyModifierMask s_ignoreMask =
+ KeyModifierAltGr | KeyModifierCapsLock |
+ KeyModifierNumLock | KeyModifierScrollLock;
+
+ EFilterStatus status;
+
+ // check for hotkey events
+ Event::Type type = event.getType();
+ if (type == m_events->forIPrimaryScreen().buttonDown()) {
+ status = kActivate;
+ }
+ else if (type == m_events->forIPrimaryScreen().buttonUp()) {
+ 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::ButtonInfo* minfo =
+ static_cast<IPlatformScreen::ButtonInfo*>(event.getData());
+ if (minfo->m_button != m_button ||
+ (minfo->m_mask & ~s_ignoreMask) != m_mask) {
+ return kNoMatch;
+ }
+
+ return status;
+}
+
+InputFilter::ScreenConnectedCondition::ScreenConnectedCondition(
+ IEventQueue* events, const String& screen) :
+ m_screen(screen),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::ScreenConnectedCondition::~ScreenConnectedCondition()
+{
+ // do nothing
+}
+
+InputFilter::Condition*
+InputFilter::ScreenConnectedCondition::clone() const
+{
+ return new ScreenConnectedCondition(m_events, m_screen);
+}
+
+String
+InputFilter::ScreenConnectedCondition::format() const
+{
+ return barrier::string::sprintf("connect(%s)", m_screen.c_str());
+}
+
+InputFilter::EFilterStatus
+InputFilter::ScreenConnectedCondition::match(const Event& event)
+{
+ if (event.getType() == m_events->forServer().connected()) {
+ Server::ScreenConnectedInfo* info =
+ static_cast<Server::ScreenConnectedInfo*>(event.getData());
+ if (m_screen == info->m_screen || m_screen.empty()) {
+ return kActivate;
+ }
+ }
+
+ return kNoMatch;
+}
+
+// -----------------------------------------------------------------------------
+// Input Filter Action Classes
+// -----------------------------------------------------------------------------
+InputFilter::Action::Action()
+{
+ // do nothing
+}
+
+InputFilter::Action::~Action()
+{
+ // do nothing
+}
+
+InputFilter::LockCursorToScreenAction::LockCursorToScreenAction(
+ IEventQueue* events, Mode mode) :
+ m_mode(mode),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::LockCursorToScreenAction::Mode
+InputFilter::LockCursorToScreenAction::getMode() const
+{
+ return m_mode;
+}
+
+InputFilter::Action*
+InputFilter::LockCursorToScreenAction::clone() const
+{
+ return new LockCursorToScreenAction(*this);
+}
+
+String
+InputFilter::LockCursorToScreenAction::format() const
+{
+ static const char* s_mode[] = { "off", "on", "toggle" };
+
+ return barrier::string::sprintf("lockCursorToScreen(%s)", s_mode[m_mode]);
+}
+
+void
+InputFilter::LockCursorToScreenAction::perform(const Event& event)
+{
+ static const Server::LockCursorToScreenInfo::State s_state[] = {
+ Server::LockCursorToScreenInfo::kOff,
+ Server::LockCursorToScreenInfo::kOn,
+ Server::LockCursorToScreenInfo::kToggle
+ };
+
+ // send event
+ Server::LockCursorToScreenInfo* info =
+ Server::LockCursorToScreenInfo::alloc(s_state[m_mode]);
+ m_events->addEvent(Event(m_events->forServer().lockCursorToScreen(),
+ event.getTarget(), info,
+ Event::kDeliverImmediately));
+}
+
+InputFilter::SwitchToScreenAction::SwitchToScreenAction(
+ IEventQueue* events, const String& screen) :
+ m_screen(screen),
+ m_events(events)
+{
+ // do nothing
+}
+
+String
+InputFilter::SwitchToScreenAction::getScreen() const
+{
+ return m_screen;
+}
+
+InputFilter::Action*
+InputFilter::SwitchToScreenAction::clone() const
+{
+ return new SwitchToScreenAction(*this);
+}
+
+String
+InputFilter::SwitchToScreenAction::format() const
+{
+ return barrier::string::sprintf("switchToScreen(%s)", m_screen.c_str());
+}
+
+void
+InputFilter::SwitchToScreenAction::perform(const Event& event)
+{
+ // pick screen name. if m_screen is empty then use the screen from
+ // event if it has one.
+ String screen = m_screen;
+ if (screen.empty() && event.getType() == m_events->forServer().connected()) {
+ Server::ScreenConnectedInfo* info =
+ static_cast<Server::ScreenConnectedInfo*>(event.getData());
+ screen = info->m_screen;
+ }
+
+ // send event
+ Server::SwitchToScreenInfo* info =
+ Server::SwitchToScreenInfo::alloc(screen);
+ m_events->addEvent(Event(m_events->forServer().switchToScreen(),
+ event.getTarget(), info,
+ Event::kDeliverImmediately));
+}
+
+InputFilter::SwitchInDirectionAction::SwitchInDirectionAction(
+ IEventQueue* events, EDirection direction) :
+ m_direction(direction),
+ m_events(events)
+{
+ // do nothing
+}
+
+EDirection
+InputFilter::SwitchInDirectionAction::getDirection() const
+{
+ return m_direction;
+}
+
+InputFilter::Action*
+InputFilter::SwitchInDirectionAction::clone() const
+{
+ return new SwitchInDirectionAction(*this);
+}
+
+String
+InputFilter::SwitchInDirectionAction::format() const
+{
+ static const char* s_names[] = {
+ "",
+ "left",
+ "right",
+ "up",
+ "down"
+ };
+
+ return barrier::string::sprintf("switchInDirection(%s)", s_names[m_direction]);
+}
+
+void
+InputFilter::SwitchInDirectionAction::perform(const Event& event)
+{
+ Server::SwitchInDirectionInfo* info =
+ Server::SwitchInDirectionInfo::alloc(m_direction);
+ m_events->addEvent(Event(m_events->forServer().switchInDirection(),
+ event.getTarget(), info,
+ Event::kDeliverImmediately));
+}
+
+InputFilter::KeyboardBroadcastAction::KeyboardBroadcastAction(
+ IEventQueue* events, Mode mode) :
+ m_mode(mode),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::KeyboardBroadcastAction::KeyboardBroadcastAction(
+ IEventQueue* events,
+ Mode mode,
+ const std::set<String>& screens) :
+ m_mode(mode),
+ m_screens(IKeyState::KeyInfo::join(screens)),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::KeyboardBroadcastAction::Mode
+InputFilter::KeyboardBroadcastAction::getMode() const
+{
+ return m_mode;
+}
+
+std::set<String>
+InputFilter::KeyboardBroadcastAction::getScreens() const
+{
+ std::set<String> screens;
+ IKeyState::KeyInfo::split(m_screens.c_str(), screens);
+ return screens;
+}
+
+InputFilter::Action*
+InputFilter::KeyboardBroadcastAction::clone() const
+{
+ return new KeyboardBroadcastAction(*this);
+}
+
+String
+InputFilter::KeyboardBroadcastAction::format() const
+{
+ static const char* s_mode[] = { "off", "on", "toggle" };
+ static const char* s_name = "keyboardBroadcast";
+
+ if (m_screens.empty() || m_screens[0] == '*') {
+ return barrier::string::sprintf("%s(%s)", s_name, s_mode[m_mode]);
+ }
+ else {
+ return barrier::string::sprintf("%s(%s,%.*s)", s_name, s_mode[m_mode],
+ m_screens.size() - 2,
+ m_screens.c_str() + 1);
+ }
+}
+
+void
+InputFilter::KeyboardBroadcastAction::perform(const Event& event)
+{
+ static const Server::KeyboardBroadcastInfo::State s_state[] = {
+ Server::KeyboardBroadcastInfo::kOff,
+ Server::KeyboardBroadcastInfo::kOn,
+ Server::KeyboardBroadcastInfo::kToggle
+ };
+
+ // send event
+ Server::KeyboardBroadcastInfo* info =
+ Server::KeyboardBroadcastInfo::alloc(s_state[m_mode], m_screens);
+ m_events->addEvent(Event(m_events->forServer().keyboardBroadcast(),
+ event.getTarget(), info,
+ Event::kDeliverImmediately));
+}
+
+InputFilter::KeystrokeAction::KeystrokeAction(
+ IEventQueue* events, IPlatformScreen::KeyInfo* info, bool press) :
+ m_keyInfo(info),
+ m_press(press),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::KeystrokeAction::~KeystrokeAction()
+{
+ free(m_keyInfo);
+}
+
+void
+InputFilter::KeystrokeAction::adoptInfo(IPlatformScreen::KeyInfo* info)
+{
+ free(m_keyInfo);
+ m_keyInfo = info;
+}
+
+const IPlatformScreen::KeyInfo*
+InputFilter::KeystrokeAction::getInfo() const
+{
+ return m_keyInfo;
+}
+
+bool
+InputFilter::KeystrokeAction::isOnPress() const
+{
+ return m_press;
+}
+
+InputFilter::Action*
+InputFilter::KeystrokeAction::clone() const
+{
+ IKeyState::KeyInfo* info = IKeyState::KeyInfo::alloc(*m_keyInfo);
+ return new KeystrokeAction(m_events, info, m_press);
+}
+
+String
+InputFilter::KeystrokeAction::format() const
+{
+ const char* type = formatName();
+
+ if (m_keyInfo->m_screens[0] == '\0') {
+ return barrier::string::sprintf("%s(%s)", type,
+ barrier::KeyMap::formatKey(m_keyInfo->m_key,
+ m_keyInfo->m_mask).c_str());
+ }
+ else if (m_keyInfo->m_screens[0] == '*') {
+ return barrier::string::sprintf("%s(%s,*)", type,
+ barrier::KeyMap::formatKey(m_keyInfo->m_key,
+ m_keyInfo->m_mask).c_str());
+ }
+ else {
+ return barrier::string::sprintf("%s(%s,%.*s)", type,
+ barrier::KeyMap::formatKey(m_keyInfo->m_key,
+ m_keyInfo->m_mask).c_str(),
+ strlen(m_keyInfo->m_screens + 1) - 1,
+ m_keyInfo->m_screens + 1);
+ }
+}
+
+void
+InputFilter::KeystrokeAction::perform(const Event& event)
+{
+ Event::Type type = m_press ?
+ m_events->forIKeyState().keyDown() :
+ m_events->forIKeyState().keyUp();
+
+ m_events->addEvent(Event(m_events->forIPrimaryScreen().fakeInputBegin(),
+ event.getTarget(), NULL,
+ Event::kDeliverImmediately));
+ m_events->addEvent(Event(type, event.getTarget(), m_keyInfo,
+ Event::kDeliverImmediately |
+ Event::kDontFreeData));
+ m_events->addEvent(Event(m_events->forIPrimaryScreen().fakeInputEnd(),
+ event.getTarget(), NULL,
+ Event::kDeliverImmediately));
+}
+
+const char*
+InputFilter::KeystrokeAction::formatName() const
+{
+ return (m_press ? "keyDown" : "keyUp");
+}
+
+InputFilter::MouseButtonAction::MouseButtonAction(
+ IEventQueue* events, IPlatformScreen::ButtonInfo* info, bool press) :
+ m_buttonInfo(info),
+ m_press(press),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::MouseButtonAction::~MouseButtonAction()
+{
+ free(m_buttonInfo);
+}
+
+const IPlatformScreen::ButtonInfo*
+InputFilter::MouseButtonAction::getInfo() const
+{
+ return m_buttonInfo;
+}
+
+bool
+InputFilter::MouseButtonAction::isOnPress() const
+{
+ return m_press;
+}
+
+InputFilter::Action*
+InputFilter::MouseButtonAction::clone() const
+{
+ IPlatformScreen::ButtonInfo* info =
+ IPrimaryScreen::ButtonInfo::alloc(*m_buttonInfo);
+ return new MouseButtonAction(m_events, info, m_press);
+}
+
+String
+InputFilter::MouseButtonAction::format() const
+{
+ const char* type = formatName();
+
+ String key = barrier::KeyMap::formatKey(kKeyNone, m_buttonInfo->m_mask);
+ return barrier::string::sprintf("%s(%s%s%d)", type,
+ key.c_str(), key.empty() ? "" : "+",
+ m_buttonInfo->m_button);
+}
+
+void
+InputFilter::MouseButtonAction::perform(const Event& event)
+
+{
+ // send modifiers
+ IPlatformScreen::KeyInfo* modifierInfo = NULL;
+ if (m_buttonInfo->m_mask != 0) {
+ KeyID key = m_press ? kKeySetModifiers : kKeyClearModifiers;
+ modifierInfo =
+ IKeyState::KeyInfo::alloc(key, m_buttonInfo->m_mask, 0, 1);
+ m_events->addEvent(Event(m_events->forIKeyState().keyDown(),
+ event.getTarget(), modifierInfo,
+ Event::kDeliverImmediately));
+ }
+
+ // send button
+ Event::Type type = m_press ? m_events->forIPrimaryScreen().buttonDown() :
+ m_events->forIPrimaryScreen().buttonUp();
+ m_events->addEvent(Event(type, event.getTarget(), m_buttonInfo,
+ Event::kDeliverImmediately |
+ Event::kDontFreeData));
+}
+
+const char*
+InputFilter::MouseButtonAction::formatName() const
+{
+ return (m_press ? "mouseDown" : "mouseUp");
+}
+
+//
+// InputFilter::Rule
+//
+
+InputFilter::Rule::Rule() :
+ m_condition(NULL)
+{
+ // do nothing
+}
+
+InputFilter::Rule::Rule(Condition* adoptedCondition) :
+ m_condition(adoptedCondition)
+{
+ // do nothing
+}
+
+InputFilter::Rule::Rule(const Rule& rule) :
+ m_condition(NULL)
+{
+ copy(rule);
+}
+
+InputFilter::Rule::~Rule()
+{
+ clear();
+}
+
+InputFilter::Rule&
+InputFilter::Rule::operator=(const Rule& rule)
+{
+ if (&rule != this) {
+ copy(rule);
+ }
+ return *this;
+}
+
+void
+InputFilter::Rule::clear()
+{
+ delete m_condition;
+ for (ActionList::iterator i = m_activateActions.begin();
+ i != m_activateActions.end(); ++i) {
+ delete *i;
+ }
+ for (ActionList::iterator i = m_deactivateActions.begin();
+ i != m_deactivateActions.end(); ++i) {
+ delete *i;
+ }
+
+ m_condition = NULL;
+ m_activateActions.clear();
+ m_deactivateActions.clear();
+}
+
+void
+InputFilter::Rule::copy(const Rule& rule)
+{
+ clear();
+ if (rule.m_condition != NULL) {
+ m_condition = rule.m_condition->clone();
+ }
+ for (ActionList::const_iterator i = rule.m_activateActions.begin();
+ i != rule.m_activateActions.end(); ++i) {
+ m_activateActions.push_back((*i)->clone());
+ }
+ for (ActionList::const_iterator i = rule.m_deactivateActions.begin();
+ i != rule.m_deactivateActions.end(); ++i) {
+ m_deactivateActions.push_back((*i)->clone());
+ }
+}
+
+void
+InputFilter::Rule::setCondition(Condition* adopted)
+{
+ delete m_condition;
+ m_condition = adopted;
+}
+
+void
+InputFilter::Rule::adoptAction(Action* action, bool onActivation)
+{
+ if (action != NULL) {
+ if (onActivation) {
+ m_activateActions.push_back(action);
+ }
+ else {
+ m_deactivateActions.push_back(action);
+ }
+ }
+}
+
+void
+InputFilter::Rule::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
+InputFilter::Rule::replaceAction(Action* 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
+InputFilter::Rule::enable(PrimaryClient* primaryClient)
+{
+ if (m_condition != NULL) {
+ m_condition->enablePrimary(primaryClient);
+ }
+}
+
+void
+InputFilter::Rule::disable(PrimaryClient* primaryClient)
+{
+ if (m_condition != NULL) {
+ m_condition->disablePrimary(primaryClient);
+ }
+}
+
+bool
+InputFilter::Rule::handleEvent(const Event& event)
+{
+ // NULL condition never matches
+ if (m_condition == NULL) {
+ return false;
+ }
+
+ // match
+ const ActionList* 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 (ActionList::const_iterator i = actions->begin();
+ i != actions->end(); ++i) {
+ LOG((CLOG_DEBUG1 "hotkey: %s", (*i)->format().c_str()));
+ (*i)->perform(event);
+ }
+
+ return true;
+}
+
+String
+InputFilter::Rule::format() const
+{
+ String s;
+ if (m_condition != NULL) {
+ // condition
+ s += m_condition->format();
+ s += " = ";
+
+ // activate actions
+ ActionList::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 InputFilter::Condition*
+InputFilter::Rule::getCondition() const
+{
+ return m_condition;
+}
+
+UInt32
+InputFilter::Rule::getNumActions(bool onActivation) const
+{
+ if (onActivation) {
+ return static_cast<UInt32>(m_activateActions.size());
+ }
+ else {
+ return static_cast<UInt32>(m_deactivateActions.size());
+ }
+}
+
+const InputFilter::Action&
+InputFilter::Rule::getAction(bool onActivation, UInt32 index) const
+{
+ if (onActivation) {
+ return *m_activateActions[index];
+ }
+ else {
+ return *m_deactivateActions[index];
+ }
+}
+
+
+// -----------------------------------------------------------------------------
+// Input Filter Class
+// -----------------------------------------------------------------------------
+InputFilter::InputFilter(IEventQueue* events) :
+ m_primaryClient(NULL),
+ m_events(events)
+{
+ // do nothing
+}
+
+InputFilter::InputFilter(const InputFilter& x) :
+ m_ruleList(x.m_ruleList),
+ m_primaryClient(NULL),
+ m_events(x.m_events)
+{
+ setPrimaryClient(x.m_primaryClient);
+}
+
+InputFilter::~InputFilter()
+{
+ setPrimaryClient(NULL);
+}
+
+InputFilter&
+InputFilter::operator=(const InputFilter& x)
+{
+ if (&x != this) {
+ PrimaryClient* oldClient = m_primaryClient;
+ setPrimaryClient(NULL);
+
+ m_ruleList = x.m_ruleList;
+
+ setPrimaryClient(oldClient);
+ }
+ return *this;
+}
+
+void
+InputFilter::addFilterRule(const Rule& rule)
+{
+ m_ruleList.push_back(rule);
+ if (m_primaryClient != NULL) {
+ m_ruleList.back().enable(m_primaryClient);
+ }
+}
+
+void
+InputFilter::removeFilterRule(UInt32 index)
+{
+ if (m_primaryClient != NULL) {
+ m_ruleList[index].disable(m_primaryClient);
+ }
+ m_ruleList.erase(m_ruleList.begin() + index);
+}
+
+InputFilter::Rule&
+InputFilter::getRule(UInt32 index)
+{
+ return m_ruleList[index];
+}
+
+void
+InputFilter::setPrimaryClient(PrimaryClient* client)
+{
+ if (m_primaryClient == client) {
+ return;
+ }
+
+ if (m_primaryClient != NULL) {
+ for (RuleList::iterator rule = m_ruleList.begin();
+ rule != m_ruleList.end(); ++rule) {
+ rule->disable(m_primaryClient);
+ }
+
+ m_events->removeHandler(m_events->forIKeyState().keyDown(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIKeyState().keyUp(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIKeyState().keyRepeat(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().buttonDown(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().buttonUp(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().hotKeyDown(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().hotKeyUp(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forServer().connected(),
+ m_primaryClient->getEventTarget());
+ }
+
+ m_primaryClient = client;
+
+ if (m_primaryClient != NULL) {
+ m_events->adoptHandler(m_events->forIKeyState().keyDown(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIKeyState().keyUp(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIKeyState().keyRepeat(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().buttonDown(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().buttonUp(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().hotKeyDown(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().hotKeyUp(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+ m_events->adoptHandler(m_events->forServer().connected(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<InputFilter>(this,
+ &InputFilter::handleEvent));
+
+ for (RuleList::iterator rule = m_ruleList.begin();
+ rule != m_ruleList.end(); ++rule) {
+ rule->enable(m_primaryClient);
+ }
+ }
+}
+
+String
+InputFilter::format(const String& linePrefix) const
+{
+ String s;
+ for (RuleList::const_iterator i = m_ruleList.begin();
+ i != m_ruleList.end(); ++i) {
+ s += linePrefix;
+ s += i->format();
+ s += "\n";
+ }
+ return s;
+}
+
+UInt32
+InputFilter::getNumRules() const
+{
+ return static_cast<UInt32>(m_ruleList.size());
+}
+
+bool
+InputFilter::operator==(const InputFilter& 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<String> aList, bList;
+ for (RuleList::const_iterator i = m_ruleList.begin();
+ i != m_ruleList.end(); ++i) {
+ aList.push_back(i->format());
+ }
+ for (RuleList::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
+InputFilter::operator!=(const InputFilter& x) const
+{
+ return !operator==(x);
+}
+
+void
+InputFilter::handleEvent(const Event& event, void*)
+{
+ // copy event and adjust target
+ Event myEvent(event.getType(), this, event.getData(),
+ event.getFlags() | Event::kDontFreeData |
+ Event::kDeliverImmediately);
+
+ // let each rule try to match the event until one does
+ for (RuleList::iterator rule = m_ruleList.begin();
+ rule != m_ruleList.end(); ++rule) {
+ if (rule->handleEvent(myEvent)) {
+ // handled
+ return;
+ }
+ }
+
+ // not handled so pass through
+ m_events->addEvent(myEvent);
+}
diff --git a/src/lib/server/InputFilter.h b/src/lib/server/InputFilter.h
new file mode 100644
index 0000000..73afe97
--- /dev/null
+++ b/src/lib/server/InputFilter.h
@@ -0,0 +1,361 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "barrier/key_types.h"
+#include "barrier/mouse_types.h"
+#include "barrier/protocol_types.h"
+#include "barrier/IPlatformScreen.h"
+#include "base/String.h"
+#include "common/stdmap.h"
+#include "common/stdset.h"
+
+class PrimaryClient;
+class Event;
+class IEventQueue;
+
+class InputFilter {
+public:
+ // -------------------------------------------------------------------------
+ // Input Filter Condition Classes
+ // -------------------------------------------------------------------------
+ enum EFilterStatus {
+ kNoMatch,
+ kActivate,
+ kDeactivate
+ };
+
+ class Condition {
+ public:
+ Condition();
+ virtual ~Condition();
+
+ virtual Condition* clone() const = 0;
+ virtual String format() const = 0;
+
+ virtual EFilterStatus match(const Event&) = 0;
+
+ virtual void enablePrimary(PrimaryClient*);
+ virtual void disablePrimary(PrimaryClient*);
+ };
+
+ // KeystrokeCondition
+ class KeystrokeCondition : public Condition {
+ public:
+ KeystrokeCondition(IEventQueue* events, IPlatformScreen::KeyInfo*);
+ KeystrokeCondition(IEventQueue* events, KeyID key, KeyModifierMask mask);
+ virtual ~KeystrokeCondition();
+
+ KeyID getKey() const;
+ KeyModifierMask getMask() const;
+
+ // Condition overrides
+ virtual Condition* clone() const;
+ virtual String format() const;
+ virtual EFilterStatus match(const Event&);
+ virtual void enablePrimary(PrimaryClient*);
+ virtual void disablePrimary(PrimaryClient*);
+
+ private:
+ UInt32 m_id;
+ KeyID m_key;
+ KeyModifierMask m_mask;
+ IEventQueue* m_events;
+ };
+
+ // MouseButtonCondition
+ class MouseButtonCondition : public Condition {
+ public:
+ MouseButtonCondition(IEventQueue* events, IPlatformScreen::ButtonInfo*);
+ MouseButtonCondition(IEventQueue* events, ButtonID, KeyModifierMask mask);
+ virtual ~MouseButtonCondition();
+
+ ButtonID getButton() const;
+ KeyModifierMask getMask() const;
+
+ // Condition overrides
+ virtual Condition* clone() const;
+ virtual String format() const;
+ virtual EFilterStatus match(const Event&);
+
+ private:
+ ButtonID m_button;
+ KeyModifierMask m_mask;
+ IEventQueue* m_events;
+ };
+
+ // ScreenConnectedCondition
+ class ScreenConnectedCondition : public Condition {
+ public:
+ ScreenConnectedCondition(IEventQueue* events, const String& screen);
+ virtual ~ScreenConnectedCondition();
+
+ // Condition overrides
+ virtual Condition* clone() const;
+ virtual String format() const;
+ virtual EFilterStatus match(const Event&);
+
+ private:
+ String m_screen;
+ IEventQueue* m_events;
+ };
+
+ // -------------------------------------------------------------------------
+ // Input Filter Action Classes
+ // -------------------------------------------------------------------------
+
+ class Action {
+ public:
+ Action();
+ virtual ~Action();
+
+ virtual Action* clone() const = 0;
+ virtual String format() const = 0;
+
+ virtual void perform(const Event&) = 0;
+ };
+
+ // LockCursorToScreenAction
+ class LockCursorToScreenAction : public Action {
+ public:
+ enum Mode { kOff, kOn, kToggle };
+
+ LockCursorToScreenAction(IEventQueue* events, Mode = kToggle);
+
+ Mode getMode() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ private:
+ Mode m_mode;
+ IEventQueue* m_events;
+ };
+
+ // SwitchToScreenAction
+ class SwitchToScreenAction : public Action {
+ public:
+ SwitchToScreenAction(IEventQueue* events, const String& screen);
+
+ String getScreen() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ private:
+ String m_screen;
+ IEventQueue* m_events;
+ };
+
+ // SwitchInDirectionAction
+ class SwitchInDirectionAction : public Action {
+ public:
+ SwitchInDirectionAction(IEventQueue* events, EDirection);
+
+ EDirection getDirection() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ private:
+ EDirection m_direction;
+ IEventQueue* m_events;
+ };
+
+ // KeyboardBroadcastAction
+ class KeyboardBroadcastAction : public Action {
+ public:
+ enum Mode { kOff, kOn, kToggle };
+
+ KeyboardBroadcastAction(IEventQueue* events, Mode = kToggle);
+ KeyboardBroadcastAction(IEventQueue* events, Mode, const std::set<String>& screens);
+
+ Mode getMode() const;
+ std::set<String> getScreens() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ private:
+ Mode m_mode;
+ String m_screens;
+ IEventQueue* m_events;
+ };
+
+ // KeystrokeAction
+ class KeystrokeAction : public Action {
+ public:
+ KeystrokeAction(IEventQueue* events, IPlatformScreen::KeyInfo* adoptedInfo, bool press);
+ ~KeystrokeAction();
+
+ void adoptInfo(IPlatformScreen::KeyInfo*);
+ const IPlatformScreen::KeyInfo*
+ getInfo() const;
+ bool isOnPress() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ protected:
+ virtual const char* formatName() const;
+
+ private:
+ IPlatformScreen::KeyInfo* m_keyInfo;
+ bool m_press;
+ IEventQueue* m_events;
+ };
+
+ // MouseButtonAction -- modifier combinations not implemented yet
+ class MouseButtonAction : public Action {
+ public:
+ MouseButtonAction(IEventQueue* events,
+ IPlatformScreen::ButtonInfo* adoptedInfo,
+ bool press);
+ ~MouseButtonAction();
+
+ const IPlatformScreen::ButtonInfo*
+ getInfo() const;
+ bool isOnPress() const;
+
+ // Action overrides
+ virtual Action* clone() const;
+ virtual String format() const;
+ virtual void perform(const Event&);
+
+ protected:
+ virtual const char* formatName() const;
+
+ private:
+ IPlatformScreen::ButtonInfo* m_buttonInfo;
+ bool m_press;
+ IEventQueue* m_events;
+ };
+
+ class Rule {
+ public:
+ Rule();
+ Rule(Condition* adopted);
+ Rule(const Rule&);
+ ~Rule();
+
+ Rule& operator=(const Rule&);
+
+ // replace the condition
+ void setCondition(Condition* adopted);
+
+ // add an action to the rule
+ void adoptAction(Action*, bool onActivation);
+
+ // remove an action from the rule
+ void removeAction(bool onActivation, UInt32 index);
+
+ // replace an action in the rule
+ void replaceAction(Action* adopted,
+ bool onActivation, UInt32 index);
+
+ // enable/disable
+ void enable(PrimaryClient*);
+ void disable(PrimaryClient*);
+
+ // event handling
+ bool handleEvent(const Event&);
+
+ // convert rule to a string
+ String format() const;
+
+ // get the rule's condition
+ const Condition*
+ getCondition() const;
+
+ // get number of actions
+ UInt32 getNumActions(bool onActivation) const;
+
+ // get action by index
+ const Action& getAction(bool onActivation, UInt32 index) const;
+
+ private:
+ void clear();
+ void copy(const Rule&);
+
+ private:
+ typedef std::vector<Action*> ActionList;
+
+ Condition* m_condition;
+ ActionList m_activateActions;
+ ActionList m_deactivateActions;
+ };
+
+ // -------------------------------------------------------------------------
+ // Input Filter Class
+ // -------------------------------------------------------------------------
+ typedef std::vector<Rule> RuleList;
+
+ InputFilter(IEventQueue* events);
+ InputFilter(const InputFilter&);
+ virtual ~InputFilter();
+
+#ifdef TEST_ENV
+ InputFilter() : m_primaryClient(NULL) { }
+#endif
+
+ InputFilter& operator=(const InputFilter&);
+
+ // add rule, adopting the condition and the actions
+ void addFilterRule(const Rule& rule);
+
+ // remove a rule
+ void removeFilterRule(UInt32 index);
+
+ // get rule by index
+ Rule& getRule(UInt32 index);
+
+ // enable event filtering using the given primary client. disable
+ // if client is NULL.
+ virtual void setPrimaryClient(PrimaryClient* client);
+
+ // convert rules to a string
+ String format(const String& linePrefix) const;
+
+ // get number of rules
+ UInt32 getNumRules() const;
+
+ //! Compare filters
+ bool operator==(const InputFilter&) const;
+ //! Compare filters
+ bool operator!=(const InputFilter&) const;
+
+private:
+ // event handling
+ void handleEvent(const Event&, void*);
+
+private:
+ RuleList m_ruleList;
+ PrimaryClient* m_primaryClient;
+ IEventQueue* m_events;
+};
diff --git a/src/lib/server/PrimaryClient.cpp b/src/lib/server/PrimaryClient.cpp
new file mode 100644
index 0000000..4c9fe50
--- /dev/null
+++ b/src/lib/server/PrimaryClient.cpp
@@ -0,0 +1,274 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/PrimaryClient.h"
+
+#include "barrier/Screen.h"
+#include "barrier/Clipboard.h"
+#include "base/Log.h"
+
+//
+// PrimaryClient
+//
+
+PrimaryClient::PrimaryClient(const String& name, barrier::Screen* screen) :
+ BaseClientProxy(name),
+ m_screen(screen),
+ m_fakeInputCount(0)
+{
+ // all clipboards are clean
+ for (UInt32 i = 0; i < kClipboardEnd; ++i) {
+ m_clipboardDirty[i] = false;
+ }
+}
+
+PrimaryClient::~PrimaryClient()
+{
+ // do nothing
+}
+
+void
+PrimaryClient::reconfigure(UInt32 activeSides)
+{
+ m_screen->reconfigure(activeSides);
+}
+
+UInt32
+PrimaryClient::registerHotKey(KeyID key, KeyModifierMask mask)
+{
+ return m_screen->registerHotKey(key, mask);
+}
+
+void
+PrimaryClient::unregisterHotKey(UInt32 id)
+{
+ m_screen->unregisterHotKey(id);
+}
+
+void
+PrimaryClient::fakeInputBegin()
+{
+ if (++m_fakeInputCount == 1) {
+ m_screen->fakeInputBegin();
+ }
+}
+
+void
+PrimaryClient::fakeInputEnd()
+{
+ if (--m_fakeInputCount == 0) {
+ m_screen->fakeInputEnd();
+ }
+}
+
+SInt32
+PrimaryClient::getJumpZoneSize() const
+{
+ return m_screen->getJumpZoneSize();
+}
+
+void
+PrimaryClient::getCursorCenter(SInt32& x, SInt32& y) const
+{
+ m_screen->getCursorCenter(x, y);
+}
+
+KeyModifierMask
+PrimaryClient::getToggleMask() const
+{
+ return m_screen->pollActiveModifiers();
+}
+
+bool
+PrimaryClient::isLockedToScreen() const
+{
+ return m_screen->isLockedToScreen();
+}
+
+void*
+PrimaryClient::getEventTarget() const
+{
+ return m_screen->getEventTarget();
+}
+
+bool
+PrimaryClient::getClipboard(ClipboardID id, IClipboard* clipboard) const
+{
+ return m_screen->getClipboard(id, clipboard);
+}
+
+void
+PrimaryClient::getShape(SInt32& x, SInt32& y,
+ SInt32& width, SInt32& height) const
+{
+ m_screen->getShape(x, y, width, height);
+}
+
+void
+PrimaryClient::getCursorPos(SInt32& x, SInt32& y) const
+{
+ m_screen->getCursorPos(x, y);
+}
+
+void
+PrimaryClient::enable()
+{
+ m_screen->enable();
+}
+
+void
+PrimaryClient::disable()
+{
+ m_screen->disable();
+}
+
+void
+PrimaryClient::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
+PrimaryClient::leave()
+{
+ return m_screen->leave();
+}
+
+void
+PrimaryClient::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
+PrimaryClient::grabClipboard(ClipboardID id)
+{
+ // grab clipboard
+ m_screen->grabClipboard(id);
+
+ // clipboard is dirty (because someone else owns it now)
+ m_clipboardDirty[id] = true;
+}
+
+void
+PrimaryClient::setClipboardDirty(ClipboardID id, bool dirty)
+{
+ m_clipboardDirty[id] = dirty;
+}
+
+void
+PrimaryClient::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
+PrimaryClient::keyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton)
+{
+ // ignore
+}
+
+void
+PrimaryClient::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
+PrimaryClient::mouseDown(ButtonID)
+{
+ // ignore
+}
+
+void
+PrimaryClient::mouseUp(ButtonID)
+{
+ // ignore
+}
+
+void
+PrimaryClient::mouseMove(SInt32 x, SInt32 y)
+{
+ m_screen->warpCursor(x, y);
+}
+
+void
+PrimaryClient::mouseRelativeMove(SInt32, SInt32)
+{
+ // ignore
+}
+
+void
+PrimaryClient::mouseWheel(SInt32, SInt32)
+{
+ // ignore
+}
+
+void
+PrimaryClient::screensaver(bool)
+{
+ // ignore
+}
+
+void
+PrimaryClient::sendDragInfo(UInt32 fileCount, const char* info, size_t size)
+{
+ // ignore
+}
+
+void
+PrimaryClient::fileChunkSending(UInt8 mark, char* data, size_t dataSize)
+{
+ // ignore
+}
+
+void
+PrimaryClient::resetOptions()
+{
+ m_screen->resetOptions();
+}
+
+void
+PrimaryClient::setOptions(const OptionsList& options)
+{
+ m_screen->setOptions(options);
+}
diff --git a/src/lib/server/PrimaryClient.h b/src/lib/server/PrimaryClient.h
new file mode 100644
index 0000000..4296aaa
--- /dev/null
+++ b/src/lib/server/PrimaryClient.h
@@ -0,0 +1,156 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/BaseClientProxy.h"
+#include "barrier/protocol_types.h"
+
+namespace barrier { class Screen; }
+
+//! 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 PrimaryClient : public BaseClientProxy {
+public:
+ /*!
+ \c name is the name of the server and \p screen is primary screen.
+ */
+ PrimaryClient(const String& name, barrier::Screen* screen);
+ ~PrimaryClient();
+
+#ifdef TEST_ENV
+ PrimaryClient() : BaseClientProxy("") { }
+#endif
+
+ //! @name manipulators
+ //@{
+
+ //! Update configuration
+ /*!
+ Handles reconfiguration of jump zones.
+ */
+ virtual 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.
+ */
+ virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask);
+
+ //! Unregister a system hotkey
+ /*!
+ Unregisters a previously registered hot key.
+ */
+ virtual 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.
+ */
+ virtual 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 OptionsList& options);
+ virtual void sendDragInfo(UInt32 fileCount, const char* info, size_t size);
+ virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize);
+
+ virtual barrier::IStream*
+ getStream() const { return NULL; }
+ bool isPrimary() const { return true; }
+private:
+ barrier::Screen* m_screen;
+ bool m_clipboardDirty[kClipboardEnd];
+ SInt32 m_fakeInputCount;
+};
diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp
new file mode 100644
index 0000000..32153a6
--- /dev/null
+++ b/src/lib/server/Server.cpp
@@ -0,0 +1,2405 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server/Server.h"
+
+#include "server/ClientProxy.h"
+#include "server/ClientProxyUnknown.h"
+#include "server/PrimaryClient.h"
+#include "server/ClientListener.h"
+#include "barrier/FileChunk.h"
+#include "barrier/IPlatformScreen.h"
+#include "barrier/DropHelper.h"
+#include "barrier/option_types.h"
+#include "barrier/protocol_types.h"
+#include "barrier/XScreen.h"
+#include "barrier/XBarrier.h"
+#include "barrier/StreamChunker.h"
+#include "barrier/KeyState.h"
+#include "barrier/Screen.h"
+#include "barrier/PacketStreamFilter.h"
+#include "net/TCPSocket.h"
+#include "net/IDataSocket.h"
+#include "net/IListenSocket.h"
+#include "net/XSocket.h"
+#include "mt/Thread.h"
+#include "arch/Arch.h"
+#include "base/TMethodJob.h"
+#include "base/IEventQueue.h"
+#include "base/Log.h"
+#include "base/TMethodEventJob.h"
+#include "common/stdexcept.h"
+
+#include <cstring>
+#include <cstdlib>
+#include <sstream>
+#include <fstream>
+#include <ctime>
+
+//
+// Server
+//
+
+Server::Server(
+ Config& config,
+ PrimaryClient* primaryClient,
+ barrier::Screen* screen,
+ IEventQueue* events,
+ ServerArgs const& args) :
+ m_mock(false),
+ m_primaryClient(primaryClient),
+ m_active(primaryClient),
+ m_seqNum(0),
+ m_xDelta(0),
+ m_yDelta(0),
+ m_xDelta2(0),
+ m_yDelta2(0),
+ m_config(&config),
+ m_inputFilter(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_switchNeedsShift(false),
+ m_switchNeedsControl(false),
+ m_switchNeedsAlt(false),
+ m_relativeMoves(false),
+ m_keyboardBroadcasting(false),
+ m_lockedToScreen(false),
+ m_screen(screen),
+ m_events(events),
+ m_sendFileThread(NULL),
+ m_writeToDropDirThread(NULL),
+ m_ignoreFileTransfer(false),
+ m_enableClipboard(true),
+ m_sendDragInfoThread(NULL),
+ m_waitDragInfoThread(true),
+ m_args(args)
+{
+ // must have a primary client and it must have a canonical name
+ assert(m_primaryClient != NULL);
+ assert(config.isScreen(primaryClient->getName()));
+ assert(m_screen != NULL);
+
+ String primaryName = getName(primaryClient);
+
+ // clear clipboards
+ for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+ ClipboardInfo& 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
+ m_events->adoptHandler(Event::kTimer, this,
+ new TMethodEventJob<Server>(this,
+ &Server::handleSwitchWaitTimeout));
+ m_events->adoptHandler(m_events->forIKeyState().keyDown(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleKeyDownEvent));
+ m_events->adoptHandler(m_events->forIKeyState().keyUp(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleKeyUpEvent));
+ m_events->adoptHandler(m_events->forIKeyState().keyRepeat(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleKeyRepeatEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().buttonDown(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleButtonDownEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().buttonUp(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleButtonUpEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().motionOnPrimary(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleMotionPrimaryEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().motionOnSecondary(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleMotionSecondaryEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().wheel(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleWheelEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().screensaverActivated(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleScreensaverActivatedEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().screensaverDeactivated(),
+ m_primaryClient->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleScreensaverDeactivatedEvent));
+ m_events->adoptHandler(m_events->forServer().switchToScreen(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleSwitchToScreenEvent));
+ m_events->adoptHandler(m_events->forServer().switchInDirection(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleSwitchInDirectionEvent));
+ m_events->adoptHandler(m_events->forServer().keyboardBroadcast(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleKeyboardBroadcastEvent));
+ m_events->adoptHandler(m_events->forServer().lockCursorToScreen(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleLockCursorToScreenEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().fakeInputBegin(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleFakeInputBeginEvent));
+ m_events->adoptHandler(m_events->forIPrimaryScreen().fakeInputEnd(),
+ m_inputFilter,
+ new TMethodEventJob<Server>(this,
+ &Server::handleFakeInputEndEvent));
+
+ if (m_args.m_enableDragDrop) {
+ m_events->adoptHandler(m_events->forFile().fileChunkSending(),
+ this,
+ new TMethodEventJob<Server>(this,
+ &Server::handleFileChunkSendingEvent));
+ m_events->adoptHandler(m_events->forFile().fileRecieveCompleted(),
+ this,
+ new TMethodEventJob<Server>(this,
+ &Server::handleFileRecieveCompletedEvent));
+ }
+
+ // add connection
+ addClient(m_primaryClient);
+
+ // set initial configuration
+ setConfig(config);
+
+ // enable primary client
+ m_primaryClient->enable();
+ m_inputFilter->setPrimaryClient(m_primaryClient);
+
+ // Determine if scroll lock is already set. If so, lock the cursor to the primary screen
+ if (m_primaryClient->getToggleMask() & KeyModifierScrollLock) {
+ LOG((CLOG_NOTE "Scroll Lock is on, locking cursor to screen"));
+ m_lockedToScreen = true;
+ }
+
+}
+
+Server::~Server()
+{
+ if (m_mock) {
+ return;
+ }
+
+ // remove event handlers and timers
+ m_events->removeHandler(m_events->forIKeyState().keyDown(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIKeyState().keyUp(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIKeyState().keyRepeat(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIPrimaryScreen().buttonDown(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIPrimaryScreen().buttonUp(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIPrimaryScreen().motionOnPrimary(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().motionOnSecondary(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().wheel(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().screensaverActivated(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().screensaverDeactivated(),
+ m_primaryClient->getEventTarget());
+ m_events->removeHandler(m_events->forIPrimaryScreen().fakeInputBegin(),
+ m_inputFilter);
+ m_events->removeHandler(m_events->forIPrimaryScreen().fakeInputEnd(),
+ m_inputFilter);
+ m_events->removeHandler(Event::kTimer, this);
+ stopSwitch();
+
+ // force immediate disconnection of secondary clients
+ disconnect();
+ for (OldClients::iterator index = m_oldClients.begin();
+ index != m_oldClients.end(); ++index) {
+ BaseClientProxy* client = index->first;
+ m_events->deleteTimer(index->second);
+ m_events->removeHandler(Event::kTimer, client);
+ m_events->removeHandler(m_events->forClientProxy().disconnected(), client);
+ delete client;
+ }
+
+ // remove input filter
+ m_inputFilter->setPrimaryClient(NULL);
+
+ // disable and disconnect primary client
+ m_primaryClient->disable();
+ removeClient(m_primaryClient);
+}
+
+bool
+Server::setConfig(const Config& 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
+ 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 LockCursorToScreenAction then we don't add
+ // ScrollLock as a hotkey.
+ if (!m_config->hasLockToScreenAction()) {
+ IPlatformScreen::KeyInfo* key =
+ IPlatformScreen::KeyInfo::alloc(kKeyScrollLock, 0, 0, 0);
+ InputFilter::Rule rule(new InputFilter::KeystrokeCondition(m_events, key));
+ rule.adoptAction(new InputFilter::LockCursorToScreenAction(m_events), true);
+ m_inputFilter->addFilterRule(rule);
+ }
+
+ // tell primary screen about reconfiguration
+ m_primaryClient->reconfigure(getActivePrimarySides());
+
+ // tell all (connected) clients about current options
+ for (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ BaseClientProxy* client = index->second;
+ sendOptions(client);
+ }
+
+ return true;
+}
+
+void
+Server::adoptClient(BaseClientProxy* client)
+{
+ assert(client != NULL);
+
+ // watch for client disconnection
+ m_events->adoptHandler(m_events->forClientProxy().disconnected(), client,
+ new TMethodEventJob<Server>(this,
+ &Server::handleClientDisconnected, client));
+
+ // name must be in our configuration
+ if (!m_config->isScreen(client->getName())) {
+ LOG((CLOG_WARN "unrecognised client name \"%s\", check server config", 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
+ Server::ScreenConnectedInfo* info =
+ new Server::ScreenConnectedInfo(getName(client));
+ m_events->addEvent(Event(m_events->forServer().connected(),
+ m_primaryClient->getEventTarget(), info));
+}
+
+void
+Server::disconnect()
+{
+ // close all secondary clients
+ if (m_clients.size() > 1 || !m_oldClients.empty()) {
+ Config emptyConfig(m_events);
+ closeClients(emptyConfig);
+ }
+ else {
+ m_events->addEvent(Event(m_events->forServer().disconnected(), this));
+ }
+}
+
+UInt32
+Server::getNumClients() const
+{
+ return (SInt32)m_clients.size();
+}
+
+void
+Server::getClients(std::vector<String>& list) const
+{
+ list.clear();
+ for (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ list.push_back(index->first);
+ }
+}
+
+String
+Server::getName(const BaseClientProxy* client) const
+{
+ String name = m_config->getCanonicalName(client->getName());
+ if (name.empty()) {
+ name = client->getName();
+ }
+ return name;
+}
+
+UInt32
+Server::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
+Server::isLockedToScreenServer() const
+{
+ // locked if scroll-lock is toggled on
+ return m_lockedToScreen;
+}
+
+bool
+Server::isLockedToScreen() const
+{
+ // locked if we say we're locked
+ if (isLockedToScreenServer()) {
+ return true;
+ }
+
+ // locked if primary says we're locked
+ if (m_primaryClient->isLockedToScreen()) {
+ return true;
+ }
+
+ // not locked
+ return false;
+}
+
+SInt32
+Server::getJumpZoneSize(BaseClientProxy* client) const
+{
+ if (client == m_primaryClient) {
+ return m_primaryClient->getJumpZoneSize();
+ }
+ else {
+ return 0;
+ }
+}
+
+void
+Server::switchScreen(BaseClientProxy* 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 && m_enableClipboard) {
+ for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+ ClipboardInfo& 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);
+
+ if (m_enableClipboard) {
+ // send the clipboard data to new active screen
+ for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
+ m_active->setClipboard(id, &m_clipboards[id].m_clipboard);
+ }
+ }
+
+ Server::SwitchToScreenInfo* info =
+ Server::SwitchToScreenInfo::alloc(m_active->getName());
+ m_events->addEvent(Event(m_events->forServer().screenSwitched(), this, info));
+ }
+ else {
+ m_active->mouseMove(x, y);
+ }
+}
+
+void
+Server::jumpToScreen(BaseClientProxy* 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
+Server::mapToFraction(BaseClientProxy* 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<float>(y - sy + 0.5f) / static_cast<float>(sh);
+
+ case kTop:
+ case kBottom:
+ return static_cast<float>(x - sx + 0.5f) / static_cast<float>(sw);
+
+ case kNoDirection:
+ assert(0 && "bad direction");
+ break;
+ }
+ return 0.0f;
+}
+
+void
+Server::mapToPixel(BaseClientProxy* 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<SInt32>(f * sh) + sy;
+ break;
+
+ case kTop:
+ case kBottom:
+ x = static_cast<SInt32>(f * sw) + sx;
+ break;
+
+ case kNoDirection:
+ assert(0 && "bad direction");
+ break;
+ }
+}
+
+bool
+Server::hasAnyNeighbor(BaseClientProxy* client, EDirection dir) const
+{
+ assert(client != NULL);
+
+ return m_config->hasNeighbor(getName(client), dir);
+}
+
+BaseClientProxy*
+Server::getNeighbor(BaseClientProxy* src,
+ EDirection dir, SInt32& x, SInt32& y) const
+{
+ // note -- must be locked on entry
+
+ assert(src != NULL);
+
+ // get source screen name
+ String srcName = getName(src);
+ assert(!srcName.empty());
+ LOG((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", Config::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 (;;) {
+ String 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\"", Config::dirName(dir), srcName.c_str()));
+ return NULL;
+ }
+
+ // look up neighbor cell. if the screen is connected and
+ // ready then we can stop.
+ ClientList::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(), Config::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(), Config::dirName(dir), srcName.c_str()));
+ srcName = dstName;
+
+ // use position on skipped screen
+ t = tTmp;
+ }
+}
+
+BaseClientProxy*
+Server::mapToNeighbor(BaseClientProxy* src,
+ EDirection srcSide, SInt32& x, SInt32& y) const
+{
+ // note -- must be locked on entry
+
+ assert(src != NULL);
+
+ // get the first neighbor
+ BaseClientProxy* dst = getNeighbor(src, srcSide, x, y);
+ if (dst == NULL) {
+ return NULL;
+ }
+
+ // get the source screen's size
+ SInt32 dx, dy, dw, dh;
+ BaseClientProxy* 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
+Server::avoidJumpZone(BaseClientProxy* 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 String 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
+Server::isSwitchOkay(BaseClientProxy* 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(), Config::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", Config::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 Config::ScreenOptions* 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
+ Config::ScreenOptions::const_iterator i =
+ options->find(kOptionScreenSwitchCorners);
+ UInt32 corners = static_cast<UInt32>(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();
+ }
+
+ // check for optional needed modifiers
+ KeyModifierMask mods = this->m_primaryClient->getToggleMask();
+
+ if (!preventSwitch && (
+ (this->m_switchNeedsShift && ((mods & KeyModifierShift) != KeyModifierShift)) ||
+ (this->m_switchNeedsControl && ((mods & KeyModifierControl) != KeyModifierControl)) ||
+ (this->m_switchNeedsAlt && ((mods & KeyModifierAlt) != KeyModifierAlt))
+ )) {
+ LOG((CLOG_DEBUG1 "need modifiers to switch"));
+ preventSwitch = true;
+ stopSwitch();
+ }
+
+ return !preventSwitch;
+}
+
+void
+Server::noSwitch(SInt32 x, SInt32 y)
+{
+ armSwitchTwoTap(x, y);
+ stopSwitchWait();
+}
+
+void
+Server::stopSwitch()
+{
+ if (m_switchScreen != NULL) {
+ m_switchScreen = NULL;
+ m_switchDir = kNoDirection;
+ stopSwitchTwoTap();
+ stopSwitchWait();
+ }
+}
+
+void
+Server::startSwitchTwoTap()
+{
+ m_switchTwoTapEngaged = true;
+ m_switchTwoTapArmed = false;
+ m_switchTwoTapTimer.reset();
+ LOG((CLOG_DEBUG1 "waiting for second tap"));
+}
+
+void
+Server::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
+Server::stopSwitchTwoTap()
+{
+ m_switchTwoTapEngaged = false;
+ m_switchTwoTapArmed = false;
+}
+
+bool
+Server::isSwitchTwoTapStarted() const
+{
+ return m_switchTwoTapEngaged;
+}
+
+bool
+Server::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
+Server::startSwitchWait(SInt32 x, SInt32 y)
+{
+ stopSwitchWait();
+ m_switchWaitX = x;
+ m_switchWaitY = y;
+ m_switchWaitTimer = m_events->newOneShotTimer(m_switchWaitDelay, this);
+ LOG((CLOG_DEBUG1 "waiting to switch"));
+}
+
+void
+Server::stopSwitchWait()
+{
+ if (m_switchWaitTimer != NULL) {
+ m_events->deleteTimer(m_switchWaitTimer);
+ m_switchWaitTimer = NULL;
+ }
+}
+
+bool
+Server::isSwitchWaitStarted() const
+{
+ return (m_switchWaitTimer != NULL);
+}
+
+UInt32
+Server::getCorner(BaseClientProxy* 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
+Server::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
+Server::sendOptions(BaseClientProxy* client) const
+{
+ OptionsList optionsList;
+
+ // look up options for client
+ const Config::ScreenOptions* options =
+ m_config->getOptions(getName(client));
+ if (options != NULL) {
+ // convert options to a more convenient form for sending
+ optionsList.reserve(2 * options->size());
+ for (Config::ScreenOptions::const_iterator index = options->begin();
+ index != options->end(); ++index) {
+ optionsList.push_back(index->first);
+ optionsList.push_back(static_cast<UInt32>(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 (Config::ScreenOptions::const_iterator index = options->begin();
+ index != options->end(); ++index) {
+ optionsList.push_back(index->first);
+ optionsList.push_back(static_cast<UInt32>(index->second));
+ }
+ }
+
+ // send the options
+ client->resetOptions();
+ client->setOptions(optionsList);
+}
+
+void
+Server::processOptions()
+{
+ const Config::ScreenOptions* options = m_config->getOptions("");
+ if (options == NULL) {
+ return;
+ }
+
+ m_switchNeedsShift = false; // it seems if i don't add these
+ m_switchNeedsControl = false; // lines, the 'reload config' option
+ m_switchNeedsAlt = false; // doesnt' work correct.
+
+ bool newRelativeMoves = m_relativeMoves;
+ for (Config::ScreenOptions::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<double>(value);
+ if (m_switchWaitDelay < 0.0) {
+ m_switchWaitDelay = 0.0;
+ }
+ stopSwitchWait();
+ }
+ else if (id == kOptionScreenSwitchTwoTap) {
+ m_switchTwoTapDelay = 1.0e-3 * static_cast<double>(value);
+ if (m_switchTwoTapDelay < 0.0) {
+ m_switchTwoTapDelay = 0.0;
+ }
+ stopSwitchTwoTap();
+ }
+ else if (id == kOptionScreenSwitchNeedsControl) {
+ m_switchNeedsControl = (value != 0);
+ }
+ else if (id == kOptionScreenSwitchNeedsShift) {
+ m_switchNeedsShift = (value != 0);
+ }
+ else if (id == kOptionScreenSwitchNeedsAlt) {
+ m_switchNeedsAlt = (value != 0);
+ }
+ else if (id == kOptionRelativeMouseMoves) {
+ newRelativeMoves = (value != 0);
+ }
+ else if (id == kOptionClipboardSharing) {
+ m_enableClipboard = (value != 0);
+
+ if (m_enableClipboard == false) {
+ LOG((CLOG_NOTE "clipboard sharing is disabled"));
+ }
+ }
+ }
+ if (m_relativeMoves && !newRelativeMoves) {
+ stopRelativeMoves();
+ }
+ m_relativeMoves = newRelativeMoves;
+}
+
+void
+Server::handleShapeChanged(const Event&, void* vclient)
+{
+ // ignore events from unknown clients
+ BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient);
+ if (m_clientSet.count(client) == 0) {
+ return;
+ }
+
+ LOG((CLOG_DEBUG "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
+Server::handleClipboardGrabbed(const Event& event, void* vclient)
+{
+ if (!m_enableClipboard) {
+ return;
+ }
+
+ // ignore events from unknown clients
+ BaseClientProxy* grabber = static_cast<BaseClientProxy*>(vclient);
+ if (m_clientSet.count(grabber) == 0) {
+ return;
+ }
+ const IScreen::ClipboardInfo* info =
+ static_cast<const IScreen::ClipboardInfo*>(event.getData());
+
+ // ignore grab if sequence number is old. always allow primary
+ // screen to grab.
+ ClipboardInfo& 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 (ClientList::iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ BaseClientProxy* client = index->second;
+ if (client == grabber) {
+ client->setClipboardDirty(info->m_id, false);
+ }
+ else {
+ client->grabClipboard(info->m_id);
+ }
+ }
+}
+
+void
+Server::handleClipboardChanged(const Event& event, void* vclient)
+{
+ // ignore events from unknown clients
+ BaseClientProxy* sender = static_cast<BaseClientProxy*>(vclient);
+ if (m_clientSet.count(sender) == 0) {
+ return;
+ }
+ const IScreen::ClipboardInfo* info =
+ static_cast<const IScreen::ClipboardInfo*>(event.getData());
+ onClipboardChanged(sender, info->m_id, info->m_sequenceNumber);
+}
+
+void
+Server::handleKeyDownEvent(const Event& event, void*)
+{
+ IPlatformScreen::KeyInfo* info =
+ static_cast<IPlatformScreen::KeyInfo*>(event.getData());
+ onKeyDown(info->m_key, info->m_mask, info->m_button, info->m_screens);
+}
+
+void
+Server::handleKeyUpEvent(const Event& event, void*)
+{
+ IPlatformScreen::KeyInfo* info =
+ static_cast<IPlatformScreen::KeyInfo*>(event.getData());
+ onKeyUp(info->m_key, info->m_mask, info->m_button, info->m_screens);
+}
+
+void
+Server::handleKeyRepeatEvent(const Event& event, void*)
+{
+ IPlatformScreen::KeyInfo* info =
+ static_cast<IPlatformScreen::KeyInfo*>(event.getData());
+ onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button);
+}
+
+void
+Server::handleButtonDownEvent(const Event& event, void*)
+{
+ IPlatformScreen::ButtonInfo* info =
+ static_cast<IPlatformScreen::ButtonInfo*>(event.getData());
+ onMouseDown(info->m_button);
+}
+
+void
+Server::handleButtonUpEvent(const Event& event, void*)
+{
+ IPlatformScreen::ButtonInfo* info =
+ static_cast<IPlatformScreen::ButtonInfo*>(event.getData());
+ onMouseUp(info->m_button);
+}
+
+void
+Server::handleMotionPrimaryEvent(const Event& event, void*)
+{
+ IPlatformScreen::MotionInfo* info =
+ static_cast<IPlatformScreen::MotionInfo*>(event.getData());
+ onMouseMovePrimary(info->m_x, info->m_y);
+}
+
+void
+Server::handleMotionSecondaryEvent(const Event& event, void*)
+{
+ IPlatformScreen::MotionInfo* info =
+ static_cast<IPlatformScreen::MotionInfo*>(event.getData());
+ onMouseMoveSecondary(info->m_x, info->m_y);
+}
+
+void
+Server::handleWheelEvent(const Event& event, void*)
+{
+ IPlatformScreen::WheelInfo* info =
+ static_cast<IPlatformScreen::WheelInfo*>(event.getData());
+ onMouseWheel(info->m_xDelta, info->m_yDelta);
+}
+
+void
+Server::handleScreensaverActivatedEvent(const Event&, void*)
+{
+ onScreensaver(true);
+}
+
+void
+Server::handleScreensaverDeactivatedEvent(const Event&, void*)
+{
+ onScreensaver(false);
+}
+
+void
+Server::handleSwitchWaitTimeout(const Event&, 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
+Server::handleClientDisconnected(const Event&, 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.
+ BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient);
+ removeActiveClient(client);
+ removeOldClient(client);
+
+ delete client;
+}
+
+void
+Server::handleClientCloseTimeout(const Event&, void* vclient)
+{
+ // client took too long to disconnect. just dump it.
+ BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient);
+ LOG((CLOG_NOTE "forced disconnection of client \"%s\"", getName(client).c_str()));
+ removeOldClient(client);
+
+ delete client;
+}
+
+void
+Server::handleSwitchToScreenEvent(const Event& event, void*)
+{
+ SwitchToScreenInfo* info =
+ static_cast<SwitchToScreenInfo*>(event.getData());
+
+ ClientList::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
+Server::handleSwitchInDirectionEvent(const Event& event, void*)
+{
+ SwitchInDirectionInfo* info =
+ static_cast<SwitchInDirectionInfo*>(event.getData());
+
+ // jump to screen in chosen direction from center of this screen
+ SInt32 x = m_x, y = m_y;
+ BaseClientProxy* newScreen =
+ getNeighbor(m_active, info->m_direction, x, y);
+ if (newScreen == NULL) {
+ LOG((CLOG_DEBUG1 "no neighbor %s", Config::dirName(info->m_direction)));
+ }
+ else {
+ jumpToScreen(newScreen);
+ }
+}
+
+void
+Server::handleKeyboardBroadcastEvent(const Event& event, void*)
+{
+ KeyboardBroadcastInfo* info = (KeyboardBroadcastInfo*)event.getData();
+
+ // choose new state
+ bool newState;
+ switch (info->m_state) {
+ case KeyboardBroadcastInfo::kOff:
+ newState = false;
+ break;
+
+ default:
+ case KeyboardBroadcastInfo::kOn:
+ newState = true;
+ break;
+
+ case KeyboardBroadcastInfo::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
+Server::handleLockCursorToScreenEvent(const Event& event, void*)
+{
+ LockCursorToScreenInfo* info = (LockCursorToScreenInfo*)event.getData();
+
+ // choose new state
+ bool newState;
+ switch (info->m_state) {
+ case LockCursorToScreenInfo::kOff:
+ newState = false;
+ break;
+
+ default:
+ case LockCursorToScreenInfo::kOn:
+ newState = true;
+ break;
+
+ case LockCursorToScreenInfo::kToggle:
+ newState = !m_lockedToScreen;
+ break;
+ }
+
+ // enter new state
+ if (newState != m_lockedToScreen) {
+ m_lockedToScreen = newState;
+ LOG((CLOG_NOTE "cursor %s current screen", m_lockedToScreen ? "locked to" : "unlocked from"));
+
+ m_primaryClient->reconfigure(getActivePrimarySides());
+ if (!isLockedToScreenServer()) {
+ stopRelativeMoves();
+ }
+ }
+}
+
+void
+Server::handleFakeInputBeginEvent(const Event&, void*)
+{
+ m_primaryClient->fakeInputBegin();
+}
+
+void
+Server::handleFakeInputEndEvent(const Event&, void*)
+{
+ m_primaryClient->fakeInputEnd();
+}
+
+void
+Server::handleFileChunkSendingEvent(const Event& event, void*)
+{
+ onFileChunkSending(event.getData());
+}
+
+void
+Server::handleFileRecieveCompletedEvent(const Event& event, void*)
+{
+ onFileRecieveCompleted();
+}
+
+void
+Server::onClipboardChanged(BaseClientProxy* sender,
+ ClipboardID id, UInt32 seqNum)
+{
+ ClipboardInfo& 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
+ String 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 (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ BaseClientProxy* client = index->second;
+ client->setClipboardDirty(id, client != sender);
+ }
+
+ // send the new clipboard to the active screen
+ m_active->setClipboard(id, &clipboard.m_clipboard);
+}
+
+void
+Server::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
+ BaseClientProxy* 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 (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ BaseClientProxy* client = index->second;
+ client->screensaver(activated);
+ }
+}
+
+void
+Server::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 && IKeyState::KeyInfo::isDefault(screens)) {
+ m_active->keyDown(id, mask, button);
+ }
+ else {
+ if (!screens && m_keyboardBroadcasting) {
+ screens = m_keyboardBroadcastingScreens.c_str();
+ if (IKeyState::KeyInfo::isDefault(screens)) {
+ screens = "*";
+ }
+ }
+ for (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ if (IKeyState::KeyInfo::contains(screens, index->first)) {
+ index->second->keyDown(id, mask, button);
+ }
+ }
+ }
+}
+
+void
+Server::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 && IKeyState::KeyInfo::isDefault(screens)) {
+ m_active->keyUp(id, mask, button);
+ }
+ else {
+ if (!screens && m_keyboardBroadcasting) {
+ screens = m_keyboardBroadcastingScreens.c_str();
+ if (IKeyState::KeyInfo::isDefault(screens)) {
+ screens = "*";
+ }
+ }
+ for (ClientList::const_iterator index = m_clients.begin();
+ index != m_clients.end(); ++index) {
+ if (IKeyState::KeyInfo::contains(screens, index->first)) {
+ index->second->keyUp(id, mask, button);
+ }
+ }
+ }
+}
+
+void
+Server::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
+Server::onMouseDown(ButtonID id)
+{
+ LOG((CLOG_DEBUG1 "onMouseDown id=%d", id));
+ assert(m_active != NULL);
+
+ // relay
+ m_active->mouseDown(id);
+
+ // reset this variable back to default value true
+ m_waitDragInfoThread = true;
+}
+
+void
+Server::onMouseUp(ButtonID id)
+{
+ LOG((CLOG_DEBUG1 "onMouseUp id=%d", id));
+ assert(m_active != NULL);
+
+ // relay
+ m_active->mouseUp(id);
+
+ if (m_ignoreFileTransfer) {
+ m_ignoreFileTransfer = false;
+ return;
+ }
+
+ if (m_args.m_enableDragDrop) {
+ if (!m_screen->isOnScreen()) {
+ String& file = m_screen->getDraggingFilename();
+ if (!file.empty()) {
+ sendFileToClient(file.c_str());
+ }
+ }
+
+ // always clear dragging filename
+ m_screen->clearDraggingFilename();
+ }
+}
+
+bool
+Server::onMouseMovePrimary(SInt32 x, SInt32 y)
+{
+ LOG((CLOG_DEBUG4 "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
+ // when the cursor is in a corner, there may be a screen either
+ // horizontally or vertically. check both directions.
+ EDirection dirh = kNoDirection, dirv = kNoDirection;
+ SInt32 xh = x, yv = y;
+ if (x < ax + zoneSize) {
+ xh -= zoneSize;
+ dirh = kLeft;
+ }
+ else if (x >= ax + aw - zoneSize) {
+ xh += zoneSize;
+ dirh = kRight;
+ }
+ if (y < ay + zoneSize) {
+ yv -= zoneSize;
+ dirv = kTop;
+ }
+ else if (y >= ay + ah - zoneSize) {
+ yv += zoneSize;
+ dirv = kBottom;
+ }
+ if (dirh == kNoDirection && dirv == kNoDirection) {
+ // still on local screen
+ noSwitch(x, y);
+ return false;
+ }
+
+ // check both horizontally and vertically
+ EDirection dirs[] = {dirh, dirv};
+ SInt32 xs[] = {xh, x}, ys[] = {y, yv};
+ for (int i = 0; i < 2; ++i) {
+ EDirection dir = dirs[i];
+ if (dir == kNoDirection) {
+ continue;
+ }
+ x = xs[i], y = ys[i];
+
+ // get jump destination
+ BaseClientProxy* newScreen = mapToNeighbor(m_active, dir, x, y);
+
+ // should we switch or not?
+ if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) {
+ if (m_args.m_enableDragDrop
+ && m_screen->isDraggingStarted()
+ && m_active != newScreen
+ && m_waitDragInfoThread) {
+ if (m_sendDragInfoThread == NULL) {
+ m_sendDragInfoThread = new Thread(
+ new TMethodJob<Server>(
+ this,
+ &Server::sendDragInfoThread, newScreen));
+ }
+
+ return false;
+ }
+
+ // switch screen
+ switchScreen(newScreen, x, y, false);
+ m_waitDragInfoThread = true;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+Server::sendDragInfoThread(void* arg)
+{
+ BaseClientProxy* newScreen = static_cast<BaseClientProxy*>(arg);
+
+ m_dragFileList.clear();
+ String& dragFileList = m_screen->getDraggingFilename();
+ if (!dragFileList.empty()) {
+ DragInformation di;
+ di.setFilename(dragFileList);
+ m_dragFileList.push_back(di);
+ }
+
+#if defined(__APPLE__)
+ // on mac it seems that after faking a LMB up, system would signal back
+ // to barrier a mouse up event, which doesn't happen on windows. as a
+ // result, barrier would send dragging file to client twice. This variable
+ // is used to ignore the first file sending.
+ m_ignoreFileTransfer = true;
+#endif
+
+ // send drag file info to client if there is any
+ if (m_dragFileList.size() > 0) {
+ sendDragInfo(newScreen);
+ m_dragFileList.clear();
+ }
+ m_waitDragInfoThread = false;
+ m_sendDragInfoThread = NULL;
+}
+
+void
+Server::sendDragInfo(BaseClientProxy* newScreen)
+{
+ String infoString;
+ UInt32 fileCount = DragInformation::setupDragInfo(m_dragFileList, infoString);
+
+ if (fileCount > 0) {
+ char* info = NULL;
+ size_t size = infoString.size();
+ info = new char[size];
+ memcpy(info, infoString.c_str(), size);
+
+ LOG((CLOG_DEBUG2 "sending drag information to client"));
+ LOG((CLOG_DEBUG3 "dragging file list: %s", info));
+ LOG((CLOG_DEBUG3 "dragging file list string size: %i", size));
+ newScreen->sendDragInfo(fileCount, info, size);
+ }
+}
+
+void
+Server::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;
+ BaseClientProxy* 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) {
+ if (m_sendFileThread != NULL) {
+ StreamChunker::interruptFile();
+ m_sendFileThread = NULL;
+ }
+
+ SInt32 newX = m_x;
+ SInt32 newY = m_y;
+
+ // switch screens
+ switchScreen(newScreen, newX, newY, 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
+Server::onMouseWheel(SInt32 xDelta, SInt32 yDelta)
+{
+ LOG((CLOG_DEBUG1 "onMouseWheel %+d,%+d", xDelta, yDelta));
+ assert(m_active != NULL);
+
+ // relay
+ m_active->mouseWheel(xDelta, yDelta);
+}
+
+void
+Server::onFileChunkSending(const void* data)
+{
+ FileChunk* chunk = static_cast<FileChunk*>(const_cast<void*>(data));
+
+ LOG((CLOG_DEBUG1 "sending file chunk"));
+ assert(m_active != NULL);
+
+ // relay
+ m_active->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize);
+}
+
+void
+Server::onFileRecieveCompleted()
+{
+ if (isReceivedFileSizeValid()) {
+ m_writeToDropDirThread = new Thread(
+ new TMethodJob<Server>(
+ this, &Server::writeToDropDirThread));
+ }
+}
+
+void
+Server::writeToDropDirThread(void*)
+{
+ LOG((CLOG_DEBUG "starting write to drop dir thread"));
+
+ while (m_screen->isFakeDraggingStarted()) {
+ ARCH->sleep(.1f);
+ }
+
+ DropHelper::writeToDir(m_screen->getDropTarget(), m_fakeDragFileList,
+ m_receivedFileData);
+}
+
+bool
+Server::addClient(BaseClientProxy* client)
+{
+ String name = getName(client);
+ if (m_clients.count(name) != 0) {
+ return false;
+ }
+
+ // add event handlers
+ m_events->adoptHandler(m_events->forIScreen().shapeChanged(),
+ client->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleShapeChanged, client));
+ m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(),
+ client->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::handleClipboardGrabbed, client));
+ m_events->adoptHandler(m_events->forClipboard().clipboardChanged(),
+ client->getEventTarget(),
+ new TMethodEventJob<Server>(this,
+ &Server::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
+Server::removeClient(BaseClientProxy* client)
+{
+ // return false if not in list
+ ClientSet::iterator i = m_clientSet.find(client);
+ if (i == m_clientSet.end()) {
+ return false;
+ }
+
+ // remove event handlers
+ m_events->removeHandler(m_events->forIScreen().shapeChanged(),
+ client->getEventTarget());
+ m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(),
+ client->getEventTarget());
+ m_events->removeHandler(m_events->forClipboard().clipboardChanged(),
+ client->getEventTarget());
+
+ // remove from list
+ m_clients.erase(getName(client));
+ m_clientSet.erase(i);
+
+ return true;
+}
+
+void
+Server::closeClient(BaseClientProxy* 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)
+ ((ClientProxy*)client)->close(msg);
+
+ // install timer. wait timeout seconds for client to close.
+ double timeout = 5.0;
+ EventQueueTimer* timer = m_events->newOneShotTimer(timeout, NULL);
+ m_events->adoptHandler(Event::kTimer, timer,
+ new TMethodEventJob<Server>(this,
+ &Server::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
+Server::closeClients(const Config& config)
+{
+ // collect the clients that are connected but are being dropped
+ // from the configuration (or who's canonical name is changing).
+ typedef std::set<BaseClientProxy*> RemovedClients;
+ RemovedClients removed;
+ for (ClientList::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 (RemovedClients::iterator index = removed.begin();
+ index != removed.end(); ++index) {
+ closeClient(*index, kMsgCClose);
+ }
+}
+
+void
+Server::removeActiveClient(BaseClientProxy* client)
+{
+ if (removeClient(client)) {
+ forceLeaveClient(client);
+ m_events->removeHandler(m_events->forClientProxy().disconnected(), client);
+ if (m_clients.size() == 1 && m_oldClients.empty()) {
+ m_events->addEvent(Event(m_events->forServer().disconnected(), this));
+ }
+ }
+}
+
+void
+Server::removeOldClient(BaseClientProxy* client)
+{
+ OldClients::iterator i = m_oldClients.find(client);
+ if (i != m_oldClients.end()) {
+ m_events->removeHandler(m_events->forClientProxy().disconnected(), client);
+ m_events->removeHandler(Event::kTimer, i->second);
+ m_events->deleteTimer(i->second);
+ m_oldClients.erase(i);
+ if (m_clients.size() == 1 && m_oldClients.empty()) {
+ m_events->addEvent(Event(m_events->forServer().disconnected(), this));
+ }
+ }
+}
+
+void
+Server::forceLeaveClient(BaseClientProxy* client)
+{
+ BaseClientProxy* 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());
+}
+
+
+//
+// Server::ClipboardInfo
+//
+
+Server::ClipboardInfo::ClipboardInfo() :
+ m_clipboard(),
+ m_clipboardData(),
+ m_clipboardOwner(),
+ m_clipboardSeqNum(0)
+{
+ // do nothing
+}
+
+
+//
+// Server::LockCursorToScreenInfo
+//
+
+Server::LockCursorToScreenInfo*
+Server::LockCursorToScreenInfo::alloc(State state)
+{
+ LockCursorToScreenInfo* info =
+ (LockCursorToScreenInfo*)malloc(sizeof(LockCursorToScreenInfo));
+ info->m_state = state;
+ return info;
+}
+
+
+//
+// Server::SwitchToScreenInfo
+//
+
+Server::SwitchToScreenInfo*
+Server::SwitchToScreenInfo::alloc(const String& screen)
+{
+ SwitchToScreenInfo* info =
+ (SwitchToScreenInfo*)malloc(sizeof(SwitchToScreenInfo) +
+ screen.size());
+ strcpy(info->m_screen, screen.c_str());
+ return info;
+}
+
+
+//
+// Server::SwitchInDirectionInfo
+//
+
+Server::SwitchInDirectionInfo*
+Server::SwitchInDirectionInfo::alloc(EDirection direction)
+{
+ SwitchInDirectionInfo* info =
+ (SwitchInDirectionInfo*)malloc(sizeof(SwitchInDirectionInfo));
+ info->m_direction = direction;
+ return info;
+}
+
+//
+// Server::KeyboardBroadcastInfo
+//
+
+Server::KeyboardBroadcastInfo*
+Server::KeyboardBroadcastInfo::alloc(State state)
+{
+ KeyboardBroadcastInfo* info =
+ (KeyboardBroadcastInfo*)malloc(sizeof(KeyboardBroadcastInfo));
+ info->m_state = state;
+ info->m_screens[0] = '\0';
+ return info;
+}
+
+Server::KeyboardBroadcastInfo*
+Server::KeyboardBroadcastInfo::alloc(State state, const String& screens)
+{
+ KeyboardBroadcastInfo* info =
+ (KeyboardBroadcastInfo*)malloc(sizeof(KeyboardBroadcastInfo) +
+ screens.size());
+ info->m_state = state;
+ strcpy(info->m_screens, screens.c_str());
+ return info;
+}
+
+bool
+Server::isReceivedFileSizeValid()
+{
+ return m_expectedFileSize == m_receivedFileData.size();
+}
+
+void
+Server::sendFileToClient(const char* filename)
+{
+ if (m_sendFileThread != NULL) {
+ StreamChunker::interruptFile();
+ }
+
+ m_sendFileThread = new Thread(
+ new TMethodJob<Server>(
+ this, &Server::sendFileThread,
+ static_cast<void*>(const_cast<char*>(filename))));
+}
+
+void
+Server::sendFileThread(void* data)
+{
+ try {
+ char* filename = static_cast<char*>(data);
+ LOG((CLOG_DEBUG "sending file to client, filename=%s", filename));
+ StreamChunker::sendFile(filename, m_events, this);
+ }
+ catch (std::runtime_error &error) {
+ LOG((CLOG_ERR "failed sending file chunks, error: %s", error.what()));
+ }
+
+ m_sendFileThread = NULL;
+}
+
+void
+Server::dragInfoReceived(UInt32 fileNum, String content)
+{
+ if (!m_args.m_enableDragDrop) {
+ LOG((CLOG_DEBUG "drag drop not enabled, ignoring drag info."));
+ return;
+ }
+
+ DragInformation::parseDragInfo(m_fakeDragFileList, fileNum, content);
+
+ m_screen->startDraggingFiles(m_fakeDragFileList);
+}
diff --git a/src/lib/server/Server.h b/src/lib/server/Server.h
new file mode 100644
index 0000000..609af21
--- /dev/null
+++ b/src/lib/server/Server.h
@@ -0,0 +1,483 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * 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 LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "server/Config.h"
+#include "barrier/clipboard_types.h"
+#include "barrier/Clipboard.h"
+#include "barrier/key_types.h"
+#include "barrier/mouse_types.h"
+#include "barrier/INode.h"
+#include "barrier/DragInformation.h"
+#include "barrier/ServerArgs.h"
+#include "base/Event.h"
+#include "base/Stopwatch.h"
+#include "base/EventTypes.h"
+#include "common/stdmap.h"
+#include "common/stdset.h"
+#include "common/stdvector.h"
+
+class BaseClientProxy;
+class EventQueueTimer;
+class PrimaryClient;
+class InputFilter;
+namespace barrier { class Screen; }
+class IEventQueue;
+class Thread;
+class ClientListener;
+
+//! Barrier server
+/*!
+This class implements the top-level server algorithms for barrier.
+*/
+class Server : public INode {
+public:
+ //! Lock cursor to screen data
+ class LockCursorToScreenInfo {
+ public:
+ enum State { kOff, kOn, kToggle };
+
+ static LockCursorToScreenInfo* alloc(State state = kToggle);
+
+ public:
+ State m_state;
+ };
+
+ //! Switch to screen data
+ class SwitchToScreenInfo {
+ public:
+ static SwitchToScreenInfo* alloc(const String& screen);
+
+ public:
+ // this is a C-string; this type is a variable size structure
+ char m_screen[1];
+ };
+
+ //! Switch in direction data
+ class SwitchInDirectionInfo {
+ public:
+ static SwitchInDirectionInfo* alloc(EDirection direction);
+
+ public:
+ EDirection m_direction;
+ };
+
+ //! Screen connected data
+ class ScreenConnectedInfo {
+ public:
+ ScreenConnectedInfo(String screen) : m_screen(screen) { }
+
+ public:
+ String m_screen; // was char[1]
+ };
+
+ //! Keyboard broadcast data
+ class KeyboardBroadcastInfo {
+ public:
+ enum State { kOff, kOn, kToggle };
+
+ static KeyboardBroadcastInfo* alloc(State state = kToggle);
+ static KeyboardBroadcastInfo* alloc(State state,
+ const String& 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.
+ */
+ Server(Config& config, PrimaryClient* primaryClient,
+ barrier::Screen* screen, IEventQueue* events, ServerArgs const& args);
+ ~Server();
+
+#ifdef TEST_ENV
+ Server() : m_mock(true), m_config(NULL) { }
+ void setActive(BaseClientProxy* active) { m_active = active; }
+#endif
+
+ //! @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 Config&);
+
+ //! 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(BaseClientProxy* 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();
+
+ //! Create a new thread and use it to send file to client
+ void sendFileToClient(const char* filename);
+
+ //! Received dragging information from client
+ void dragInfoReceived(UInt32 fileNum, String content);
+
+ //! Store ClientListener pointer
+ void setListener(ClientListener* p) { m_clientListener = p; }
+
+ //@}
+ //! @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<String>& list) const;
+
+ //! Return true if recieved file size is valid
+ bool isReceivedFileSizeValid();
+
+ //! Return expected file data size
+ size_t& getExpectedFileSize() { return m_expectedFileSize; }
+
+ //! Return received file data
+ String& getReceivedFileData() { return m_receivedFileData; }
+
+ //! Return fake drag file list
+ DragFileList getFakeDragFileList() { return m_fakeDragFileList; }
+
+ //@}
+
+private:
+ // get canonical name of client
+ String getName(const BaseClientProxy*) 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(BaseClientProxy*) const;
+
+ // change the active screen
+ void switchScreen(BaseClientProxy*,
+ SInt32 x, SInt32 y, bool forScreenSaver);
+
+ // jump to screen
+ void jumpToScreen(BaseClientProxy*);
+
+ // convert pixel position to fraction, using x or y depending on the
+ // direction.
+ float mapToFraction(BaseClientProxy*, EDirection,
+ SInt32 x, SInt32 y) const;
+
+ // convert fraction to pixel position, writing only x or y depending
+ // on the direction.
+ void mapToPixel(BaseClientProxy*, 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(BaseClientProxy*, EDirection) const;
+
+ // lookup neighboring screen, mapping the coordinate independent of
+ // the direction to the neighbor's coordinate space.
+ BaseClientProxy* getNeighbor(BaseClientProxy*, 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.
+ BaseClientProxy* mapToNeighbor(BaseClientProxy*, 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(BaseClientProxy*, 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(BaseClientProxy* 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(BaseClientProxy*,
+ SInt32 x, SInt32 y, SInt32 size) const;
+
+ // stop relative mouse moves
+ void stopRelativeMoves();
+
+ // send screen options to \c client
+ void sendOptions(BaseClientProxy* client) const;
+
+ // process options from configuration
+ void processOptions();
+
+ // event handlers
+ void handleShapeChanged(const Event&, void*);
+ void handleClipboardGrabbed(const Event&, void*);
+ void handleClipboardChanged(const Event&, void*);
+ void handleKeyDownEvent(const Event&, void*);
+ void handleKeyUpEvent(const Event&, void*);
+ void handleKeyRepeatEvent(const Event&, void*);
+ void handleButtonDownEvent(const Event&, void*);
+ void handleButtonUpEvent(const Event&, void*);
+ void handleMotionPrimaryEvent(const Event&, void*);
+ void handleMotionSecondaryEvent(const Event&, void*);
+ void handleWheelEvent(const Event&, void*);
+ void handleScreensaverActivatedEvent(const Event&, void*);
+ void handleScreensaverDeactivatedEvent(const Event&, void*);
+ void handleSwitchWaitTimeout(const Event&, void*);
+ void handleClientDisconnected(const Event&, void*);
+ void handleClientCloseTimeout(const Event&, void*);
+ void handleSwitchToScreenEvent(const Event&, void*);
+ void handleSwitchInDirectionEvent(const Event&, void*);
+ void handleKeyboardBroadcastEvent(const Event&,void*);
+ void handleLockCursorToScreenEvent(const Event&, void*);
+ void handleFakeInputBeginEvent(const Event&, void*);
+ void handleFakeInputEndEvent(const Event&, void*);
+ void handleFileChunkSendingEvent(const Event&, void*);
+ void handleFileRecieveCompletedEvent(const Event&, void*);
+
+ // event processing
+ void onClipboardChanged(BaseClientProxy* 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);
+ void onFileChunkSending(const void* data);
+ void onFileRecieveCompleted();
+
+ // add client to list and attach event handlers for client
+ bool addClient(BaseClientProxy*);
+
+ // remove client from list and detach event handlers for client
+ bool removeClient(BaseClientProxy*);
+
+ // close a client
+ void closeClient(BaseClientProxy*, const char* msg);
+
+ // close clients not in \p config
+ void closeClients(const Config& 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(BaseClientProxy*);
+ void removeOldClient(BaseClientProxy*);
+
+ // force the cursor off of \p client
+ void forceLeaveClient(BaseClientProxy* client);
+
+ // thread funciton for sending file
+ void sendFileThread(void*);
+
+ // thread function for writing file to drop directory
+ void writeToDropDirThread(void*);
+
+ // thread function for sending drag information
+ void sendDragInfoThread(void*);
+
+ // send drag info to new client screen
+ void sendDragInfo(BaseClientProxy* newScreen);
+
+public:
+ bool m_mock;
+
+private:
+ class ClipboardInfo {
+ public:
+ ClipboardInfo();
+
+ public:
+ Clipboard m_clipboard;
+ String m_clipboardData;
+ String m_clipboardOwner;
+ UInt32 m_clipboardSeqNum;
+ };
+
+ // the primary screen client
+ PrimaryClient* m_primaryClient;
+
+ // all clients (including the primary client) indexed by name
+ typedef std::map<String, BaseClientProxy*> ClientList;
+ typedef std::set<BaseClientProxy*> ClientSet;
+ ClientList m_clients;
+ ClientSet m_clientSet;
+
+ // all old connections that we're waiting to hangup
+ typedef std::map<BaseClientProxy*, EventQueueTimer*> OldClients;
+ OldClients m_oldClients;
+
+ // the client with focus
+ BaseClientProxy* 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
+ Config* m_config;
+
+ // input filter (from m_config);
+ InputFilter* m_inputFilter;
+
+ // clipboard cache
+ ClipboardInfo m_clipboards[kClipboardEnd];
+
+ // state saved when screen saver activates
+ BaseClientProxy* 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;
+ BaseClientProxy* m_switchScreen;
+
+ // state for delayed screen switching
+ double m_switchWaitDelay;
+ EventQueueTimer* m_switchWaitTimer;
+ SInt32 m_switchWaitX, m_switchWaitY;
+
+ // state for double-tap screen switching
+ double m_switchTwoTapDelay;
+ Stopwatch m_switchTwoTapTimer;
+ bool m_switchTwoTapEngaged;
+ bool m_switchTwoTapArmed;
+ SInt32 m_switchTwoTapZone;
+
+ // modifiers needed before switching
+ bool m_switchNeedsShift;
+ bool m_switchNeedsControl;
+ bool m_switchNeedsAlt;
+
+ // 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;
+ String m_keyboardBroadcastingScreens;
+
+ // screen locking (former scroll lock)
+ bool m_lockedToScreen;
+
+ // server screen
+ barrier::Screen* m_screen;
+
+ IEventQueue* m_events;
+
+ // file transfer
+ size_t m_expectedFileSize;
+ String m_receivedFileData;
+ DragFileList m_dragFileList;
+ DragFileList m_fakeDragFileList;
+ Thread* m_sendFileThread;
+ Thread* m_writeToDropDirThread;
+ String m_dragFileExt;
+ bool m_ignoreFileTransfer;
+ bool m_enableClipboard;
+
+ Thread* m_sendDragInfoThread;
+ bool m_waitDragInfoThread;
+
+ ClientListener* m_clientListener;
+ ServerArgs m_args;
+};