barrier/lib/platform/CMSWindowsScreen.cpp
crs 19559d4b4e Updated keyboard handling on win32. Still needs some work to
avoid shadowing key state in multiple places.  Also got locked
to screen and reported key appeared to be wrong.
2004-03-17 20:59:25 +00:00

2032 lines
52 KiB
C++

/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "CMSWindowsScreen.h"
#include "CMSWindowsClipboard.h"
#include "CMSWindowsDesktop.h"
#include "CMSWindowsEventQueueBuffer.h"
#include "CMSWindowsScreenSaver.h"
#include "CClipboard.h"
#include "XScreen.h"
#include "CLock.h"
#include "CThread.h"
#include "CFunctionJob.h"
#include "CLog.h"
#include "CString.h"
#include "CStringUtil.h"
#include "IEventQueue.h"
#include "TMethodEventJob.h"
#include "TMethodJob.h"
#include "CArch.h"
#include "CArchMiscWindows.h"
#include <cstring>
#include <malloc.h>
#include <tchar.h>
// <unused>; <unused>
#define SYNERGY_MSG_SWITCH SYNERGY_HOOK_LAST_MSG + 1
// <unused>; <unused>
#define SYNERGY_MSG_ENTER SYNERGY_HOOK_LAST_MSG + 2
// <unused>; <unused>
#define SYNERGY_MSG_LEAVE SYNERGY_HOOK_LAST_MSG + 3
// wParam = flags, HIBYTE(lParam) = virtual key, LOBYTE(lParam) = scan code
#define SYNERGY_MSG_FAKE_KEY SYNERGY_HOOK_LAST_MSG + 4
// flags, XBUTTON id
#define SYNERGY_MSG_FAKE_BUTTON SYNERGY_HOOK_LAST_MSG + 5
// x; y
#define SYNERGY_MSG_FAKE_MOVE SYNERGY_HOOK_LAST_MSG + 6
// delta; <unused>
#define SYNERGY_MSG_FAKE_WHEEL SYNERGY_HOOK_LAST_MSG + 7
// POINT*; <unused>
#define SYNERGY_MSG_CURSOR_POS SYNERGY_HOOK_LAST_MSG + 8
// IKeyState*; <unused>
#define SYNERGY_MSG_SYNC_KEYS SYNERGY_HOOK_LAST_MSG + 9
//
// add backwards compatible multihead support (and suppress bogus warning)
//
#pragma warning(push)
#pragma warning(disable: 4706) // assignment within conditional
#define COMPILE_MULTIMON_STUBS
#include <multimon.h>
#pragma warning(pop)
// 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
// 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 0x0100
#define MOUSEEVENTF_XUP 0x0200
#define XBUTTON1 0x0001
#define XBUTTON2 0x0002
#endif
//
// CMSWindowsScreen
//
HINSTANCE CMSWindowsScreen::s_instance = NULL;
CMSWindowsScreen* CMSWindowsScreen::s_screen = NULL;
CMSWindowsScreen::CMSWindowsScreen(bool isPrimary,
IJob* suspend, IJob* resume) :
m_isPrimary(isPrimary),
m_is95Family(CArchMiscWindows::isWindows95Family()),
m_isOnScreen(m_isPrimary),
m_class(0),
m_cursor(NULL),
m_window(NULL),
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_keyLayout(NULL),
m_timer(NULL),
m_screensaver(NULL),
m_screensaverNotify(false),
m_nextClipboardWindow(NULL),
m_ownClipboard(false),
m_activeDesk(NULL),
m_activeDeskName(),
m_hookLibrary(NULL),
m_init(NULL),
m_cleanup(NULL),
m_install(NULL),
m_uninstall(NULL),
m_setSides(NULL),
m_setZone(NULL),
m_setMode(NULL),
m_installScreensaver(NULL),
m_uninstallScreensaver(NULL),
m_keyState(NULL),
m_mutex(),
m_deskReady(&m_mutex, false),
m_suspend(suspend),
m_resume(resume)
{
assert(s_instance != NULL);
assert(s_screen == NULL);
s_screen = this;
try {
if (m_isPrimary) {
m_hookLibrary = openHookLibrary("synrgyhk");
}
m_cursor = createBlankCursor();
m_class = createWindowClass();
m_deskClass = createDeskWindowClass(m_isPrimary);
updateScreenShape();
m_window = createWindow(m_class, "Synergy");
m_screensaver = new CMSWindowsScreenSaver();
LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : ""));
LOG((CLOG_DEBUG "window is 0x%08x", m_window));
}
catch (...) {
delete m_screensaver;
destroyWindow(m_window);
destroyClass(m_deskClass);
destroyClass(m_class);
destroyCursor(m_cursor);
closeHookLibrary(m_hookLibrary);
m_screensaver = NULL;
m_class = 0;
m_cursor = NULL;
m_hookLibrary = NULL;
s_screen = NULL;
throw;
}
// install event handlers
EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(),
new TMethodEventJob<CMSWindowsScreen>(this,
&CMSWindowsScreen::handleSystemEvent));
// install the platform event queue
EVENTQUEUE->adoptBuffer(new CMSWindowsEventQueueBuffer);
}
CMSWindowsScreen::~CMSWindowsScreen()
{
assert(s_screen != NULL);
disable();
EVENTQUEUE->adoptBuffer(NULL);
EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget());
delete m_screensaver;
destroyWindow(m_window);
destroyClass(m_deskClass);
destroyClass(m_class);
destroyCursor(m_cursor);
closeHookLibrary(m_hookLibrary);
delete m_suspend;
delete m_resume;
s_screen = NULL;
}
void
CMSWindowsScreen::init(HINSTANCE instance)
{
assert(s_instance == NULL);
assert(instance != NULL);
s_instance = instance;
}
HINSTANCE
CMSWindowsScreen::getInstance()
{
return s_instance;
}
void
CMSWindowsScreen::setKeyState(IKeyState* keyState)
{
m_keyState = keyState;
updateKeys();
}
void
CMSWindowsScreen::enable()
{
assert(m_isOnScreen == m_isPrimary);
// install our clipboard snooper
m_nextClipboardWindow = SetClipboardViewer(m_window);
if (m_isPrimary) {
// update shadow key state
m_keyMapper.update(NULL);
// set jump zones
m_setZone(m_x, m_y, m_w, m_h, getJumpZoneSize());
// watch jump zones
m_setMode(kHOOK_WATCH_JUMP_ZONE);
}
else {
// prevent the system from entering power saving modes. if
// it did we'd be forced to disconnect from the server and
// the server would not be able to wake us up.
CArchMiscWindows::addBusyState(CArchMiscWindows::kSYSTEM);
}
// set the active desk and (re)install the hooks
checkDesk();
// install the desk timer. this timer periodically checks
// which desk is active and reinstalls the hooks as necessary.
// we wouldn't need this if windows notified us of a desktop
// change but as far as i can tell it doesn't.
m_timer = EVENTQUEUE->newTimer(0.2, NULL);
EVENTQUEUE->adoptHandler(CEvent::kTimer, m_timer,
new TMethodEventJob<CMSWindowsScreen>(
this, &CMSWindowsScreen::handleCheckDesk));
}
void
CMSWindowsScreen::disable()
{
// remove timer
if (m_timer != NULL) {
EVENTQUEUE->removeHandler(CEvent::kTimer, m_timer);
EVENTQUEUE->deleteTimer(m_timer);
m_timer = NULL;
}
if (m_isPrimary) {
// disable hooks
m_setMode(kHOOK_DISABLE);
// enable special key sequences on win95 family
enableSpecialKeys(true);
}
else {
// allow the system to enter power saving mode
CArchMiscWindows::removeBusyState(CArchMiscWindows::kSYSTEM |
CArchMiscWindows::kDISPLAY);
}
// destroy desks
removeDesks();
// stop snooping the clipboard
ChangeClipboardChain(m_window, m_nextClipboardWindow);
m_nextClipboardWindow = NULL;
m_isOnScreen = m_isPrimary;
}
void
CMSWindowsScreen::enter()
{
sendDeskMessage(SYNERGY_MSG_ENTER, 0, 0);
if (m_isPrimary) {
// enable special key sequences on win95 family
enableSpecialKeys(true);
// watch jump zones
m_setMode(kHOOK_WATCH_JUMP_ZONE);
// all messages prior to now are invalid
nextMark();
}
// now on screen
m_isOnScreen = true;
}
bool
CMSWindowsScreen::leave()
{
// get keyboard layout of foreground window. we'll use this
// keyboard layout for translating keys sent to clients.
HWND window = GetForegroundWindow();
DWORD thread = GetWindowThreadProcessId(window, NULL);
m_keyLayout = GetKeyboardLayout(thread);
// tell the key mapper about the keyboard layout
m_keyMapper.setKeyLayout(m_keyLayout);
sendDeskMessage(SYNERGY_MSG_SYNC_KEYS, 0, 0);
// tell desk that we're leaving and tell it the keyboard layout
sendDeskMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0);
if (m_isPrimary) {
/* XXX
// update keys
m_keyMapper.update(NULL);
*/
// warp to center
warpCursor(m_xCenter, m_yCenter);
// disable special key sequences on win95 family
enableSpecialKeys(false);
// all messages prior to now are invalid
nextMark();
// capture events
m_setMode(kHOOK_RELAY_EVENTS);
}
// now off screen
m_isOnScreen = false;
return true;
}
bool
CMSWindowsScreen::setClipboard(ClipboardID, const IClipboard* src)
{
CMSWindowsClipboard dst(m_window);
if (src != NULL) {
// save clipboard data
return CClipboard::copy(&dst, src);
}
else {
// assert clipboard ownership
if (!dst.open(0)) {
return false;
}
dst.empty();
dst.close();
return true;
}
}
void
CMSWindowsScreen::checkClipboards()
{
// if we think we own the clipboard but we don't then somebody
// grabbed the clipboard on this screen without us knowing.
// tell the server that this screen grabbed the clipboard.
//
// this works around bugs in the clipboard viewer chain.
// sometimes NT will simply never send WM_DRAWCLIPBOARD
// messages for no apparent reason and rebooting fixes the
// problem. since we don't want a broken clipboard until the
// next reboot we do this double check. clipboard ownership
// won't be reflected on other screens until we leave but at
// least the clipboard itself will work.
if (m_ownClipboard && !CMSWindowsClipboard::isOwnedBySynergy()) {
LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received"));
m_ownClipboard = false;
sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard);
sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection);
}
}
void
CMSWindowsScreen::openScreensaver(bool notify)
{
assert(m_screensaver != NULL);
m_screensaverNotify = notify;
if (m_screensaverNotify) {
m_installScreensaver();
}
else {
m_screensaver->disable();
}
}
void
CMSWindowsScreen::closeScreensaver()
{
if (m_screensaver != NULL) {
if (m_screensaverNotify) {
m_uninstallScreensaver();
}
else {
m_screensaver->enable();
}
}
m_screensaverNotify = false;
}
void
CMSWindowsScreen::screensaver(bool activate)
{
assert(m_screensaver != NULL);
if (activate) {
m_screensaver->activate();
}
else {
m_screensaver->deactivate();
}
}
void
CMSWindowsScreen::resetOptions()
{
// no options
}
void
CMSWindowsScreen::setOptions(const COptionsList&)
{
// no options
}
void
CMSWindowsScreen::updateKeys()
{
sendDeskMessage(SYNERGY_MSG_SYNC_KEYS, 0, 0);
memset(m_buttons, 0, sizeof(m_buttons));
// FIXME -- get the button state
}
void
CMSWindowsScreen::setSequenceNumber(UInt32 seqNum)
{
m_sequenceNumber = seqNum;
}
bool
CMSWindowsScreen::isPrimary() const
{
return m_isPrimary;
}
void*
CMSWindowsScreen::getEventTarget() const
{
return const_cast<CMSWindowsScreen*>(this);
}
bool
CMSWindowsScreen::getClipboard(ClipboardID, IClipboard* dst) const
{
CMSWindowsClipboard src(m_window);
CClipboard::copy(dst, &src);
return true;
}
void
CMSWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
{
assert(m_class != 0);
x = m_x;
y = m_y;
w = m_w;
h = m_h;
}
void
CMSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const
{
POINT pos;
sendDeskMessage(SYNERGY_MSG_CURSOR_POS,
reinterpret_cast<WPARAM>(&pos), 0);
x = pos.x;
y = pos.y;
}
void
CMSWindowsScreen::reconfigure(UInt32 activeSides)
{
assert(m_isPrimary);
LOG((CLOG_DEBUG "active sides: %x", activeSides));
m_setSides(activeSides);
}
void
CMSWindowsScreen::warpCursor(SInt32 x, SInt32 y)
{
// warp mouse
warpCursorNoFlush(x, y);
// remove all input events before and including warp
MSG msg;
while (PeekMessage(&msg, NULL, SYNERGY_MSG_INPUT_FIRST,
SYNERGY_MSG_INPUT_LAST, PM_REMOVE)) {
// do nothing
}
// save position as last position
m_xCursor = x;
m_yCursor = y;
}
SInt32
CMSWindowsScreen::getJumpZoneSize() const
{
return 1;
}
bool
CMSWindowsScreen::isAnyMouseButtonDown() const
{
static const char* buttonToName[] = {
"button 0",
"Left Button",
"Middle Button",
"Right Button",
"X Button 1",
"X Button 2"
};
for (UInt32 i = 0; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) {
if ((m_buttons[i] & 0x80) != 0) {
LOG((CLOG_DEBUG "locked by \"%s\"", buttonToName[i]));
return true;
}
}
return false;
}
KeyModifierMask
CMSWindowsScreen::getActiveModifiers() const
{
return m_keyMapper.getActiveModifiers();
}
void
CMSWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const
{
x = m_xCenter;
y = m_yCenter;
}
const char*
CMSWindowsScreen::getKeyName(KeyButton virtualKey) const
{
return m_keyMapper.getKeyName(virtualKey);
}
void
CMSWindowsScreen::fakeKeyEvent(KeyButton id, bool press) const
{
DWORD flags = 0;
if (m_keyMapper.isExtendedKey(id)) {
flags |= KEYEVENTF_EXTENDEDKEY;
}
if (!press) {
flags |= KEYEVENTF_KEYUP;
}
UINT vk = m_keyMapper.buttonToVirtualKey(id);
sendDeskMessage(SYNERGY_MSG_FAKE_KEY, flags,
MAKEWORD(static_cast<BYTE>(id & 0xffu),
static_cast<BYTE>(vk & 0xffu)));
}
bool
CMSWindowsScreen::fakeCtrlAltDel() const
{
if (!m_is95Family) {
// to fake ctrl+alt+del on the NT family we broadcast a suitable
// hotkey to all windows on the winlogon desktop. however, the
// current thread must be on that desktop to do the broadcast
// and we can't switch just any thread because some own windows
// or hooks. so start a new thread to do the real work.
CThread cad(new CFunctionJob(&CMSWindowsScreen::ctrlAltDelThread));
cad.wait();
}
else {
// get the sequence of keys to simulate ctrl+alt+del
IKeyState::Keystrokes keys;
KeyID key = kKeyDelete;
KeyModifierMask mask = KeyModifierControl | KeyModifierAlt;
if (mapKey(keys, *m_keyState, key, mask, false) == 0) {
keys.clear();
}
// do it
for (IKeyState::Keystrokes::const_iterator k = keys.begin();
k != keys.end(); ++k) {
fakeKeyEvent(k->m_key, k->m_press);
}
}
return true;
}
void
CMSWindowsScreen::fakeMouseButton(ButtonID id, bool press) const
{
DWORD data;
DWORD flags = mapButtonToEvent(id, press, &data);
sendDeskMessage(SYNERGY_MSG_FAKE_BUTTON, flags, data);
}
void
CMSWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) const
{
sendDeskMessage(SYNERGY_MSG_FAKE_MOVE,
static_cast<WPARAM>(x),
static_cast<LPARAM>(y));
}
void
CMSWindowsScreen::fakeMouseWheel(SInt32 delta) const
{
sendDeskMessage(SYNERGY_MSG_FAKE_WHEEL, delta, 0);
}
KeyButton
CMSWindowsScreen::mapKey(IKeyState::Keystrokes& keys,
const IKeyState& keyState, KeyID id,
KeyModifierMask desiredMask,
bool isAutoRepeat) const
{
return m_keyMapper.mapKey(keys, keyState, id, desiredMask, isAutoRepeat);
}
HINSTANCE
CMSWindowsScreen::openHookLibrary(const char* name)
{
// load the hook library
HINSTANCE hookLibrary = LoadLibrary(name);
if (hookLibrary == NULL) {
LOG((CLOG_ERR "Failed to load hook library; %s.dll is missing", name));
throw XScreenOpenFailure();
}
// look up functions
m_setSides = (SetSidesFunc)GetProcAddress(hookLibrary, "setSides");
m_setZone = (SetZoneFunc)GetProcAddress(hookLibrary, "setZone");
m_setMode = (SetModeFunc)GetProcAddress(hookLibrary, "setMode");
m_install = (InstallFunc)GetProcAddress(hookLibrary, "install");
m_uninstall = (UninstallFunc)GetProcAddress(hookLibrary, "uninstall");
m_init = (InitFunc)GetProcAddress(hookLibrary, "init");
m_cleanup = (CleanupFunc)GetProcAddress(hookLibrary, "cleanup");
m_installScreensaver =
(InstallScreenSaverFunc)GetProcAddress(
hookLibrary, "installScreenSaver");
m_uninstallScreensaver =
(UninstallScreenSaverFunc)GetProcAddress(
hookLibrary, "uninstallScreenSaver");
if (m_setSides == NULL ||
m_setZone == NULL ||
m_setMode == NULL ||
m_install == NULL ||
m_uninstall == NULL ||
m_init == NULL ||
m_cleanup == NULL ||
m_installScreensaver == NULL ||
m_uninstallScreensaver == NULL) {
LOG((CLOG_ERR "Invalid hook library; use a newer %s.dll", name));
throw XScreenOpenFailure();
}
// initialize hook library
if (m_init(GetCurrentThreadId()) == 0) {
LOG((CLOG_ERR "Cannot initialize hook library; is synergy already running?"));
throw XScreenOpenFailure();
}
return hookLibrary;
}
void
CMSWindowsScreen::closeHookLibrary(HINSTANCE hookLibrary) const
{
if (hookLibrary != NULL) {
m_cleanup();
FreeLibrary(hookLibrary);
}
}
HCURSOR
CMSWindowsScreen::createBlankCursor() const
{
// create a transparent cursor
int cw = GetSystemMetrics(SM_CXCURSOR);
int ch = GetSystemMetrics(SM_CYCURSOR);
UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)];
UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)];
memset(cursorAND, 0xff, ch * ((cw + 31) >> 2));
memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2));
HCURSOR c = CreateCursor(s_instance, 0, 0, cw, ch, cursorAND, cursorXOR);
delete[] cursorXOR;
delete[] cursorAND;
return c;
}
void
CMSWindowsScreen::destroyCursor(HCURSOR cursor) const
{
if (cursor != NULL) {
DestroyCursor(cursor);
}
}
ATOM
CMSWindowsScreen::createWindowClass() const
{
WNDCLASSEX classInfo;
classInfo.cbSize = sizeof(classInfo);
classInfo.style = CS_DBLCLKS | CS_NOCLOSE;
classInfo.lpfnWndProc = &CMSWindowsScreen::wndProc;
classInfo.cbClsExtra = 0;
classInfo.cbWndExtra = 0;
classInfo.hInstance = s_instance;
classInfo.hIcon = NULL;
classInfo.hCursor = NULL;
classInfo.hbrBackground = NULL;
classInfo.lpszMenuName = NULL;
classInfo.lpszClassName = "Synergy";
classInfo.hIconSm = NULL;
return RegisterClassEx(&classInfo);
}
ATOM
CMSWindowsScreen::createDeskWindowClass(bool isPrimary) const
{
WNDCLASSEX classInfo;
classInfo.cbSize = sizeof(classInfo);
classInfo.style = CS_DBLCLKS | CS_NOCLOSE;
classInfo.lpfnWndProc = isPrimary ?
&CMSWindowsScreen::primaryDeskProc :
&CMSWindowsScreen::secondaryDeskProc;
classInfo.cbClsExtra = 0;
classInfo.cbWndExtra = 0;
classInfo.hInstance = s_instance;
classInfo.hIcon = NULL;
classInfo.hCursor = m_cursor;
classInfo.hbrBackground = NULL;
classInfo.lpszMenuName = NULL;
classInfo.lpszClassName = "SynergyDesk";
classInfo.hIconSm = NULL;
return RegisterClassEx(&classInfo);
}
void
CMSWindowsScreen::destroyClass(ATOM windowClass) const
{
if (windowClass != 0) {
UnregisterClass((LPCTSTR)windowClass, s_instance);
}
}
HWND
CMSWindowsScreen::createWindow(ATOM windowClass, const char* name) const
{
HWND window = CreateWindowEx(WS_EX_TOPMOST |
WS_EX_TRANSPARENT |
WS_EX_TOOLWINDOW,
(LPCTSTR)windowClass,
name,
WS_POPUP,
0, 0, 1, 1,
NULL, NULL,
s_instance,
NULL);
if (window == NULL) {
LOG((CLOG_ERR "failed to create window: %d", GetLastError()));
throw XScreenOpenFailure();
}
return window;
}
void
CMSWindowsScreen::destroyWindow(HWND hwnd) const
{
if (hwnd != NULL) {
DestroyWindow(hwnd);
}
}
void
CMSWindowsScreen::sendEvent(CEvent::Type type, void* data)
{
EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data));
}
void
CMSWindowsScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id)
{
CClipboardInfo* info = (CClipboardInfo*)malloc(sizeof(CClipboardInfo));
info->m_id = id;
info->m_sequenceNumber = m_sequenceNumber;
sendEvent(type, info);
}
void
CMSWindowsScreen::handleSystemEvent(const CEvent& event, void*)
{
MSG* msg = reinterpret_cast<MSG*>(event.getData());
assert(msg != NULL);
if (CArchMiscWindows::processDialog(msg)) {
return;
}
if (onPreDispatch(msg->hwnd, msg->message, msg->wParam, msg->lParam)) {
return;
}
TranslateMessage(msg);
DispatchMessage(msg);
}
bool
CMSWindowsScreen::onPreDispatch(HWND hwnd,
UINT message, WPARAM wParam, LPARAM lParam)
{
// handle event
switch (message) {
case SYNERGY_MSG_SCREEN_SAVER:
return onScreensaver(wParam != 0);
}
if (m_isPrimary) {
return onPreDispatchPrimary(hwnd, message, wParam, lParam);
}
return false;
}
bool
CMSWindowsScreen::onPreDispatchPrimary(HWND,
UINT message, WPARAM wParam, LPARAM lParam)
{
// check if windows key is up but we think it's down. if so then
// synthesize a key release for it. we have to do this because
// if the user presses and releases a windows key without pressing
// any other key while it's down then windows will eat the key
// release. if we don't detect that and synthesize the release
// then the cclient won't take the usual windows key release action
// (which on windows is to show the start menu).
//
// we can use GetKeyState() to check the state of the windows keys
// because, event though the key release is not reported to us,
// the event is processed and the keyboard state updated by the
// system. since the key could go up at any time we'll check the
// state on every event. only check on windows 95 family since
// NT family reports the key release as usual. obviously we skip
// this if the event is for the windows key itself.
if (m_is95Family && message != SYNERGY_MSG_KEY) {
if (wParam != VK_LWIN) {
fixKey(VK_LWIN);
}
if (wParam != VK_RWIN) {
fixKey(VK_RWIN);
}
}
// handle event
switch (message) {
case SYNERGY_MSG_MARK:
return onMark(static_cast<UInt32>(wParam));
case SYNERGY_MSG_KEY:
return onKey(wParam, lParam);
case SYNERGY_MSG_MOUSE_BUTTON:
return onMouseButton(wParam, lParam);
case SYNERGY_MSG_MOUSE_MOVE:
return onMouseMove(static_cast<SInt32>(wParam),
static_cast<SInt32>(lParam));
case SYNERGY_MSG_MOUSE_WHEEL:
return onMouseWheel(static_cast<SInt32>(wParam));
case SYNERGY_MSG_PRE_WARP:
{
// save position to compute delta of next motion
m_xCursor = static_cast<SInt32>(wParam);
m_yCursor = 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, SYNERGY_MSG_MOUSE_MOVE,
SYNERGY_MSG_POST_WARP);
} while (msg.message != SYNERGY_MSG_POST_WARP);
}
return true;
case SYNERGY_MSG_POST_WARP:
LOG((CLOG_WARN "unmatched post warp"));
return true;
}
return false;
}
bool
CMSWindowsScreen::onEvent(HWND, UINT msg,
WPARAM wParam, LPARAM lParam, LRESULT* result)
{
switch (msg) {
case WM_QUERYENDSESSION:
if (m_is95Family) {
*result = TRUE;
return true;
}
break;
case WM_ENDSESSION:
if (m_is95Family) {
if (wParam == TRUE && lParam == 0) {
EVENTQUEUE->addEvent(CEvent(CEvent::kQuit));
}
return true;
}
break;
case WM_DRAWCLIPBOARD:
LOG((CLOG_DEBUG "clipboard was taken"));
// 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) {
LOG((CLOG_DEBUG "clipboard chain: forward: %d 0x%08x 0x%08x", msg, wParam, lParam));
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:
if (m_resume != NULL) {
m_resume->run();
}
break;
case PBT_APMSUSPEND:
if (m_suspend != NULL) {
m_suspend->run();
}
break;
}
*result = TRUE;
return true;
}
return false;
}
bool
CMSWindowsScreen::onMark(UInt32 mark)
{
m_markReceived = mark;
return true;
}
bool
CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam)
{
WPARAM charAndVirtKey = wParam;
wParam &= 0xffu;
// update key state. ignore key repeats.
if ((lParam & 0xc0000000u) == 0x00000000) {
KeyButton scancode = (KeyButton)((lParam & 0x01ff0000) >> 16);
m_keyState->setKeyDown(scancode, true);
}
else if ((lParam & 0xc0000000u) == 0xc0000000) {
KeyButton scancode = (KeyButton)((lParam & 0x01ff0000) >> 16);
m_keyState->setKeyDown(scancode, false);
}
// ignore message if posted prior to last mark change
if (!ignore()) {
// check for ctrl+alt+del emulation
if ((wParam == VK_PAUSE || wParam == VK_CANCEL) &&
(m_keyMapper.isPressed(VK_CONTROL) &&
m_keyMapper.isPressed(VK_MENU))) {
LOG((CLOG_DEBUG "emulate ctrl+alt+del"));
wParam = VK_DELETE;
lParam &= 0xfffe0000;
lParam |= m_keyMapper.virtualKeyToButton(wParam) << 16;
lParam |= 0x00000001;
charAndVirtKey = wParam;
}
// process key normally
bool altgr;
KeyModifierMask mask;
const KeyID key = m_keyMapper.mapKeyFromEvent(
charAndVirtKey, lParam, &mask, &altgr);
KeyButton button = static_cast<KeyButton>(
(lParam & 0x01ff0000u) >> 16);
if (key != kKeyNone && key != kKeyMultiKey) {
if ((lParam & 0x80000000) == 0) {
// key press
// if AltGr required for this key then make sure
// the ctrl and alt keys are *not* down on the
// client. windows simulates AltGr with ctrl and
// alt for some inexplicable reason and clients
// will get confused if they see mode switch and
// ctrl and alt. we'll also need to put ctrl and
// alt back the way they were after we simulate
// the key.
bool ctrlL = m_keyMapper.isPressed(VK_LCONTROL);
bool ctrlR = m_keyMapper.isPressed(VK_RCONTROL);
bool altL = m_keyMapper.isPressed(VK_LMENU);
bool altR = m_keyMapper.isPressed(VK_RMENU);
if (altgr) {
KeyID key;
KeyButton button;
KeyModifierMask mask2 = (mask &
~(KeyModifierControl |
KeyModifierAlt |
KeyModifierModeSwitch));
if (ctrlL) {
key = kKeyControl_L;
button = m_keyMapper.virtualKeyToButton(VK_LCONTROL);
LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button));
sendEvent(getKeyUpEvent(),
CKeyInfo::alloc(key, mask2, button, 1));
}
if (ctrlR) {
key = kKeyControl_R;
button = m_keyMapper.virtualKeyToButton(VK_RCONTROL);
LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button));
sendEvent(getKeyUpEvent(),
CKeyInfo::alloc(key, mask2, button, 1));
}
if (altL) {
key = kKeyAlt_L;
button = m_keyMapper.virtualKeyToButton(VK_LMENU);
LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button));
sendEvent(getKeyUpEvent(),
CKeyInfo::alloc(key, mask2, button, 1));
}
if (altR) {
key = kKeyAlt_R;
button = m_keyMapper.virtualKeyToButton(VK_RMENU);
LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button));
sendEvent(getKeyUpEvent(),
CKeyInfo::alloc(key, mask2, button, 1));
}
}
// send key
const bool wasDown = ((lParam & 0x40000000) != 0);
SInt32 repeat = (SInt32)(lParam & 0xffff);
if (!wasDown) {
LOG((CLOG_DEBUG1 "event: key press key=%d mask=0x%04x button=0x%04x", key, mask, button));
sendEvent(getKeyDownEvent(),
CKeyInfo::alloc(key, mask, button, 1));
if (repeat > 0) {
--repeat;
}
}
if (repeat >= 1) {
LOG((CLOG_DEBUG1 "event: key repeat key=%d mask=0x%04x count=%d button=0x%04x", key, mask, repeat, button));
sendEvent(getKeyRepeatEvent(),
CKeyInfo::alloc(key, mask, button, repeat));
}
// restore ctrl and alt state
if (altgr) {
KeyID key;
KeyButton button;
KeyModifierMask mask2 = (mask &
~(KeyModifierControl |
KeyModifierAlt |
KeyModifierModeSwitch));
if (ctrlL) {
key = kKeyControl_L;
button = m_keyMapper.virtualKeyToButton(VK_LCONTROL);
LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button));
sendEvent(getKeyDownEvent(),
CKeyInfo::alloc(key, mask2, button, 1));
mask2 |= KeyModifierControl;
}
if (ctrlR) {
key = kKeyControl_R;
button = m_keyMapper.virtualKeyToButton(VK_RCONTROL);
LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button));
sendEvent(getKeyDownEvent(),
CKeyInfo::alloc(key, mask2, button, 1));
mask2 |= KeyModifierControl;
}
if (altL) {
key = kKeyAlt_L;
button = m_keyMapper.virtualKeyToButton(VK_LMENU);
LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button));
sendEvent(getKeyDownEvent(),
CKeyInfo::alloc(key, mask2, button, 1));
mask2 |= KeyModifierAlt;
}
if (altR) {
key = kKeyAlt_R;
button = m_keyMapper.virtualKeyToButton(VK_RMENU);
LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button));
sendEvent(getKeyDownEvent(),
CKeyInfo::alloc(key, mask2, button, 1));
mask2 |= KeyModifierAlt;
}
}
}
else {
// key release. if the key isn't down according to
// our table then we never got the key press event
// for it. if it's not a modifier key then we'll
// synthesize the press first. only do this on
// the windows 95 family, which eats certain special
// keys like alt+tab, ctrl+esc, etc.
if (m_is95Family &&
!m_keyMapper.isModifier(wParam) &&
m_keyMapper.isPressed(wParam)) {
LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask, button));
sendEvent(getKeyDownEvent(),
CKeyInfo::alloc(key, mask, button, 1));
m_keyMapper.updateKey(lParam & 0x3fffffffu);
}
// do key up
LOG((CLOG_DEBUG1 "event: key release key=%d mask=0x%04x button=0x%04x", key, mask, button));
sendEvent(getKeyUpEvent(),
CKeyInfo::alloc(key, mask, button, 1));
}
}
else {
LOG((CLOG_DEBUG2 "event: cannot map key wParam=%d lParam=0x%08x", wParam, lParam));
}
}
// keep our shadow key state up to date
m_keyMapper.updateKey(lParam);
return true;
}
bool
CMSWindowsScreen::onMouseButton(WPARAM wParam, LPARAM lParam)
{
// get which button
bool pressed = false;
const ButtonID button = mapButtonFromEvent(wParam, lParam);
// ignore message if posted prior to last mark change
if (!ignore()) {
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_NCLBUTTONDOWN:
case WM_NCMBUTTONDOWN:
case WM_NCRBUTTONDOWN:
case WM_NCXBUTTONDOWN:
case WM_NCLBUTTONDBLCLK:
case WM_NCMBUTTONDBLCLK:
case WM_NCRBUTTONDBLCLK:
case WM_NCXBUTTONDBLCLK:
LOG((CLOG_DEBUG1 "event: button press button=%d", button));
if (button != kButtonNone) {
sendEvent(getButtonDownEvent(), CButtonInfo::alloc(button));
}
pressed = true;
break;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
case WM_XBUTTONUP:
case WM_NCLBUTTONUP:
case WM_NCMBUTTONUP:
case WM_NCRBUTTONUP:
case WM_NCXBUTTONUP:
LOG((CLOG_DEBUG1 "event: button release button=%d", button));
if (button != kButtonNone) {
sendEvent(getButtonUpEvent(), CButtonInfo::alloc(button));
}
pressed = false;
break;
}
}
// keep our shadow key state up to date
if (button >= kButtonLeft && button <= kButtonExtra0 + 1) {
if (pressed) {
m_buttons[button] |= 0x80;
}
else {
m_buttons[button] &= ~0x80;
}
}
return true;
}
bool
CMSWindowsScreen::onMouseMove(SInt32 mx, SInt32 my)
{
// compute motion delta (relative to the last known
// mouse position)
SInt32 x = mx - m_xCursor;
SInt32 y = my - m_yCursor;
// ignore if the mouse didn't move or if message posted prior
// to last mark change.
if (ignore() || (x == 0 && y == 0)) {
return true;
}
// save position to compute delta of next motion
m_xCursor = mx;
m_yCursor = my;
if (m_isOnScreen) {
// motion on primary screen
sendEvent(getMotionOnPrimaryEvent(),
CMotionInfo::alloc(m_xCursor, m_yCursor));
}
else {
// motion on secondary screen. warp mouse back to
// center.
warpCursorNoFlush(m_xCenter, m_yCenter);
// examine the motion. if it's about the distance
// from the center of the screen to an edge then
// it's probably a bogus motion that we want to
// ignore (see warpCursorNoFlush() for a further
// description).
static SInt32 bogusZoneSize = 10;
if (-x + bogusZoneSize > m_xCenter - m_x ||
x + bogusZoneSize > m_x + m_w - m_xCenter ||
-y + bogusZoneSize > m_yCenter - m_y ||
y + bogusZoneSize > m_y + m_h - m_yCenter) {
LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y));
}
else {
// send motion
sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y));
}
}
return true;
}
bool
CMSWindowsScreen::onMouseWheel(SInt32 delta)
{
// ignore message if posted prior to last mark change
if (!ignore()) {
LOG((CLOG_DEBUG1 "event: button wheel delta=%d", delta));
sendEvent(getWheelEvent(), CWheelInfo::alloc(delta));
}
return true;
}
bool
CMSWindowsScreen::onScreensaver(bool activated)
{
// ignore this message if there are any other screen saver
// messages already in the queue. this is important because
// our checkStarted() function has a deliberate delay, so it
// can't respond to events at full CPU speed and will fall
// behind if a lot of screen saver events are generated.
// that can easily happen because windows will continually
// send SC_SCREENSAVE until the screen saver starts, even if
// the screen saver is disabled!
MSG msg;
if (PeekMessage(&msg, NULL, SYNERGY_MSG_SCREEN_SAVER,
SYNERGY_MSG_SCREEN_SAVER, PM_NOREMOVE)) {
return true;
}
if (activated) {
if (m_screensaver->checkStarted(SYNERGY_MSG_SCREEN_SAVER, FALSE, 0)) {
sendEvent(getScreensaverActivatedEvent());
// enable display power down
CArchMiscWindows::removeBusyState(CArchMiscWindows::kDISPLAY);
}
}
else {
sendEvent(getScreensaverDeactivatedEvent());
// disable display power down
CArchMiscWindows::addBusyState(CArchMiscWindows::kDISPLAY);
}
return true;
}
bool
CMSWindowsScreen::onDisplayChange()
{
// screen resolution may have changed. save old shape.
SInt32 xOld = m_x, yOld = m_y, wOld = m_w, hOld = m_h;
// update shape
updateScreenShape();
// do nothing if resolution hasn't changed
if (xOld != m_x || yOld != m_y || wOld != m_w || hOld != m_h) {
if (m_isPrimary) {
// warp mouse to center if off screen
if (!m_isOnScreen) {
warpCursor(m_xCenter, m_yCenter);
}
// tell hook about resize if on screen
else {
m_setZone(m_x, m_y, m_w, m_h, getJumpZoneSize());
}
}
// send new screen info
sendEvent(getShapeChangedEvent());
}
return true;
}
bool
CMSWindowsScreen::onClipboardChange()
{
// now notify client that somebody changed the clipboard (unless
// we're the owner).
if (!CMSWindowsClipboard::isOwnedBySynergy()) {
LOG((CLOG_DEBUG "clipboard changed: foreign owned"));
if (m_ownClipboard) {
LOG((CLOG_DEBUG "clipboard changed: lost ownership"));
m_ownClipboard = false;
sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard);
sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection);
}
}
else {
LOG((CLOG_DEBUG "clipboard changed: synergy owned"));
m_ownClipboard = true;
}
return true;
}
void
CMSWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y)
{
// send an event that we can recognize before the mouse warp
PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_PRE_WARP, x, y);
// warp mouse. hopefully this inserts a mouse motion event
// between the previous message and the following message.
SetCursorPos(x, y);
// yield the CPU. there's a race condition when warping:
// a hardware mouse event occurs
// the mouse hook is not called because that process doesn't have the CPU
// we send PRE_WARP, SetCursorPos(), send POST_WARP
// we process all of those events and update m_x, m_y
// we finish our time slice
// the hook is called
// the hook sends us a mouse event from the pre-warp position
// we get the CPU
// we compute a bogus warp
// we need the hook to process all mouse events that occur
// before we warp before we do the warp but i'm not sure how
// to guarantee that. yielding the CPU here may reduce the
// chance of undesired behavior. we'll also check for very
// large motions that look suspiciously like about half width
// or height of the screen.
ARCH->sleep(0.0);
// send an event that we can recognize after the mouse warp
PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_POST_WARP, 0, 0);
}
void
CMSWindowsScreen::nextMark()
{
// next mark
++m_mark;
// mark point in message queue where the mark was changed
PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_MARK, m_mark, 0);
}
bool
CMSWindowsScreen::ignore() const
{
return (m_mark != m_markReceived);
}
void
CMSWindowsScreen::updateScreenShape()
{
// get shape
m_x = GetSystemMetrics(SM_XVIRTUALSCREEN);
m_y = GetSystemMetrics(SM_YVIRTUALSCREEN);
m_w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
m_h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
// get center for cursor
m_xCenter = GetSystemMetrics(SM_CXSCREEN) >> 1;
m_yCenter = GetSystemMetrics(SM_CYSCREEN) >> 1;
// check for multiple monitors
m_multimon = (m_w != GetSystemMetrics(SM_CXSCREEN) ||
m_h != GetSystemMetrics(SM_CYSCREEN));
LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : ""));
}
void
CMSWindowsScreen::enableSpecialKeys(bool enable) const
{
// enable/disable ctrl+alt+del, alt+tab, etc on win95 family.
// since the win95 family doesn't support low-level hooks, we
// use this undocumented feature to suppress normal handling
// of certain key combinations.
if (m_is95Family) {
DWORD dummy = 0;
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,
enable ? FALSE : TRUE, &dummy, 0);
}
}
void
CMSWindowsScreen::fixKey(UINT virtualKey)
{
if (m_keyMapper.isPressed(virtualKey) &&
(GetAsyncKeyState(virtualKey) & 0x8000) == 0) {
// compute appropriate parameters for fake event
KeyButton button = m_keyMapper.virtualKeyToButton(virtualKey);
LPARAM lParam = 0xc0000000 | ((LPARAM)button << 16);
// process as if it were a key up
KeyModifierMask mask;
KeyID key = m_keyMapper.mapKeyFromEvent(virtualKey,
lParam, &mask, NULL);
LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask, button));
sendEvent(getKeyUpEvent(), CKeyInfo::alloc(key, mask, button, 1));
m_keyMapper.updateKey(lParam);
}
}
DWORD
CMSWindowsScreen::mapButtonToEvent(ButtonID button,
bool press, DWORD* inData) const
{
DWORD dummy;
DWORD* data = (inData != NULL) ? inData : &dummy;
// 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
*data = 0;
switch (button) {
case kButtonLeft:
return press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
case kButtonMiddle:
return press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
case kButtonRight:
return press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
case kButtonExtra0 + 0:
*data = XBUTTON1;
return press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
case kButtonExtra0 + 1:
*data = XBUTTON2;
return press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
default:
return 0;
}
}
ButtonID
CMSWindowsScreen::mapButtonFromEvent(WPARAM msg, LPARAM button) const
{
switch (msg) {
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK:
case WM_LBUTTONUP:
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONDBLCLK:
case WM_NCLBUTTONUP:
return kButtonLeft;
case WM_MBUTTONDOWN:
case WM_MBUTTONDBLCLK:
case WM_MBUTTONUP:
case WM_NCMBUTTONDOWN:
case WM_NCMBUTTONDBLCLK:
case WM_NCMBUTTONUP:
return kButtonMiddle;
case WM_RBUTTONDOWN:
case WM_RBUTTONDBLCLK:
case WM_RBUTTONUP:
case WM_NCRBUTTONDOWN:
case WM_NCRBUTTONDBLCLK:
case WM_NCRBUTTONUP:
return kButtonRight;
case WM_XBUTTONDOWN:
case WM_XBUTTONDBLCLK:
case WM_XBUTTONUP:
case WM_NCXBUTTONDOWN:
case WM_NCXBUTTONDBLCLK:
case WM_NCXBUTTONUP:
switch (button) {
case XBUTTON1:
return kButtonExtra0 + 0;
case XBUTTON2:
return kButtonExtra0 + 1;
}
return kButtonNone;
default:
return kButtonNone;
}
}
void
CMSWindowsScreen::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()));
}
}
LRESULT CALLBACK
CMSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
assert(s_screen != NULL);
LRESULT result = 0;
if (!s_screen->onEvent(hwnd, msg, wParam, lParam, &result)) {
result = DefWindowProc(hwnd, msg, wParam, lParam);
}
return result;
}
LRESULT CALLBACK
CMSWindowsScreen::primaryDeskProc(
HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK
CMSWindowsScreen::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
CMSWindowsScreen::deskMouseMove(SInt32 x, SInt32 y) const
{
// motion is simple (i.e. it's on the primary monitor) if there
// is only one monitor.
bool simple = !m_multimon;
if (!simple) {
// also simple if motion is within the primary monitor
simple = (x >= 0 && x < GetSystemMetrics(SM_CXSCREEN) &&
y >= 0 && y < GetSystemMetrics(SM_CYSCREEN));
}
// move the mouse directly to target position if motion is simple
if (simple) {
// when using absolute positioning with mouse_event(),
// the normalized device coordinates range over only
// the primary screen.
SInt32 w = GetSystemMetrics(SM_CXSCREEN);
SInt32 h = GetSystemMetrics(SM_CYSCREEN);
mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
(DWORD)((65535.0f * x) / (w - 1) + 0.5f),
(DWORD)((65535.0f * y) / (h - 1) + 0.5f),
0, 0);
}
// windows 98 (and Me?) is broken. you cannot set the absolute
// position of the mouse except on the primary monitor but you
// can do relative moves onto any monitor. this is, in microsoft's
// words, "by design." apparently the designers of windows 2000
// we're a little less lazy and did it right.
//
// microsoft recommends in Q193003 to absolute position the cursor
// somewhere on the primary monitor then relative move to the
// desired location. this doesn't work for us because when the
// user drags a scrollbar, a window, etc. it causes the dragged
// item to jump back and forth between the position on the primary
// monitor and the desired position. while it always ends up in
// the right place, the effect is disconcerting.
//
// instead we'll get the cursor's current position and do just a
// relative move from there to the desired position. relative
// moves are subject to cursor acceleration which we don't want.
// so we disable acceleration, do the relative move, then restore
// acceleration. there's a slight chance we'll end up in the
// wrong place if the user moves the cursor using this system's
// mouse while simultaneously moving the mouse on the server
// system. that defeats the purpose of synergy so we'll assume
// that won't happen. even if it does, the next mouse move will
// correct the position.
else {
// 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
POINT pos;
GetCursorPos(&pos);
mouse_event(MOUSEEVENTF_MOVE, x - pos.x, y - pos.y, 0, 0);
// restore mouse speed & acceleration
if (accelChanged) {
SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0);
SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0);
}
}
}
void
CMSWindowsScreen::deskEnter(CDesk* desk)
{
if (!m_isPrimary) {
ReleaseCapture();
}
ShowCursor(TRUE);
SetWindowPos(desk->m_window, HWND_BOTTOM, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE |
SWP_NOACTIVATE | SWP_HIDEWINDOW);
}
void
CMSWindowsScreen::deskLeave(CDesk* desk, HKL keyLayout)
{
ShowCursor(FALSE);
if (m_isPrimary) {
// map a window to hide the cursor and to use whatever keyboard
// layout we choose rather than the keyboard layout of the last
// active window.
int x, y, w, h;
if (desk->m_lowLevel) {
// with a low level hook the cursor will never budge so
// just a 1x1 window is sufficient.
x = m_xCenter;
y = m_yCenter;
w = 1;
h = 1;
}
else {
// with regular hooks the cursor will jitter as it's moved
// by the user then back to the center by us. to be sure
// we never lose it, cover all the monitors with the window.
x = m_x;
y = m_y;
w = m_w;
h = m_h;
}
SetWindowPos(desk->m_window, HWND_TOPMOST, x, y, w, h,
SWP_NOACTIVATE | SWP_SHOWWINDOW);
// switch to requested keyboard layout
ActivateKeyboardLayout(keyLayout, 0);
}
else {
// move hider window under the cursor center, raise, and show it
SetWindowPos(desk->m_window, HWND_TOPMOST,
m_xCenter, m_yCenter, 1, 1,
SWP_NOACTIVATE | SWP_SHOWWINDOW);
// watch for mouse motion. if we see any then we hide the
// hider window so the user can use the physically attached
// mouse if desired. we'd rather not capture the mouse but
// we aren't notified when the mouse leaves our window.
SetCapture(desk->m_window);
// warp the mouse to the cursor center
deskMouseMove(m_xCenter, m_yCenter);
}
}
void
CMSWindowsScreen::deskThread(void* vdesk)
{
MSG msg;
// use given desktop for this thread
CDesk* desk = reinterpret_cast<CDesk*>(vdesk);
desk->m_threadID = GetCurrentThreadId();
desk->m_window = NULL;
if (desk->m_desk != NULL && SetThreadDesktop(desk->m_desk) != 0) {
// create a message queue
PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE);
// create a window. we use this window to hide the cursor.
try {
desk->m_window = createWindow(m_deskClass, "SynergyDesk");
LOG((CLOG_DEBUG "desk %s window is 0x%08x", desk->m_name.c_str(), desk->m_window));
}
catch (...) {
// ignore
LOG((CLOG_DEBUG "can't create desk window for %s", desk->m_name.c_str()));
}
// a window on the primary screen should never activate
if (m_isPrimary && desk->m_window != NULL) {
EnableWindow(desk->m_window, FALSE);
}
}
// tell main thread that we're ready
{
CLock lock(&m_mutex);
m_deskReady = true;
m_deskReady.broadcast();
}
while (GetMessage(&msg, NULL, 0, 0)) {
switch (msg.message) {
default:
TranslateMessage(&msg);
DispatchMessage(&msg);
continue;
case SYNERGY_MSG_SWITCH:
if (m_isPrimary) {
m_uninstall();
if (m_screensaverNotify) {
m_uninstallScreensaver();
m_installScreensaver();
}
switch (m_install()) {
case kHOOK_FAILED:
// we won't work on this desk
desk->m_lowLevel = false;
break;
case kHOOK_OKAY:
desk->m_lowLevel = false;
break;
case kHOOK_OKAY_LL:
desk->m_lowLevel = true;
break;
}
}
break;
case SYNERGY_MSG_ENTER:
deskEnter(desk);
break;
case SYNERGY_MSG_LEAVE:
deskLeave(desk, (HKL)msg.wParam);
break;
case SYNERGY_MSG_FAKE_KEY:
keybd_event(HIBYTE(msg.lParam), LOBYTE(msg.lParam), msg.wParam, 0);
break;
case SYNERGY_MSG_FAKE_BUTTON:
if (msg.wParam != 0) {
mouse_event(msg.wParam, 0, 0, msg.lParam, 0);
}
break;
case SYNERGY_MSG_FAKE_MOVE:
deskMouseMove(static_cast<SInt32>(msg.wParam),
static_cast<SInt32>(msg.lParam));
break;
case SYNERGY_MSG_FAKE_WHEEL:
mouse_event(MOUSEEVENTF_WHEEL, 0, 0, msg.wParam, 0);
break;
case SYNERGY_MSG_CURSOR_POS: {
POINT* pos = reinterpret_cast<POINT*>(msg.wParam);
if (!GetCursorPos(pos)) {
pos->x = m_xCenter;
pos->y = m_yCenter;
}
break;
}
case SYNERGY_MSG_SYNC_KEYS:
m_keyMapper.update(m_keyState);
break;
}
// notify that message was processed
CLock lock(&m_mutex);
m_deskReady = true;
m_deskReady.broadcast();
}
// clean up
deskEnter(desk);
if (desk->m_window != NULL) {
DestroyWindow(desk->m_window);
}
if (desk->m_desk != NULL) {
CMSWindowsDesktop::closeDesktop(desk->m_desk);
}
}
CMSWindowsScreen::CDesk*
CMSWindowsScreen::addDesk(const CString& name, HDESK hdesk)
{
CDesk* desk = new CDesk;
desk->m_name = name;
desk->m_desk = hdesk;
desk->m_targetID = GetCurrentThreadId();
desk->m_thread = new CThread(new TMethodJob<CMSWindowsScreen>(
this, &CMSWindowsScreen::deskThread, desk));
waitForDesk();
m_desks.insert(std::make_pair(name, desk));
return desk;
}
void
CMSWindowsScreen::removeDesks()
{
for (CDesks::iterator index = m_desks.begin();
index != m_desks.end(); ++index) {
CDesk* desk = index->second;
PostThreadMessage(desk->m_threadID, WM_QUIT, 0, 0);
desk->m_thread->wait();
delete desk->m_thread;
delete desk;
}
m_desks.clear();
m_activeDesk = NULL;
m_activeDeskName = "";
}
void
CMSWindowsScreen::checkDesk()
{
// get current desktop. if we already know about it then return.
CDesk* desk;
HDESK hdesk = CMSWindowsDesktop::openInputDesktop();
CString name = CMSWindowsDesktop::getDesktopName(hdesk);
CDesks::const_iterator index = m_desks.find(name);
if (index == m_desks.end()) {
desk = addDesk(name, hdesk);
// hold on to hdesk until thread exits so the desk can't
// be removed by the system
}
else {
CMSWindowsDesktop::closeDesktop(hdesk);
desk = index->second;
}
// if active desktop changed then tell the old and new desk threads
// about the change. don't switch desktops when the screensaver is
// active becaue we'd most likely switch to the screensaver desktop
// which would have the side effect of forcing the screensaver to
// stop.
if (name != m_activeDeskName && !m_screensaver->isActive()) {
// show cursor on previous desk
if (!m_isOnScreen) {
sendDeskMessage(SYNERGY_MSG_ENTER, 0, 0);
}
// check for desk accessibility change. we don't get events
// from an inaccessible desktop so when we switch from an
// inaccessible desktop to an accessible one we have to
// update the keyboard state.
LOG((CLOG_DEBUG "switched to desk \"%s\"", name.c_str()));
bool isAccessible = isDeskAccessible(desk);
if (isDeskAccessible(m_activeDesk) != isAccessible) {
if (isAccessible) {
LOG((CLOG_DEBUG "desktop is now accessible"));
updateKeys();
}
else {
LOG((CLOG_DEBUG "desktop is now inaccessible"));
}
}
// switch desk
m_activeDesk = desk;
m_activeDeskName = name;
sendDeskMessage(SYNERGY_MSG_SWITCH, 0, 0);
// hide cursor on new desk
if (!m_isOnScreen) {
sendDeskMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0);
}
}
}
bool
CMSWindowsScreen::isDeskAccessible(const CDesk* desk) const
{
return (desk != NULL && desk->m_desk != NULL);
}
void
CMSWindowsScreen::sendDeskMessage(UINT msg, WPARAM wParam, LPARAM lParam) const
{
if (m_activeDesk != NULL && m_activeDesk->m_window != NULL) {
PostThreadMessage(m_activeDesk->m_threadID, msg, wParam, lParam);
waitForDesk();
}
}
void
CMSWindowsScreen::waitForDesk() const
{
CMSWindowsScreen* self = const_cast<CMSWindowsScreen*>(this);
CLock lock(&m_mutex);
while (!(bool)m_deskReady) {
m_deskReady.wait();
}
self->m_deskReady = false;
}
void
CMSWindowsScreen::handleCheckDesk(const CEvent&, void*)
{
checkDesk();
}