refactoring. refactored stuff in client (with changes to server

as necessary).
This commit is contained in:
crs 2002-07-12 20:41:23 +00:00
parent ef7fe1f283
commit 52b60d5175
14 changed files with 1325 additions and 1045 deletions

View File

@ -50,53 +50,60 @@ CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen()
void
CMSWindowsSecondaryScreen::run()
{
assert(m_window != NULL);
// must call run() from same thread as open()
assert(m_threadID == GetCurrentThreadId());
// change our priority
CThread::getCurrentThread().setPriority(-7);
// poll input desktop to see if it changes (onPreTranslate()
// handles WM_TIMER)
UINT timer = 0;
if (!m_is95Family) {
SetTimer(NULL, 0, 200, NULL);
}
// run event loop
try {
log((CLOG_INFO "entering event loop"));
doRun();
mainLoop();
log((CLOG_INFO "exiting event loop"));
// remove timer
if (!m_is95Family) {
KillTimer(NULL, timer);
}
catch (...) {
log((CLOG_INFO "exiting event loop"));
throw;
}
}
void
CMSWindowsSecondaryScreen::stop()
{
doStop();
exitMainLoop();
}
void
CMSWindowsSecondaryScreen::open()
{
assert(m_window == NULL);
try {
// open the display
openDisplay();
// update key state
updateKeys();
updateModifiers();
// create and prepare our window
createWindow();
// assume primary has all clipboards
// initialize the clipboards; assume primary has all clipboards
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
grabClipboard(id);
}
// get keyboard state
updateKeys();
updateModifiers();
// disable the screen saver
getScreenSaver()->disable();
installScreenSaver();
}
catch (...) {
close();
throw;
}
// hide the cursor
m_active = true;
@ -106,13 +113,8 @@ CMSWindowsSecondaryScreen::open()
void
CMSWindowsSecondaryScreen::close()
{
// release keys that are logically pressed
releaseKeys();
// restore the screen saver settings
getScreenSaver()->enable();
// close the display
uninstallScreenSaver();
destroyWindow();
closeDisplay();
}
@ -145,8 +147,11 @@ CMSWindowsSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask)
toggleKey(VK_SCROLL, KeyModifierScrollLock);
}
// warp to requested location
warpCursor(x, y);
// show mouse
onEnter(x, y);
hideWindow();
}
void
@ -161,30 +166,13 @@ CMSWindowsSecondaryScreen::leave()
syncDesktop();
// hide mouse
onLeave();
showWindow();
// not active anymore
m_active = false;
// 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.
HWND clipboardOwner = GetClipboardOwner();
if (m_clipboardOwner != clipboardOwner) {
m_clipboardOwner = clipboardOwner;
if (m_clipboardOwner != m_window) {
m_receiver->onGrabClipboard(kClipboardClipboard);
m_receiver->onGrabClipboard(kClipboardSelection);
}
}
// make sure our idea of clipboard ownership is correct
checkClipboard();
}
void
@ -402,15 +390,7 @@ CMSWindowsSecondaryScreen::getMousePos(SInt32& x, SInt32& y) const
assert(m_window != NULL);
syncDesktop();
POINT pos;
if (GetCursorPos(&pos)) {
x = pos.x;
y = pos.y;
}
else {
x = 0;
y = 0;
}
getCursorPos(x, y);
}
void
@ -437,52 +417,18 @@ CMSWindowsSecondaryScreen::getClipboard(ClipboardID /*id*/,
CClipboard::copy(dst, &src);
}
void
CMSWindowsSecondaryScreen::onOpenDisplay()
{
assert(m_window == NULL);
// note if using multiple monitors
m_multimon = isMultimon();
// save thread id. we'll need to pass this to the hook library.
m_threadID = GetCurrentThreadId();
// get the input desktop and switch to it
if (m_is95Family) {
if (!openDesktop()) {
throw XScreenOpenFailure();
}
}
else {
if (!switchDesktop(openInputDesktop())) {
throw XScreenOpenFailure();
}
}
}
void
CMSWindowsSecondaryScreen::onCloseDisplay()
{
// disconnect from desktop
if (m_is95Family) {
closeDesktop();
}
else {
switchDesktop(NULL);
}
// clear thread id
m_threadID = 0;
assert(m_window == NULL);
assert(m_desk == NULL);
}
bool
CMSWindowsSecondaryScreen::onPreTranslate(MSG* msg)
CMSWindowsSecondaryScreen::onPreDispatch(const CEvent* event)
{
assert(event != NULL);
// forward to superclass
if (CMSWindowsScreen::onPreDispatch(event)) {
return true;
}
// handle event
const MSG* msg = &event->m_msg;
switch (msg->message) {
case WM_TIMER:
// if current desktop is not the input desktop then switch to it
@ -503,32 +449,35 @@ CMSWindowsSecondaryScreen::onPreTranslate(MSG* msg)
return false;
}
LRESULT
CMSWindowsSecondaryScreen::onEvent(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
bool
CMSWindowsSecondaryScreen::onEvent(CEvent* event)
{
switch (msg) {
assert(event != NULL);
const MSG& msg = event->msg;
switch (msg.message) {
case WM_QUERYENDSESSION:
if (m_is95Family) {
return TRUE;
event->m_result = TRUE;
return true;
}
break;
case WM_ENDSESSION:
if (m_is95Family) {
if (wParam == TRUE && lParam == 0) {
if (msg.wParam == TRUE && msg.lParam == 0) {
stop();
}
return 0;
return true;
}
break;
case WM_PAINT:
ValidateRect(hwnd, NULL);
return 0;
ValidateRect(msg.hwnd, NULL);
return true;
case WM_ACTIVATEAPP:
if (wParam == FALSE) {
if (msg.wParam == FALSE) {
// some other app activated. hide the hider window.
ShowWindow(m_window, SW_HIDE);
}
@ -538,67 +487,265 @@ CMSWindowsSecondaryScreen::onEvent(HWND hwnd, UINT msg,
log((CLOG_DEBUG "clipboard was taken"));
// first pass it on
SendMessage(m_nextClipboardWindow, msg, wParam, lParam);
if (m_nextClipboardWindow != NULL) {
SendMessage(m_nextClipboardWindow,
msg.message, msg.wParam, msg.lParam);
}
// now notify client that somebody changed the clipboard (unless
// we're now the owner, in which case it's because we took
// ownership, or now it's owned by nobody, which will happen if
// we owned it and switched desktops because we destroy our
// window to do that).
try {
m_clipboardOwner = GetClipboardOwner();
if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) {
m_receiver->onGrabClipboard(kClipboardClipboard);
m_receiver->onGrabClipboard(kClipboardSelection);
}
return 0;
}
catch (XBadClient&) {
// ignore. this can happen if we receive this event
// before we've fully started up.
}
return true;
case WM_CHANGECBCHAIN:
if (m_nextClipboardWindow == (HWND)wParam) {
m_nextClipboardWindow = (HWND)lParam;
if (m_nextClipboardWindow == (HWND)msg.wParam) {
m_nextClipboardWindow = (HWND)msg.lParam;
}
else {
SendMessage(m_nextClipboardWindow, msg, wParam, lParam);
else if (m_nextClipboardWindow != NULL) {
SendMessage(m_nextClipboardWindow,
msg.message, msg.wParam, msg.lParam);
}
return 0;
return true;
case WM_DISPLAYCHANGE:
// screen resolution has changed
{
// screen resolution may have changed. get old shape.
SInt32 xOld, yOld, wOld, hOld;
getScreenShape(xOld, yOld, wOld, hOld);
// update shape
updateScreenShape();
m_multimon = isMultimon();
// send new info
// collect new screen info
CClientInfo info;
getShape(info.m_x, info.m_y, info.m_w, info.m_h);
getMousePos(info.m_mx, info.m_my);
getScreenShape(info.m_x, info.m_y, info.m_w, info.m_h);
getCursorPos(info.m_mx, info.m_my);
info.m_zoneSize = getJumpZoneSize();
// do nothing if resolution hasn't changed
if (info.m_x != xOld || info.m_y != yOld ||
info.m_w != wOld || info.m_h != hOld) {
// send new screen info
m_receiver->onInfoChanged(info);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
return true;
}
}
return false;
}
CString
CMSWindowsSecondaryScreen::getCurrentDesktopName() const
{
return m_deskName;
}
void
CMSWindowsSecondaryScreen::onEnter(SInt32 x, SInt32 y)
CMSWindowsSecondaryScreen::showWindow()
{
// warp to requested location
warpCursor(x, y);
// move hider window under the mouse (rather than moving the mouse
// somewhere else on the screen)
SInt32 x, y;
getCursorPos(x, y);
MoveWindow(m_window, x, y, 1, 1, FALSE);
// show cursor
// raise and show the hider window. take activation.
ShowWindow(m_window, SW_SHOWNORMAL);
}
void
CMSWindowsSecondaryScreen::hideWindow()
{
ShowWindow(m_window, SW_HIDE);
}
void
CMSWindowsSecondaryScreen::onLeave()
CMSWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y)
{
// move hider window under the mouse (rather than moving the mouse
// somewhere else on the screen)
POINT point;
GetCursorPos(&point);
MoveWindow(m_window, point.x, point.y, 1, 1, FALSE);
// move the mouse directly to target position on NT family or if
// not using multiple monitors.
if (!m_multimon || !m_is95Family) {
SInt32 x0, y0, w, h;
getScreenShape(x0, y0, w, h);
mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
(DWORD)((65535.99 * (x - x0)) / (w - 1)),
(DWORD)((65535.99 * (y - y0)) / (h - 1)),
0, 0);
}
// raise and show the hider window. take activation.
ShowWindow(m_window, SW_SHOWNORMAL);
// 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.
//
// we use the microsoft recommendation (Q193003): set the absolute
// position on the primary monitor, disable mouse acceleration,
// relative move the mouse to the final location, restore mouse
// acceleration. to avoid one kind of race condition (the user
// clicking the mouse or pressing a key between the absolute and
// relative move) we'll use SendInput() which guarantees that the
// events are delivered uninterrupted. we cannot prevent changes
// to the mouse acceleration at inopportune times, though.
//
// point-to-activate (x-mouse) doesn't seem to be bothered by the
// absolute/relative combination. a window over the absolute
// position (0,0) does *not* get activated (at least not on win2k)
// if the relative move puts the cursor elsewhere. similarly, the
// app under the final mouse position does *not* get deactivated
// by the absolute move to 0,0.
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);
}
// send events
INPUT events[2];
events[0].type = INPUT_MOUSE;
events[0].mi.dx = 0;
events[0].mi.dy = 0;
events[0].mi.mouseData = 0;
events[0].mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
events[0].mi.time = GetTickCount();
events[0].mi.dwExtraInfo = 0;
events[1].type = INPUT_MOUSE;
events[1].mi.dx = x;
events[1].mi.dy = y;
events[1].mi.mouseData = 0;
events[1].mi.dwFlags = MOUSEEVENTF_MOVE;
events[1].mi.time = events[0].mi.time;
events[1].mi.dwExtraInfo = 0;
SendInput(sizeof(events) / sizeof(events[0]),
events, sizeof(events[0]));
// restore mouse speed & acceleration
if (accelChanged) {
SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0);
SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0);
}
}
}
void
CMSWindowsSecondaryScreen::checkClipboard()
{
// 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.
HWND clipboardOwner = GetClipboardOwner();
if (m_clipboardOwner != clipboardOwner) {
try {
m_clipboardOwner = clipboardOwner;
if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) {
m_receiver->onGrabClipboard(kClipboardClipboard);
m_receiver->onGrabClipboard(kClipboardSelection);
}
}
catch (XBadClient&) {
// ignore
}
}
}
void
CMSWindowsPrimaryScreen::createWindow()
{
// save thread id
m_threadID = GetCurrentThreadId();
// note if using multiple monitors
m_multimon = isMultimon();
// get the input desktop and switch to it
if (m_is95Family) {
if (!openDesktop()) {
throw XScreenOpenFailure();
}
}
else {
if (!switchDesktop(openInputDesktop())) {
throw XScreenOpenFailure();
}
}
// poll input desktop to see if it changes (onPreDispatch()
// handles WM_TIMER)
m_timer = 0;
if (!m_is95Family) {
m_timer = SetTimer(NULL, 0, 200, NULL);
}
}
void
CMSWindowsPrimaryScreen::destroyWindow()
{
// remove timer
if (m_timer != 0) {
KillTimer(NULL, m_timer);
}
// release keys that are logically pressed
releaseKeys();
// disconnect from desktop
if (m_is95Family) {
closeDesktop();
}
else {
switchDesktop(NULL);
}
// clear thread id
m_threadID = 0;
assert(m_window == NULL);
assert(m_desk == NULL);
}
void
CMSWindowsSecondaryScreen::installScreenSaver()
{
getScreenSaver()->disable();
}
void
CMSWindowsSecondaryScreen::uninstallScreenSaver()
{
getScreenSaver()->enable();
}
bool
@ -720,7 +867,7 @@ CMSWindowsSecondaryScreen::switchDesktop(HDESK desk)
// get desktop up to date
if (!m_active) {
onLeave();
showWindow();
}
return true;
@ -750,89 +897,6 @@ CMSWindowsSecondaryScreen::syncDesktop() const
}
}
CString
CMSWindowsSecondaryScreen::getCurrentDesktopName() const
{
return m_deskName;
}
void
CMSWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y)
{
// move the mouse directly to target position on NT family or if
// not using multiple monitors.
if (!m_multimon || !m_is95Family) {
SInt32 x0, y0, w, h;
getScreenShape(x0, y0, w, h);
mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
(DWORD)((65535.99 * (x - x0)) / (w - 1)),
(DWORD)((65535.99 * (y - y0)) / (h - 1)),
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.
//
// we use the microsoft recommendation (Q193003): set the absolute
// position on the primary monitor, disable mouse acceleration,
// relative move the mouse to the final location, restore mouse
// acceleration. to avoid one kind of race condition (the user
// clicking the mouse or pressing a key between the absolute and
// relative move) we'll use SendInput() which guarantees that the
// events are delivered uninterrupted. we cannot prevent changes
// to the mouse acceleration at inopportune times, though.
//
// point-to-activate (x-mouse) doesn't seem to be bothered by the
// absolute/relative combination. a window over the absolute
// position (0,0) does *not* get activated (at least not on win2k)
// if the relative move puts the cursor elsewhere. similarly, the
// app under the final mouse position does *not* get deactivated
// by the absolute move to 0,0.
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);
}
// send events
INPUT events[2];
events[0].type = INPUT_MOUSE;
events[0].mi.dx = 0;
events[0].mi.dy = 0;
events[0].mi.mouseData = 0;
events[0].mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
events[0].mi.time = GetTickCount();
events[0].mi.dwExtraInfo = 0;
events[1].type = INPUT_MOUSE;
events[1].mi.dx = x;
events[1].mi.dy = y;
events[1].mi.mouseData = 0;
events[1].mi.dwFlags = MOUSEEVENTF_MOVE;
events[1].mi.time = events[0].mi.time;
events[1].mi.dwExtraInfo = 0;
SendInput(sizeof(events) / sizeof(events[0]),
events, sizeof(events[0]));
// restore mouse speed & acceleration
if (accelChanged) {
SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0);
SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0);
}
}
}
bool
CMSWindowsSecondaryScreen::isMultimon() const
{

View File

@ -46,10 +46,8 @@ public:
protected:
// CMSWindowsScreen overrides
virtual bool onPreTranslate(MSG*);
virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM);
virtual void onOpenDisplay();
virtual void onCloseDisplay();
virtual bool onPreDispatch(const CEvent* event);
virtual bool onEvent(CEvent* event);
virtual CString getCurrentDesktopName() const;
private:
@ -62,8 +60,25 @@ private:
};
typedef std::vector<Keystroke> Keystrokes;
void onEnter(SInt32 x, SInt32 y);
void onLeave();
void showWindow();
void hideWindow();
// warp the mouse to the specified position
void warpCursor(SInt32 x, SInt32 y);
// check clipboard ownership and, if necessary, tell the receiver
// of a grab.
void checkClipboard();
// create/destroy window
// also attach to desktop; this destroys and recreates the window
// as necessary.
void createWindow();
void destroyWindow();
// start/stop watch for screen saver changes
void installScreenSaver();
void uninstallScreenSaver();
// open/close desktop (for windows 95/98/me)
bool openDesktop();
@ -75,9 +90,6 @@ private:
// get calling thread to use the input desktop
void syncDesktop() const;
// warp the mouse to the specified position
void warpCursor(SInt32 x, SInt32 y);
// returns true iff there appear to be multiple monitors
bool isMultimon() const;
@ -108,6 +120,9 @@ private:
// the main loop's thread id
DWORD m_threadID;
// the timer used to check for desktop switching
UINT m_timer;
// the thread id of the last attached thread
mutable DWORD m_lastThreadID;

View File

@ -1,8 +1,9 @@
#include "CXWindowsSecondaryScreen.h"
#include "CClient.h"
#include "IScreenReceiver.h"
#include "CXWindowsClipboard.h"
#include "CXWindowsScreenSaver.h"
#include "CXWindowsUtil.h"
#include "XScreen.h"
#include "CThread.h"
#include "CLog.h"
#if defined(X_DISPLAY_MISSING)
@ -26,9 +27,10 @@
CXWindowsSecondaryScreen::CXWindowsSecondaryScreen(IScreenReceiver* receiver) :
m_receiver(receiver),
m_window(None)
m_window(None),
m_active(false)
{
// do nothing
assert(m_receiver != NULL);
}
CXWindowsSecondaryScreen::~CXWindowsSecondaryScreen()
@ -41,95 +43,43 @@ CXWindowsSecondaryScreen::run()
{
assert(m_window != None);
for (;;) {
// wait for and get the next event
XEvent xevent;
if (!getEvent(&xevent)) {
break;
}
// change our priority
CThread::getCurrentThread().setPriority(-7);
// handle event
switch (xevent.type) {
case MappingNotify:
{
// keyboard mapping changed
CDisplayLock display(this);
XRefreshKeyboardMapping(&xevent.xmapping);
updateKeys(display);
updateKeycodeMap(display);
updateModifierMap(display);
updateModifiers(display);
}
break;
case LeaveNotify:
{
// mouse moved out of hider window somehow. hide the window.
assert(m_window != None);
CDisplayLock display(this);
XUnmapWindow(display, m_window);
}
break;
// run event loop
try {
log((CLOG_INFO "entering event loop"));
mainLoop();
log((CLOG_INFO "exiting event loop"));
}
catch (...) {
log((CLOG_INFO "exiting event loop"));
throw;
}
}
void
CXWindowsSecondaryScreen::stop()
{
CDisplayLock display(this);
doStop();
exitMainLoop();
}
void
CXWindowsSecondaryScreen::open()
{
assert(m_receiver != NULL);
assert(m_window == None);
try {
// open the display
openDisplay();
{
CDisplayLock display(this);
// create and prepare our window
createWindow();
// verify the availability of the XTest extension
int majorOpcode, firstEvent, firstError;
if (!XQueryExtension(display, XTestExtensionName,
&majorOpcode, &firstEvent, &firstError)) {
throw int(6); // FIXME -- make exception for this
}
// create the cursor hiding window. this window is used to hide the
// cursor when it's not on the screen. the window is hidden as soon
// as the cursor enters the screen or the display's real cursor is
// moved.
XSetWindowAttributes attr;
attr.event_mask = LeaveWindowMask;
attr.do_not_propagate_mask = 0;
attr.override_redirect = True;
attr.cursor = getBlankCursor();
m_window = XCreateWindow(display, getRoot(), 0, 0, 1, 1, 0, 0,
InputOnly, CopyFromParent,
CWDontPropagate | CWEventMask |
CWOverrideRedirect | CWCursor,
&attr);
log((CLOG_DEBUG "window is 0x%08x", m_window));
// become impervious to server grabs
XTestGrabControl(display, True);
// hide the cursor
leaveNoLock(display);
// initialize the clipboards
// initialize the clipboards; assume primary has all clipboards
initClipboards(m_window);
// update key state
updateKeys(display);
updateKeycodeMap(display);
updateModifierMap(display);
updateModifiers(display);
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
grabClipboard(id);
}
// check for peculiarities
@ -139,37 +89,33 @@ CXWindowsSecondaryScreen::open()
// m_numLockHalfDuplex = true;
// m_capsLockHalfDuplex = true;
// assume primary has all clipboards
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
grabClipboard(id);
}
// get the display
CDisplayLock display(this);
// update key state
updateKeys(display);
updateKeycodeMap(display);
updateModifierMap(display);
updateModifiers(display);
// disable the screen saver
getScreenSaver()->disable();
installScreenSaver();
}
catch (...) {
close();
throw;
}
// hide the cursor
m_active = true;
leave();
}
void
CXWindowsSecondaryScreen::close()
{
// release keys that are logically pressed
releaseKeys();
// restore the screen saver settings
getScreenSaver()->enable();
{
CDisplayLock display(this);
if (display != NULL) {
// no longer impervious to server grabs
XTestGrabControl(display, False);
// destroy window
XDestroyWindow(display, m_window);
}
m_window = None;
}
// close the display
uninstallScreenSaver();
destroyWindow();
closeDisplay();
}
@ -177,15 +123,14 @@ void
CXWindowsSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask)
{
assert(m_window != None);
assert(m_active == false);
log((CLOG_INFO "entering screen at %d,%d mask=%04x", x, y, mask));
CDisplayLock display(this);
// warp to requested location
XTestFakeMotionEvent(display, getScreen(), x, y, CurrentTime);
XSync(display, False);
// show cursor
XUnmapWindow(display, m_window);
// now active
m_active = true;
// update our keyboard state to reflect the local state
updateKeys(display);
@ -202,14 +147,32 @@ CXWindowsSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask)
if ((xMask & m_scrollLockMask) != (m_mask & m_scrollLockMask)) {
toggleKey(display, XK_Scroll_Lock, m_scrollLockMask);
}
XSync(display, False);
// warp to requested location
warpCursor(x, y);
// show mouse
hideWindow();
}
void
CXWindowsSecondaryScreen::leave()
{
assert(m_window != None);
assert(m_active == true);
log((CLOG_INFO "leaving screen"));
CDisplayLock display(this);
leaveNoLock(display);
// hide mouse
showWindow();
// not active anymore
m_active = false;
// make sure our idea of clipboard ownership is correct
checkClipboard();
}
void
@ -290,8 +253,7 @@ void
CXWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y)
{
CDisplayLock display(this);
XTestFakeMotionEvent(display, getScreen(), x, y, CurrentTime);
XSync(display, False);
warpCursor(x, y);
}
void
@ -366,6 +328,44 @@ CXWindowsSecondaryScreen::getClipboard(ClipboardID id,
getDisplayClipboard(id, clipboard);
}
bool
CXWindowsSecondaryScreen::onPreDispatch(const CEvent* event)
{
// forward to superclass
return CXWindowsScreen::onPreDispatch(event);
}
bool
CXWindowsSecondaryScreen::onEvent(CEvent* event)
{
assert(event != NULL);
XEvent& xevent = event->m_event;
// handle event
switch (xevent.type) {
case MappingNotify:
{
// keyboard mapping changed
CDisplayLock display(this);
XRefreshKeyboardMapping(&xevent.xmapping);
updateKeys(display);
updateKeycodeMap(display);
updateModifierMap(display);
updateModifiers(display);
}
return true;
case LeaveNotify:
{
// mouse moved out of hider window somehow. hide the window.
assert(m_window != None);
CDisplayLock display(this);
hideWindow();
}
return true;
}
}
void
CXWindowsSecondaryScreen::onLostClipboard(ClipboardID id)
{
@ -374,25 +374,110 @@ CXWindowsSecondaryScreen::onLostClipboard(ClipboardID id)
}
void
CXWindowsSecondaryScreen::leaveNoLock(Display* display)
CXWindowsSecondaryScreen::showWindow()
{
assert(display != NULL);
assert(m_window != None);
// move hider window under the mouse (rather than moving the mouse
// somewhere else on the screen)
int x, y, dummy;
unsigned int dummyMask;
Window dummyWindow;
XQueryPointer(display, getRoot(), &dummyWindow, &dummyWindow,
&x, &y, &dummy, &dummy, &dummyMask);
XMoveWindow(display, m_window, x, y);
SInt32 x, y;
getCursorPos(x, y);
XMoveWindow(getDisplay(), m_window, x, y);
// raise and show the hider window
XMapRaised(display, m_window);
// raise and show the hider window. take activation.
// FIXME -- take focus?
XMapRaised(getDisplay(), m_window);
/* XXX -- this should have no effect
// hide cursor by moving it into the hider window
XWarpPointer(display, None, m_window, 0, 0, 0, 0, 0, 0);
XWarpPointer(getDisplay(), None, m_window, 0, 0, 0, 0, 0, 0);
*/
}
void
CXWindowsSecondaryScreen::hideWindow()
{
XUnmapWindow(getDisplay(), m_window);
}
void
CXWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y)
{
XTestFakeMotionEvent(getDisplay(), getScreen(), x, y, CurrentTime);
XSync(getDisplay(), False);
}
void
CXWindowsSecondaryScreen::checkClipboard()
{
// do nothing, we're always up to date
}
void
CXWindowsSecondaryScreen::createWindow()
{
CDisplayLock display(this);
// verify the availability of the XTest extension
int majorOpcode, firstEvent, firstError;
if (!XQueryExtension(display, XTestExtensionName,
&majorOpcode, &firstEvent, &firstError)) {
// FIXME -- subclass exception for more info?
throw XScreenOpenFailure();
}
// cursor hider window attributes. this window is used to hide the
// cursor when it's not on the screen. the window is hidden as soon
// as the cursor enters the screen or the display's real cursor is
// moved.
XSetWindowAttributes attr;
attr.event_mask = LeaveWindowMask;
attr.do_not_propagate_mask = 0;
attr.override_redirect = True;
attr.cursor = getBlankCursor();
// create the cursor hider window
m_window = XCreateWindow(display, getRoot(),
0, 0, 1, 1, 0, 0,
InputOnly, CopyFromParent,
CWDontPropagate | CWEventMask |
CWOverrideRedirect | CWCursor,
&attr);
if (m_window == None) {
throw XScreenOpenFailure();
}
log((CLOG_DEBUG "window is 0x%08x", m_window));
// become impervious to server grabs
XTestGrabControl(display, True);
}
void
CXWindowsSecondaryScreen::destroyWindow()
{
releaseKeys();
CDisplayLock display(this);
if (display != NULL) {
// no longer impervious to server grabs
XTestGrabControl(display, False);
// destroy window
if (m_window != None) {
XDestroyWindow(display, m_window);
m_window = None;
}
}
}
void
CXWindowsSecondaryScreen::installScreenSaver()
{
getScreenSaver()->disable();
}
void
CXWindowsSecondaryScreen::uninstallScreenSaver()
{
getScreenSaver()->enable();
}
unsigned int
@ -829,6 +914,7 @@ CXWindowsSecondaryScreen::releaseKeys()
{
CDisplayLock display(this);
if (display != NULL) {
// key up for each key that's down
for (UInt32 i = 0; i < 256; ++i) {
if (m_keys[i]) {
@ -839,7 +925,7 @@ CXWindowsSecondaryScreen::releaseKeys()
// update
XSync(display, False);
}
}
void

View File

@ -39,6 +39,8 @@ public:
protected:
// CXWindowsScreen overrides
virtual bool onPreDispatch(const CEvent* event);
virtual bool onEvent(CEvent* event);
virtual void onLostClipboard(ClipboardID);
private:
@ -60,7 +62,26 @@ private:
typedef std::map<KeyID, KeyCodeMask> KeyCodeMap;
typedef std::map<KeyCode, unsigned int> ModifierMap;
void leaveNoLock(Display*);
void showWindow();
void hideWindow();
// warp the mouse to the specified position
void warpCursor(SInt32 x, SInt32 y);
// check clipboard ownership and, if necessary, tell the receiver
// of a grab.
void checkClipboard();
// create/destroy window
// also attach to desktop; this destroys and recreates the window
// as necessary.
void createWindow();
void destroyWindow();
// start/stop watch for screen saver changes
void installScreenSaver();
void uninstallScreenSaver();
unsigned int mapButton(ButtonID button) const;
unsigned int mapKey(Keystrokes&, KeyCode&, KeyID,
@ -82,6 +103,9 @@ private:
IScreenReceiver* m_receiver;
Window m_window;
// m_active is true if this screen has been entered
bool m_active;
// note toggle keys that toggles on up/down (false) or on
// transition (true)
bool m_numLockHalfDuplex;

View File

@ -48,36 +48,44 @@ CMSWindowsScreen::init(HINSTANCE instance)
}
void
CMSWindowsScreen::doRun()
CMSWindowsScreen::mainLoop()
{
// save thread id for posting quit message
m_thread = GetCurrentThreadId();
// event loop
CEvent event;
event.m_result = 0;
for (;;) {
// wait for and get the next event
MSG msg;
getEvent(&msg);
// wait for an event in a cancellable way
CThread::waitForEvent();
GetMessage(&event.m_msg, NULL, 0, 0);
// handle quit message
if (msg.message == WM_QUIT) {
if (event.m_msg.message == WM_QUIT) {
break;
}
// dispatch message
if (!onPreTranslate(&msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
if (!onPreDispatch(&event)) {
TranslateMessage(&event.m_msg);
DispatchMessage(&event.m_msg);
}
}
}
void
CMSWindowsScreen::doStop()
CMSWindowsScreen::exitMainLoop()
{
PostThreadMessage(m_thread, WM_QUIT, 0, 0);
}
bool
CMSWindowsScreen::onPreDispatch(const CEvent*)
{
return false;
}
void
CMSWindowsScreen::openDisplay()
{
@ -172,10 +180,14 @@ void
CMSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const
{
POINT pos;
GetCursorPos(&pos);
if (GetCursorPos(&pos)) {
x = pos.x;
y = pos.y;
}
else {
getCursorCenter(x, y);
}
}
void
CMSWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const
@ -244,17 +256,22 @@ CMSWindowsScreen::getScreenSaver() const
return m_screenSaver;
}
void
CMSWindowsScreen::getEvent(MSG* msg) const
{
// wait for an event in a cancellable way
CThread::waitForEvent();
GetMessage(msg, NULL, 0, 0);
}
LRESULT CALLBACK
CMSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
assert(s_screen != NULL);
return s_screen->onEvent(hwnd, msg, wParam, lParam);
CEvent event;
event.m_msg.hwnd = hwnd;
event.m_msg.message = msg;
event.m_msg.wParam = wParam;
event.m_msg.lParam = lParam;
event.m_result = 0;
if (s_screen->onEvent(&event)) {
return event.m_result;
}
else {
return DefWindowProc(hwnd, msg, wParam, lParam);
}
}

View File

@ -10,6 +10,12 @@
class CMSWindowsScreenSaver;
class CThread;
class CEvent {
public:
MSG m_msg;
LRESULT m_result;
};
class CMSWindowsScreen {
public:
CMSWindowsScreen();
@ -25,11 +31,11 @@ public:
static HINSTANCE getInstance();
protected:
// runs an event loop and returns when WM_QUIT is received
void doRun();
// runs an event loop and returns when exitMainLoop() is called
void mainLoop();
// sends WM_QUIT to force doRun() to return
void doStop();
// force mainLoop() to return
void exitMainLoop();
// open the X display. calls onOpenDisplay() after opening the display,
// getting the screen, its size, and root window. then it starts the
@ -72,15 +78,14 @@ protected:
CMSWindowsScreenSaver*
getScreenSaver() const;
// wait for and get the next message. cancellable.
void getEvent(MSG*) const;
// called for each event before event translation and dispatch. return
// true to skip translation and dispatch. subclasses should call the
// superclass's version first and return true if it returns true.
virtual bool onPreDispatch(const CEvent* event);
// called by doRun() to handle an event. return true to skip
// event translation and dispatch.
virtual bool onPreTranslate(MSG*) = 0;
// called by window proc. subclass must call DefWindowProc() if necessary
virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM) = 0;
// called by mainLoop(). iff the event was handled return true and
// store the result, if any, in m_result, which defaults to zero.
virtual bool onEvent(CEvent* event) = 0;
// called by isCurrentDesktop() to get the current desktop name
virtual CString getCurrentDesktopName() const = 0;
@ -89,6 +94,7 @@ private:
// create the transparent cursor
void createBlankCursor();
// our window proc
static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM);
private:

View File

@ -120,6 +120,129 @@ CXWindowsScreen::removeTimerNoLock(IJob* job)
m_timers.swap(tmp);
}
void
CXWindowsScreen::mainLoop()
{
// wait for an event in a cancellable way and don't lock the
// display while we're waiting.
CEvent event;
m_mutex.lock();
while (!m_stop) {
while (!m_stop && XPending(m_display) == 0) {
// check timers
if (processTimers()) {
continue;
}
// wait
m_mutex.unlock();
CThread::sleep(0.01);
m_mutex.lock();
}
if (!m_stop) {
// get the event
XNextEvent(m_display, &event.m_event);
// process the event. if unhandled then let the subclass
// have a go at it.
m_mutex.unlock();
if (!onPreDispatch(&event)) {
onEvent(&event);
}
m_mutex.lock();
}
}
m_mutex.unlock();
}
void
CXWindowsScreen::exitMainLoop()
{
CLock lock(&m_mutex);
m_stop = true;
}
bool
CXWindowsScreen::onPreDispatch(const CEvent* event)
{
assert(event != NULL);
const XEvent* xevent = &event->m_event;
switch (xevent->type) {
case SelectionClear:
{
// we just lost the selection. that means someone else
// grabbed the selection so this screen is now the
// selection owner. report that to the subclass.
ClipboardID id = getClipboardID(xevent->xselectionclear.selection);
if (id != kClipboardEnd) {
log((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time));
m_clipboard[id]->lost(xevent->xselectionclear.time);
onLostClipboard(id);
return true;
}
}
break;
case SelectionNotify:
// notification of selection transferred. we shouldn't
// get this here because we handle them in the selection
// retrieval methods. we'll just delete the property
// with the data (satisfying the usual ICCCM protocol).
if (xevent->xselection.property != None) {
CLock lock(&m_mutex);
XDeleteProperty(m_display,
xevent->xselection.requestor,
xevent->xselection.property);
}
return true;
case SelectionRequest:
{
// somebody is asking for clipboard data
ClipboardID id = getClipboardID(
xevent->xselectionrequest.selection);
if (id != kClipboardEnd) {
CLock lock(&m_mutex);
m_clipboard[id]->addRequest(
xevent->xselectionrequest.owner,
xevent->xselectionrequest.requestor,
xevent->xselectionrequest.target,
xevent->xselectionrequest.time,
xevent->xselectionrequest.property);
return true;
}
}
break;
case PropertyNotify:
// property delete may be part of a selection conversion
if (xevent->xproperty.state == PropertyDelete) {
processClipboardRequest(xevent->xproperty.window,
xevent->xproperty.time,
xevent->xproperty.atom);
return true;
}
break;
case DestroyNotify:
// looks like one of the windows that requested a clipboard
// transfer has gone bye-bye.
destroyClipboardRequest(xevent->xdestroywindow.window);
// we don't know if the event was handled or not so continue
break;
}
// let screen saver have a go
{
CLock lock(&m_mutex);
m_screenSaver->onPreDispatch(xevent);
}
return false;
}
void
CXWindowsScreen::openDisplay()
{
@ -179,6 +302,12 @@ CXWindowsScreen::closeDisplay()
XSetIOErrorHandler(NULL);
}
Display*
CXWindowsScreen::getDisplay() const
{
return m_display;
}
int
CXWindowsScreen::getScreen() const
{
@ -294,49 +423,6 @@ CXWindowsScreen::getBlankCursor() const
return m_cursor;
}
bool
CXWindowsScreen::getEvent(XEvent* xevent) const
{
// wait for an event in a cancellable way and don't lock the
// display while we're waiting.
m_mutex.lock();
for (;;) {
while (!m_stop && XPending(m_display) == 0) {
// check timers
if (const_cast<CXWindowsScreen*>(this)->processTimers()) {
continue;
}
// wait
m_mutex.unlock();
CThread::sleep(0.01);
m_mutex.lock();
}
if (m_stop) {
m_mutex.unlock();
return false;
}
else {
// get the event
XNextEvent(m_display, xevent);
// process the event. return the event if unhandled.
m_mutex.unlock();
if (!const_cast<CXWindowsScreen*>(this)->processEvent(xevent)) {
return true;
}
m_mutex.lock();
}
}
}
void
CXWindowsScreen::doStop()
{
// caller must have locked display
m_stop = true;
}
ClipboardID
CXWindowsScreen::getClipboardID(Atom selection) const
{
@ -355,84 +441,6 @@ CXWindowsScreen::onUnexpectedClose()
// do nothing
}
bool
CXWindowsScreen::processEvent(XEvent* xevent)
{
switch (xevent->type) {
case SelectionClear:
{
// we just lost the selection. that means someone else
// grabbed the selection so this screen is now the
// selection owner. report that to the subclass.
ClipboardID id = getClipboardID(xevent->xselectionclear.selection);
if (id != kClipboardEnd) {
log((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time));
m_clipboard[id]->lost(xevent->xselectionclear.time);
onLostClipboard(id);
return true;
}
}
break;
case SelectionNotify:
// notification of selection transferred. we shouldn't
// get this here because we handle them in the selection
// retrieval methods. we'll just delete the property
// with the data (satisfying the usual ICCCM protocol).
if (xevent->xselection.property != None) {
CLock lock(&m_mutex);
XDeleteProperty(m_display,
xevent->xselection.requestor,
xevent->xselection.property);
}
return true;
case SelectionRequest:
{
// somebody is asking for clipboard data
ClipboardID id = getClipboardID(
xevent->xselectionrequest.selection);
if (id != kClipboardEnd) {
CLock lock(&m_mutex);
m_clipboard[id]->addRequest(
xevent->xselectionrequest.owner,
xevent->xselectionrequest.requestor,
xevent->xselectionrequest.target,
xevent->xselectionrequest.time,
xevent->xselectionrequest.property);
return true;
}
}
break;
case PropertyNotify:
// property delete may be part of a selection conversion
if (xevent->xproperty.state == PropertyDelete) {
processClipboardRequest(xevent->xproperty.window,
xevent->xproperty.time,
xevent->xproperty.atom);
return true;
}
break;
case DestroyNotify:
// looks like one of the windows that requested a clipboard
// transfer has gone bye-bye.
destroyClipboardRequest(xevent->xdestroywindow.window);
// we don't know if the event was handled or not so continue
break;
}
// let screen saver have a go
{
CLock lock(&m_mutex);
m_screenSaver->processEvent(xevent);
}
return false;
}
bool
CXWindowsScreen::processTimers()
{

View File

@ -19,6 +19,12 @@ class IScreenSaver;
class CXWindowsClipboard;
class CXWindowsScreenSaver;
class CEvent {
public:
XEvent m_event;
SInt32 m_result;
};
class CXWindowsScreen {
public:
CXWindowsScreen();
@ -47,6 +53,12 @@ protected:
};
friend class CDisplayLock;
// runs an event loop and returns when exitMainLoop() is called
void mainLoop();
// force mainLoop() to return
void exitMainLoop();
// open the X display. calls onOpenDisplay() after opening the display,
// getting the screen, its size, and root window. then it starts the
// event thread.
@ -57,6 +69,10 @@ protected:
// is closed.
void closeDisplay();
// get the Display*. only use this when you know the display is
// locked but don't have the CDisplayLock available.
Display* getDisplay() const;
// get the opened screen and its root window. to get the display
// create a CDisplayLock object passing this. while the object
// exists no other threads may access the display. do not save
@ -83,13 +99,6 @@ protected:
// get a cursor that is transparent everywhere
Cursor getBlankCursor() const;
// wait for and get the next X event. cancellable.
bool getEvent(XEvent*) const;
// cause getEvent() to return false immediately and forever after.
// the caller must have locked the display.
void doStop();
// set the contents of the clipboard (i.e. primary selection)
bool setDisplayClipboard(ClipboardID,
const IClipboard* clipboard);
@ -102,6 +111,15 @@ protected:
CXWindowsScreenSaver*
getScreenSaver() const;
// called for each event before event translation and dispatch. return
// true to skip translation and dispatch. subclasses should call the
// superclass's version first and return true if it returns true.
virtual bool onPreDispatch(const CEvent* event) = 0;
// called by mainLoop(). iff the event was handled return true and
// store the result, if any, in m_result, which defaults to zero.
virtual bool onEvent(CEvent* event) = 0;
// called if the display is unexpectedly closing. default does nothing.
virtual void onUnexpectedClose();
@ -115,9 +133,6 @@ private:
// remove a timer without locking
void removeTimerNoLock(IJob*);
// internal event processing
bool processEvent(XEvent*);
// process timers
bool processTimers();

View File

@ -97,7 +97,7 @@ CXWindowsScreenSaver::~CXWindowsScreenSaver()
}
bool
CXWindowsScreenSaver::processEvent(XEvent* xevent)
CXWindowsScreenSaver::onPreDispatch(const XEvent* xevent)
{
switch (xevent->type) {
case CreateNotify:

View File

@ -20,8 +20,10 @@ public:
CXWindowsScreenSaver(CXWindowsScreen*, Display*);
virtual ~CXWindowsScreenSaver();
// process X event. returns true if the event was handled.
bool processEvent(XEvent*);
// called for each event before event translation and dispatch. return
// true to skip translation and dispatch. subclasses should call the
// superclass's version first and return true if it returns true.
bool onPreDispatch(const XEvent*);
// tells this object to send a ClientMessage to the given window
// when the screen saver activates or deactivates. only one

View File

@ -22,6 +22,7 @@ CMSWindowsPrimaryScreen::CMSWindowsPrimaryScreen(
m_receiver(receiver),
m_primaryReceiver(primaryReceiver),
m_threadID(0),
m_timer(0),
m_desk(NULL),
m_deskName(),
m_window(NULL),
@ -95,28 +96,22 @@ CMSWindowsPrimaryScreen::run()
// change our priority
CThread::getCurrentThread().setPriority(-3);
// poll input desktop to see if it changes (preTranslateMessage()
// handles WM_TIMER)
UINT timer = 0;
if (!m_is95Family) {
SetTimer(NULL, 0, 200, NULL);
}
// run event loop
try {
log((CLOG_INFO "entering event loop"));
doRun();
mainLoop();
log((CLOG_INFO "exiting event loop"));
// remove timer
if (!m_is95Family) {
KillTimer(NULL, timer);
}
catch (...) {
log((CLOG_INFO "exiting event loop"));
throw;
}
}
void
CMSWindowsPrimaryScreen::stop()
{
doStop();
exitMainLoop();
}
void
@ -269,50 +264,6 @@ CMSWindowsPrimaryScreen::warpCursor(SInt32 x, SInt32 y)
m_y = y;
}
void
CMSWindowsPrimaryScreen::warpCursorToCenter()
{
// warp to center. the extra info tells the hook DLL to send
// SYNERGY_MSG_POST_WARP instead of SYNERGY_MSG_MOUSE_MOVE.
SInt32 x, y, w, h;
getScreenShape(x, y, w, h);
mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE,
(DWORD)((65535.99 * (m_xCenter - x)) / (w - 1)),
(DWORD)((65535.99 * (m_yCenter - y)) / (h - 1)),
0,
0x12345678);
// FIXME -- ignore mouse until we get warp notification?
}
void
CMSWindowsPrimaryScreen::checkClipboard()
{
// 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.
HWND clipboardOwner = GetClipboardOwner();
if (m_clipboardOwner != clipboardOwner) {
try {
m_clipboardOwner = clipboardOwner;
if (m_clipboardOwner != m_window) {
m_receiver->onGrabClipboard(kClipboardClipboard);
m_receiver->onGrabClipboard(kClipboardSelection);
}
}
catch (XBadClient&) {
// ignore
}
}
}
void
CMSWindowsPrimaryScreen::setClipboard(ClipboardID /*id*/,
const IClipboard* src)
@ -408,9 +359,17 @@ CMSWindowsPrimaryScreen::isLockedToScreen() const
}
bool
CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg)
CMSWindowsPrimaryScreen::onPreTranslate(const CEvent* event)
{
assert(event != NULL);
// forward to superclass
if (CMSWindowsScreen::onPreTranslate(event)) {
return true;
}
// handle event
const MSG* msg = &event->m_msg;
switch (msg->message) {
case SYNERGY_MSG_MARK:
m_markReceived = msg->wParam;
@ -567,43 +526,47 @@ CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg)
return false;
}
LRESULT
CMSWindowsPrimaryScreen::onEvent(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
bool
CMSWindowsPrimaryScreen::onEvent(CEvent* event)
{
switch (msg) {
assert(event != NULL);
const MSG& msg = event->msg;
switch (msg.message) {
case WM_QUERYENDSESSION:
if (m_is95Family) {
return TRUE;
event->m_result = TRUE;
return true;
}
break;
case WM_ENDSESSION:
if (m_is95Family) {
if (wParam == TRUE && lParam == 0) {
if (msg.wParam == TRUE && msg.lParam == 0) {
stop();
}
return 0;
return true;
}
break;
case WM_PAINT:
ValidateRect(hwnd, NULL);
return 0;
ValidateRect(msg.hwnd, NULL);
return true;
case WM_DRAWCLIPBOARD:
log((CLOG_DEBUG "clipboard was taken"));
// first pass it on
if (m_nextClipboardWindow != NULL) {
SendMessage(m_nextClipboardWindow, msg, wParam, lParam);
SendMessage(m_nextClipboardWindow,
msg.message, msg.wParam, msg.lParam);
}
// now notify server that somebody changed the clipboard.
// skip that if we're the new owner.
try {
m_clipboardOwner = GetClipboardOwner();
if (m_clipboardOwner != m_window) {
if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) {
m_receiver->onGrabClipboard(kClipboardClipboard);
m_receiver->onGrabClipboard(kClipboardSelection);
}
@ -612,28 +575,36 @@ CMSWindowsPrimaryScreen::onEvent(HWND hwnd, UINT msg,
// ignore. this can happen if we receive this event
// before we've fully started up.
}
return 0;
return true;
case WM_CHANGECBCHAIN:
if (m_nextClipboardWindow == (HWND)wParam) {
m_nextClipboardWindow = (HWND)lParam;
if (m_nextClipboardWindow == (HWND)msg.wParam) {
m_nextClipboardWindow = (HWND)msg.lParam;
}
else if (m_nextClipboardWindow != NULL) {
SendMessage(m_nextClipboardWindow, msg, wParam, lParam);
SendMessage(m_nextClipboardWindow,
msg.message, msg.wParam, msg.lParam);
}
return 0;
return true;
case WM_DISPLAYCHANGE:
{
// screen resolution may have changed
// screen resolution may have changed. get old shape.
SInt32 xOld, yOld, wOld, hOld;
getScreenShape(xOld, yOld, wOld, hOld);
// update shape
updateScreenShape();
SInt32 x, y, w, h;
getScreenShape(x, y, w, h);
// collect new screen info
CClientInfo info;
getScreenShape(info.m_x, info.m_y, info.m_w, info.m_h);
getCursorPos(info.m_mx, info.m_my);
info.m_zoneSize = getJumpZoneSize();
// do nothing if resolution hasn't changed
if (x != xOld || y != yOld || w != wOld || h != hOld) {
if (info.m_x != xOld || info.m_y != yOld ||
info.m_w != wOld || info.m_h != hOld) {
// recompute center pixel of primary screen
getCursorCenter(m_xCenter, m_yCenter);
@ -644,28 +615,46 @@ CMSWindowsPrimaryScreen::onEvent(HWND hwnd, UINT msg,
// tell hook about resize if not active
else {
m_setZone(x, y, w, h, getJumpZoneSize());
m_setZone(info.m_x, info.m_y,
info.m_w, info.m_h, info.m_zoneSize);
}
// send new screen info
POINT pos;
GetCursorPos(&pos);
CClientInfo info;
info.m_x = x;
info.m_y = y;
info.m_w = w;
info.m_h = h;
info.m_zoneSize = getJumpZoneSize();
info.m_mx = pos.x;
info.m_my = pos.y;
m_receiver->onInfoChanged(info);
}
return 0;
return true;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
return false;
}
CString
CMSWindowsPrimaryScreen::getCurrentDesktopName() const
{
return m_deskName;
}
SInt32
CMSWindowsPrimaryScreen::getJumpZoneSize() const
{
return 1;
}
void
CMSWindowsPrimaryScreen::warpCursorToCenter()
{
// warp to center. the extra info tells the hook DLL to send
// SYNERGY_MSG_POST_WARP instead of SYNERGY_MSG_MOUSE_MOVE.
SInt32 x, y, w, h;
getScreenShape(x, y, w, h);
mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE,
(DWORD)((65535.99 * (m_xCenter - x)) / (w - 1)),
(DWORD)((65535.99 * (m_yCenter - y)) / (h - 1)),
0,
0x12345678);
// FIXME -- ignore mouse until we get warp notification?
}
void
@ -743,20 +732,33 @@ CMSWindowsPrimaryScreen::hideWindow()
ShowWindow(m_window, SW_HIDE);
}
SInt32
CMSWindowsPrimaryScreen::getJumpZoneSize() const
{
return 1;
}
void
CMSWindowsPrimaryScreen::nextMark()
CMSWindowsPrimaryScreen::checkClipboard()
{
// next mark
++m_mark;
// mark point in message queue where the mark was changed
PostThreadMessage(m_threadID, SYNERGY_MSG_MARK, m_mark, 0);
// 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.
HWND clipboardOwner = GetClipboardOwner();
if (m_clipboardOwner != clipboardOwner) {
try {
m_clipboardOwner = clipboardOwner;
if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) {
m_receiver->onGrabClipboard(kClipboardClipboard);
m_receiver->onGrabClipboard(kClipboardSelection);
}
}
catch (XBadClient&) {
// ignore
}
}
}
void
@ -773,11 +775,23 @@ CMSWindowsPrimaryScreen::createWindow()
throw XScreenOpenFailure();
}
}
// poll input desktop to see if it changes (preTranslateMessage()
// handles WM_TIMER)
m_timer = 0;
if (!m_is95Family) {
m_timer = SetTimer(NULL, 0, 200, NULL);
}
}
void
CMSWindowsPrimaryScreen::destroyWindow()
{
// remove timer
if (m_timer != 0) {
KillTimer(NULL, m_timer);
}
// disconnect from desktop
if (m_is95Family) {
closeDesktop();
@ -977,10 +991,14 @@ CMSWindowsPrimaryScreen::switchDesktop(HDESK desk)
return true;
}
CString
CMSWindowsPrimaryScreen::getCurrentDesktopName() const
void
CMSWindowsPrimaryScreen::nextMark()
{
return m_deskName;
// next mark
++m_mark;
// mark point in message queue where the mark was changed
PostThreadMessage(m_threadID, SYNERGY_MSG_MARK, m_mark, 0);
}
static const KeyID g_virtualKey[] =

View File

@ -34,30 +34,25 @@ public:
protected:
// CMSWindowsScreen overrides
virtual bool onPreTranslate(MSG*);
virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM);
virtual void onOpenDisplay();
virtual void onCloseDisplay();
virtual bool onPreTranslate(const CEvent* event);
virtual bool onEvent(CEvent* event);
virtual CString getCurrentDesktopName() const;
private:
void enterNoWarp();
bool showWindow();
void hideWindow();
SInt32 getJumpZoneSize() const;
// warp mouse to center of primary display (used when computing
// motion deltas while mouse is on secondary screen).
void warpCursorToCenter();
void enterNoWarp();
bool showWindow();
void hideWindow();
// check clipboard ownership and, if necessary, tell the receiver
// of a grab.
void checkClipboard();
// discard posted messages
void nextMark();
// create/destroy window
// also attach to desktop; this destroys and recreates the window
// as necessary.
@ -75,6 +70,9 @@ private:
// make desk the thread desktop (for windows NT/2000/XP)
bool switchDesktop(HDESK desk);
// discard posted messages
void nextMark();
// key and button queries
KeyID mapKey(WPARAM keycode, LPARAM info,
KeyModifierMask* maskOut);
@ -92,6 +90,9 @@ private:
// the main loop's thread id
DWORD m_threadID;
// the timer used to check for desktop switching
UINT m_timer;
// the current desk and it's name
HDESK m_desk;
CString m_deskName;

View File

@ -42,199 +42,25 @@ CXWindowsPrimaryScreen::~CXWindowsPrimaryScreen()
void
CXWindowsPrimaryScreen::run()
{
for (;;) {
// wait for and get the next event
XEvent xevent;
if (!getEvent(&xevent)) {
break;
}
// change our priority
CThread::getCurrentThread().setPriority(-3);
// handle event
switch (xevent.type) {
case CreateNotify:
{
// select events on new window
CDisplayLock display(this);
selectEvents(display, xevent.xcreatewindow.window);
}
break;
case MappingNotify:
{
// keyboard mapping changed
CDisplayLock display(this);
XRefreshKeyboardMapping(&xevent.xmapping);
updateModifierMap(display);
}
break;
case ClientMessage:
if (xevent.xclient.message_type == m_atomScreenSaver ||
xevent.xclient.format == 32) {
// screen saver activation/deactivation event
m_primaryReceiver->onScreenSaver(xevent.xclient.data.l[0] != 0);
}
break;
case KeyPress:
{
log((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state));
const KeyModifierMask mask = mapModifier(xevent.xkey.state);
const KeyID key = mapKey(&xevent.xkey);
if (key != kKeyNone) {
m_primaryReceiver->onKeyDown(key, mask);
if (key == XK_Caps_Lock && m_capsLockHalfDuplex) {
m_primaryReceiver->onKeyUp(key, mask | KeyModifierCapsLock);
}
else if (key == XK_Num_Lock && m_numLockHalfDuplex) {
m_primaryReceiver->onKeyUp(key, mask | KeyModifierNumLock);
}
}
}
break;
case KeyRelease:
{
const KeyModifierMask mask = mapModifier(xevent.xkey.state);
const KeyID key = mapKey(&xevent.xkey);
if (key != kKeyNone) {
// check if this is a key repeat by getting the next
// KeyPress event that has the same key and time as
// this release event, if any. first prepare the
// filter info.
CKeyEventInfo filter;
filter.m_event = KeyPress;
filter.m_window = xevent.xkey.window;
filter.m_time = xevent.xkey.time;
filter.m_keycode = xevent.xkey.keycode;
// now check for event
XEvent xevent2;
CDisplayLock display(this);
if (XCheckIfEvent(display, &xevent2,
&CXWindowsPrimaryScreen::findKeyEvent,
(XPointer)&filter) != True) {
// no press event follows so it's a plain release
log((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state));
if (key == XK_Caps_Lock && m_capsLockHalfDuplex) {
m_primaryReceiver->onKeyDown(key, mask);
}
else if (key == XK_Num_Lock && m_numLockHalfDuplex) {
m_primaryReceiver->onKeyDown(key, mask);
}
m_primaryReceiver->onKeyUp(key, mask);
}
else {
// found a press event following so it's a repeat.
// we could attempt to count the already queued
// repeats but we'll just send a repeat of 1.
// note that we discard the press event.
log((CLOG_DEBUG1 "event: repeat code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state));
m_primaryReceiver->onKeyRepeat(key, mask, 1);
}
}
}
break;
case ButtonPress:
{
log((CLOG_DEBUG1 "event: ButtonPress button=%d", xevent.xbutton.button));
const ButtonID button = mapButton(xevent.xbutton.button);
if (button != kButtonNone) {
m_primaryReceiver->onMouseDown(button);
}
}
break;
case ButtonRelease:
{
log((CLOG_DEBUG1 "event: ButtonRelease button=%d", xevent.xbutton.button));
const ButtonID button = mapButton(xevent.xbutton.button);
if (button != kButtonNone) {
m_primaryReceiver->onMouseUp(button);
}
else if (xevent.xbutton.button == 4) {
// wheel forward (away from user)
m_primaryReceiver->onMouseWheel(120);
}
else if (xevent.xbutton.button == 5) {
// wheel backward (toward user)
m_primaryReceiver->onMouseWheel(-120);
}
}
break;
case MotionNotify:
{
log((CLOG_DEBUG2 "event: MotionNotify %d,%d", xevent.xmotion.x_root, xevent.xmotion.y_root));
// compute motion delta (relative to the last known
// mouse position)
SInt32 x = xevent.xmotion.x_root - m_x;
SInt32 y = xevent.xmotion.y_root - m_y;
// save position to compute delta of next motion
m_x = xevent.xmotion.x_root;
m_y = xevent.xmotion.y_root;
if (xevent.xmotion.send_event) {
// we warped the mouse. discard events until we
// find the matching sent event. see
// warpCursorNoFlush() for where the events are
// sent. we discard the matching sent event and
// can be sure we've skipped the warp event.
CDisplayLock display(this);
do {
XMaskEvent(display, PointerMotionMask, &xevent);
} while (!xevent.xmotion.send_event);
}
else if (!m_active) {
// motion on primary screen
m_primaryReceiver->onMouseMovePrimary(m_x, m_y);
}
else {
// motion on secondary screen. warp mouse back to
// center.
//
// my lombard (powerbook g3) running linux and
// using the adbmouse driver has two problems:
// first, the driver only sends motions of +/-2
// pixels and, second, it seems to discard some
// physical input after a warp. the former isn't a
// big deal (we're just limited to every other
// pixel) but the latter is a PITA. to work around
// it we only warp when the mouse has moved more
// than s_size pixels from the center.
static const SInt32 s_size = 32;
if (xevent.xmotion.x_root - m_xCenter < -s_size ||
xevent.xmotion.x_root - m_xCenter > s_size ||
xevent.xmotion.y_root - m_yCenter < -s_size ||
xevent.xmotion.y_root - m_yCenter > s_size) {
CDisplayLock display(this);
warpCursorNoFlush(display, m_xCenter, m_yCenter);
}
// send event if mouse moved. do this after warping
// back to center in case the motion takes us onto
// the primary screen. if we sent the event first
// in that case then the warp would happen after
// warping to the primary screen's enter position,
// effectively overriding it.
if (x != 0 || y != 0) {
m_primaryReceiver->onMouseMoveSecondary(x, y);
}
}
}
break;
// run event loop
try {
log((CLOG_INFO "entering event loop"));
mainLoop();
log((CLOG_INFO "exiting event loop"));
}
catch (...) {
log((CLOG_INFO "exiting event loop"));
throw;
}
}
void
CXWindowsPrimaryScreen::stop()
{
CDisplayLock display(this);
doStop();
exitMainLoop();
}
void
@ -387,196 +213,6 @@ CXWindowsPrimaryScreen::warpCursor(SInt32 x, SInt32 y)
m_y = y;
}
void
CXWindowsPrimaryScreen::warpCursorToCenter()
{
warpCursor(m_xCenter, m_yCenter);
}
void
CXWindowsPrimaryScreen::warpCursorNoFlush(
Display* display, SInt32 x, SInt32 y)
{
assert(display != NULL);
assert(m_window != None);
// send an event that we can recognize before the mouse warp
XEvent eventBefore;
eventBefore.type = MotionNotify;
eventBefore.xmotion.display = display;
eventBefore.xmotion.window = m_window;
eventBefore.xmotion.root = getRoot();
eventBefore.xmotion.subwindow = m_window;
eventBefore.xmotion.time = CurrentTime;
eventBefore.xmotion.x = x;
eventBefore.xmotion.y = y;
eventBefore.xmotion.x_root = x;
eventBefore.xmotion.y_root = y;
eventBefore.xmotion.state = 0;
eventBefore.xmotion.is_hint = False;
eventBefore.xmotion.same_screen = True;
XEvent eventAfter = eventBefore;
XSendEvent(display, m_window, False, 0, &eventBefore);
// warp mouse
XWarpPointer(display, None, getRoot(), 0, 0, 0, 0, x, y);
// send an event that we can recognize after the mouse warp
XSendEvent(display, m_window, False, 0, &eventAfter);
XSync(display, False);
log((CLOG_DEBUG2 "warped to %d,%d", x, y));
}
void
CXWindowsPrimaryScreen::checkClipboard()
{
// do nothing, we're always up to date
}
void
CXWindowsPrimaryScreen::enterNoWarp()
{
m_active = false;
hideWindow();
}
bool
CXWindowsPrimaryScreen::showWindow()
{
CDisplayLock display(this);
// raise and show the input window
XMapRaised(display, m_window);
// grab the mouse and keyboard. keep trying until we get them.
// if we can't grab one after grabbing the other then ungrab
// and wait before retrying. give up after s_timeout seconds.
static const double s_timeout = 1.0;
int result;
CStopwatch timer;
do {
// keyboard first
do {
result = XGrabKeyboard(display, m_window, True,
GrabModeAsync, GrabModeAsync, CurrentTime);
assert(result != GrabNotViewable);
if (result != GrabSuccess) {
log((CLOG_DEBUG2 "waiting to grab keyboard"));
CThread::sleep(0.05);
if (timer.getTime() >= s_timeout) {
log((CLOG_DEBUG2 "grab keyboard timed out"));
XUnmapWindow(display, m_window);
return false;
}
}
} while (result != GrabSuccess);
log((CLOG_DEBUG2 "grabbed keyboard"));
// now the mouse
result = XGrabPointer(display, m_window, True, 0,
GrabModeAsync, GrabModeAsync,
m_window, None, CurrentTime);
assert(result != GrabNotViewable);
if (result != GrabSuccess) {
// back off to avoid grab deadlock
XUngrabKeyboard(display, CurrentTime);
log((CLOG_DEBUG2 "ungrabbed keyboard, waiting to grab pointer"));
CThread::sleep(0.05);
if (timer.getTime() >= s_timeout) {
log((CLOG_DEBUG2 "grab pointer timed out"));
XUnmapWindow(display, m_window);
return false;
}
}
} while (result != GrabSuccess);
log((CLOG_DEBUG1 "grabbed pointer and keyboard"));
return true;
}
void
CXWindowsPrimaryScreen::hideWindow()
{
CDisplayLock display(this);
// unmap the grab window. this also ungrabs the mouse and keyboard.
XUnmapWindow(display, m_window);
}
SInt32
CXWindowsPrimaryScreen::getJumpZoneSize() const
{
return 1;
}
void
CXWindowsPrimaryScreen::createWindow()
{
assert(m_window == None);
// get size of screen
SInt32 x, y, w, h;
getScreenShape(x, y, w, h);
// grab window attributes. this window is used to capture user
// input when the user is focused on another client. don't let
// the window manager mess with it.
XSetWindowAttributes attr;
attr.event_mask = PointerMotionMask |
ButtonPressMask | ButtonReleaseMask |
KeyPressMask | KeyReleaseMask |
KeymapStateMask | PropertyChangeMask;
attr.do_not_propagate_mask = 0;
attr.override_redirect = True;
attr.cursor = getBlankCursor();
// create the grab window
CDisplayLock display(this);
m_window = XCreateWindow(display, getRoot(),
x, y, w, h, 0, 0,
InputOnly, CopyFromParent,
CWDontPropagate | CWEventMask |
CWOverrideRedirect | CWCursor,
&attr);
if (m_window == None) {
throw XScreenOpenFailure();
}
log((CLOG_DEBUG "window is 0x%08x", m_window));
// start watching for events on other windows
selectEvents(display, getRoot());
}
void
CXWindowsPrimaryScreen::destroyWindow()
{
// display can be NULL if the server unexpectedly disconnected
CDisplayLock display(this);
if (display != NULL && m_window != None) {
XDestroyWindow(display, m_window);
}
m_window = None;
}
void
CXWindowsPrimaryScreen::installScreenSaver()
{
assert(getScreenSaver() != NULL);
getScreenSaver()->setNotify(m_window);
}
void
CXWindowsPrimaryScreen::uninstallScreenSaver()
{
// stop being notified of screen saver activation/deactivation
if (getScreenSaver() != NULL) {
getScreenSaver()->setNotify(None);
}
m_atomScreenSaver = None;
}
void
CXWindowsPrimaryScreen::setClipboard(ClipboardID id,
const IClipboard* clipboard)
@ -659,6 +295,202 @@ CXWindowsPrimaryScreen::isLockedToScreen() const
return false;
}
bool
CXWindowsPrimaryScreen::onPreDispatch(const CEvent* event)
{
// forward to superclass
return CXWindowsScreen::onPreDispatch(event);
}
bool
CXWindowsPrimaryScreen::onEvent(CEvent* event)
{
assert(event != NULL);
XEvent& xevent = event->m_event;
// handle event
switch (xevent.type) {
case CreateNotify:
{
// select events on new window
CDisplayLock display(this);
selectEvents(display, xevent.xcreatewindow.window);
}
return true;
case MappingNotify:
{
// keyboard mapping changed
CDisplayLock display(this);
XRefreshKeyboardMapping(&xevent.xmapping);
updateModifierMap(display);
}
return true;
case ClientMessage:
if (xevent.xclient.message_type == m_atomScreenSaver ||
xevent.xclient.format == 32) {
// screen saver activation/deactivation event
m_primaryReceiver->onScreenSaver(xevent.xclient.data.l[0] != 0);
return true;
}
break;
case KeyPress:
{
log((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state));
const KeyModifierMask mask = mapModifier(xevent.xkey.state);
const KeyID key = mapKey(&xevent.xkey);
if (key != kKeyNone) {
m_primaryReceiver->onKeyDown(key, mask);
if (key == XK_Caps_Lock && m_capsLockHalfDuplex) {
m_primaryReceiver->onKeyUp(key, mask | KeyModifierCapsLock);
}
else if (key == XK_Num_Lock && m_numLockHalfDuplex) {
m_primaryReceiver->onKeyUp(key, mask | KeyModifierNumLock);
}
}
}
return true;
case KeyRelease:
{
const KeyModifierMask mask = mapModifier(xevent.xkey.state);
const KeyID key = mapKey(&xevent.xkey);
if (key != kKeyNone) {
// check if this is a key repeat by getting the next
// KeyPress event that has the same key and time as
// this release event, if any. first prepare the
// filter info.
CKeyEventInfo filter;
filter.m_event = KeyPress;
filter.m_window = xevent.xkey.window;
filter.m_time = xevent.xkey.time;
filter.m_keycode = xevent.xkey.keycode;
// now check for event
XEvent xevent2;
CDisplayLock display(this);
if (XCheckIfEvent(display, &xevent2,
&CXWindowsPrimaryScreen::findKeyEvent,
(XPointer)&filter) != True) {
// no press event follows so it's a plain release
log((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state));
if (key == XK_Caps_Lock && m_capsLockHalfDuplex) {
m_primaryReceiver->onKeyDown(key, mask);
}
else if (key == XK_Num_Lock && m_numLockHalfDuplex) {
m_primaryReceiver->onKeyDown(key, mask);
}
m_primaryReceiver->onKeyUp(key, mask);
}
else {
// found a press event following so it's a repeat.
// we could attempt to count the already queued
// repeats but we'll just send a repeat of 1.
// note that we discard the press event.
log((CLOG_DEBUG1 "event: repeat code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state));
m_primaryReceiver->onKeyRepeat(key, mask, 1);
}
}
}
return true;
case ButtonPress:
{
log((CLOG_DEBUG1 "event: ButtonPress button=%d", xevent.xbutton.button));
const ButtonID button = mapButton(xevent.xbutton.button);
if (button != kButtonNone) {
m_primaryReceiver->onMouseDown(button);
}
}
return true;
case ButtonRelease:
{
log((CLOG_DEBUG1 "event: ButtonRelease button=%d", xevent.xbutton.button));
const ButtonID button = mapButton(xevent.xbutton.button);
if (button != kButtonNone) {
m_primaryReceiver->onMouseUp(button);
}
else if (xevent.xbutton.button == 4) {
// wheel forward (away from user)
m_primaryReceiver->onMouseWheel(120);
}
else if (xevent.xbutton.button == 5) {
// wheel backward (toward user)
m_primaryReceiver->onMouseWheel(-120);
}
}
return true;
case MotionNotify:
{
log((CLOG_DEBUG2 "event: MotionNotify %d,%d", xevent.xmotion.x_root, xevent.xmotion.y_root));
// compute motion delta (relative to the last known
// mouse position)
SInt32 x = xevent.xmotion.x_root - m_x;
SInt32 y = xevent.xmotion.y_root - m_y;
// save position to compute delta of next motion
m_x = xevent.xmotion.x_root;
m_y = xevent.xmotion.y_root;
if (xevent.xmotion.send_event) {
// we warped the mouse. discard events until we
// find the matching sent event. see
// warpCursorNoFlush() for where the events are
// sent. we discard the matching sent event and
// can be sure we've skipped the warp event.
CDisplayLock display(this);
do {
XMaskEvent(display, PointerMotionMask, &xevent);
} while (!xevent.xmotion.send_event);
}
else if (!m_active) {
// motion on primary screen
m_primaryReceiver->onMouseMovePrimary(m_x, m_y);
}
else {
// motion on secondary screen. warp mouse back to
// center.
//
// my lombard (powerbook g3) running linux and
// using the adbmouse driver has two problems:
// first, the driver only sends motions of +/-2
// pixels and, second, it seems to discard some
// physical input after a warp. the former isn't a
// big deal (we're just limited to every other
// pixel) but the latter is a PITA. to work around
// it we only warp when the mouse has moved more
// than s_size pixels from the center.
static const SInt32 s_size = 32;
if (xevent.xmotion.x_root - m_xCenter < -s_size ||
xevent.xmotion.x_root - m_xCenter > s_size ||
xevent.xmotion.y_root - m_yCenter < -s_size ||
xevent.xmotion.y_root - m_yCenter > s_size) {
CDisplayLock display(this);
warpCursorNoFlush(display, m_xCenter, m_yCenter);
}
// send event if mouse moved. do this after warping
// back to center in case the motion takes us onto
// the primary screen. if we sent the event first
// in that case then the warp would happen after
// warping to the primary screen's enter position,
// effectively overriding it.
if (x != 0 || y != 0) {
m_primaryReceiver->onMouseMoveSecondary(x, y);
}
}
}
return true;
}
return false;
}
void
CXWindowsPrimaryScreen::onUnexpectedClose()
{
@ -673,6 +505,196 @@ CXWindowsPrimaryScreen::onLostClipboard(ClipboardID id)
m_receiver->onGrabClipboard(id);
}
SInt32
CXWindowsPrimaryScreen::getJumpZoneSize() const
{
return 1;
}
void
CXWindowsPrimaryScreen::warpCursorToCenter()
{
warpCursor(m_xCenter, m_yCenter);
}
void
CXWindowsPrimaryScreen::warpCursorNoFlush(
Display* display, SInt32 x, SInt32 y)
{
assert(display != NULL);
assert(m_window != None);
// send an event that we can recognize before the mouse warp
XEvent eventBefore;
eventBefore.type = MotionNotify;
eventBefore.xmotion.display = display;
eventBefore.xmotion.window = m_window;
eventBefore.xmotion.root = getRoot();
eventBefore.xmotion.subwindow = m_window;
eventBefore.xmotion.time = CurrentTime;
eventBefore.xmotion.x = x;
eventBefore.xmotion.y = y;
eventBefore.xmotion.x_root = x;
eventBefore.xmotion.y_root = y;
eventBefore.xmotion.state = 0;
eventBefore.xmotion.is_hint = False;
eventBefore.xmotion.same_screen = True;
XEvent eventAfter = eventBefore;
XSendEvent(display, m_window, False, 0, &eventBefore);
// warp mouse
XWarpPointer(display, None, getRoot(), 0, 0, 0, 0, x, y);
// send an event that we can recognize after the mouse warp
XSendEvent(display, m_window, False, 0, &eventAfter);
XSync(display, False);
log((CLOG_DEBUG2 "warped to %d,%d", x, y));
}
void
CXWindowsPrimaryScreen::enterNoWarp()
{
m_active = false;
hideWindow();
}
bool
CXWindowsPrimaryScreen::showWindow()
{
CDisplayLock display(this);
// raise and show the input window
XMapRaised(display, m_window);
// grab the mouse and keyboard. keep trying until we get them.
// if we can't grab one after grabbing the other then ungrab
// and wait before retrying. give up after s_timeout seconds.
static const double s_timeout = 1.0;
int result;
CStopwatch timer;
do {
// keyboard first
do {
result = XGrabKeyboard(display, m_window, True,
GrabModeAsync, GrabModeAsync, CurrentTime);
assert(result != GrabNotViewable);
if (result != GrabSuccess) {
log((CLOG_DEBUG2 "waiting to grab keyboard"));
CThread::sleep(0.05);
if (timer.getTime() >= s_timeout) {
log((CLOG_DEBUG2 "grab keyboard timed out"));
XUnmapWindow(display, m_window);
return false;
}
}
} while (result != GrabSuccess);
log((CLOG_DEBUG2 "grabbed keyboard"));
// now the mouse
result = XGrabPointer(display, m_window, True, 0,
GrabModeAsync, GrabModeAsync,
m_window, None, CurrentTime);
assert(result != GrabNotViewable);
if (result != GrabSuccess) {
// back off to avoid grab deadlock
XUngrabKeyboard(display, CurrentTime);
log((CLOG_DEBUG2 "ungrabbed keyboard, waiting to grab pointer"));
CThread::sleep(0.05);
if (timer.getTime() >= s_timeout) {
log((CLOG_DEBUG2 "grab pointer timed out"));
XUnmapWindow(display, m_window);
return false;
}
}
} while (result != GrabSuccess);
log((CLOG_DEBUG1 "grabbed pointer and keyboard"));
return true;
}
void
CXWindowsPrimaryScreen::hideWindow()
{
CDisplayLock display(this);
// unmap the grab window. this also ungrabs the mouse and keyboard.
XUnmapWindow(display, m_window);
}
void
CXWindowsPrimaryScreen::checkClipboard()
{
// do nothing, we're always up to date
}
void
CXWindowsPrimaryScreen::createWindow()
{
assert(m_window == None);
// get size of screen
SInt32 x, y, w, h;
getScreenShape(x, y, w, h);
// grab window attributes. this window is used to capture user
// input when the user is focused on another client. don't let
// the window manager mess with it.
XSetWindowAttributes attr;
attr.event_mask = PointerMotionMask |
ButtonPressMask | ButtonReleaseMask |
KeyPressMask | KeyReleaseMask |
KeymapStateMask | PropertyChangeMask;
attr.do_not_propagate_mask = 0;
attr.override_redirect = True;
attr.cursor = getBlankCursor();
// create the grab window
CDisplayLock display(this);
m_window = XCreateWindow(display, getRoot(),
x, y, w, h, 0, 0,
InputOnly, CopyFromParent,
CWDontPropagate | CWEventMask |
CWOverrideRedirect | CWCursor,
&attr);
if (m_window == None) {
throw XScreenOpenFailure();
}
log((CLOG_DEBUG "window is 0x%08x", m_window));
// start watching for events on other windows
selectEvents(display, getRoot());
}
void
CXWindowsPrimaryScreen::destroyWindow()
{
// display can be NULL if the server unexpectedly disconnected
CDisplayLock display(this);
if (display != NULL && m_window != None) {
XDestroyWindow(display, m_window);
}
m_window = None;
}
void
CXWindowsPrimaryScreen::installScreenSaver()
{
assert(getScreenSaver() != NULL);
getScreenSaver()->setNotify(m_window);
}
void
CXWindowsPrimaryScreen::uninstallScreenSaver()
{
// stop being notified of screen saver activation/deactivation
if (getScreenSaver() != NULL) {
getScreenSaver()->setNotify(None);
}
m_atomScreenSaver = None;
}
void
CXWindowsPrimaryScreen::selectEvents(Display* display, Window w) const
{

View File

@ -30,23 +30,22 @@ public:
protected:
// CXWindowsScreen overrides
virtual bool onPreDispatch(const CEvent* event);
virtual bool onEvent(CEvent* event);
virtual void onUnexpectedClose();
virtual void onLostClipboard(ClipboardID);
private:
void selectEvents(Display*, Window) const;
void doSelectEvents(Display*, Window) const;
void enterNoWarp();
bool showWindow();
void hideWindow();
SInt32 getJumpZoneSize() const;
void warpCursorToCenter();
void warpCursorNoFlush(Display*,
SInt32 xAbsolute, SInt32 yAbsolute);
void enterNoWarp();
bool showWindow();
void hideWindow();
// check clipboard ownership and, if necessary, tell the receiver
// of a grab.
void checkClipboard();
@ -59,6 +58,9 @@ private:
void installScreenSaver();
void uninstallScreenSaver();
void selectEvents(Display*, Window) const;
void doSelectEvents(Display*, Window) const;
KeyModifierMask mapModifier(unsigned int state) const;
KeyID mapKey(XKeyEvent*) const;
ButtonID mapButton(unsigned int button) const;