summaryrefslogtreecommitdiffstats
path: root/src/lib/platform
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@ubuntu.com>2018-04-25 18:07:30 -0400
committerLibravatarUnit 193 <unit193@ubuntu.com>2018-04-25 18:07:30 -0400
commit9b1b081cfdb1c0fb6457278775e0823f8bc10f62 (patch)
treece8840148d8445055ba9e4f12263b2208f234c16 /src/lib/platform
Import Upstream version 2.0.0+dfsgupstream/2.0.0+dfsg
Diffstat (limited to 'src/lib/platform')
-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
106 files changed, 25266 insertions, 0 deletions
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);
+
+*/
+
+}