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 void
CMSWindowsSecondaryScreen::run() CMSWindowsSecondaryScreen::run()
{ {
assert(m_window != NULL);
// must call run() from same thread as open() // must call run() from same thread as open()
assert(m_threadID == GetCurrentThreadId()); assert(m_threadID == GetCurrentThreadId());
// change our priority // change our priority
CThread::getCurrentThread().setPriority(-7); 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 // run event loop
try {
log((CLOG_INFO "entering event loop")); log((CLOG_INFO "entering event loop"));
doRun(); mainLoop();
log((CLOG_INFO "exiting event loop")); log((CLOG_INFO "exiting event loop"));
}
// remove timer catch (...) {
if (!m_is95Family) { log((CLOG_INFO "exiting event loop"));
KillTimer(NULL, timer); throw;
} }
} }
void void
CMSWindowsSecondaryScreen::stop() CMSWindowsSecondaryScreen::stop()
{ {
doStop(); exitMainLoop();
} }
void void
CMSWindowsSecondaryScreen::open() CMSWindowsSecondaryScreen::open()
{ {
assert(m_window == NULL);
try {
// open the display // open the display
openDisplay(); openDisplay();
// update key state // create and prepare our window
updateKeys(); createWindow();
updateModifiers();
// assume primary has all clipboards // initialize the clipboards; assume primary has all clipboards
for (ClipboardID id = 0; id < kClipboardEnd; ++id) { for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
grabClipboard(id); grabClipboard(id);
} }
// get keyboard state
updateKeys();
updateModifiers();
// disable the screen saver // disable the screen saver
getScreenSaver()->disable(); installScreenSaver();
}
catch (...) {
close();
throw;
}
// hide the cursor // hide the cursor
m_active = true; m_active = true;
@ -106,13 +113,8 @@ CMSWindowsSecondaryScreen::open()
void void
CMSWindowsSecondaryScreen::close() CMSWindowsSecondaryScreen::close()
{ {
// release keys that are logically pressed uninstallScreenSaver();
releaseKeys(); destroyWindow();
// restore the screen saver settings
getScreenSaver()->enable();
// close the display
closeDisplay(); closeDisplay();
} }
@ -145,8 +147,11 @@ CMSWindowsSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask)
toggleKey(VK_SCROLL, KeyModifierScrollLock); toggleKey(VK_SCROLL, KeyModifierScrollLock);
} }
// warp to requested location
warpCursor(x, y);
// show mouse // show mouse
onEnter(x, y); hideWindow();
} }
void void
@ -161,30 +166,13 @@ CMSWindowsSecondaryScreen::leave()
syncDesktop(); syncDesktop();
// hide mouse // hide mouse
onLeave(); showWindow();
// not active anymore // not active anymore
m_active = false; m_active = false;
// if we think we own the clipboard but we don't then somebody // make sure our idea of clipboard ownership is correct
// grabbed the clipboard on this screen without us knowing. checkClipboard();
// 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);
}
}
} }
void void
@ -402,15 +390,7 @@ CMSWindowsSecondaryScreen::getMousePos(SInt32& x, SInt32& y) const
assert(m_window != NULL); assert(m_window != NULL);
syncDesktop(); syncDesktop();
POINT pos; getCursorPos(x, y);
if (GetCursorPos(&pos)) {
x = pos.x;
y = pos.y;
}
else {
x = 0;
y = 0;
}
} }
void void
@ -437,52 +417,18 @@ CMSWindowsSecondaryScreen::getClipboard(ClipboardID /*id*/,
CClipboard::copy(dst, &src); 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 bool
CMSWindowsSecondaryScreen::onPreTranslate(MSG* msg) CMSWindowsSecondaryScreen::onPreDispatch(const CEvent* event)
{ {
assert(event != NULL);
// forward to superclass
if (CMSWindowsScreen::onPreDispatch(event)) {
return true;
}
// handle event // handle event
const MSG* msg = &event->m_msg;
switch (msg->message) { switch (msg->message) {
case WM_TIMER: case WM_TIMER:
// if current desktop is not the input desktop then switch to it // if current desktop is not the input desktop then switch to it
@ -503,32 +449,35 @@ CMSWindowsSecondaryScreen::onPreTranslate(MSG* msg)
return false; return false;
} }
LRESULT bool
CMSWindowsSecondaryScreen::onEvent(HWND hwnd, UINT msg, CMSWindowsSecondaryScreen::onEvent(CEvent* event)
WPARAM wParam, LPARAM lParam)
{ {
switch (msg) { assert(event != NULL);
const MSG& msg = event->msg;
switch (msg.message) {
case WM_QUERYENDSESSION: case WM_QUERYENDSESSION:
if (m_is95Family) { if (m_is95Family) {
return TRUE; event->m_result = TRUE;
return true;
} }
break; break;
case WM_ENDSESSION: case WM_ENDSESSION:
if (m_is95Family) { if (m_is95Family) {
if (wParam == TRUE && lParam == 0) { if (msg.wParam == TRUE && msg.lParam == 0) {
stop(); stop();
} }
return 0; return true;
} }
break; break;
case WM_PAINT: case WM_PAINT:
ValidateRect(hwnd, NULL); ValidateRect(msg.hwnd, NULL);
return 0; return true;
case WM_ACTIVATEAPP: case WM_ACTIVATEAPP:
if (wParam == FALSE) { if (msg.wParam == FALSE) {
// some other app activated. hide the hider window. // some other app activated. hide the hider window.
ShowWindow(m_window, SW_HIDE); ShowWindow(m_window, SW_HIDE);
} }
@ -538,67 +487,265 @@ CMSWindowsSecondaryScreen::onEvent(HWND hwnd, UINT msg,
log((CLOG_DEBUG "clipboard was taken")); log((CLOG_DEBUG "clipboard was taken"));
// first pass it on // 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 // now notify client that somebody changed the clipboard (unless
// we're now the owner, in which case it's because we took // 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 // ownership, or now it's owned by nobody, which will happen if
// we owned it and switched desktops because we destroy our // we owned it and switched desktops because we destroy our
// window to do that). // window to do that).
try {
m_clipboardOwner = GetClipboardOwner(); m_clipboardOwner = GetClipboardOwner();
if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) { if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) {
m_receiver->onGrabClipboard(kClipboardClipboard); m_receiver->onGrabClipboard(kClipboardClipboard);
m_receiver->onGrabClipboard(kClipboardSelection); 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: case WM_CHANGECBCHAIN:
if (m_nextClipboardWindow == (HWND)wParam) { if (m_nextClipboardWindow == (HWND)msg.wParam) {
m_nextClipboardWindow = (HWND)lParam; m_nextClipboardWindow = (HWND)msg.lParam;
} }
else { 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: 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(); updateScreenShape();
m_multimon = isMultimon(); m_multimon = isMultimon();
// send new info // collect new screen info
CClientInfo info; CClientInfo info;
getShape(info.m_x, info.m_y, info.m_w, info.m_h); getScreenShape(info.m_x, info.m_y, info.m_w, info.m_h);
getMousePos(info.m_mx, info.m_my); getCursorPos(info.m_mx, info.m_my);
info.m_zoneSize = getJumpZoneSize(); 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); m_receiver->onInfoChanged(info);
return 0;
} }
return DefWindowProc(hwnd, msg, wParam, lParam); return true;
}
}
return false;
}
CString
CMSWindowsSecondaryScreen::getCurrentDesktopName() const
{
return m_deskName;
} }
void void
CMSWindowsSecondaryScreen::onEnter(SInt32 x, SInt32 y) CMSWindowsSecondaryScreen::showWindow()
{ {
// warp to requested location // move hider window under the mouse (rather than moving the mouse
warpCursor(x, y); // 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); ShowWindow(m_window, SW_HIDE);
} }
void void
CMSWindowsSecondaryScreen::onLeave() CMSWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y)
{ {
// move hider window under the mouse (rather than moving the mouse // move the mouse directly to target position on NT family or if
// somewhere else on the screen) // not using multiple monitors.
POINT point; if (!m_multimon || !m_is95Family) {
GetCursorPos(&point); SInt32 x0, y0, w, h;
MoveWindow(m_window, point.x, point.y, 1, 1, FALSE); 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. // windows 98 (and Me?) is broken. you cannot set the absolute
ShowWindow(m_window, SW_SHOWNORMAL); // 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 bool
@ -720,7 +867,7 @@ CMSWindowsSecondaryScreen::switchDesktop(HDESK desk)
// get desktop up to date // get desktop up to date
if (!m_active) { if (!m_active) {
onLeave(); showWindow();
} }
return true; 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 bool
CMSWindowsSecondaryScreen::isMultimon() const CMSWindowsSecondaryScreen::isMultimon() const
{ {

View File

@ -46,10 +46,8 @@ public:
protected: protected:
// CMSWindowsScreen overrides // CMSWindowsScreen overrides
virtual bool onPreTranslate(MSG*); virtual bool onPreDispatch(const CEvent* event);
virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM); virtual bool onEvent(CEvent* event);
virtual void onOpenDisplay();
virtual void onCloseDisplay();
virtual CString getCurrentDesktopName() const; virtual CString getCurrentDesktopName() const;
private: private:
@ -62,8 +60,25 @@ private:
}; };
typedef std::vector<Keystroke> Keystrokes; typedef std::vector<Keystroke> Keystrokes;
void onEnter(SInt32 x, SInt32 y); void showWindow();
void onLeave(); 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) // open/close desktop (for windows 95/98/me)
bool openDesktop(); bool openDesktop();
@ -75,9 +90,6 @@ private:
// get calling thread to use the input desktop // get calling thread to use the input desktop
void syncDesktop() const; 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 // returns true iff there appear to be multiple monitors
bool isMultimon() const; bool isMultimon() const;
@ -108,6 +120,9 @@ private:
// the main loop's thread id // the main loop's thread id
DWORD m_threadID; DWORD m_threadID;
// the timer used to check for desktop switching
UINT m_timer;
// the thread id of the last attached thread // the thread id of the last attached thread
mutable DWORD m_lastThreadID; mutable DWORD m_lastThreadID;

View File

@ -1,8 +1,9 @@
#include "CXWindowsSecondaryScreen.h" #include "CXWindowsSecondaryScreen.h"
#include "CClient.h" #include "IScreenReceiver.h"
#include "CXWindowsClipboard.h" #include "CXWindowsClipboard.h"
#include "CXWindowsScreenSaver.h" #include "CXWindowsScreenSaver.h"
#include "CXWindowsUtil.h" #include "CXWindowsUtil.h"
#include "XScreen.h"
#include "CThread.h" #include "CThread.h"
#include "CLog.h" #include "CLog.h"
#if defined(X_DISPLAY_MISSING) #if defined(X_DISPLAY_MISSING)
@ -26,9 +27,10 @@
CXWindowsSecondaryScreen::CXWindowsSecondaryScreen(IScreenReceiver* receiver) : CXWindowsSecondaryScreen::CXWindowsSecondaryScreen(IScreenReceiver* receiver) :
m_receiver(receiver), m_receiver(receiver),
m_window(None) m_window(None),
m_active(false)
{ {
// do nothing assert(m_receiver != NULL);
} }
CXWindowsSecondaryScreen::~CXWindowsSecondaryScreen() CXWindowsSecondaryScreen::~CXWindowsSecondaryScreen()
@ -41,95 +43,43 @@ CXWindowsSecondaryScreen::run()
{ {
assert(m_window != None); assert(m_window != None);
for (;;) { // change our priority
// wait for and get the next event CThread::getCurrentThread().setPriority(-7);
XEvent xevent;
if (!getEvent(&xevent)) {
break;
}
// handle event // run event loop
switch (xevent.type) { try {
case MappingNotify: log((CLOG_INFO "entering event loop"));
{ mainLoop();
// keyboard mapping changed log((CLOG_INFO "exiting event loop"));
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;
} }
catch (...) {
log((CLOG_INFO "exiting event loop"));
throw;
} }
} }
void void
CXWindowsSecondaryScreen::stop() CXWindowsSecondaryScreen::stop()
{ {
CDisplayLock display(this); exitMainLoop();
doStop();
} }
void void
CXWindowsSecondaryScreen::open() CXWindowsSecondaryScreen::open()
{ {
assert(m_receiver != NULL);
assert(m_window == None); assert(m_window == None);
try {
// open the display // open the display
openDisplay(); openDisplay();
{ // create and prepare our window
CDisplayLock display(this); createWindow();
// verify the availability of the XTest extension // initialize the clipboards; assume primary has all clipboards
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
initClipboards(m_window); initClipboards(m_window);
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
// update key state grabClipboard(id);
updateKeys(display);
updateKeycodeMap(display);
updateModifierMap(display);
updateModifiers(display);
} }
// check for peculiarities // check for peculiarities
@ -139,37 +89,33 @@ CXWindowsSecondaryScreen::open()
// m_numLockHalfDuplex = true; // m_numLockHalfDuplex = true;
// m_capsLockHalfDuplex = true; // m_capsLockHalfDuplex = true;
// assume primary has all clipboards // get the display
for (ClipboardID id = 0; id < kClipboardEnd; ++id) { CDisplayLock display(this);
grabClipboard(id);
} // update key state
updateKeys(display);
updateKeycodeMap(display);
updateModifierMap(display);
updateModifiers(display);
// disable the screen saver // disable the screen saver
getScreenSaver()->disable(); installScreenSaver();
}
catch (...) {
close();
throw;
}
// hide the cursor
m_active = true;
leave();
} }
void void
CXWindowsSecondaryScreen::close() CXWindowsSecondaryScreen::close()
{ {
// release keys that are logically pressed uninstallScreenSaver();
releaseKeys(); destroyWindow();
// 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
closeDisplay(); closeDisplay();
} }
@ -177,15 +123,14 @@ void
CXWindowsSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask) CXWindowsSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask)
{ {
assert(m_window != None); assert(m_window != None);
assert(m_active == false);
log((CLOG_INFO "entering screen at %d,%d mask=%04x", x, y, mask));
CDisplayLock display(this); CDisplayLock display(this);
// warp to requested location // now active
XTestFakeMotionEvent(display, getScreen(), x, y, CurrentTime); m_active = true;
XSync(display, False);
// show cursor
XUnmapWindow(display, m_window);
// update our keyboard state to reflect the local state // update our keyboard state to reflect the local state
updateKeys(display); updateKeys(display);
@ -202,14 +147,32 @@ CXWindowsSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask)
if ((xMask & m_scrollLockMask) != (m_mask & m_scrollLockMask)) { if ((xMask & m_scrollLockMask) != (m_mask & m_scrollLockMask)) {
toggleKey(display, XK_Scroll_Lock, m_scrollLockMask); toggleKey(display, XK_Scroll_Lock, m_scrollLockMask);
} }
XSync(display, False);
// warp to requested location
warpCursor(x, y);
// show mouse
hideWindow();
} }
void void
CXWindowsSecondaryScreen::leave() CXWindowsSecondaryScreen::leave()
{ {
assert(m_window != None);
assert(m_active == true);
log((CLOG_INFO "leaving screen"));
CDisplayLock display(this); 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 void
@ -290,8 +253,7 @@ void
CXWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y) CXWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y)
{ {
CDisplayLock display(this); CDisplayLock display(this);
XTestFakeMotionEvent(display, getScreen(), x, y, CurrentTime); warpCursor(x, y);
XSync(display, False);
} }
void void
@ -366,6 +328,44 @@ CXWindowsSecondaryScreen::getClipboard(ClipboardID id,
getDisplayClipboard(id, clipboard); 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 void
CXWindowsSecondaryScreen::onLostClipboard(ClipboardID id) CXWindowsSecondaryScreen::onLostClipboard(ClipboardID id)
{ {
@ -374,25 +374,110 @@ CXWindowsSecondaryScreen::onLostClipboard(ClipboardID id)
} }
void 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 // move hider window under the mouse (rather than moving the mouse
// somewhere else on the screen) // somewhere else on the screen)
int x, y, dummy; SInt32 x, y;
unsigned int dummyMask; getCursorPos(x, y);
Window dummyWindow; XMoveWindow(getDisplay(), m_window, x, y);
XQueryPointer(display, getRoot(), &dummyWindow, &dummyWindow,
&x, &y, &dummy, &dummy, &dummyMask);
XMoveWindow(display, m_window, x, y);
// raise and show the hider window // raise and show the hider window. take activation.
XMapRaised(display, m_window); // FIXME -- take focus?
XMapRaised(getDisplay(), m_window);
/* XXX -- this should have no effect
// hide cursor by moving it into the hider window // 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 unsigned int
@ -829,6 +914,7 @@ CXWindowsSecondaryScreen::releaseKeys()
{ {
CDisplayLock display(this); CDisplayLock display(this);
if (display != NULL) {
// key up for each key that's down // key up for each key that's down
for (UInt32 i = 0; i < 256; ++i) { for (UInt32 i = 0; i < 256; ++i) {
if (m_keys[i]) { if (m_keys[i]) {
@ -839,7 +925,7 @@ CXWindowsSecondaryScreen::releaseKeys()
// update // update
XSync(display, False); XSync(display, False);
}
} }
void void

View File

@ -39,6 +39,8 @@ public:
protected: protected:
// CXWindowsScreen overrides // CXWindowsScreen overrides
virtual bool onPreDispatch(const CEvent* event);
virtual bool onEvent(CEvent* event);
virtual void onLostClipboard(ClipboardID); virtual void onLostClipboard(ClipboardID);
private: private:
@ -60,7 +62,26 @@ private:
typedef std::map<KeyID, KeyCodeMask> KeyCodeMap; typedef std::map<KeyID, KeyCodeMask> KeyCodeMap;
typedef std::map<KeyCode, unsigned int> ModifierMap; 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 mapButton(ButtonID button) const;
unsigned int mapKey(Keystrokes&, KeyCode&, KeyID, unsigned int mapKey(Keystrokes&, KeyCode&, KeyID,
@ -82,6 +103,9 @@ private:
IScreenReceiver* m_receiver; IScreenReceiver* m_receiver;
Window m_window; 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 // note toggle keys that toggles on up/down (false) or on
// transition (true) // transition (true)
bool m_numLockHalfDuplex; bool m_numLockHalfDuplex;

View File

@ -48,36 +48,44 @@ CMSWindowsScreen::init(HINSTANCE instance)
} }
void void
CMSWindowsScreen::doRun() CMSWindowsScreen::mainLoop()
{ {
// save thread id for posting quit message // save thread id for posting quit message
m_thread = GetCurrentThreadId(); m_thread = GetCurrentThreadId();
// event loop // event loop
CEvent event;
event.m_result = 0;
for (;;) { for (;;) {
// wait for and get the next event // wait for an event in a cancellable way
MSG msg; CThread::waitForEvent();
getEvent(&msg); GetMessage(&event.m_msg, NULL, 0, 0);
// handle quit message // handle quit message
if (msg.message == WM_QUIT) { if (event.m_msg.message == WM_QUIT) {
break; break;
} }
// dispatch message // dispatch message
if (!onPreTranslate(&msg)) { if (!onPreDispatch(&event)) {
TranslateMessage(&msg); TranslateMessage(&event.m_msg);
DispatchMessage(&msg); DispatchMessage(&event.m_msg);
} }
} }
} }
void void
CMSWindowsScreen::doStop() CMSWindowsScreen::exitMainLoop()
{ {
PostThreadMessage(m_thread, WM_QUIT, 0, 0); PostThreadMessage(m_thread, WM_QUIT, 0, 0);
} }
bool
CMSWindowsScreen::onPreDispatch(const CEvent*)
{
return false;
}
void void
CMSWindowsScreen::openDisplay() CMSWindowsScreen::openDisplay()
{ {
@ -172,9 +180,13 @@ void
CMSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const CMSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const
{ {
POINT pos; POINT pos;
GetCursorPos(&pos); if (GetCursorPos(&pos)) {
x = pos.x; x = pos.x;
y = pos.y; y = pos.y;
}
else {
getCursorCenter(x, y);
}
} }
void void
@ -244,17 +256,22 @@ CMSWindowsScreen::getScreenSaver() const
return m_screenSaver; 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 LRESULT CALLBACK
CMSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) CMSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{ {
assert(s_screen != NULL); 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 CMSWindowsScreenSaver;
class CThread; class CThread;
class CEvent {
public:
MSG m_msg;
LRESULT m_result;
};
class CMSWindowsScreen { class CMSWindowsScreen {
public: public:
CMSWindowsScreen(); CMSWindowsScreen();
@ -25,11 +31,11 @@ public:
static HINSTANCE getInstance(); static HINSTANCE getInstance();
protected: protected:
// runs an event loop and returns when WM_QUIT is received // runs an event loop and returns when exitMainLoop() is called
void doRun(); void mainLoop();
// sends WM_QUIT to force doRun() to return // force mainLoop() to return
void doStop(); void exitMainLoop();
// open the X display. calls onOpenDisplay() after opening the display, // open the X display. calls onOpenDisplay() after opening the display,
// getting the screen, its size, and root window. then it starts the // getting the screen, its size, and root window. then it starts the
@ -72,15 +78,14 @@ protected:
CMSWindowsScreenSaver* CMSWindowsScreenSaver*
getScreenSaver() const; getScreenSaver() const;
// wait for and get the next message. cancellable. // called for each event before event translation and dispatch. return
void getEvent(MSG*) const; // 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 // called by mainLoop(). iff the event was handled return true and
// event translation and dispatch. // store the result, if any, in m_result, which defaults to zero.
virtual bool onPreTranslate(MSG*) = 0; virtual bool onEvent(CEvent* event) = 0;
// called by window proc. subclass must call DefWindowProc() if necessary
virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM) = 0;
// called by isCurrentDesktop() to get the current desktop name // called by isCurrentDesktop() to get the current desktop name
virtual CString getCurrentDesktopName() const = 0; virtual CString getCurrentDesktopName() const = 0;
@ -89,6 +94,7 @@ private:
// create the transparent cursor // create the transparent cursor
void createBlankCursor(); void createBlankCursor();
// our window proc
static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM); static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM);
private: private:

View File

@ -120,6 +120,129 @@ CXWindowsScreen::removeTimerNoLock(IJob* job)
m_timers.swap(tmp); 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 void
CXWindowsScreen::openDisplay() CXWindowsScreen::openDisplay()
{ {
@ -179,6 +302,12 @@ CXWindowsScreen::closeDisplay()
XSetIOErrorHandler(NULL); XSetIOErrorHandler(NULL);
} }
Display*
CXWindowsScreen::getDisplay() const
{
return m_display;
}
int int
CXWindowsScreen::getScreen() const CXWindowsScreen::getScreen() const
{ {
@ -294,49 +423,6 @@ CXWindowsScreen::getBlankCursor() const
return m_cursor; 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 ClipboardID
CXWindowsScreen::getClipboardID(Atom selection) const CXWindowsScreen::getClipboardID(Atom selection) const
{ {
@ -355,84 +441,6 @@ CXWindowsScreen::onUnexpectedClose()
// do nothing // 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 bool
CXWindowsScreen::processTimers() CXWindowsScreen::processTimers()
{ {

View File

@ -19,6 +19,12 @@ class IScreenSaver;
class CXWindowsClipboard; class CXWindowsClipboard;
class CXWindowsScreenSaver; class CXWindowsScreenSaver;
class CEvent {
public:
XEvent m_event;
SInt32 m_result;
};
class CXWindowsScreen { class CXWindowsScreen {
public: public:
CXWindowsScreen(); CXWindowsScreen();
@ -47,6 +53,12 @@ protected:
}; };
friend class CDisplayLock; 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, // open the X display. calls onOpenDisplay() after opening the display,
// getting the screen, its size, and root window. then it starts the // getting the screen, its size, and root window. then it starts the
// event thread. // event thread.
@ -57,6 +69,10 @@ protected:
// is closed. // is closed.
void closeDisplay(); 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 // get the opened screen and its root window. to get the display
// create a CDisplayLock object passing this. while the object // create a CDisplayLock object passing this. while the object
// exists no other threads may access the display. do not save // exists no other threads may access the display. do not save
@ -83,13 +99,6 @@ protected:
// get a cursor that is transparent everywhere // get a cursor that is transparent everywhere
Cursor getBlankCursor() const; 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) // set the contents of the clipboard (i.e. primary selection)
bool setDisplayClipboard(ClipboardID, bool setDisplayClipboard(ClipboardID,
const IClipboard* clipboard); const IClipboard* clipboard);
@ -102,6 +111,15 @@ protected:
CXWindowsScreenSaver* CXWindowsScreenSaver*
getScreenSaver() const; 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. // called if the display is unexpectedly closing. default does nothing.
virtual void onUnexpectedClose(); virtual void onUnexpectedClose();
@ -115,9 +133,6 @@ private:
// remove a timer without locking // remove a timer without locking
void removeTimerNoLock(IJob*); void removeTimerNoLock(IJob*);
// internal event processing
bool processEvent(XEvent*);
// process timers // process timers
bool processTimers(); bool processTimers();

View File

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

View File

@ -20,8 +20,10 @@ public:
CXWindowsScreenSaver(CXWindowsScreen*, Display*); CXWindowsScreenSaver(CXWindowsScreen*, Display*);
virtual ~CXWindowsScreenSaver(); virtual ~CXWindowsScreenSaver();
// process X event. returns true if the event was handled. // called for each event before event translation and dispatch. return
bool processEvent(XEvent*); // 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 // tells this object to send a ClientMessage to the given window
// when the screen saver activates or deactivates. only one // when the screen saver activates or deactivates. only one

View File

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

View File

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

View File

@ -42,199 +42,25 @@ CXWindowsPrimaryScreen::~CXWindowsPrimaryScreen()
void void
CXWindowsPrimaryScreen::run() CXWindowsPrimaryScreen::run()
{ {
for (;;) { // change our priority
// wait for and get the next event CThread::getCurrentThread().setPriority(-3);
XEvent xevent;
if (!getEvent(&xevent)) {
break;
}
// handle event // run event loop
switch (xevent.type) { try {
case CreateNotify: log((CLOG_INFO "entering event loop"));
{ mainLoop();
// select events on new window log((CLOG_INFO "exiting event loop"));
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;
} }
catch (...) {
log((CLOG_INFO "exiting event loop"));
throw;
} }
} }
void void
CXWindowsPrimaryScreen::stop() CXWindowsPrimaryScreen::stop()
{ {
CDisplayLock display(this); exitMainLoop();
doStop();
} }
void void
@ -387,196 +213,6 @@ CXWindowsPrimaryScreen::warpCursor(SInt32 x, SInt32 y)
m_y = 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 void
CXWindowsPrimaryScreen::setClipboard(ClipboardID id, CXWindowsPrimaryScreen::setClipboard(ClipboardID id,
const IClipboard* clipboard) const IClipboard* clipboard)
@ -659,6 +295,202 @@ CXWindowsPrimaryScreen::isLockedToScreen() const
return false; 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 void
CXWindowsPrimaryScreen::onUnexpectedClose() CXWindowsPrimaryScreen::onUnexpectedClose()
{ {
@ -673,6 +505,196 @@ CXWindowsPrimaryScreen::onLostClipboard(ClipboardID id)
m_receiver->onGrabClipboard(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 void
CXWindowsPrimaryScreen::selectEvents(Display* display, Window w) const CXWindowsPrimaryScreen::selectEvents(Display* display, Window w) const
{ {

View File

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