mirror of
https://github.com/debauchee/barrier.git
synced 2024-11-30 21:39:19 +03:00
33e359a384
when returning due to a quit event.
1910 lines
48 KiB
C++
1910 lines
48 KiB
C++
/*
|
|
* synergy -- mouse and keyboard sharing utility
|
|
* Copyright (C) 2002 Chris Schoeneman
|
|
*
|
|
* This package is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* found in the file COPYING that should have accompanied this file.
|
|
*
|
|
* This package is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "CXWindowsScreen.h"
|
|
#include "CXWindowsClipboard.h"
|
|
#include "CXWindowsScreenSaver.h"
|
|
#include "CXWindowsUtil.h"
|
|
#include "CClipboard.h"
|
|
#include "IScreenReceiver.h"
|
|
#include "IPrimaryScreenReceiver.h"
|
|
#include "XScreen.h"
|
|
#include "CLock.h"
|
|
#include "CThread.h"
|
|
#include "CLog.h"
|
|
#include "CStopwatch.h"
|
|
#include "CStringUtil.h"
|
|
#include "IJob.h"
|
|
#include <cstring>
|
|
#if defined(X_DISPLAY_MISSING)
|
|
# error X11 is required to build synergy
|
|
#else
|
|
# include <X11/X.h>
|
|
# include <X11/Xutil.h>
|
|
# define XK_XKB_KEYS
|
|
# include <X11/keysymdef.h>
|
|
# if defined(HAVE_X11_EXTENSIONS_XTEST_H)
|
|
# include <X11/extensions/XTest.h>
|
|
# else
|
|
# error The XTest extension is required to build synergy
|
|
# endif
|
|
# if HAVE_X11_EXTENSIONS_XINERAMA_H
|
|
// Xinerama.h may lack extern "C" for inclusion by C++
|
|
extern "C" {
|
|
# include <X11/extensions/Xinerama.h>
|
|
}
|
|
# endif
|
|
#endif
|
|
#if UNIX_LIKE
|
|
# if HAVE_POLL
|
|
# include <sys/poll.h>
|
|
# else
|
|
# if HAVE_SYS_SELECT_H
|
|
# include <sys/select.h>
|
|
# endif
|
|
# if HAVE_SYS_TIME_H
|
|
# include <sys/time.h>
|
|
# endif
|
|
# if HAVE_SYS_TYPES_H
|
|
# include <sys/types.h>
|
|
# endif
|
|
# if HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
# endif
|
|
# endif
|
|
#endif
|
|
#include "CArch.h"
|
|
|
|
// map "Internet" keys to KeyIDs
|
|
static const KeySym g_map1008FF[] =
|
|
{
|
|
/* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x10 */ 0, kKeyAudioDown, kKeyAudioMute, kKeyAudioUp,
|
|
/* 0x14 */ kKeyAudioPlay, kKeyAudioStop, kKeyAudioPrev, kKeyAudioNext,
|
|
/* 0x18 */ kKeyWWWHome, kKeyAppMail, 0, kKeyWWWSearch, 0, 0, 0, 0,
|
|
/* 0x20 */ 0, 0, 0, 0, 0, 0, kKeyWWWBack, kKeyWWWForward,
|
|
/* 0x28 */ kKeyWWWStop, kKeyWWWRefresh, 0, 0, 0, 0, 0, 0,
|
|
/* 0x30 */ kKeyWWWFavorites, 0, kKeyAppMedia, 0, 0, 0, 0, 0,
|
|
/* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x40 */ kKeyAppUser1, kKeyAppUser2, 0, 0, 0, 0, 0, 0,
|
|
/* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x50 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x60 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x68 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x78 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x88 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x98 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xa8 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xb0 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xb8 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xc0 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xc8 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xd0 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xe0 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xe8 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
|
|
|
|
//
|
|
// CXWindowsScreen
|
|
//
|
|
|
|
CXWindowsScreen* CXWindowsScreen::s_screen = NULL;
|
|
|
|
CXWindowsScreen::CXWindowsScreen(IScreenReceiver* receiver,
|
|
IPrimaryScreenReceiver* primaryReceiver) :
|
|
m_isPrimary(primaryReceiver != NULL),
|
|
m_display(NULL),
|
|
m_root(None),
|
|
m_window(None),
|
|
m_receiver(receiver),
|
|
m_primaryReceiver(primaryReceiver),
|
|
m_isOnScreen(m_isPrimary),
|
|
m_x(0), m_y(0),
|
|
m_w(0), m_h(0),
|
|
m_xCursor(0), m_yCursor(0),
|
|
m_xCenter(0), m_yCenter(0),
|
|
m_keyState(NULL),
|
|
m_keyMapper(),
|
|
m_im(NULL),
|
|
m_ic(NULL),
|
|
m_lastKeycode(0),
|
|
m_atomQuit(None),
|
|
m_screensaver(NULL),
|
|
m_screensaverNotify(false),
|
|
m_atomScreensaver(None),
|
|
m_oneShotTimer(NULL),
|
|
m_xtestIsXineramaUnaware(true)
|
|
{
|
|
assert(s_screen == NULL);
|
|
assert(m_receiver != NULL);
|
|
|
|
s_screen = this;
|
|
|
|
// no clipboards to start with
|
|
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
|
|
m_clipboard[id] = NULL;
|
|
}
|
|
}
|
|
|
|
CXWindowsScreen::~CXWindowsScreen()
|
|
{
|
|
assert(s_screen != NULL);
|
|
assert(m_display == NULL);
|
|
|
|
delete m_oneShotTimer;
|
|
s_screen = NULL;
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::open(IKeyState* keyState)
|
|
{
|
|
assert(m_display == NULL);
|
|
|
|
try {
|
|
// set the X I/O error handler so we catch the display disconnecting
|
|
XSetIOErrorHandler(&CXWindowsScreen::ioErrorHandler);
|
|
|
|
// get the DISPLAY
|
|
const char* display = getenv("DISPLAY");
|
|
if (display == NULL) {
|
|
display = ":0.0";
|
|
}
|
|
|
|
// open the display
|
|
LOG((CLOG_DEBUG "XOpenDisplay(\"%s\")", display));
|
|
m_display = XOpenDisplay(display);
|
|
if (m_display == NULL) {
|
|
throw XScreenUnavailable(60.0);
|
|
}
|
|
|
|
// verify the availability of the XTest extension
|
|
if (!m_isPrimary) {
|
|
int majorOpcode, firstEvent, firstError;
|
|
if (!XQueryExtension(m_display, XTestExtensionName,
|
|
&majorOpcode, &firstEvent, &firstError)) {
|
|
LOG((CLOG_ERR "XTEST extension not available"));
|
|
throw XScreenOpenFailure();
|
|
}
|
|
}
|
|
|
|
// get root window
|
|
m_root = DefaultRootWindow(m_display);
|
|
|
|
// get shape of default screen
|
|
m_x = 0;
|
|
m_y = 0;
|
|
m_w = WidthOfScreen(DefaultScreenOfDisplay(m_display));
|
|
m_h = HeightOfScreen(DefaultScreenOfDisplay(m_display));
|
|
LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d", m_x, m_y, m_w, m_h));
|
|
|
|
// get center of default screen
|
|
m_xCenter = m_x + (m_w >> 1);
|
|
m_yCenter = m_y + (m_h >> 1);
|
|
|
|
// check if xinerama is enabled and there is more than one screen.
|
|
// get center of first Xinerama screen. Xinerama appears to have
|
|
// a bug when XWarpPointer() is used in combination with
|
|
// XGrabPointer(). in that case, the warp is successful but the
|
|
// next pointer motion warps the pointer again, apparently to
|
|
// constrain it to some unknown region, possibly the region from
|
|
// 0,0 to Wm,Hm where Wm (Hm) is the minimum width (height) over
|
|
// all physical screens. this warp only seems to happen if the
|
|
// pointer wasn't in that region before the XWarpPointer(). the
|
|
// second (unexpected) warp causes synergy to think the pointer
|
|
// has been moved when it hasn't. to work around the problem,
|
|
// we warp the pointer to the center of the first physical
|
|
// screen instead of the logical screen.
|
|
m_xinerama = false;
|
|
#if HAVE_X11_EXTENSIONS_XINERAMA_H
|
|
int eventBase, errorBase;
|
|
if (XineramaQueryExtension(m_display, &eventBase, &errorBase) &&
|
|
XineramaIsActive(m_display)) {
|
|
int numScreens;
|
|
XineramaScreenInfo* screens;
|
|
screens = XineramaQueryScreens(m_display, &numScreens);
|
|
if (screens != NULL) {
|
|
if (numScreens > 1) {
|
|
m_xinerama = true;
|
|
m_xCenter = screens[0].x_org + (screens[0].width >> 1);
|
|
m_yCenter = screens[0].y_org + (screens[0].height >> 1);
|
|
}
|
|
XFree(screens);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// create the window
|
|
m_window = createWindow();
|
|
if (m_window == None) {
|
|
throw XScreenOpenFailure();
|
|
}
|
|
LOG((CLOG_DEBUG "window is 0x%08x", m_window));
|
|
|
|
if (m_isPrimary) {
|
|
// start watching for events on other windows
|
|
selectEvents(m_root);
|
|
|
|
// prepare to use input methods
|
|
openIM();
|
|
}
|
|
|
|
// initialize the clipboards
|
|
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
|
|
m_clipboard[id] = new CXWindowsClipboard(m_display, m_window, id);
|
|
}
|
|
|
|
// initialize the screen saver
|
|
m_atomScreensaver = XInternAtom(m_display,
|
|
"SYNERGY_SCREENSAVER", False);
|
|
m_screensaver = new CXWindowsScreenSaver(this, m_display);
|
|
}
|
|
catch (...) {
|
|
close();
|
|
throw;
|
|
}
|
|
|
|
// save the IKeyState
|
|
m_keyState = keyState;
|
|
|
|
// we'll send ourself an event of this type to exit the main loop
|
|
m_atomQuit = XInternAtom(m_display, "SYNERGY_QUIT", False);
|
|
|
|
if (!m_isPrimary) {
|
|
// become impervious to server grabs
|
|
XTestGrabControl(m_display, True);
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::close()
|
|
{
|
|
// done with m_keyState
|
|
m_keyState = NULL;
|
|
|
|
// done with screen saver
|
|
delete m_screensaver;
|
|
|
|
// destroy clipboards
|
|
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
|
|
delete m_clipboard[id];
|
|
m_clipboard[id] = NULL;
|
|
}
|
|
|
|
// done with input methods
|
|
if (m_ic != NULL) {
|
|
XDestroyIC(m_ic);
|
|
}
|
|
if (m_im != NULL) {
|
|
XCloseIM(m_im);
|
|
}
|
|
|
|
// done with window
|
|
if (m_window != None) {
|
|
XDestroyWindow(m_display, m_window);
|
|
}
|
|
|
|
// close the display
|
|
if (m_display != NULL) {
|
|
XCloseDisplay(m_display);
|
|
}
|
|
|
|
// restore error handler
|
|
XSetIOErrorHandler(NULL);
|
|
|
|
// reset state
|
|
m_atomQuit = None;
|
|
m_screensaver = NULL;
|
|
m_atomScreensaver = None;
|
|
m_ic = NULL;
|
|
m_im = NULL;
|
|
m_window = None;
|
|
m_root = None;
|
|
m_display = NULL;
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::enable()
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
if (!m_isPrimary) {
|
|
// get the keyboard control state
|
|
XKeyboardState keyControl;
|
|
XGetKeyboardControl(m_display, &keyControl);
|
|
m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn);
|
|
|
|
// move hider window under the cursor center
|
|
XMoveWindow(m_display, m_window, m_xCenter, m_yCenter);
|
|
|
|
// raise and show the window
|
|
// FIXME -- take focus?
|
|
XMapRaised(m_display, m_window);
|
|
|
|
// warp the mouse to the cursor center
|
|
fakeMouseMove(m_xCenter, m_yCenter);
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::disable()
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
// release input context focus
|
|
if (m_ic != NULL) {
|
|
XUnsetICFocus(m_ic);
|
|
}
|
|
|
|
// unmap the hider/grab window. this also ungrabs the mouse and
|
|
// keyboard if they're grabbed.
|
|
XUnmapWindow(m_display, m_window);
|
|
|
|
// restore auto-repeat state
|
|
if (!m_isPrimary && m_autoRepeat) {
|
|
XAutoRepeatOn(m_display);
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::mainLoop()
|
|
{
|
|
// wait for an event in a cancellable way and don't lock the
|
|
// display while we're waiting. we use CLock to ensure that
|
|
// we unlock on exit but we directly unlock/lock the mutex
|
|
// for certain sections when we mustn't hold the lock. it's
|
|
// very important that these sections not return (even by
|
|
// exception or cancellation) without first reestablishing
|
|
// the lock.
|
|
XEvent event;
|
|
CLock lock(&m_mutex);
|
|
|
|
for (;;) {
|
|
|
|
#if UNIX_LIKE
|
|
|
|
// compute timeout to next timer
|
|
double dtimeout;
|
|
{
|
|
CLock timersLock(&m_timersMutex);
|
|
dtimeout = (m_timers.empty() ? -1.0 : m_timers.top());
|
|
if (m_oneShotTimer != NULL &&
|
|
(dtimeout == -1.0 || *m_oneShotTimer < dtimeout)) {
|
|
dtimeout = *m_oneShotTimer;
|
|
}
|
|
}
|
|
|
|
// use poll() to wait for a message from the X server or for timeout.
|
|
// this is a good deal more efficient than polling and sleeping.
|
|
#if HAVE_POLL
|
|
struct pollfd pfds[1];
|
|
pfds[0].fd = ConnectionNumber(m_display);
|
|
pfds[0].events = POLLIN;
|
|
int timeout = static_cast<int>(1000.0 * dtimeout);
|
|
#else
|
|
struct timeval timeout;
|
|
struct timeval* timeoutPtr;
|
|
if (dtimeout < 0.0) {
|
|
timeoutPtr = NULL;
|
|
}
|
|
else {
|
|
timeout.tv_sec = static_cast<int>(dtimeout);
|
|
timeout.tv_usec = static_cast<int>(1.0e+6 *
|
|
(dtimeout - timeout.tv_sec));
|
|
timeoutPtr = &timeout;
|
|
}
|
|
|
|
// initialize file descriptor sets
|
|
fd_set rfds;
|
|
FD_ZERO(&rfds);
|
|
FD_SET(ConnectionNumber(m_display), &rfds);
|
|
#endif
|
|
|
|
// wait for message from X server or for timeout. also check
|
|
// if the thread has been cancelled. poll() should return -1
|
|
// with EINTR when the thread is cancelled.
|
|
CThread::testCancel();
|
|
m_mutex.unlock();
|
|
#if HAVE_POLL
|
|
poll(pfds, 1, timeout);
|
|
#else
|
|
select(ConnectionNumber(m_display) + 1,
|
|
SELECT_TYPE_ARG234 &rfds,
|
|
SELECT_TYPE_ARG234 NULL,
|
|
SELECT_TYPE_ARG234 NULL,
|
|
SELECT_TYPE_ARG5 timeoutPtr);
|
|
#endif
|
|
m_mutex.lock();
|
|
CThread::testCancel();
|
|
|
|
// process timers
|
|
processTimers();
|
|
|
|
#else // !UNIX_LIKE
|
|
|
|
// poll for pending events and process timers
|
|
while (XPending(m_display) == 0) {
|
|
// check timers
|
|
if (processTimers()) {
|
|
continue;
|
|
}
|
|
|
|
// wait
|
|
try {
|
|
m_mutex.unlock();
|
|
CThread::sleep(0.01);
|
|
m_mutex.lock();
|
|
}
|
|
catch (...) {
|
|
m_mutex.lock();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
#endif // !UNIX_LIKE
|
|
|
|
// process events
|
|
while (XPending(m_display) > 0) {
|
|
// get the event
|
|
XNextEvent(m_display, &event);
|
|
if (isQuitEvent(&event)) {
|
|
return;
|
|
}
|
|
|
|
// process the event
|
|
onEvent(&event);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::exitMainLoop()
|
|
{
|
|
// send ourself a quit event. this will wake up the event loop
|
|
// and cause it to exit.
|
|
if (m_atomQuit != None) {
|
|
XEvent event;
|
|
event.xclient.type = ClientMessage;
|
|
event.xclient.display = m_display;
|
|
event.xclient.window = m_window;
|
|
event.xclient.message_type = m_atomQuit;
|
|
event.xclient.format = 32;
|
|
event.xclient.data.l[0] = 0;
|
|
event.xclient.data.l[1] = 0;
|
|
event.xclient.data.l[2] = 0;
|
|
event.xclient.data.l[3] = 0;
|
|
event.xclient.data.l[4] = 0;
|
|
CLock lock(&m_mutex);
|
|
CXWindowsUtil::CErrorLock errorLock(m_display);
|
|
XSendEvent(m_display, m_window, False, 0, &event);
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::enter()
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
// release input context focus
|
|
if (m_ic != NULL) {
|
|
XUnsetICFocus(m_ic);
|
|
}
|
|
|
|
// unmap the hider/grab window. this also ungrabs the mouse and
|
|
// keyboard if they're grabbed.
|
|
XUnmapWindow(m_display, m_window);
|
|
|
|
/* maybe call this if entering for the screensaver
|
|
// set keyboard focus to root window. the screensaver should then
|
|
// pick up key events for when the user enters a password to unlock.
|
|
XSetInputFocus(m_display, PointerRoot, PointerRoot, CurrentTime);
|
|
*/
|
|
|
|
if (!m_isPrimary) {
|
|
// get the keyboard control state
|
|
XKeyboardState keyControl;
|
|
XGetKeyboardControl(m_display, &keyControl);
|
|
m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn);
|
|
|
|
// turn off auto-repeat. we do this so fake key press events don't
|
|
// cause the local server to generate their own auto-repeats of
|
|
// those keys.
|
|
XAutoRepeatOff(m_display);
|
|
}
|
|
|
|
// now on screen
|
|
m_isOnScreen = true;
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreen::leave()
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
if (!m_isPrimary) {
|
|
// restore the previous keyboard auto-repeat state. if the user
|
|
// changed the auto-repeat configuration while on the client then
|
|
// that state is lost. that's because we can't get notified by
|
|
// the X server when the auto-repeat configuration is changed so
|
|
// we can't track the desired configuration.
|
|
if (m_autoRepeat) {
|
|
XAutoRepeatOn(m_display);
|
|
}
|
|
|
|
// move hider window under the cursor center
|
|
XMoveWindow(m_display, m_window, m_xCenter, m_yCenter);
|
|
}
|
|
|
|
// raise and show the window
|
|
// FIXME -- take focus?
|
|
XMapRaised(m_display, m_window);
|
|
|
|
// grab the mouse and keyboard, if primary and possible
|
|
if (m_isPrimary && !grabMouseAndKeyboard()) {
|
|
XUnmapWindow(m_display, m_window);
|
|
return false;
|
|
}
|
|
|
|
// now warp the mouse. we warp after showing the window so we're
|
|
// guaranteed to get the mouse leave event and to prevent the
|
|
// keyboard focus from changing under point-to-focus policies.
|
|
if (m_isPrimary) {
|
|
warpCursor(m_xCenter, m_yCenter);
|
|
}
|
|
else {
|
|
fakeMouseMove(m_xCenter, m_yCenter);
|
|
}
|
|
|
|
// set input context focus to our window
|
|
if (m_ic != NULL) {
|
|
XmbResetIC(m_ic);
|
|
XSetICFocus(m_ic);
|
|
}
|
|
|
|
// now off screen
|
|
m_isOnScreen = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreen::setClipboard(ClipboardID id, const IClipboard* clipboard)
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
// fail if we don't have the requested clipboard
|
|
if (m_clipboard[id] == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// get the actual time. ICCCM does not allow CurrentTime.
|
|
Time timestamp = CXWindowsUtil::getCurrentTime(
|
|
m_display, m_clipboard[id]->getWindow());
|
|
|
|
if (clipboard != NULL) {
|
|
// save clipboard data
|
|
return CClipboard::copy(m_clipboard[id], clipboard, timestamp);
|
|
}
|
|
else {
|
|
// assert clipboard ownership
|
|
if (!m_clipboard[id]->open(timestamp)) {
|
|
return false;
|
|
}
|
|
m_clipboard[id]->empty();
|
|
m_clipboard[id]->close();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::checkClipboards()
|
|
{
|
|
// do nothing, we're always up to date
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::openScreensaver(bool notify)
|
|
{
|
|
CLock lock(&m_mutex);
|
|
assert(m_screensaver != NULL);
|
|
|
|
m_screensaverNotify = notify;
|
|
if (m_screensaverNotify) {
|
|
m_screensaver->setNotify(m_window);
|
|
}
|
|
else {
|
|
m_screensaver->disable();
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::closeScreensaver()
|
|
{
|
|
CLock lock(&m_mutex);
|
|
if (m_screensaver != NULL) {
|
|
if (m_screensaverNotify) {
|
|
m_screensaver->setNotify(None);
|
|
}
|
|
else {
|
|
m_screensaver->enable();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::screensaver(bool activate)
|
|
{
|
|
CLock lock(&m_mutex);
|
|
assert(m_screensaver != NULL);
|
|
|
|
if (activate) {
|
|
m_screensaver->activate();
|
|
}
|
|
else {
|
|
m_screensaver->deactivate();
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::resetOptions()
|
|
{
|
|
m_xtestIsXineramaUnaware = true;
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::setOptions(const COptionsList& options)
|
|
{
|
|
for (UInt32 i = 0, n = options.size(); i < n; i += 2) {
|
|
if (options[i] == kOptionXTestXineramaUnaware) {
|
|
m_xtestIsXineramaUnaware = (options[i + 1] != 0);
|
|
LOG((CLOG_DEBUG1 "XTest is Xinerama unaware %s", m_xtestIsXineramaUnaware ? "true" : "false"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::updateKeys()
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
// update keyboard and mouse button mappings
|
|
m_keyMapper.update(m_display, m_keyState);
|
|
updateButtons();
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreen::isPrimary() const
|
|
{
|
|
return m_isPrimary;
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const
|
|
{
|
|
assert(clipboard != NULL);
|
|
|
|
// block others from using the display while we get the clipboard
|
|
CLock lock(&m_mutex);
|
|
|
|
// fail if we don't have the requested clipboard
|
|
if (m_clipboard[id] == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// get the actual time. ICCCM does not allow CurrentTime.
|
|
Time timestamp = CXWindowsUtil::getCurrentTime(
|
|
m_display, m_clipboard[id]->getWindow());
|
|
|
|
// copy the clipboard
|
|
return CClipboard::copy(clipboard, m_clipboard[id], timestamp);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
|
|
{
|
|
x = m_x;
|
|
y = m_y;
|
|
w = m_w;
|
|
h = m_h;
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const
|
|
{
|
|
CLock lock(&m_mutex);
|
|
assert(m_display != NULL);
|
|
|
|
Window root, window;
|
|
int mx, my, xWindow, yWindow;
|
|
unsigned int mask;
|
|
if (XQueryPointer(m_display, m_root, &root, &window,
|
|
&mx, &my, &xWindow, &yWindow, &mask)) {
|
|
x = mx;
|
|
y = my;
|
|
}
|
|
else {
|
|
x = m_xCenter;
|
|
y = m_yCenter;
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::reconfigure(UInt32)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::warpCursor(SInt32 x, SInt32 y)
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
// warp mouse
|
|
warpCursorNoFlush(x, y);
|
|
|
|
// remove all input events before and including warp
|
|
XEvent event;
|
|
while (XCheckMaskEvent(m_display, PointerMotionMask |
|
|
ButtonPressMask | ButtonReleaseMask |
|
|
KeyPressMask | KeyReleaseMask |
|
|
KeymapStateMask,
|
|
&event)) {
|
|
// do nothing
|
|
}
|
|
|
|
// save position as last position
|
|
m_xCursor = x;
|
|
m_yCursor = y;
|
|
}
|
|
|
|
UInt32
|
|
CXWindowsScreen::addOneShotTimer(double timeout)
|
|
{
|
|
CLock lock(&m_timersMutex);
|
|
// FIXME -- support multiple one-shot timers
|
|
m_oneShotTimer = new CTimer(NULL, m_time.getTime(), timeout);
|
|
return 0;
|
|
}
|
|
|
|
SInt32
|
|
CXWindowsScreen::getJumpZoneSize() const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreen::isAnyMouseButtonDown() const
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
// query the pointer to get the button state
|
|
Window root, window;
|
|
int xRoot, yRoot, xWindow, yWindow;
|
|
unsigned int state;
|
|
if (XQueryPointer(m_display, m_root, &root, &window,
|
|
&xRoot, &yRoot, &xWindow, &yWindow, &state)) {
|
|
return ((state & (Button1Mask | Button2Mask | Button3Mask |
|
|
Button4Mask | Button5Mask)) != 0);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const char*
|
|
CXWindowsScreen::getKeyName(KeyButton keycode) const
|
|
{
|
|
CLock lock(&m_mutex);
|
|
|
|
KeySym keysym = XKeycodeToKeysym(m_display, keycode, 0);
|
|
char* name = XKeysymToString(keysym);
|
|
if (name != NULL) {
|
|
return name;
|
|
}
|
|
else {
|
|
static char buffer[20];
|
|
return strcpy(buffer,
|
|
CStringUtil::print("keycode %d", keycode).c_str());
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::fakeKeyEvent(KeyButton keycode, bool press) const
|
|
{
|
|
CLock lock(&m_mutex);
|
|
XTestFakeKeyEvent(m_display, keycode, press ? True : False, CurrentTime);
|
|
XFlush(m_display);
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreen::fakeCtrlAltDel() const
|
|
{
|
|
// pass keys through unchanged
|
|
return false;
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::fakeMouseButton(ButtonID button, bool press) const
|
|
{
|
|
const unsigned int xButton = mapButtonToX(button);
|
|
if (xButton != 0) {
|
|
CLock lock(&m_mutex);
|
|
XTestFakeButtonEvent(m_display, xButton,
|
|
press ? True : False, CurrentTime);
|
|
XFlush(m_display);
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) const
|
|
{
|
|
CLock lock(&m_mutex);
|
|
if (m_xinerama && m_xtestIsXineramaUnaware) {
|
|
XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y);
|
|
}
|
|
else {
|
|
XTestFakeMotionEvent(m_display, DefaultScreen(m_display),
|
|
x, y, CurrentTime);
|
|
}
|
|
XFlush(m_display);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::fakeMouseWheel(SInt32 delta) const
|
|
{
|
|
// choose button depending on rotation direction
|
|
const unsigned int xButton = mapButtonToX(static_cast<ButtonID>(
|
|
(delta >= 0) ? -1 : -2));
|
|
if (xButton == 0) {
|
|
return;
|
|
}
|
|
|
|
// now use absolute value of delta
|
|
if (delta < 0) {
|
|
delta = -delta;
|
|
}
|
|
|
|
// send as many clicks as necessary
|
|
CLock lock(&m_mutex);
|
|
for (; delta >= 120; delta -= 120) {
|
|
XTestFakeButtonEvent(m_display, xButton, True, CurrentTime);
|
|
XTestFakeButtonEvent(m_display, xButton, False, CurrentTime);
|
|
}
|
|
XFlush(m_display);
|
|
}
|
|
|
|
KeyButton
|
|
CXWindowsScreen::mapKey(IKeyState::Keystrokes& keys,
|
|
const IKeyState& keyState, KeyID id,
|
|
KeyModifierMask desiredMask,
|
|
bool isAutoRepeat) const
|
|
{
|
|
return m_keyMapper.mapKey(keys, keyState, id, desiredMask, isAutoRepeat);
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreen::isQuitEvent(XEvent* event) const
|
|
{
|
|
return (m_atomQuit != None &&
|
|
event->type == ClientMessage &&
|
|
event->xclient.window == m_window &&
|
|
event->xclient.message_type == m_atomQuit);
|
|
}
|
|
|
|
Window
|
|
CXWindowsScreen::createWindow() const
|
|
{
|
|
// default window attributes. we don't want the window manager
|
|
// messing with our window and we don't want the cursor to be
|
|
// visible inside the window.
|
|
XSetWindowAttributes attr;
|
|
attr.do_not_propagate_mask = 0;
|
|
attr.override_redirect = True;
|
|
attr.cursor = createBlankCursor();
|
|
|
|
// adjust attributes and get size and shape
|
|
SInt32 x, y, w, h;
|
|
if (m_isPrimary) {
|
|
// grab window attributes. this window is used to capture user
|
|
// input when the user is focused on another client. it covers
|
|
// the whole screen.
|
|
attr.event_mask = PointerMotionMask |
|
|
ButtonPressMask | ButtonReleaseMask |
|
|
KeyPressMask | KeyReleaseMask |
|
|
KeymapStateMask | PropertyChangeMask;
|
|
x = m_x;
|
|
y = m_y;
|
|
w = m_w;
|
|
h = m_h;
|
|
}
|
|
else {
|
|
// 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 mouse is
|
|
// moved. we'll reposition the window as necessary so its
|
|
// position here doesn't matter. it only needs to be 1x1 because
|
|
// it only needs to contain the cursor's hotspot.
|
|
attr.event_mask = LeaveWindowMask;
|
|
x = 0;
|
|
y = 0;
|
|
w = 1;
|
|
h = 1;
|
|
}
|
|
|
|
// create and return the window
|
|
return XCreateWindow(m_display, m_root, x, y, w, h, 0, 0,
|
|
InputOnly, CopyFromParent,
|
|
CWDontPropagate | CWEventMask |
|
|
CWOverrideRedirect | CWCursor,
|
|
&attr);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::openIM()
|
|
{
|
|
// open the input methods
|
|
XIM im = XOpenIM(m_display, NULL, NULL, NULL);
|
|
if (im == NULL) {
|
|
return;
|
|
}
|
|
|
|
// find the appropriate style. synergy supports XIMPreeditNothing
|
|
// only at the moment.
|
|
XIMStyles* styles;
|
|
if (XGetIMValues(im, XNQueryInputStyle, &styles, NULL) != NULL ||
|
|
styles == NULL) {
|
|
LOG((CLOG_WARN "cannot get IM styles"));
|
|
XCloseIM(im);
|
|
return;
|
|
}
|
|
XIMStyle style = 0;
|
|
for (unsigned short i = 0; i < styles->count_styles; ++i) {
|
|
style = styles->supported_styles[i];
|
|
if ((style & XIMPreeditNothing) != 0) {
|
|
if ((style & (XIMStatusNothing | XIMStatusNone)) != 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
XFree(styles);
|
|
if (style == 0) {
|
|
LOG((CLOG_WARN "no supported IM styles"));
|
|
XCloseIM(im);
|
|
return;
|
|
}
|
|
|
|
// create an input context for the style and tell it about our window
|
|
XIC ic = XCreateIC(im, XNInputStyle, style, XNClientWindow, m_window, NULL);
|
|
if (ic == NULL) {
|
|
LOG((CLOG_WARN "cannot create IC"));
|
|
XCloseIM(im);
|
|
return;
|
|
}
|
|
|
|
// find out the events we must select for and do so
|
|
unsigned long mask;
|
|
if (XGetICValues(ic, XNFilterEvents, &mask, NULL) != NULL) {
|
|
LOG((CLOG_WARN "cannot get IC filter events"));
|
|
XDestroyIC(ic);
|
|
XCloseIM(im);
|
|
return;
|
|
}
|
|
|
|
// we have IM
|
|
m_im = im;
|
|
m_ic = ic;
|
|
m_lastKeycode = 0;
|
|
|
|
// select events on our window that IM requires
|
|
XWindowAttributes attr;
|
|
XGetWindowAttributes(m_display, m_window, &attr);
|
|
XSelectInput(m_display, m_window, attr.your_event_mask | mask);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::addTimer(IJob* job, double timeout)
|
|
{
|
|
CLock lock(&m_timersMutex);
|
|
removeTimerNoLock(job);
|
|
m_timers.push(CTimer(job, m_time.getTime(), timeout));
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::removeTimer(IJob* job)
|
|
{
|
|
CLock lock(&m_timersMutex);
|
|
removeTimerNoLock(job);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::removeTimerNoLock(IJob* job)
|
|
{
|
|
// do it the hard way. first collect all jobs that are not
|
|
// the removed job.
|
|
CTimerPriorityQueue::container_type tmp;
|
|
for (CTimerPriorityQueue::iterator index = m_timers.begin();
|
|
index != m_timers.end(); ++index) {
|
|
if (index->getJob() != job) {
|
|
tmp.push_back(*index);
|
|
}
|
|
}
|
|
|
|
// now swap in the new list
|
|
m_timers.swap(tmp);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::onEvent(XEvent* xevent)
|
|
{
|
|
assert(xevent != NULL);
|
|
|
|
// let input methods try to handle event first
|
|
if (m_ic != NULL) {
|
|
// XFilterEvent() may eat the event and generate a new KeyPress
|
|
// event with a keycode of 0 because there isn't an actual key
|
|
// associated with the keysym. but the KeyRelease may pass
|
|
// through XFilterEvent() and keep its keycode. this means
|
|
// there's a mismatch between KeyPress and KeyRelease keycodes.
|
|
// since we use the keycode on the client to detect when a key
|
|
// is released this won't do. so we remember the keycode on
|
|
// the most recent KeyPress (and clear it on a matching
|
|
// KeyRelease) so we have a keycode for a synthesized KeyPress.
|
|
if (xevent->type == KeyPress && xevent->xkey.keycode != 0) {
|
|
m_lastKeycode = xevent->xkey.keycode;
|
|
}
|
|
else if (xevent->type == KeyRelease &&
|
|
xevent->xkey.keycode == m_lastKeycode) {
|
|
m_lastKeycode = 0;
|
|
}
|
|
|
|
// now filter the event
|
|
if (XFilterEvent(xevent, None)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (xevent->type) {
|
|
case CreateNotify:
|
|
if (m_isPrimary) {
|
|
// select events on new window
|
|
selectEvents(xevent->xcreatewindow.window);
|
|
}
|
|
return;
|
|
|
|
case MappingNotify:
|
|
if (XPending(m_display) > 0) {
|
|
XEvent tmpEvent;
|
|
XPeekEvent(m_display, &tmpEvent);
|
|
if (tmpEvent.type == MappingNotify) {
|
|
// discard this MappingNotify since another follows.
|
|
// we tend to get a bunch of these in a row.
|
|
return;
|
|
}
|
|
}
|
|
|
|
// keyboard mapping changed
|
|
XRefreshKeyboardMapping(&xevent->xmapping);
|
|
m_keyState->updateKeys();
|
|
break;
|
|
|
|
case LeaveNotify:
|
|
if (!m_isPrimary) {
|
|
// mouse moved out of hider window somehow. hide the window.
|
|
XUnmapWindow(m_display, m_window);
|
|
}
|
|
break;
|
|
|
|
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 receiver.
|
|
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);
|
|
m_receiver->onGrabClipboard(id);
|
|
return;
|
|
}
|
|
}
|
|
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) {
|
|
XDeleteProperty(m_display,
|
|
xevent->xselection.requestor,
|
|
xevent->xselection.property);
|
|
}
|
|
return;
|
|
|
|
case SelectionRequest:
|
|
{
|
|
// somebody is asking for clipboard data
|
|
ClipboardID id = getClipboardID(
|
|
xevent->xselectionrequest.selection);
|
|
if (id != kClipboardEnd) {
|
|
m_clipboard[id]->addRequest(
|
|
xevent->xselectionrequest.owner,
|
|
xevent->xselectionrequest.requestor,
|
|
xevent->xselectionrequest.target,
|
|
xevent->xselectionrequest.time,
|
|
xevent->xselectionrequest.property);
|
|
return;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
break;
|
|
|
|
case ClientMessage:
|
|
if (m_isPrimary &&
|
|
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;
|
|
}
|
|
break;
|
|
|
|
case DestroyNotify:
|
|
// looks like one of the windows that requested a clipboard
|
|
// transfer has gone bye-bye.
|
|
destroyClipboardRequest(xevent->xdestroywindow.window);
|
|
break;
|
|
|
|
case KeyPress:
|
|
if (m_isPrimary) {
|
|
onKeyPress(xevent->xkey);
|
|
}
|
|
return;
|
|
|
|
case KeyRelease:
|
|
if (m_isPrimary) {
|
|
onKeyRelease(xevent->xkey);
|
|
}
|
|
return;
|
|
|
|
case ButtonPress:
|
|
if (m_isPrimary) {
|
|
onMousePress(xevent->xbutton);
|
|
}
|
|
return;
|
|
|
|
case ButtonRelease:
|
|
if (m_isPrimary) {
|
|
onMouseRelease(xevent->xbutton);
|
|
}
|
|
return;
|
|
|
|
case MotionNotify:
|
|
if (m_isPrimary) {
|
|
onMouseMove(xevent->xmotion);
|
|
}
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// let screen saver have a go
|
|
m_screensaver->onPreDispatch(xevent);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::onKeyPress(XKeyEvent& xkey)
|
|
{
|
|
LOG((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xkey.keycode, xkey.state));
|
|
const KeyModifierMask mask = m_keyMapper.mapModifier(xkey.state);
|
|
KeyID key = mapKeyFromX(&xkey);
|
|
if (key != kKeyNone) {
|
|
// check for ctrl+alt+del emulation
|
|
if ((key == kKeyPause || key == kKeyBreak) &&
|
|
(mask & (KeyModifierControl | KeyModifierAlt)) ==
|
|
(KeyModifierControl | KeyModifierAlt)) {
|
|
// pretend it's ctrl+alt+del
|
|
LOG((CLOG_DEBUG "emulate ctrl+alt+del"));
|
|
key = kKeyDelete;
|
|
}
|
|
|
|
// get which button. see call to XFilterEvent() in onEvent()
|
|
// for more info.
|
|
KeyButton keycode = static_cast<KeyButton>(xkey.keycode);
|
|
if (keycode == 0) {
|
|
keycode = static_cast<KeyButton>(m_lastKeycode);
|
|
}
|
|
|
|
// handle key
|
|
m_primaryReceiver->onKeyDown(key, mask, keycode);
|
|
KeyModifierMask keyMask = m_keyState->getMaskForKey(keycode);
|
|
if (m_keyState->isHalfDuplex(keyMask)) {
|
|
m_primaryReceiver->onKeyUp(key, mask | keyMask, keycode);
|
|
}
|
|
}
|
|
}
|
|
|
|
Bool
|
|
CXWindowsScreen::findKeyEvent(Display*, XEvent* xevent, XPointer arg)
|
|
{
|
|
CKeyEventInfo* filter = reinterpret_cast<CKeyEventInfo*>(arg);
|
|
return (xevent->type == filter->m_event &&
|
|
xevent->xkey.window == filter->m_window &&
|
|
xevent->xkey.time == filter->m_time &&
|
|
xevent->xkey.keycode == filter->m_keycode) ? True : False;
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::onKeyRelease(XKeyEvent& xkey)
|
|
{
|
|
const KeyModifierMask mask = m_keyMapper.mapModifier(xkey.state);
|
|
KeyID key = mapKeyFromX(&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 = xkey.window;
|
|
filter.m_time = xkey.time;
|
|
filter.m_keycode = xkey.keycode;
|
|
|
|
// now check for event
|
|
bool hasPress;
|
|
{
|
|
XEvent xevent2;
|
|
hasPress = (XCheckIfEvent(m_display, &xevent2,
|
|
&CXWindowsScreen::findKeyEvent,
|
|
(XPointer)&filter) == True);
|
|
}
|
|
|
|
// check for ctrl+alt+del emulation
|
|
if ((key == kKeyPause || key == kKeyBreak) &&
|
|
(mask & (KeyModifierControl | KeyModifierAlt)) ==
|
|
(KeyModifierControl | KeyModifierAlt)) {
|
|
// pretend it's ctrl+alt+del and ignore autorepeat
|
|
LOG((CLOG_DEBUG "emulate ctrl+alt+del"));
|
|
key = kKeyDelete;
|
|
hasPress = false;
|
|
}
|
|
|
|
KeyButton keycode = static_cast<KeyButton>(xkey.keycode);
|
|
if (!hasPress) {
|
|
// no press event follows so it's a plain release
|
|
LOG((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", keycode, xkey.state));
|
|
KeyModifierMask keyMask = m_keyState->getMaskForKey(keycode);
|
|
if (m_keyState->isHalfDuplex(keyMask)) {
|
|
m_primaryReceiver->onKeyDown(key, mask, keycode);
|
|
}
|
|
m_primaryReceiver->onKeyUp(key, mask, keycode);
|
|
}
|
|
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", keycode, xkey.state));
|
|
m_primaryReceiver->onKeyRepeat(key, mask, 1, keycode);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::onMousePress(const XButtonEvent& xbutton)
|
|
{
|
|
LOG((CLOG_DEBUG1 "event: ButtonPress button=%d", xbutton.button));
|
|
const ButtonID button = mapButtonFromX(&xbutton);
|
|
if (button != kButtonNone) {
|
|
m_primaryReceiver->onMouseDown(button);
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::onMouseRelease(const XButtonEvent& xbutton)
|
|
{
|
|
LOG((CLOG_DEBUG1 "event: ButtonRelease button=%d", xbutton.button));
|
|
const ButtonID button = mapButtonFromX(&xbutton);
|
|
if (button != kButtonNone) {
|
|
m_primaryReceiver->onMouseUp(button);
|
|
}
|
|
else if (xbutton.button == 4) {
|
|
// wheel forward (away from user)
|
|
m_primaryReceiver->onMouseWheel(120);
|
|
}
|
|
else if (xbutton.button == 5) {
|
|
// wheel backward (toward user)
|
|
m_primaryReceiver->onMouseWheel(-120);
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::onMouseMove(const XMotionEvent& xmotion)
|
|
{
|
|
LOG((CLOG_DEBUG2 "event: MotionNotify %d,%d", xmotion.x_root, xmotion.y_root));
|
|
|
|
// compute motion delta (relative to the last known
|
|
// mouse position)
|
|
SInt32 x = xmotion.x_root - m_xCursor;
|
|
SInt32 y = xmotion.y_root - m_yCursor;
|
|
|
|
// save position to compute delta of next motion
|
|
m_xCursor = xmotion.x_root;
|
|
m_yCursor = xmotion.y_root;
|
|
|
|
if (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.
|
|
XEvent xevent;
|
|
do {
|
|
XMaskEvent(m_display, PointerMotionMask, &xevent);
|
|
} while (!xevent.xany.send_event);
|
|
}
|
|
else if (m_isOnScreen) {
|
|
// motion on primary screen
|
|
m_primaryReceiver->onMouseMovePrimary(m_xCursor, m_yCursor);
|
|
}
|
|
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 (xmotion.x_root - m_xCenter < -s_size ||
|
|
xmotion.x_root - m_xCenter > s_size ||
|
|
xmotion.y_root - m_yCenter < -s_size ||
|
|
xmotion.y_root - m_yCenter > s_size) {
|
|
warpCursorNoFlush(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);
|
|
}
|
|
}
|
|
}
|
|
|
|
Cursor
|
|
CXWindowsScreen::createBlankCursor() const
|
|
{
|
|
// this seems just a bit more complicated than really necessary
|
|
|
|
// get the closet cursor size to 1x1
|
|
unsigned int w, h;
|
|
XQueryBestCursor(m_display, m_root, 1, 1, &w, &h);
|
|
|
|
// make bitmap data for cursor of closet size. since the cursor
|
|
// is blank we can use the same bitmap for shape and mask: all
|
|
// zeros.
|
|
const int size = ((w + 7) >> 3) * h;
|
|
char* data = new char[size];
|
|
memset(data, 0, size);
|
|
|
|
// make bitmap
|
|
Pixmap bitmap = XCreateBitmapFromData(m_display, m_root, data, w, h);
|
|
|
|
// need an arbitrary color for the cursor
|
|
XColor color;
|
|
color.pixel = 0;
|
|
color.red = color.green = color.blue = 0;
|
|
color.flags = DoRed | DoGreen | DoBlue;
|
|
|
|
// make cursor from bitmap
|
|
Cursor cursor = XCreatePixmapCursor(m_display, bitmap, bitmap,
|
|
&color, &color, 0, 0);
|
|
|
|
// don't need bitmap or the data anymore
|
|
delete[] data;
|
|
XFreePixmap(m_display, bitmap);
|
|
|
|
return cursor;
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreen::processTimers()
|
|
{
|
|
bool oneShot = false;
|
|
std::vector<IJob*> jobs;
|
|
{
|
|
CLock lock(&m_timersMutex);
|
|
|
|
// get current time
|
|
const double time = m_time.getTime();
|
|
|
|
// done if no timers have expired
|
|
if ((m_oneShotTimer == NULL || *m_oneShotTimer > time) &&
|
|
(m_timers.empty() || m_timers.top() > time)) {
|
|
return false;
|
|
}
|
|
|
|
// handle one shot timers
|
|
if (m_oneShotTimer != NULL) {
|
|
*m_oneShotTimer -= time;
|
|
if (*m_oneShotTimer <= 0.0) {
|
|
delete m_oneShotTimer;
|
|
m_oneShotTimer = NULL;
|
|
oneShot = true;
|
|
}
|
|
}
|
|
|
|
// subtract current time from all timers. note that this won't
|
|
// change the order of elements in the priority queue (except
|
|
// for floating point round off which we'll ignore).
|
|
for (CTimerPriorityQueue::iterator index = m_timers.begin();
|
|
index != m_timers.end(); ++index) {
|
|
(*index) -= time;
|
|
}
|
|
|
|
// process all timers at or below zero, saving the jobs
|
|
if (!m_timers.empty()) {
|
|
while (m_timers.top() <= 0.0) {
|
|
CTimer timer = m_timers.top();
|
|
jobs.push_back(timer.getJob());
|
|
timer.reset();
|
|
m_timers.pop();
|
|
m_timers.push(timer);
|
|
}
|
|
}
|
|
|
|
// reset the clock
|
|
m_time.reset();
|
|
}
|
|
|
|
// now notify of the one shot timers
|
|
if (oneShot) {
|
|
m_mutex.unlock();
|
|
m_primaryReceiver->onOneShotTimerExpired(0);
|
|
m_mutex.lock();
|
|
}
|
|
|
|
// now run the jobs. note that if one of these jobs removes
|
|
// a timer later in the jobs list and deletes that job pointer
|
|
// then this will crash when it tries to run that job.
|
|
for (std::vector<IJob*>::iterator index = jobs.begin();
|
|
index != jobs.end(); ++index) {
|
|
(*index)->run();
|
|
}
|
|
}
|
|
|
|
ClipboardID
|
|
CXWindowsScreen::getClipboardID(Atom selection) const
|
|
{
|
|
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
|
|
if (m_clipboard[id] != NULL &&
|
|
m_clipboard[id]->getSelection() == selection) {
|
|
return id;
|
|
}
|
|
}
|
|
return kClipboardEnd;
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::processClipboardRequest(Window requestor,
|
|
Time time, Atom property)
|
|
{
|
|
// check every clipboard until one returns success
|
|
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
|
|
if (m_clipboard[id] != NULL &&
|
|
m_clipboard[id]->processRequest(requestor, time, property)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::destroyClipboardRequest(Window requestor)
|
|
{
|
|
// check every clipboard until one returns success
|
|
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
|
|
if (m_clipboard[id] != NULL &&
|
|
m_clipboard[id]->destroyRequest(requestor)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
CXWindowsScreen::ioErrorHandler(Display*)
|
|
{
|
|
// the display has disconnected, probably because X is shutting
|
|
// down. X forces us to exit at this point. that's arguably
|
|
// a flaw in X but, realistically, it's difficult to gracefully
|
|
// handle not having a Display* anymore. we'll simply log the
|
|
// error, notify the subclass (which must not use the display
|
|
// so we set it to NULL), and exit.
|
|
LOG((CLOG_WARN "X display has unexpectedly disconnected"));
|
|
s_screen->m_display = NULL;
|
|
s_screen->m_receiver->onError();
|
|
LOG((CLOG_CRIT "quiting due to X display disconnection"));
|
|
exit(17);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::selectEvents(Window w) const
|
|
{
|
|
// ignore errors while we adjust event masks. windows could be
|
|
// destroyed at any time after the XQueryTree() in doSelectEvents()
|
|
// so we must ignore BadWindow errors.
|
|
CXWindowsUtil::CErrorLock lock(m_display);
|
|
|
|
// adjust event masks
|
|
doSelectEvents(w);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::doSelectEvents(Window w) const
|
|
{
|
|
// we want to track the mouse everywhere on the display. to achieve
|
|
// that we select PointerMotionMask on every window. we also select
|
|
// SubstructureNotifyMask in order to get CreateNotify events so we
|
|
// select events on new windows too.
|
|
//
|
|
// note that this can break certain clients due a design flaw of X.
|
|
// X will deliver a PointerMotion event to the deepest window in the
|
|
// hierarchy that contains the pointer and has PointerMotionMask
|
|
// selected by *any* client. if another client doesn't select
|
|
// motion events in a subwindow so the parent window will get them
|
|
// then by selecting for motion events on the subwindow we break
|
|
// that client because the parent will no longer get the events.
|
|
|
|
// FIXME -- should provide some workaround for event selection
|
|
// design flaw. perhaps only select for motion events on windows
|
|
// that already do or are top-level windows or don't propagate
|
|
// pointer events. or maybe an option to simply poll the mouse.
|
|
|
|
// we don't want to adjust our grab window
|
|
if (w == m_window) {
|
|
return;
|
|
}
|
|
|
|
// select events of interest. do this before querying the tree so
|
|
// we'll get notifications of children created after the XQueryTree()
|
|
// so we won't miss them.
|
|
XSelectInput(m_display, w, PointerMotionMask | SubstructureNotifyMask);
|
|
|
|
// recurse on child windows
|
|
Window rw, pw, *cw;
|
|
unsigned int nc;
|
|
if (XQueryTree(m_display, w, &rw, &pw, &cw, &nc)) {
|
|
for (unsigned int i = 0; i < nc; ++i) {
|
|
doSelectEvents(cw[i]);
|
|
}
|
|
XFree(cw);
|
|
}
|
|
}
|
|
|
|
KeyID
|
|
CXWindowsScreen::mapKeyFromX(XKeyEvent* event) const
|
|
{
|
|
// convert to a keysym
|
|
KeySym keysym;
|
|
if (event->type == KeyPress && m_ic != NULL) {
|
|
// do multibyte lookup. can only call XmbLookupString with a
|
|
// key press event and a valid XIC so we checked those above.
|
|
char scratch[32];
|
|
int n = sizeof(scratch) / sizeof(scratch[0]);
|
|
char* buffer = scratch;
|
|
int status;
|
|
n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status);
|
|
if (status == XBufferOverflow) {
|
|
// not enough space. grow buffer and try again.
|
|
buffer = new char[n];
|
|
n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status);
|
|
delete[] buffer;
|
|
}
|
|
|
|
// see what we got. since we don't care about the string
|
|
// we'll just look for a keysym.
|
|
switch (status) {
|
|
default:
|
|
case XLookupNone:
|
|
case XLookupChars:
|
|
keysym = 0;
|
|
break;
|
|
|
|
case XLookupKeySym:
|
|
case XLookupBoth:
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
// plain old lookup
|
|
char dummy[1];
|
|
XLookupString(event, dummy, 0, &keysym, NULL);
|
|
}
|
|
|
|
// convert key
|
|
switch (keysym & 0xffffff00) {
|
|
case 0x0000:
|
|
// Latin-1
|
|
return static_cast<KeyID>(keysym);
|
|
|
|
case 0xfe00:
|
|
// ISO 9995 Function and Modifier Keys
|
|
if (keysym == XK_ISO_Left_Tab) {
|
|
return kKeyLeftTab;
|
|
}
|
|
return kKeyNone;
|
|
|
|
case 0xff00:
|
|
// MISCELLANY
|
|
return static_cast<KeyID>(keysym - 0xff00 + 0xef00);
|
|
|
|
case 0x1008ff00:
|
|
// "Internet" keys
|
|
return g_map1008FF[keysym & 0xff];
|
|
|
|
default: {
|
|
// lookup character in table
|
|
UInt32 key = CXWindowsUtil::mapKeySymToUCS4(keysym);
|
|
if (key != 0x0000ffff) {
|
|
return static_cast<KeyID>(key);
|
|
}
|
|
|
|
// unknown character
|
|
return kKeyNone;
|
|
}
|
|
}
|
|
}
|
|
|
|
ButtonID
|
|
CXWindowsScreen::mapButtonFromX(const XButtonEvent* event) const
|
|
{
|
|
unsigned int button = event->button;
|
|
|
|
// first three buttons map to 1, 2, 3 (kButtonLeft, Middle, Right)
|
|
if (button >= 1 && button <= 3) {
|
|
return static_cast<ButtonID>(button);
|
|
}
|
|
|
|
// buttons 4 and 5 are ignored here. they're used for the wheel.
|
|
// buttons 6, 7, etc and up map to 4, 5, etc.
|
|
else if (button >= 6) {
|
|
return static_cast<ButtonID>(button - 2);
|
|
}
|
|
|
|
// unknown button
|
|
else {
|
|
return kButtonNone;
|
|
}
|
|
}
|
|
|
|
unsigned int
|
|
CXWindowsScreen::mapButtonToX(ButtonID id) const
|
|
{
|
|
// map button -1 to button 4 (+wheel)
|
|
if (id == static_cast<ButtonID>(-1)) {
|
|
id = 4;
|
|
}
|
|
|
|
// map button -2 to button 5 (-wheel)
|
|
else if (id == static_cast<ButtonID>(-2)) {
|
|
id = 5;
|
|
}
|
|
|
|
// map buttons 4, 5, etc. to 6, 7, etc. to make room for buttons
|
|
// 4 and 5 used to simulate the mouse wheel.
|
|
else if (id >= 4) {
|
|
id += 2;
|
|
}
|
|
|
|
// check button is in legal range
|
|
if (id < 1 || id > m_buttons.size()) {
|
|
// out of range
|
|
return 0;
|
|
}
|
|
|
|
// map button
|
|
return static_cast<unsigned int>(m_buttons[id - 1]);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y)
|
|
{
|
|
assert(m_window != None);
|
|
|
|
// send an event that we can recognize before the mouse warp
|
|
XEvent eventBefore;
|
|
eventBefore.type = MotionNotify;
|
|
eventBefore.xmotion.display = m_display;
|
|
eventBefore.xmotion.window = m_window;
|
|
eventBefore.xmotion.root = m_root;
|
|
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 = NotifyNormal;
|
|
eventBefore.xmotion.same_screen = True;
|
|
XEvent eventAfter = eventBefore;
|
|
XSendEvent(m_display, m_window, False, 0, &eventBefore);
|
|
|
|
// warp mouse
|
|
XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y);
|
|
|
|
// send an event that we can recognize after the mouse warp
|
|
XSendEvent(m_display, m_window, False, 0, &eventAfter);
|
|
XSync(m_display, False);
|
|
|
|
LOG((CLOG_DEBUG2 "warped to %d,%d", x, y));
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::updateButtons()
|
|
{
|
|
// query the button mapping
|
|
UInt32 numButtons = XGetPointerMapping(m_display, NULL, 0);
|
|
unsigned char* tmpButtons = new unsigned char[numButtons];
|
|
XGetPointerMapping(m_display, tmpButtons, numButtons);
|
|
|
|
// find the largest logical button id
|
|
unsigned char maxButton = 0;
|
|
for (UInt32 i = 0; i < numButtons; ++i) {
|
|
if (tmpButtons[i] > maxButton) {
|
|
maxButton = tmpButtons[i];
|
|
}
|
|
}
|
|
|
|
// allocate button array
|
|
m_buttons.resize(maxButton);
|
|
|
|
// fill in button array values. m_buttons[i] is the physical
|
|
// button number for logical button i+1.
|
|
for (UInt32 i = 0; i < numButtons; ++i) {
|
|
m_buttons[i] = 0;
|
|
}
|
|
for (UInt32 i = 0; i < numButtons; ++i) {
|
|
m_buttons[tmpButtons[i] - 1] = i + 1;
|
|
}
|
|
|
|
// clean up
|
|
delete[] tmpButtons;
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreen::grabMouseAndKeyboard()
|
|
{
|
|
// 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(m_display, m_window, True,
|
|
GrabModeAsync, GrabModeAsync, CurrentTime);
|
|
assert(result != GrabNotViewable);
|
|
if (result != GrabSuccess) {
|
|
LOG((CLOG_DEBUG2 "waiting to grab keyboard"));
|
|
ARCH->sleep(0.05);
|
|
if (timer.getTime() >= s_timeout) {
|
|
LOG((CLOG_DEBUG2 "grab keyboard timed out"));
|
|
return false;
|
|
}
|
|
}
|
|
} while (result != GrabSuccess);
|
|
LOG((CLOG_DEBUG2 "grabbed keyboard"));
|
|
|
|
// now the mouse
|
|
result = XGrabPointer(m_display, m_window, True, 0,
|
|
GrabModeAsync, GrabModeAsync,
|
|
m_window, None, CurrentTime);
|
|
assert(result != GrabNotViewable);
|
|
if (result != GrabSuccess) {
|
|
// back off to avoid grab deadlock
|
|
XUngrabKeyboard(m_display, CurrentTime);
|
|
LOG((CLOG_DEBUG2 "ungrabbed keyboard, waiting to grab pointer"));
|
|
ARCH->sleep(0.05);
|
|
if (timer.getTime() >= s_timeout) {
|
|
LOG((CLOG_DEBUG2 "grab pointer timed out"));
|
|
return false;
|
|
}
|
|
}
|
|
} while (result != GrabSuccess);
|
|
|
|
LOG((CLOG_DEBUG1 "grabbed pointer and keyboard"));
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// CXWindowsScreen::CTimer
|
|
//
|
|
|
|
CXWindowsScreen::CTimer::CTimer(IJob* job, double startTime, double resetTime) :
|
|
m_job(job),
|
|
m_timeout(resetTime),
|
|
m_time(resetTime),
|
|
m_startTime(startTime)
|
|
{
|
|
assert(m_timeout > 0.0);
|
|
}
|
|
|
|
CXWindowsScreen::CTimer::~CTimer()
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::CTimer::run()
|
|
{
|
|
if (m_job != NULL) {
|
|
m_job->run();
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreen::CTimer::reset()
|
|
{
|
|
m_time = m_timeout;
|
|
m_startTime = 0.0;
|
|
}
|
|
|
|
CXWindowsScreen::CTimer::CTimer&
|
|
CXWindowsScreen::CTimer::operator-=(double dt)
|
|
{
|
|
m_time -= dt - m_startTime;
|
|
m_startTime = 0.0;
|
|
return *this;
|
|
}
|
|
|
|
CXWindowsScreen::CTimer::operator double() const
|
|
{
|
|
return m_time;
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreen::CTimer::operator<(const CTimer& t) const
|
|
{
|
|
return m_time < t.m_time;
|
|
}
|