aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/platform/MSWindowsScreen.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/platform/MSWindowsScreen.cpp')
-rw-r--r--src/lib/platform/MSWindowsScreen.cpp1959
1 files changed, 1959 insertions, 0 deletions
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;
+}