source: trunk/synergy/lib/platform/CXWindowsScreen.cpp

Last change on this file was 2749, checked in by bird, 19 years ago

synergy v1.3.1 sources (zip).

File size: 49.4 KB
Line 
1/*
2 * synergy -- mouse and keyboard sharing utility
3 * Copyright (C) 2002 Chris Schoeneman
4 *
5 * This package is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * found in the file COPYING that should have accompanied this file.
8 *
9 * This package is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15#include "CXWindowsScreen.h"
16#include "CXWindowsClipboard.h"
17#include "CXWindowsEventQueueBuffer.h"
18#include "CXWindowsKeyState.h"
19#include "CXWindowsScreenSaver.h"
20#include "CXWindowsUtil.h"
21#include "CClipboard.h"
22#include "CKeyMap.h"
23#include "XScreen.h"
24#include "CLog.h"
25#include "CStopwatch.h"
26#include "CStringUtil.h"
27#include "IEventQueue.h"
28#include "TMethodEventJob.h"
29#include <cstring>
30#if X_DISPLAY_MISSING
31# error X11 is required to build synergy
32#else
33# include <X11/X.h>
34# include <X11/Xutil.h>
35# define XK_MISCELLANY
36# define XK_XKB_KEYS
37# include <X11/keysymdef.h>
38# if HAVE_X11_EXTENSIONS_XTEST_H
39# include <X11/extensions/XTest.h>
40# else
41# error The XTest extension is required to build synergy
42# endif
43# if HAVE_X11_EXTENSIONS_XINERAMA_H
44 // Xinerama.h may lack extern "C" for inclusion by C++
45 extern "C" {
46# include <X11/extensions/Xinerama.h>
47 }
48# endif
49# if HAVE_XKB_EXTENSION
50# include <X11/XKBlib.h>
51# endif
52#endif
53#include "CArch.h"
54
55
56//
57// CXWindowsScreen
58//
59
60// NOTE -- the X display is shared among several objects but is owned
61// by the CXWindowsScreen. Xlib is not reentrant so we must ensure
62// that no two objects can simultaneously call Xlib with the display.
63// this is easy since we only make X11 calls from the main thread.
64// we must also ensure that these objects do not use the display in
65// their destructors or, if they do, we can tell them not to. This
66// is to handle unexpected disconnection of the X display, when any
67// call on the display is invalid. In that situation we discard the
68// display and the X11 event queue buffer, ignore any calls that try
69// to use the display, and wait to be destroyed.
70
71CXWindowsScreen* CXWindowsScreen::s_screen = NULL;
72
73CXWindowsScreen::CXWindowsScreen(const char* displayName, bool isPrimary) :
74 m_isPrimary(isPrimary),
75 m_display(NULL),
76 m_root(None),
77 m_window(None),
78 m_isOnScreen(m_isPrimary),
79 m_x(0), m_y(0),
80 m_w(0), m_h(0),
81 m_xCenter(0), m_yCenter(0),
82 m_xCursor(0), m_yCursor(0),
83 m_keyState(NULL),
84 m_lastFocus(None),
85 m_lastFocusRevert(RevertToNone),
86 m_im(NULL),
87 m_ic(NULL),
88 m_lastKeycode(0),
89 m_sequenceNumber(0),
90 m_screensaver(NULL),
91 m_screensaverNotify(false),
92 m_xtestIsXineramaUnaware(true),
93 m_xkb(false)
94{
95 assert(s_screen == NULL);
96
97 s_screen = this;
98
99 // set the X I/O error handler so we catch the display disconnecting
100 XSetIOErrorHandler(&CXWindowsScreen::ioErrorHandler);
101
102 try {
103 m_display = openDisplay(displayName);
104 m_root = DefaultRootWindow(m_display);
105 saveShape();
106 m_window = openWindow();
107 m_screensaver = new CXWindowsScreenSaver(m_display,
108 m_window, getEventTarget());
109 m_keyState = new CXWindowsKeyState(m_display, m_xkb);
110 LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_xinerama ? "(xinerama)" : ""));
111 LOG((CLOG_DEBUG "window is 0x%08x", m_window));
112 }
113 catch (...) {
114 if (m_display != NULL) {
115 XCloseDisplay(m_display);
116 }
117 throw;
118 }
119
120 // primary/secondary screen only initialization
121 if (m_isPrimary) {
122 // start watching for events on other windows
123 selectEvents(m_root);
124
125 // prepare to use input methods
126 openIM();
127 }
128 else {
129 // become impervious to server grabs
130 XTestGrabControl(m_display, True);
131 }
132
133 // initialize the clipboards
134 for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
135 m_clipboard[id] = new CXWindowsClipboard(m_display, m_window, id);
136 }
137
138 // install event handlers
139 EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(),
140 new TMethodEventJob<CXWindowsScreen>(this,
141 &CXWindowsScreen::handleSystemEvent));
142
143 // install the platform event queue
144 EVENTQUEUE->adoptBuffer(new CXWindowsEventQueueBuffer(m_display, m_window));
145}
146
147CXWindowsScreen::~CXWindowsScreen()
148{
149 assert(s_screen != NULL);
150 assert(m_display != NULL);
151
152 EVENTQUEUE->adoptBuffer(NULL);
153 EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget());
154 for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
155 delete m_clipboard[id];
156 }
157 delete m_keyState;
158 delete m_screensaver;
159 m_keyState = NULL;
160 m_screensaver = NULL;
161 if (m_display != NULL) {
162 // FIXME -- is it safe to clean up the IC and IM without a display?
163 if (m_ic != NULL) {
164 XDestroyIC(m_ic);
165 }
166 if (m_im != NULL) {
167 XCloseIM(m_im);
168 }
169 XDestroyWindow(m_display, m_window);
170 XCloseDisplay(m_display);
171 }
172 XSetIOErrorHandler(NULL);
173
174 s_screen = NULL;
175}
176
177void
178CXWindowsScreen::enable()
179{
180 if (!m_isPrimary) {
181 // get the keyboard control state
182 XKeyboardState keyControl;
183 XGetKeyboardControl(m_display, &keyControl);
184 m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn);
185 m_keyState->setAutoRepeat(keyControl);
186
187 // move hider window under the cursor center
188 XMoveWindow(m_display, m_window, m_xCenter, m_yCenter);
189
190 // raise and show the window
191 // FIXME -- take focus?
192 XMapRaised(m_display, m_window);
193
194 // warp the mouse to the cursor center
195 fakeMouseMove(m_xCenter, m_yCenter);
196 }
197}
198
199void
200CXWindowsScreen::disable()
201{
202 // release input context focus
203 if (m_ic != NULL) {
204 XUnsetICFocus(m_ic);
205 }
206
207 // unmap the hider/grab window. this also ungrabs the mouse and
208 // keyboard if they're grabbed.
209 XUnmapWindow(m_display, m_window);
210
211 // restore auto-repeat state
212 if (!m_isPrimary && m_autoRepeat) {
213 XAutoRepeatOn(m_display);
214 }
215}
216
217void
218CXWindowsScreen::enter()
219{
220 // release input context focus
221 if (m_ic != NULL) {
222 XUnsetICFocus(m_ic);
223 }
224
225 // set the input focus to what it had been when we took it
226 if (m_lastFocus != None) {
227 // the window may not exist anymore so ignore errors
228 CXWindowsUtil::CErrorLock lock(m_display);
229 XSetInputFocus(m_display, m_lastFocus, m_lastFocusRevert, CurrentTime);
230 }
231
232 // unmap the hider/grab window. this also ungrabs the mouse and
233 // keyboard if they're grabbed.
234 XUnmapWindow(m_display, m_window);
235
236/* maybe call this if entering for the screensaver
237 // set keyboard focus to root window. the screensaver should then
238 // pick up key events for when the user enters a password to unlock.
239 XSetInputFocus(m_display, PointerRoot, PointerRoot, CurrentTime);
240*/
241
242 if (!m_isPrimary) {
243 // get the keyboard control state
244 XKeyboardState keyControl;
245 XGetKeyboardControl(m_display, &keyControl);
246 m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn);
247 m_keyState->setAutoRepeat(keyControl);
248
249 // turn off auto-repeat. we do this so fake key press events don't
250 // cause the local server to generate their own auto-repeats of
251 // those keys.
252 XAutoRepeatOff(m_display);
253 }
254
255 // now on screen
256 m_isOnScreen = true;
257}
258
259bool
260CXWindowsScreen::leave()
261{
262 if (!m_isPrimary) {
263 // restore the previous keyboard auto-repeat state. if the user
264 // changed the auto-repeat configuration while on the client then
265 // that state is lost. that's because we can't get notified by
266 // the X server when the auto-repeat configuration is changed so
267 // we can't track the desired configuration.
268 if (m_autoRepeat) {
269 XAutoRepeatOn(m_display);
270 }
271
272 // move hider window under the cursor center
273 XMoveWindow(m_display, m_window, m_xCenter, m_yCenter);
274 }
275
276 // raise and show the window
277 XMapRaised(m_display, m_window);
278
279 // grab the mouse and keyboard, if primary and possible
280 if (m_isPrimary && !grabMouseAndKeyboard()) {
281 XUnmapWindow(m_display, m_window);
282 return false;
283 }
284
285 // save current focus
286 XGetInputFocus(m_display, &m_lastFocus, &m_lastFocusRevert);
287
288 // take focus
289 XSetInputFocus(m_display, m_window, RevertToPointerRoot, CurrentTime);
290
291 // now warp the mouse. we warp after showing the window so we're
292 // guaranteed to get the mouse leave event and to prevent the
293 // keyboard focus from changing under point-to-focus policies.
294 if (m_isPrimary) {
295 warpCursor(m_xCenter, m_yCenter);
296 }
297 else {
298 fakeMouseMove(m_xCenter, m_yCenter);
299 }
300
301 // set input context focus to our window
302 if (m_ic != NULL) {
303 XmbResetIC(m_ic);
304 XSetICFocus(m_ic);
305 m_filtered.clear();
306 }
307
308 // now off screen
309 m_isOnScreen = false;
310
311 return true;
312}
313
314bool
315CXWindowsScreen::setClipboard(ClipboardID id, const IClipboard* clipboard)
316{
317 // fail if we don't have the requested clipboard
318 if (m_clipboard[id] == NULL) {
319 return false;
320 }
321
322 // get the actual time. ICCCM does not allow CurrentTime.
323 Time timestamp = CXWindowsUtil::getCurrentTime(
324 m_display, m_clipboard[id]->getWindow());
325
326 if (clipboard != NULL) {
327 // save clipboard data
328 return CClipboard::copy(m_clipboard[id], clipboard, timestamp);
329 }
330 else {
331 // assert clipboard ownership
332 if (!m_clipboard[id]->open(timestamp)) {
333 return false;
334 }
335 m_clipboard[id]->empty();
336 m_clipboard[id]->close();
337 return true;
338 }
339}
340
341void
342CXWindowsScreen::checkClipboards()
343{
344 // do nothing, we're always up to date
345}
346
347void
348CXWindowsScreen::openScreensaver(bool notify)
349{
350 m_screensaverNotify = notify;
351 if (!m_screensaverNotify) {
352 m_screensaver->disable();
353 }
354}
355
356void
357CXWindowsScreen::closeScreensaver()
358{
359 if (!m_screensaverNotify) {
360 m_screensaver->enable();
361 }
362}
363
364void
365CXWindowsScreen::screensaver(bool activate)
366{
367 if (activate) {
368 m_screensaver->activate();
369 }
370 else {
371 m_screensaver->deactivate();
372 }
373}
374
375void
376CXWindowsScreen::resetOptions()
377{
378 m_xtestIsXineramaUnaware = true;
379}
380
381void
382CXWindowsScreen::setOptions(const COptionsList& options)
383{
384 for (UInt32 i = 0, n = options.size(); i < n; i += 2) {
385 if (options[i] == kOptionXTestXineramaUnaware) {
386 m_xtestIsXineramaUnaware = (options[i + 1] != 0);
387 LOG((CLOG_DEBUG1 "XTest is Xinerama unaware %s", m_xtestIsXineramaUnaware ? "true" : "false"));
388 }
389 }
390}
391
392void
393CXWindowsScreen::setSequenceNumber(UInt32 seqNum)
394{
395 m_sequenceNumber = seqNum;
396}
397
398bool
399CXWindowsScreen::isPrimary() const
400{
401 return m_isPrimary;
402}
403
404void*
405CXWindowsScreen::getEventTarget() const
406{
407 return const_cast<CXWindowsScreen*>(this);
408}
409
410bool
411CXWindowsScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const
412{
413 assert(clipboard != NULL);
414
415 // fail if we don't have the requested clipboard
416 if (m_clipboard[id] == NULL) {
417 return false;
418 }
419
420 // get the actual time. ICCCM does not allow CurrentTime.
421 Time timestamp = CXWindowsUtil::getCurrentTime(
422 m_display, m_clipboard[id]->getWindow());
423
424 // copy the clipboard
425 return CClipboard::copy(clipboard, m_clipboard[id], timestamp);
426}
427
428void
429CXWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
430{
431 x = m_x;
432 y = m_y;
433 w = m_w;
434 h = m_h;
435}
436
437void
438CXWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const
439{
440 Window root, window;
441 int mx, my, xWindow, yWindow;
442 unsigned int mask;
443 if (XQueryPointer(m_display, m_root, &root, &window,
444 &mx, &my, &xWindow, &yWindow, &mask)) {
445 x = mx;
446 y = my;
447 }
448 else {
449 x = m_xCenter;
450 y = m_yCenter;
451 }
452}
453
454void
455CXWindowsScreen::reconfigure(UInt32)
456{
457 // do nothing
458}
459
460void
461CXWindowsScreen::warpCursor(SInt32 x, SInt32 y)
462{
463 // warp mouse
464 warpCursorNoFlush(x, y);
465
466 // remove all input events before and including warp
467 XEvent event;
468 while (XCheckMaskEvent(m_display, PointerMotionMask |
469 ButtonPressMask | ButtonReleaseMask |
470 KeyPressMask | KeyReleaseMask |
471 KeymapStateMask,
472 &event)) {
473 // do nothing
474 }
475
476 // save position as last position
477 m_xCursor = x;
478 m_yCursor = y;
479}
480
481UInt32
482CXWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask)
483{
484 // only allow certain modifiers
485 if ((mask & ~(KeyModifierShift | KeyModifierControl |
486 KeyModifierAlt | KeyModifierSuper)) != 0) {
487 LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask));
488 return 0;
489 }
490
491 // fail if no keys
492 if (key == kKeyNone && mask == 0) {
493 return 0;
494 }
495
496 // convert to X
497 unsigned int modifiers;
498 if (!m_keyState->mapModifiersToX(mask, modifiers)) {
499 // can't map all modifiers
500 LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask));
501 return 0;
502 }
503 CXWindowsKeyState::CKeycodeList keycodes;
504 m_keyState->mapKeyToKeycodes(key, keycodes);
505 if (key != kKeyNone && keycodes.empty()) {
506 // can't map key
507 LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask));
508 return 0;
509 }
510
511 // choose hotkey id
512 UInt32 id;
513 if (!m_oldHotKeyIDs.empty()) {
514 id = m_oldHotKeyIDs.back();
515 m_oldHotKeyIDs.pop_back();
516 }
517 else {
518 id = m_hotKeys.size() + 1;
519 }
520 HotKeyList& hotKeys = m_hotKeys[id];
521
522 // all modifier hotkey must be treated specially. for each modifier
523 // we need to grab the modifier key in combination with all the other
524 // requested modifiers.
525 bool err = false;
526 {
527 CXWindowsUtil::CErrorLock lock(m_display, &err);
528 if (key == kKeyNone) {
529 static const KeyModifierMask s_hotKeyModifiers[] = {
530 KeyModifierShift,
531 KeyModifierControl,
532 KeyModifierAlt,
533 KeyModifierMeta,
534 KeyModifierSuper
535 };
536
537 XModifierKeymap* modKeymap = XGetModifierMapping(m_display);
538 for (size_t j = 0; j < sizeof(s_hotKeyModifiers) /
539 sizeof(s_hotKeyModifiers[0]) && !err; ++j) {
540 // skip modifier if not in mask
541 if ((mask & s_hotKeyModifiers[j]) == 0) {
542 continue;
543 }
544
545 // skip with error if we can't map remaining modifiers
546 unsigned int modifiers2;
547 KeyModifierMask mask2 = (mask & ~s_hotKeyModifiers[j]);
548 if (!m_keyState->mapModifiersToX(mask2, modifiers2)) {
549 err = true;
550 continue;
551 }
552
553 // compute modifier index for modifier. there should be
554 // exactly one X modifier missing
555 int index;
556 switch (modifiers ^ modifiers2) {
557 case ShiftMask:
558 index = ShiftMapIndex;
559 break;
560
561 case LockMask:
562 index = LockMapIndex;
563 break;
564
565 case ControlMask:
566 index = ControlMapIndex;
567 break;
568
569 case Mod1Mask:
570 index = Mod1MapIndex;
571 break;
572
573 case Mod2Mask:
574 index = Mod2MapIndex;
575 break;
576
577 case Mod3Mask:
578 index = Mod3MapIndex;
579 break;
580
581 case Mod4Mask:
582 index = Mod4MapIndex;
583 break;
584
585 case Mod5Mask:
586 index = Mod5MapIndex;
587 break;
588
589 default:
590 err = true;
591 continue;
592 }
593
594 // grab each key for the modifier
595 const KeyCode* modifiermap =
596 modKeymap->modifiermap + index * modKeymap->max_keypermod;
597 for (int k = 0; k < modKeymap->max_keypermod && !err; ++k) {
598 KeyCode code = modifiermap[k];
599 if (modifiermap[k] != 0) {
600 XGrabKey(m_display, code, modifiers2, m_root,
601 False, GrabModeAsync, GrabModeAsync);
602 if (!err) {
603 hotKeys.push_back(std::make_pair(code, modifiers2));
604 m_hotKeyToIDMap[CHotKeyItem(code, modifiers2)] = id;
605 }
606 }
607 }
608 }
609 XFreeModifiermap(modKeymap);
610 }
611
612 // a non-modifier key must be insensitive to CapsLock, NumLock and
613 // ScrollLock, so we have to grab the key with every combination of
614 // those.
615 else {
616 // collect available toggle modifiers
617 unsigned int modifier;
618 unsigned int toggleModifiers[3];
619 size_t numToggleModifiers = 0;
620 if (m_keyState->mapModifiersToX(KeyModifierCapsLock, modifier)) {
621 toggleModifiers[numToggleModifiers++] = modifier;
622 }
623 if (m_keyState->mapModifiersToX(KeyModifierNumLock, modifier)) {
624 toggleModifiers[numToggleModifiers++] = modifier;
625 }
626 if (m_keyState->mapModifiersToX(KeyModifierScrollLock, modifier)) {
627 toggleModifiers[numToggleModifiers++] = modifier;
628 }
629
630
631 for (CXWindowsKeyState::CKeycodeList::iterator j = keycodes.begin();
632 j != keycodes.end() && !err; ++j) {
633 for (size_t i = 0; i < (1u << numToggleModifiers); ++i) {
634 // add toggle modifiers for index i
635 unsigned int tmpModifiers = modifiers;
636 if ((i & 1) != 0) {
637 tmpModifiers |= toggleModifiers[0];
638 }
639 if ((i & 2) != 0) {
640 tmpModifiers |= toggleModifiers[1];
641 }
642 if ((i & 4) != 0) {
643 tmpModifiers |= toggleModifiers[2];
644 }
645
646 // add grab
647 XGrabKey(m_display, *j, tmpModifiers, m_root,
648 False, GrabModeAsync, GrabModeAsync);
649 if (!err) {
650 hotKeys.push_back(std::make_pair(*j, tmpModifiers));
651 m_hotKeyToIDMap[CHotKeyItem(*j, tmpModifiers)] = id;
652 }
653 }
654 }
655 }
656 }
657
658 if (err) {
659 // if any failed then unregister any we did get
660 for (HotKeyList::iterator j = hotKeys.begin();
661 j != hotKeys.end(); ++j) {
662 XUngrabKey(m_display, j->first, j->second, m_root);
663 m_hotKeyToIDMap.erase(CHotKeyItem(j->first, j->second));
664 }
665
666 m_oldHotKeyIDs.push_back(id);
667 m_hotKeys.erase(id);
668 LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", CKeyMap::formatKey(key, mask).c_str(), key, mask));
669 return 0;
670 }
671
672 LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", CKeyMap::formatKey(key, mask).c_str(), key, mask, id));
673 return id;
674}
675
676void
677CXWindowsScreen::unregisterHotKey(UInt32 id)
678{
679 // look up hotkey
680 HotKeyMap::iterator i = m_hotKeys.find(id);
681 if (i == m_hotKeys.end()) {
682 return;
683 }
684
685 // unregister with OS
686 bool err = false;
687 {
688 CXWindowsUtil::CErrorLock lock(m_display, &err);
689 HotKeyList& hotKeys = i->second;
690 for (HotKeyList::iterator j = hotKeys.begin();
691 j != hotKeys.end(); ++j) {
692 XUngrabKey(m_display, j->first, j->second, m_root);
693 m_hotKeyToIDMap.erase(CHotKeyItem(j->first, j->second));
694 }
695 }
696 if (err) {
697 LOG((CLOG_WARN "failed to unregister hotkey id=%d", id));
698 }
699 else {
700 LOG((CLOG_DEBUG "unregistered hotkey id=%d", id));
701 }
702
703 // discard hot key from map and record old id for reuse
704 m_hotKeys.erase(i);
705 m_oldHotKeyIDs.push_back(id);
706}
707
708void
709CXWindowsScreen::fakeInputBegin()
710{
711 // FIXME -- not implemented
712}
713
714void
715CXWindowsScreen::fakeInputEnd()
716{
717 // FIXME -- not implemented
718}
719
720SInt32
721CXWindowsScreen::getJumpZoneSize() const
722{
723 return 1;
724}
725
726bool
727CXWindowsScreen::isAnyMouseButtonDown() const
728{
729 // query the pointer to get the button state
730 Window root, window;
731 int xRoot, yRoot, xWindow, yWindow;
732 unsigned int state;
733 if (XQueryPointer(m_display, m_root, &root, &window,
734 &xRoot, &yRoot, &xWindow, &yWindow, &state)) {
735 return ((state & (Button1Mask | Button2Mask | Button3Mask |
736 Button4Mask | Button5Mask)) != 0);
737 }
738
739 return false;
740}
741
742void
743CXWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const
744{
745 x = m_xCenter;
746 y = m_yCenter;
747}
748
749void
750CXWindowsScreen::fakeMouseButton(ButtonID button, bool press) const
751{
752 const unsigned int xButton = mapButtonToX(button);
753 if (xButton != 0) {
754 XTestFakeButtonEvent(m_display, xButton,
755 press ? True : False, CurrentTime);
756 XFlush(m_display);
757 }
758}
759
760void
761CXWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) const
762{
763 if (m_xinerama && m_xtestIsXineramaUnaware) {
764 XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y);
765 }
766 else {
767 XTestFakeMotionEvent(m_display, DefaultScreen(m_display),
768 x, y, CurrentTime);
769 }
770 XFlush(m_display);
771}
772
773void
774CXWindowsScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const
775{
776 // FIXME -- ignore xinerama for now
777 if (false && m_xinerama && m_xtestIsXineramaUnaware) {
778// XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y);
779 }
780 else {
781 XTestFakeRelativeMotionEvent(m_display, dx, dy, CurrentTime);
782 }
783 XFlush(m_display);
784}
785
786void
787CXWindowsScreen::fakeMouseWheel(SInt32, SInt32 yDelta) const
788{
789 // XXX -- support x-axis scrolling
790 if (yDelta == 0) {
791 return;
792 }
793
794 // choose button depending on rotation direction
795 const unsigned int xButton = mapButtonToX(static_cast<ButtonID>(
796 (yDelta >= 0) ? -1 : -2));
797 if (xButton == 0) {
798 // If we get here, then the XServer does not support the scroll
799 // wheel buttons, so send PageUp/PageDown keystrokes instead.
800 // Patch by Tom Chadwick.
801 KeyCode keycode = 0;
802 if (yDelta >= 0) {
803 keycode = XKeysymToKeycode(m_display, XK_Page_Up);
804 }
805 else {
806 keycode = XKeysymToKeycode(m_display, XK_Page_Down);
807 }
808 if (keycode != 0) {
809 XTestFakeKeyEvent(m_display, keycode, True, CurrentTime);
810 XTestFakeKeyEvent(m_display, keycode, False, CurrentTime);
811 }
812 return;
813 }
814
815 // now use absolute value of delta
816 if (yDelta < 0) {
817 yDelta = -yDelta;
818 }
819
820 // send as many clicks as necessary
821 for (; yDelta >= 120; yDelta -= 120) {
822 XTestFakeButtonEvent(m_display, xButton, True, CurrentTime);
823 XTestFakeButtonEvent(m_display, xButton, False, CurrentTime);
824 }
825 XFlush(m_display);
826}
827
828Display*
829CXWindowsScreen::openDisplay(const char* displayName)
830{
831 // get the DISPLAY
832 if (displayName == NULL) {
833 displayName = getenv("DISPLAY");
834 if (displayName == NULL) {
835 displayName = ":0.0";
836 }
837 }
838
839 // open the display
840 LOG((CLOG_DEBUG "XOpenDisplay(\"%s\")", displayName));
841 Display* display = XOpenDisplay(displayName);
842 if (display == NULL) {
843 throw XScreenUnavailable(60.0);
844 }
845
846 // verify the availability of the XTest extension
847 if (!m_isPrimary) {
848 int majorOpcode, firstEvent, firstError;
849 if (!XQueryExtension(display, XTestExtensionName,
850 &majorOpcode, &firstEvent, &firstError)) {
851 LOG((CLOG_ERR "XTEST extension not available"));
852 XCloseDisplay(display);
853 throw XScreenOpenFailure();
854 }
855 }
856
857#if HAVE_XKB_EXTENSION
858 {
859 m_xkb = false;
860 int major = XkbMajorVersion, minor = XkbMinorVersion;
861 if (XkbLibraryVersion(&major, &minor)) {
862 int opcode, firstError;
863 if (XkbQueryExtension(display, &opcode, &m_xkbEventBase,
864 &firstError, &major, &minor)) {
865 m_xkb = true;
866 XkbSelectEvents(display, XkbUseCoreKbd,
867 XkbMapNotifyMask, XkbMapNotifyMask);
868 XkbSelectEventDetails(display, XkbUseCoreKbd,
869 XkbStateNotifyMask,
870 XkbGroupStateMask, XkbGroupStateMask);
871 }
872 }
873 }
874#endif
875
876 return display;
877}
878
879void
880CXWindowsScreen::saveShape()
881{
882 // get shape of default screen
883 m_x = 0;
884 m_y = 0;
885 m_w = WidthOfScreen(DefaultScreenOfDisplay(m_display));
886 m_h = HeightOfScreen(DefaultScreenOfDisplay(m_display));
887
888 // get center of default screen
889 m_xCenter = m_x + (m_w >> 1);
890 m_yCenter = m_y + (m_h >> 1);
891
892 // check if xinerama is enabled and there is more than one screen.
893 // get center of first Xinerama screen. Xinerama appears to have
894 // a bug when XWarpPointer() is used in combination with
895 // XGrabPointer(). in that case, the warp is successful but the
896 // next pointer motion warps the pointer again, apparently to
897 // constrain it to some unknown region, possibly the region from
898 // 0,0 to Wm,Hm where Wm (Hm) is the minimum width (height) over
899 // all physical screens. this warp only seems to happen if the
900 // pointer wasn't in that region before the XWarpPointer(). the
901 // second (unexpected) warp causes synergy to think the pointer
902 // has been moved when it hasn't. to work around the problem,
903 // we warp the pointer to the center of the first physical
904 // screen instead of the logical screen.
905 m_xinerama = false;
906#if HAVE_X11_EXTENSIONS_XINERAMA_H
907 int eventBase, errorBase;
908 if (XineramaQueryExtension(m_display, &eventBase, &errorBase) &&
909 XineramaIsActive(m_display)) {
910 int numScreens;
911 XineramaScreenInfo* screens;
912 screens = XineramaQueryScreens(m_display, &numScreens);
913 if (screens != NULL) {
914 if (numScreens > 1) {
915 m_xinerama = true;
916 m_xCenter = screens[0].x_org + (screens[0].width >> 1);
917 m_yCenter = screens[0].y_org + (screens[0].height >> 1);
918 }
919 XFree(screens);
920 }
921 }
922#endif
923}
924
925Window
926CXWindowsScreen::openWindow() const
927{
928 // default window attributes. we don't want the window manager
929 // messing with our window and we don't want the cursor to be
930 // visible inside the window.
931 XSetWindowAttributes attr;
932 attr.do_not_propagate_mask = 0;
933 attr.override_redirect = True;
934 attr.cursor = createBlankCursor();
935
936 // adjust attributes and get size and shape
937 SInt32 x, y, w, h;
938 if (m_isPrimary) {
939 // grab window attributes. this window is used to capture user
940 // input when the user is focused on another client. it covers
941 // the whole screen.
942 attr.event_mask = PointerMotionMask |
943 ButtonPressMask | ButtonReleaseMask |
944 KeyPressMask | KeyReleaseMask |
945 KeymapStateMask | PropertyChangeMask;
946 x = m_x;
947 y = m_y;
948 w = m_w;
949 h = m_h;
950 }
951 else {
952 // cursor hider window attributes. this window is used to hide the
953 // cursor when it's not on the screen. the window is hidden as soon
954 // as the cursor enters the screen or the display's real mouse is
955 // moved. we'll reposition the window as necessary so its
956 // position here doesn't matter. it only needs to be 1x1 because
957 // it only needs to contain the cursor's hotspot.
958 attr.event_mask = LeaveWindowMask;
959 x = 0;
960 y = 0;
961 w = 1;
962 h = 1;
963 }
964
965 // create and return the window
966 Window window = XCreateWindow(m_display, m_root, x, y, w, h, 0, 0,
967 InputOnly, CopyFromParent,
968 CWDontPropagate | CWEventMask |
969 CWOverrideRedirect | CWCursor,
970 &attr);
971 if (window == None) {
972 throw XScreenOpenFailure();
973 }
974 return window;
975}
976
977void
978CXWindowsScreen::openIM()
979{
980 // open the input methods
981 XIM im = XOpenIM(m_display, NULL, NULL, NULL);
982 if (im == NULL) {
983 LOG((CLOG_INFO "no support for IM"));
984 return;
985 }
986
987 // find the appropriate style. synergy supports XIMPreeditNothing
988 // only at the moment.
989 XIMStyles* styles;
990 if (XGetIMValues(im, XNQueryInputStyle, &styles, NULL) != NULL ||
991 styles == NULL) {
992 LOG((CLOG_WARN "cannot get IM styles"));
993 XCloseIM(im);
994 return;
995 }
996 XIMStyle style = 0;
997 for (unsigned short i = 0; i < styles->count_styles; ++i) {
998 style = styles->supported_styles[i];
999 if ((style & XIMPreeditNothing) != 0) {
1000 if ((style & (XIMStatusNothing | XIMStatusNone)) != 0) {
1001 break;
1002 }
1003 }
1004 }
1005 XFree(styles);
1006 if (style == 0) {
1007 LOG((CLOG_INFO "no supported IM styles"));
1008 XCloseIM(im);
1009 return;
1010 }
1011
1012 // create an input context for the style and tell it about our window
1013 XIC ic = XCreateIC(im, XNInputStyle, style, XNClientWindow, m_window, NULL);
1014 if (ic == NULL) {
1015 LOG((CLOG_WARN "cannot create IC"));
1016 XCloseIM(im);
1017 return;
1018 }
1019
1020 // find out the events we must select for and do so
1021 unsigned long mask;
1022 if (XGetICValues(ic, XNFilterEvents, &mask, NULL) != NULL) {
1023 LOG((CLOG_WARN "cannot get IC filter events"));
1024 XDestroyIC(ic);
1025 XCloseIM(im);
1026 return;
1027 }
1028
1029 // we have IM
1030 m_im = im;
1031 m_ic = ic;
1032 m_lastKeycode = 0;
1033
1034 // select events on our window that IM requires
1035 XWindowAttributes attr;
1036 XGetWindowAttributes(m_display, m_window, &attr);
1037 XSelectInput(m_display, m_window, attr.your_event_mask | mask);
1038}
1039
1040void
1041CXWindowsScreen::sendEvent(CEvent::Type type, void* data)
1042{
1043 EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data));
1044}
1045
1046void
1047CXWindowsScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id)
1048{
1049 CClipboardInfo* info = (CClipboardInfo*)malloc(sizeof(CClipboardInfo));
1050 info->m_id = id;
1051 info->m_sequenceNumber = m_sequenceNumber;
1052 sendEvent(type, info);
1053}
1054
1055IKeyState*
1056CXWindowsScreen::getKeyState() const
1057{
1058 return m_keyState;
1059}
1060
1061Bool
1062CXWindowsScreen::findKeyEvent(Display*, XEvent* xevent, XPointer arg)
1063{
1064 CKeyEventFilter* filter = reinterpret_cast<CKeyEventFilter*>(arg);
1065 return (xevent->type == filter->m_event &&
1066 xevent->xkey.window == filter->m_window &&
1067 xevent->xkey.time == filter->m_time &&
1068 xevent->xkey.keycode == filter->m_keycode) ? True : False;
1069}
1070
1071void
1072CXWindowsScreen::handleSystemEvent(const CEvent& event, void*)
1073{
1074 XEvent* xevent = reinterpret_cast<XEvent*>(event.getData());
1075 assert(xevent != NULL);
1076
1077 // update key state
1078 bool isRepeat = false;
1079 if (m_isPrimary) {
1080 if (xevent->type == KeyRelease) {
1081 // check if this is a key repeat by getting the next
1082 // KeyPress event that has the same key and time as
1083 // this release event, if any. first prepare the
1084 // filter info.
1085 CKeyEventFilter filter;
1086 filter.m_event = KeyPress;
1087 filter.m_window = xevent->xkey.window;
1088 filter.m_time = xevent->xkey.time;
1089 filter.m_keycode = xevent->xkey.keycode;
1090 XEvent xevent2;
1091 isRepeat = (XCheckIfEvent(m_display, &xevent2,
1092 &CXWindowsScreen::findKeyEvent,
1093 (XPointer)&filter) == True);
1094 }
1095
1096 if (xevent->type == KeyPress || xevent->type == KeyRelease) {
1097 if (xevent->xkey.window == m_root) {
1098 // this is a hot key
1099 onHotKey(xevent->xkey, isRepeat);
1100 return;
1101 }
1102 else if (!m_isOnScreen) {
1103 // this might be a hot key
1104 if (onHotKey(xevent->xkey, isRepeat)) {
1105 return;
1106 }
1107 }
1108
1109 bool down = (isRepeat || xevent->type == KeyPress);
1110 KeyModifierMask state =
1111 m_keyState->mapModifiersFromX(xevent->xkey.state);
1112 m_keyState->onKey(xevent->xkey.keycode, down, state);
1113 }
1114 }
1115
1116 // let input methods try to handle event first
1117 if (m_ic != NULL) {
1118 // XFilterEvent() may eat the event and generate a new KeyPress
1119 // event with a keycode of 0 because there isn't an actual key
1120 // associated with the keysym. but the KeyRelease may pass
1121 // through XFilterEvent() and keep its keycode. this means
1122 // there's a mismatch between KeyPress and KeyRelease keycodes.
1123 // since we use the keycode on the client to detect when a key
1124 // is released this won't do. so we remember the keycode on
1125 // the most recent KeyPress (and clear it on a matching
1126 // KeyRelease) so we have a keycode for a synthesized KeyPress.
1127 if (xevent->type == KeyPress && xevent->xkey.keycode != 0) {
1128 m_lastKeycode = xevent->xkey.keycode;
1129 }
1130 else if (xevent->type == KeyRelease &&
1131 xevent->xkey.keycode == m_lastKeycode) {
1132 m_lastKeycode = 0;
1133 }
1134
1135 // now filter the event
1136 if (XFilterEvent(xevent, None)) {
1137 if (xevent->type == KeyPress) {
1138 // add filtered presses to the filtered list
1139 m_filtered.insert(m_lastKeycode);
1140 }
1141 return;
1142 }
1143
1144 // discard matching key releases for key presses that were
1145 // filtered and remove them from our filtered list.
1146 else if (xevent->type == KeyRelease &&
1147 m_filtered.count(xevent->xkey.keycode) > 0) {
1148 m_filtered.erase(xevent->xkey.keycode);
1149 return;
1150 }
1151 }
1152
1153 // let screen saver have a go
1154 if (m_screensaver->handleXEvent(xevent)) {
1155 // screen saver handled it
1156 return;
1157 }
1158
1159 // handle the event ourself
1160 switch (xevent->type) {
1161 case CreateNotify:
1162 if (m_isPrimary) {
1163 // select events on new window
1164 selectEvents(xevent->xcreatewindow.window);
1165 }
1166 break;
1167
1168 case MappingNotify:
1169 refreshKeyboard(xevent);
1170 break;
1171
1172 case LeaveNotify:
1173 if (!m_isPrimary) {
1174 // mouse moved out of hider window somehow. hide the window.
1175 XUnmapWindow(m_display, m_window);
1176 }
1177 break;
1178
1179 case SelectionClear:
1180 {
1181 // we just lost the selection. that means someone else
1182 // grabbed the selection so this screen is now the
1183 // selection owner. report that to the receiver.
1184 ClipboardID id = getClipboardID(xevent->xselectionclear.selection);
1185 if (id != kClipboardEnd) {
1186 LOG((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time));
1187 m_clipboard[id]->lost(xevent->xselectionclear.time);
1188 sendClipboardEvent(getClipboardGrabbedEvent(), id);
1189 return;
1190 }
1191 }
1192 break;
1193
1194 case SelectionNotify:
1195 // notification of selection transferred. we shouldn't
1196 // get this here because we handle them in the selection
1197 // retrieval methods. we'll just delete the property
1198 // with the data (satisfying the usual ICCCM protocol).
1199 if (xevent->xselection.property != None) {
1200 XDeleteProperty(m_display,
1201 xevent->xselection.requestor,
1202 xevent->xselection.property);
1203 }
1204 break;
1205
1206 case SelectionRequest:
1207 {
1208 // somebody is asking for clipboard data
1209 ClipboardID id = getClipboardID(
1210 xevent->xselectionrequest.selection);
1211 if (id != kClipboardEnd) {
1212 m_clipboard[id]->addRequest(
1213 xevent->xselectionrequest.owner,
1214 xevent->xselectionrequest.requestor,
1215 xevent->xselectionrequest.target,
1216 xevent->xselectionrequest.time,
1217 xevent->xselectionrequest.property);
1218 return;
1219 }
1220 }
1221 break;
1222
1223 case PropertyNotify:
1224 // property delete may be part of a selection conversion
1225 if (xevent->xproperty.state == PropertyDelete) {
1226 processClipboardRequest(xevent->xproperty.window,
1227 xevent->xproperty.time,
1228 xevent->xproperty.atom);
1229 }
1230 break;
1231
1232 case DestroyNotify:
1233 // looks like one of the windows that requested a clipboard
1234 // transfer has gone bye-bye.
1235 destroyClipboardRequest(xevent->xdestroywindow.window);
1236 break;
1237
1238 case KeyPress:
1239 if (m_isPrimary) {
1240 onKeyPress(xevent->xkey);
1241 }
1242 return;
1243
1244 case KeyRelease:
1245 if (m_isPrimary) {
1246 onKeyRelease(xevent->xkey, isRepeat);
1247 }
1248 return;
1249
1250 case ButtonPress:
1251 if (m_isPrimary) {
1252 onMousePress(xevent->xbutton);
1253 }
1254 return;
1255
1256 case ButtonRelease:
1257 if (m_isPrimary) {
1258 onMouseRelease(xevent->xbutton);
1259 }
1260 return;
1261
1262 case MotionNotify:
1263 if (m_isPrimary) {
1264 onMouseMove(xevent->xmotion);
1265 }
1266 return;
1267
1268 default:
1269#if HAVE_XKB_EXTENSION
1270 if (m_xkb && xevent->type == m_xkbEventBase) {
1271 XkbEvent* xkbEvent = reinterpret_cast<XkbEvent*>(xevent);
1272 switch (xkbEvent->any.xkb_type) {
1273 case XkbMapNotify:
1274 refreshKeyboard(xevent);
1275 return;
1276
1277 case XkbStateNotify:
1278 LOG((CLOG_INFO "group change: %d", xkbEvent->state.group));
1279 m_keyState->setActiveGroup((SInt32)xkbEvent->state.group);
1280 return;
1281 }
1282 }
1283#endif
1284 break;
1285 }
1286}
1287
1288void
1289CXWindowsScreen::onKeyPress(XKeyEvent& xkey)
1290{
1291 LOG((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xkey.keycode, xkey.state));
1292 const KeyModifierMask mask = m_keyState->mapModifiersFromX(xkey.state);
1293 KeyID key = mapKeyFromX(&xkey);
1294 if (key != kKeyNone) {
1295 // check for ctrl+alt+del emulation
1296 if ((key == kKeyPause || key == kKeyBreak) &&
1297 (mask & (KeyModifierControl | KeyModifierAlt)) ==
1298 (KeyModifierControl | KeyModifierAlt)) {
1299 // pretend it's ctrl+alt+del
1300 LOG((CLOG_DEBUG "emulate ctrl+alt+del"));
1301 key = kKeyDelete;
1302 }
1303
1304 // get which button. see call to XFilterEvent() in onEvent()
1305 // for more info.
1306 bool isFake = false;
1307 KeyButton keycode = static_cast<KeyButton>(xkey.keycode);
1308 if (keycode == 0) {
1309 isFake = true;
1310 keycode = static_cast<KeyButton>(m_lastKeycode);
1311 if (keycode == 0) {
1312 // no keycode
1313 return;
1314 }
1315 }
1316
1317 // handle key
1318 m_keyState->sendKeyEvent(getEventTarget(),
1319 true, false, key, mask, 1, keycode);
1320
1321 // do fake release if this is a fake press
1322 if (isFake) {
1323 m_keyState->sendKeyEvent(getEventTarget(),
1324 false, false, key, mask, 1, keycode);
1325 }
1326 }
1327}
1328
1329void
1330CXWindowsScreen::onKeyRelease(XKeyEvent& xkey, bool isRepeat)
1331{
1332 const KeyModifierMask mask = m_keyState->mapModifiersFromX(xkey.state);
1333 KeyID key = mapKeyFromX(&xkey);
1334 if (key != kKeyNone) {
1335 // check for ctrl+alt+del emulation
1336 if ((key == kKeyPause || key == kKeyBreak) &&
1337 (mask & (KeyModifierControl | KeyModifierAlt)) ==
1338 (KeyModifierControl | KeyModifierAlt)) {
1339 // pretend it's ctrl+alt+del and ignore autorepeat
1340 LOG((CLOG_DEBUG "emulate ctrl+alt+del"));
1341 key = kKeyDelete;
1342 isRepeat = false;
1343 }
1344
1345 KeyButton keycode = static_cast<KeyButton>(xkey.keycode);
1346 if (!isRepeat) {
1347 // no press event follows so it's a plain release
1348 LOG((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", keycode, xkey.state));
1349 m_keyState->sendKeyEvent(getEventTarget(),
1350 false, false, key, mask, 1, keycode);
1351 }
1352 else {
1353 // found a press event following so it's a repeat.
1354 // we could attempt to count the already queued
1355 // repeats but we'll just send a repeat of 1.
1356 // note that we discard the press event.
1357 LOG((CLOG_DEBUG1 "event: repeat code=%d, state=0x%04x", keycode, xkey.state));
1358 m_keyState->sendKeyEvent(getEventTarget(),
1359 false, true, key, mask, 1, keycode);
1360 }
1361 }
1362}
1363
1364bool
1365CXWindowsScreen::onHotKey(XKeyEvent& xkey, bool isRepeat)
1366{
1367 // find the hot key id
1368 HotKeyToIDMap::const_iterator i =
1369 m_hotKeyToIDMap.find(CHotKeyItem(xkey.keycode, xkey.state));
1370 if (i == m_hotKeyToIDMap.end()) {
1371 return false;
1372 }
1373
1374 // find what kind of event
1375 CEvent::Type type;
1376 if (xkey.type == KeyPress) {
1377 type = getHotKeyDownEvent();
1378 }
1379 else if (xkey.type == KeyRelease) {
1380 type = getHotKeyUpEvent();
1381 }
1382 else {
1383 return false;
1384 }
1385
1386 // generate event (ignore key repeats)
1387 if (!isRepeat) {
1388 EVENTQUEUE->addEvent(CEvent(type, getEventTarget(),
1389 CHotKeyInfo::alloc(i->second)));
1390 }
1391 return true;
1392}
1393
1394void
1395CXWindowsScreen::onMousePress(const XButtonEvent& xbutton)
1396{
1397 LOG((CLOG_DEBUG1 "event: ButtonPress button=%d", xbutton.button));
1398 ButtonID button = mapButtonFromX(&xbutton);
1399 KeyModifierMask mask = m_keyState->mapModifiersFromX(xbutton.state);
1400 if (button != kButtonNone) {
1401 sendEvent(getButtonDownEvent(), CButtonInfo::alloc(button, mask));
1402 }
1403}
1404
1405void
1406CXWindowsScreen::onMouseRelease(const XButtonEvent& xbutton)
1407{
1408 LOG((CLOG_DEBUG1 "event: ButtonRelease button=%d", xbutton.button));
1409 ButtonID button = mapButtonFromX(&xbutton);
1410 KeyModifierMask mask = m_keyState->mapModifiersFromX(xbutton.state);
1411 if (button != kButtonNone) {
1412 sendEvent(getButtonUpEvent(), CButtonInfo::alloc(button, mask));
1413 }
1414 else if (xbutton.button == 4) {
1415 // wheel forward (away from user)
1416 sendEvent(getWheelEvent(), CWheelInfo::alloc(0, 120));
1417 }
1418 else if (xbutton.button == 5) {
1419 // wheel backward (toward user)
1420 sendEvent(getWheelEvent(), CWheelInfo::alloc(0, -120));
1421 }
1422 // XXX -- support x-axis scrolling
1423}
1424
1425void
1426CXWindowsScreen::onMouseMove(const XMotionEvent& xmotion)
1427{
1428 LOG((CLOG_DEBUG2 "event: MotionNotify %d,%d", xmotion.x_root, xmotion.y_root));
1429
1430 // compute motion delta (relative to the last known
1431 // mouse position)
1432 SInt32 x = xmotion.x_root - m_xCursor;
1433 SInt32 y = xmotion.y_root - m_yCursor;
1434
1435 // save position to compute delta of next motion
1436 m_xCursor = xmotion.x_root;
1437 m_yCursor = xmotion.y_root;
1438
1439 if (xmotion.send_event) {
1440 // we warped the mouse. discard events until we
1441 // find the matching sent event. see
1442 // warpCursorNoFlush() for where the events are
1443 // sent. we discard the matching sent event and
1444 // can be sure we've skipped the warp event.
1445 XEvent xevent;
1446 do {
1447 XMaskEvent(m_display, PointerMotionMask, &xevent);
1448 } while (!xevent.xany.send_event);
1449 }
1450 else if (m_isOnScreen) {
1451 // motion on primary screen
1452 sendEvent(getMotionOnPrimaryEvent(),
1453 CMotionInfo::alloc(m_xCursor, m_yCursor));
1454 }
1455 else {
1456 // motion on secondary screen. warp mouse back to
1457 // center.
1458 //
1459 // my lombard (powerbook g3) running linux and
1460 // using the adbmouse driver has two problems:
1461 // first, the driver only sends motions of +/-2
1462 // pixels and, second, it seems to discard some
1463 // physical input after a warp. the former isn't a
1464 // big deal (we're just limited to every other
1465 // pixel) but the latter is a PITA. to work around
1466 // it we only warp when the mouse has moved more
1467 // than s_size pixels from the center.
1468 static const SInt32 s_size = 32;
1469 if (xmotion.x_root - m_xCenter < -s_size ||
1470 xmotion.x_root - m_xCenter > s_size ||
1471 xmotion.y_root - m_yCenter < -s_size ||
1472 xmotion.y_root - m_yCenter > s_size) {
1473 warpCursorNoFlush(m_xCenter, m_yCenter);
1474 }
1475
1476 // send event if mouse moved. do this after warping
1477 // back to center in case the motion takes us onto
1478 // the primary screen. if we sent the event first
1479 // in that case then the warp would happen after
1480 // warping to the primary screen's enter position,
1481 // effectively overriding it.
1482 if (x != 0 || y != 0) {
1483 sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y));
1484 }
1485 }
1486}
1487
1488Cursor
1489CXWindowsScreen::createBlankCursor() const
1490{
1491 // this seems just a bit more complicated than really necessary
1492
1493 // get the closet cursor size to 1x1
1494 unsigned int w, h;
1495 XQueryBestCursor(m_display, m_root, 1, 1, &w, &h);
1496
1497 // make bitmap data for cursor of closet size. since the cursor
1498 // is blank we can use the same bitmap for shape and mask: all
1499 // zeros.
1500 const int size = ((w + 7) >> 3) * h;
1501 char* data = new char[size];
1502 memset(data, 0, size);
1503
1504 // make bitmap
1505 Pixmap bitmap = XCreateBitmapFromData(m_display, m_root, data, w, h);
1506
1507 // need an arbitrary color for the cursor
1508 XColor color;
1509 color.pixel = 0;
1510 color.red = color.green = color.blue = 0;
1511 color.flags = DoRed | DoGreen | DoBlue;
1512
1513 // make cursor from bitmap
1514 Cursor cursor = XCreatePixmapCursor(m_display, bitmap, bitmap,
1515 &color, &color, 0, 0);
1516
1517 // don't need bitmap or the data anymore
1518 delete[] data;
1519 XFreePixmap(m_display, bitmap);
1520
1521 return cursor;
1522}
1523
1524ClipboardID
1525CXWindowsScreen::getClipboardID(Atom selection) const
1526{
1527 for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
1528 if (m_clipboard[id] != NULL &&
1529 m_clipboard[id]->getSelection() == selection) {
1530 return id;
1531 }
1532 }
1533 return kClipboardEnd;
1534}
1535
1536void
1537CXWindowsScreen::processClipboardRequest(Window requestor,
1538 Time time, Atom property)
1539{
1540 // check every clipboard until one returns success
1541 for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
1542 if (m_clipboard[id] != NULL &&
1543 m_clipboard[id]->processRequest(requestor, time, property)) {
1544 break;
1545 }
1546 }
1547}
1548
1549void
1550CXWindowsScreen::destroyClipboardRequest(Window requestor)
1551{
1552 // check every clipboard until one returns success
1553 for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
1554 if (m_clipboard[id] != NULL &&
1555 m_clipboard[id]->destroyRequest(requestor)) {
1556 break;
1557 }
1558 }
1559}
1560
1561void
1562CXWindowsScreen::onError()
1563{
1564 // prevent further access to the X display
1565 EVENTQUEUE->adoptBuffer(NULL);
1566 m_screensaver->destroy();
1567 m_screensaver = NULL;
1568 m_display = NULL;
1569
1570 // notify of failure
1571 sendEvent(getErrorEvent(), NULL);
1572
1573 // FIXME -- should ensure that we ignore operations that involve
1574 // m_display from now on. however, Xlib will simply exit the
1575 // application in response to the X I/O error so there's no
1576 // point in trying to really handle the error. if we did want
1577 // to handle the error, it'd probably be easiest to delegate to
1578 // one of two objects. one object would take the implementation
1579 // from this class. the other object would be stub methods that
1580 // don't use X11. on error, we'd switch to the latter.
1581}
1582
1583int
1584CXWindowsScreen::ioErrorHandler(Display*)
1585{
1586 // the display has disconnected, probably because X is shutting
1587 // down. X forces us to exit at this point which is annoying.
1588 // we'll pretend as if we won't exit so we try to make sure we
1589 // don't access the display anymore.
1590 LOG((CLOG_CRIT "X display has unexpectedly disconnected"));
1591 s_screen->onError();
1592 return 0;
1593}
1594
1595void
1596CXWindowsScreen::selectEvents(Window w) const
1597{
1598 // ignore errors while we adjust event masks. windows could be
1599 // destroyed at any time after the XQueryTree() in doSelectEvents()
1600 // so we must ignore BadWindow errors.
1601 CXWindowsUtil::CErrorLock lock(m_display);
1602
1603 // adjust event masks
1604 doSelectEvents(w);
1605}
1606
1607void
1608CXWindowsScreen::doSelectEvents(Window w) const
1609{
1610 // we want to track the mouse everywhere on the display. to achieve
1611 // that we select PointerMotionMask on every window. we also select
1612 // SubstructureNotifyMask in order to get CreateNotify events so we
1613 // select events on new windows too.
1614 //
1615 // note that this can break certain clients due a design flaw of X.
1616 // X will deliver a PointerMotion event to the deepest window in the
1617 // hierarchy that contains the pointer and has PointerMotionMask
1618 // selected by *any* client. if another client doesn't select
1619 // motion events in a subwindow so the parent window will get them
1620 // then by selecting for motion events on the subwindow we break
1621 // that client because the parent will no longer get the events.
1622
1623 // FIXME -- should provide some workaround for event selection
1624 // design flaw. perhaps only select for motion events on windows
1625 // that already do or are top-level windows or don't propagate
1626 // pointer events. or maybe an option to simply poll the mouse.
1627
1628 // we don't want to adjust our grab window
1629 if (w == m_window) {
1630 return;
1631 }
1632
1633 // select events of interest. do this before querying the tree so
1634 // we'll get notifications of children created after the XQueryTree()
1635 // so we won't miss them.
1636 XSelectInput(m_display, w, PointerMotionMask | SubstructureNotifyMask);
1637
1638 // recurse on child windows
1639 Window rw, pw, *cw;
1640 unsigned int nc;
1641 if (XQueryTree(m_display, w, &rw, &pw, &cw, &nc)) {
1642 for (unsigned int i = 0; i < nc; ++i) {
1643 doSelectEvents(cw[i]);
1644 }
1645 XFree(cw);
1646 }
1647}
1648
1649KeyID
1650CXWindowsScreen::mapKeyFromX(XKeyEvent* event) const
1651{
1652 // convert to a keysym
1653 KeySym keysym;
1654 if (event->type == KeyPress && m_ic != NULL) {
1655 // do multibyte lookup. can only call XmbLookupString with a
1656 // key press event and a valid XIC so we checked those above.
1657 char scratch[32];
1658 int n = sizeof(scratch) / sizeof(scratch[0]);
1659 char* buffer = scratch;
1660 int status;
1661 n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status);
1662 if (status == XBufferOverflow) {
1663 // not enough space. grow buffer and try again.
1664 buffer = new char[n];
1665 n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status);
1666 delete[] buffer;
1667 }
1668
1669 // see what we got. since we don't care about the string
1670 // we'll just look for a keysym.
1671 switch (status) {
1672 default:
1673 case XLookupNone:
1674 case XLookupChars:
1675 keysym = 0;
1676 break;
1677
1678 case XLookupKeySym:
1679 case XLookupBoth:
1680 break;
1681 }
1682 }
1683 else {
1684 // plain old lookup
1685 char dummy[1];
1686 XLookupString(event, dummy, 0, &keysym, NULL);
1687 }
1688
1689 // convert key
1690 return CXWindowsUtil::mapKeySymToKeyID(keysym);
1691}
1692
1693ButtonID
1694CXWindowsScreen::mapButtonFromX(const XButtonEvent* event) const
1695{
1696 unsigned int button = event->button;
1697
1698 // first three buttons map to 1, 2, 3 (kButtonLeft, Middle, Right)
1699 if (button >= 1 && button <= 3) {
1700 return static_cast<ButtonID>(button);
1701 }
1702
1703 // buttons 4 and 5 are ignored here. they're used for the wheel.
1704 // buttons 6, 7, etc and up map to 4, 5, etc.
1705 else if (button >= 6) {
1706 return static_cast<ButtonID>(button - 2);
1707 }
1708
1709 // unknown button
1710 else {
1711 return kButtonNone;
1712 }
1713}
1714
1715unsigned int
1716CXWindowsScreen::mapButtonToX(ButtonID id) const
1717{
1718 // map button -1 to button 4 (+wheel)
1719 if (id == static_cast<ButtonID>(-1)) {
1720 id = 4;
1721 }
1722
1723 // map button -2 to button 5 (-wheel)
1724 else if (id == static_cast<ButtonID>(-2)) {
1725 id = 5;
1726 }
1727
1728 // map buttons 4, 5, etc. to 6, 7, etc. to make room for buttons
1729 // 4 and 5 used to simulate the mouse wheel.
1730 else if (id >= 4) {
1731 id += 2;
1732 }
1733
1734 // check button is in legal range
1735 if (id < 1 || id > m_buttons.size()) {
1736 // out of range
1737 return 0;
1738 }
1739
1740 // map button
1741 return static_cast<unsigned int>(id);
1742}
1743
1744void
1745CXWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y)
1746{
1747 assert(m_window != None);
1748
1749 // send an event that we can recognize before the mouse warp
1750 XEvent eventBefore;
1751 eventBefore.type = MotionNotify;
1752 eventBefore.xmotion.display = m_display;
1753 eventBefore.xmotion.window = m_window;
1754 eventBefore.xmotion.root = m_root;
1755 eventBefore.xmotion.subwindow = m_window;
1756 eventBefore.xmotion.time = CurrentTime;
1757 eventBefore.xmotion.x = x;
1758 eventBefore.xmotion.y = y;
1759 eventBefore.xmotion.x_root = x;
1760 eventBefore.xmotion.y_root = y;
1761 eventBefore.xmotion.state = 0;
1762 eventBefore.xmotion.is_hint = NotifyNormal;
1763 eventBefore.xmotion.same_screen = True;
1764 XEvent eventAfter = eventBefore;
1765 XSendEvent(m_display, m_window, False, 0, &eventBefore);
1766
1767 // warp mouse
1768 XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y);
1769
1770 // send an event that we can recognize after the mouse warp
1771 XSendEvent(m_display, m_window, False, 0, &eventAfter);
1772 XSync(m_display, False);
1773
1774 LOG((CLOG_DEBUG2 "warped to %d,%d", x, y));
1775}
1776
1777void
1778CXWindowsScreen::updateButtons()
1779{
1780 // query the button mapping
1781 UInt32 numButtons = XGetPointerMapping(m_display, NULL, 0);
1782 unsigned char* tmpButtons = new unsigned char[numButtons];
1783 XGetPointerMapping(m_display, tmpButtons, numButtons);
1784
1785 // find the largest logical button id
1786 unsigned char maxButton = 0;
1787 for (UInt32 i = 0; i < numButtons; ++i) {
1788 if (tmpButtons[i] > maxButton) {
1789 maxButton = tmpButtons[i];
1790 }
1791 }
1792
1793 // allocate button array
1794 m_buttons.resize(maxButton);
1795
1796 // fill in button array values. m_buttons[i] is the physical
1797 // button number for logical button i+1.
1798 for (UInt32 i = 0; i < numButtons; ++i) {
1799 m_buttons[i] = 0;
1800 }
1801 for (UInt32 i = 0; i < numButtons; ++i) {
1802 m_buttons[tmpButtons[i] - 1] = i + 1;
1803 }
1804
1805 // clean up
1806 delete[] tmpButtons;
1807}
1808
1809bool
1810CXWindowsScreen::grabMouseAndKeyboard()
1811{
1812 // grab the mouse and keyboard. keep trying until we get them.
1813 // if we can't grab one after grabbing the other then ungrab
1814 // and wait before retrying. give up after s_timeout seconds.
1815 static const double s_timeout = 1.0;
1816 int result;
1817 CStopwatch timer;
1818 do {
1819 // keyboard first
1820 do {
1821 result = XGrabKeyboard(m_display, m_window, True,
1822 GrabModeAsync, GrabModeAsync, CurrentTime);
1823 assert(result != GrabNotViewable);
1824 if (result != GrabSuccess) {
1825 LOG((CLOG_DEBUG2 "waiting to grab keyboard"));
1826 ARCH->sleep(0.05);
1827 if (timer.getTime() >= s_timeout) {
1828 LOG((CLOG_DEBUG2 "grab keyboard timed out"));
1829 return false;
1830 }
1831 }
1832 } while (result != GrabSuccess);
1833 LOG((CLOG_DEBUG2 "grabbed keyboard"));
1834
1835 // now the mouse
1836 result = XGrabPointer(m_display, m_window, True, 0,
1837 GrabModeAsync, GrabModeAsync,
1838 m_window, None, CurrentTime);
1839 assert(result != GrabNotViewable);
1840 if (result != GrabSuccess) {
1841 // back off to avoid grab deadlock
1842 XUngrabKeyboard(m_display, CurrentTime);
1843 LOG((CLOG_DEBUG2 "ungrabbed keyboard, waiting to grab pointer"));
1844 ARCH->sleep(0.05);
1845 if (timer.getTime() >= s_timeout) {
1846 LOG((CLOG_DEBUG2 "grab pointer timed out"));
1847 return false;
1848 }
1849 }
1850 } while (result != GrabSuccess);
1851
1852 LOG((CLOG_DEBUG1 "grabbed pointer and keyboard"));
1853 return true;
1854}
1855
1856void
1857CXWindowsScreen::refreshKeyboard(XEvent* event)
1858{
1859 if (XPending(m_display) > 0) {
1860 XEvent tmpEvent;
1861 XPeekEvent(m_display, &tmpEvent);
1862 if (tmpEvent.type == MappingNotify) {
1863 // discard this event since another follows.
1864 // we tend to get a bunch of these in a row.
1865 return;
1866 }
1867 }
1868
1869 // keyboard mapping changed
1870#if HAVE_XKB_EXTENSION
1871 if (m_xkb && event->type == m_xkbEventBase) {
1872 XkbRefreshKeyboardMapping((XkbMapNotifyEvent*)event);
1873 }
1874 else
1875#else
1876 {
1877 XRefreshKeyboardMapping(&event->xmapping);
1878 }
1879#endif
1880 m_keyState->updateKeyMap();
1881 m_keyState->updateKeyState();
1882}
1883
1884
1885//
1886// CXWindowsScreen::CHotKeyItem
1887//
1888
1889CXWindowsScreen::CHotKeyItem::CHotKeyItem(int keycode, unsigned int mask) :
1890 m_keycode(keycode),
1891 m_mask(mask)
1892{
1893 // do nothing
1894}
1895
1896bool
1897CXWindowsScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const
1898{
1899 return (m_keycode < x.m_keycode ||
1900 (m_keycode == x.m_keycode && m_mask < x.m_mask));
1901}
Note: See TracBrowser for help on using the repository browser.