1964 lines
55 KiB
C++
1964 lines
55 KiB
C++
/*
|
|
* synergy -- mouse and keyboard sharing utility
|
|
* Copyright (C) 2012-2016 Symless Ltd.
|
|
* Copyright (C) 2002 Chris Schoeneman
|
|
*
|
|
* This package is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* found in the file LICENSE that should have accompanied this file.
|
|
*
|
|
* This package is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You 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 "synergy/Clipboard.h"
|
|
#include "synergy/KeyMap.h"
|
|
#include "synergy/XScreen.h"
|
|
#include "synergy/App.h"
|
|
#include "synergy/ArgsBase.h"
|
|
#include "synergy/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 {
|
|
if (m_isPrimary && !m_noHooks) {
|
|
m_hook.loadLibrary();
|
|
}
|
|
|
|
m_screensaver = new MSWindowsScreenSaver();
|
|
m_desks = new MSWindowsDesks(
|
|
m_isPrimary,
|
|
m_noHooks,
|
|
m_hook.getInstance(),
|
|
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, "Synergy");
|
|
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::isOwnedBySynergy()) {
|
|
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, SYNERGY_MSG_INPUT_FIRST,
|
|
SYNERGY_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)", synergy::KeyMap::formatKey(key, mask).c_str(), key, mask));
|
|
return 0;
|
|
}
|
|
|
|
LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", synergy::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 = "Synergy";
|
|
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,
|
|
reinterpret_cast<LPCTSTR>(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 SYNERGY_MSG_SCREEN_SAVER:
|
|
return onScreensaver(wParam != 0);
|
|
|
|
case SYNERGY_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 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:
|
|
// XXX -- support x-axis scrolling
|
|
return onMouseWheel(0, static_cast<SInt32>(wParam));
|
|
|
|
case SYNERGY_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, 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;
|
|
|
|
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) == 1, 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. synergy 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, SYNERGY_MSG_SCREEN_SAVER,
|
|
SYNERGY_MSG_SCREEN_SAVER, PM_NOREMOVE)) {
|
|
return true;
|
|
}
|
|
|
|
if (activated) {
|
|
if (!m_screensaverActive &&
|
|
m_screensaver->checkStarted(SYNERGY_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::isOwnedBySynergy()) {
|
|
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: synergy 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(), 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);
|
|
|
|
// 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(), SYNERGY_MSG_POST_WARP, 0, 0);
|
|
}
|
|
|
|
void
|
|
MSWindowsScreen::nextMark()
|
|
{
|
|
// next mark
|
|
++m_mark;
|
|
|
|
// mark point in message queue where the mark was changed
|
|
PostThreadMessage(GetCurrentThreadId(), SYNERGY_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;
|
|
}
|