diff options
Diffstat (limited to 'src/lib/platform')
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, ¬ificationPortRef, + powerChangeCallback, ¬ifier); + 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(¬ifier); + 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); + +*/ + +} |
