barrier/lib/platform/CXWindowsSecondaryScreen.cpp

1384 lines
34 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 "CXWindowsSecondaryScreen.h"
#include "CXWindowsClipboard.h"
#include "CXWindowsScreen.h"
#include "CXWindowsScreenSaver.h"
#include "CXWindowsUtil.h"
#include "IScreenReceiver.h"
#include "XScreen.h"
#include "CThread.h"
#include "CLog.h"
#if defined(X_DISPLAY_MISSING)
# error X11 is required to build synergy
#else
# include <X11/X.h>
# include <X11/Xutil.h>
# define XK_MISCELLANY
# define XK_XKB_KEYS
# define XK_LATIN1
# 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
#endif
//
// utility functions
//
inline
static
unsigned int getBits(unsigned int src, unsigned int mask)
{
return src & mask;
}
inline
static
unsigned int setBits(unsigned int src, unsigned int mask)
{
return src | mask;
}
inline
static
unsigned int clearBits(unsigned int src, unsigned int mask)
{
return src & ~mask;
}
inline
static
unsigned int flipBits(unsigned int src, unsigned int mask)
{
return src ^ mask;
}
inline
static
unsigned int assignBits(unsigned int src,
unsigned int mask, unsigned int value)
{
return setBits(clearBits(src, mask), clearBits(value, ~mask));
}
//
// CXWindowsSecondaryScreen
//
CXWindowsSecondaryScreen::CXWindowsSecondaryScreen(IScreenReceiver* receiver) :
CSecondaryScreen(),
m_window(None)
{
m_screen = new CXWindowsScreen(receiver, this);
}
CXWindowsSecondaryScreen::~CXWindowsSecondaryScreen()
{
assert(m_window == None);
delete m_screen;
}
void
CXWindowsSecondaryScreen::keyDown(KeyID key, KeyModifierMask mask)
{
Keystrokes keys;
KeyCode keycode;
// get the sequence of keys to simulate key press and the final
// modifier state.
m_mask = mapKey(keys, keycode, key, mask, kPress);
if (keys.empty()) {
return;
}
// generate key events
doKeystrokes(keys, 1);
// note that key is now down
m_keys[keycode] = true;
}
void
CXWindowsSecondaryScreen::keyRepeat(KeyID key,
KeyModifierMask mask, SInt32 count)
{
Keystrokes keys;
KeyCode keycode;
// get the sequence of keys to simulate key repeat and the final
// modifier state.
m_mask = mapKey(keys, keycode, key, mask, kRepeat);
if (keys.empty()) {
return;
}
// generate key events
doKeystrokes(keys, count);
}
void
CXWindowsSecondaryScreen::keyUp(KeyID key, KeyModifierMask mask)
{
Keystrokes keys;
KeyCode keycode;
// get the sequence of keys to simulate key release and the final
// modifier state.
m_mask = mapKey(keys, keycode, key, mask, kRelease);
if (keys.empty()) {
return;
}
// generate key events
doKeystrokes(keys, 1);
// note that key is now up
m_keys[keycode] = false;
}
void
CXWindowsSecondaryScreen::mouseDown(ButtonID button)
{
const unsigned int xButton = mapButton(button);
if (xButton != 0) {
CDisplayLock display(m_screen);
XTestFakeButtonEvent(display, xButton, True, CurrentTime);
XSync(display, False);
}
}
void
CXWindowsSecondaryScreen::mouseUp(ButtonID button)
{
const unsigned int xButton = mapButton(button);
if (xButton != 0) {
CDisplayLock display(m_screen);
XTestFakeButtonEvent(display, xButton, False, CurrentTime);
XSync(display, False);
}
}
void
CXWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y)
{
warpCursor(x, y);
}
void
CXWindowsSecondaryScreen::mouseWheel(SInt32 delta)
{
// choose button depending on rotation direction
const unsigned int xButton = mapButton((delta >= 0) ? 4 : 5);
if (xButton == 0) {
return;
}
// now use absolute value of delta
if (delta < 0) {
delta = -delta;
}
// send as many clicks as necessary
CDisplayLock display(m_screen);
for (; delta >= 120; delta -= 120) {
XTestFakeButtonEvent(display, xButton, True, CurrentTime);
XTestFakeButtonEvent(display, xButton, False, CurrentTime);
}
XSync(display, False);
}
void
CXWindowsSecondaryScreen::resetOptions()
{
m_numLockHalfDuplex = false;
m_capsLockHalfDuplex = false;
}
void
CXWindowsSecondaryScreen::setOptions(const COptionsList& options)
{
for (UInt32 i = 0, n = options.size(); i < n; i += 2) {
if (options[i] == kOptionHalfDuplexCapsLock) {
m_capsLockHalfDuplex = (options[i + 1] != 0);
LOG((CLOG_DEBUG1 "half-duplex caps-lock %s", m_capsLockHalfDuplex ? "on" : "off"));
}
else if (options[i] == kOptionHalfDuplexNumLock) {
m_numLockHalfDuplex = (options[i + 1] != 0);
LOG((CLOG_DEBUG1 "half-duplex num-lock %s", m_numLockHalfDuplex ? "on" : "off"));
}
}
}
IScreen*
CXWindowsSecondaryScreen::getScreen() const
{
return m_screen;
}
void
CXWindowsSecondaryScreen::onScreensaver(bool)
{
// ignore
}
bool
CXWindowsSecondaryScreen::onPreDispatch(const CEvent*)
{
return false;
}
bool
CXWindowsSecondaryScreen::onEvent(CEvent* event)
{
assert(event != NULL);
XEvent& xevent = event->m_event;
// handle event
switch (xevent.type) {
case MappingNotify:
// keyboard mapping changed
updateKeys();
return true;
case LeaveNotify:
// mouse moved out of hider window somehow. hide the window.
hideWindow();
return true;
}
}
SInt32
CXWindowsSecondaryScreen::getJumpZoneSize() const
{
return 0;
}
void
CXWindowsSecondaryScreen::onPreMainLoop()
{
assert(m_window != None);
}
void
CXWindowsSecondaryScreen::onPreOpen()
{
assert(m_window == None);
}
void
CXWindowsSecondaryScreen::onPostOpen()
{
assert(m_window != None);
}
void
CXWindowsSecondaryScreen::onPreEnter()
{
assert(m_window != None);
}
void
CXWindowsSecondaryScreen::onPreLeave()
{
assert(m_window != None);
}
void
CXWindowsSecondaryScreen::createWindow()
{
{
CDisplayLock display(m_screen);
// verify the availability of the XTest extension
int majorOpcode, firstEvent, firstError;
if (!XQueryExtension(display, XTestExtensionName,
&majorOpcode, &firstEvent, &firstError)) {
LOG((CLOG_ERR "XTEST extension not available"));
throw XScreenOpenFailure();
}
// cursor hider window attributes. this window is used to hide the
// cursor when it's not on the screen. the window is hidden as soon
// as the cursor enters the screen or the display's real cursor is
// moved.
XSetWindowAttributes attr;
attr.event_mask = LeaveWindowMask;
attr.do_not_propagate_mask = 0;
attr.override_redirect = True;
attr.cursor = m_screen->getBlankCursor();
// create the cursor hider window
m_window = XCreateWindow(display, m_screen->getRoot(),
0, 0, 1, 1, 0, 0,
InputOnly, CopyFromParent,
CWDontPropagate | CWEventMask |
CWOverrideRedirect | CWCursor,
&attr);
if (m_window == None) {
throw XScreenOpenFailure();
}
LOG((CLOG_DEBUG "window is 0x%08x", m_window));
// become impervious to server grabs
XTestGrabControl(display, True);
}
// tell generic screen about the window
m_screen->setWindow(m_window);
}
void
CXWindowsSecondaryScreen::destroyWindow()
{
{
CDisplayLock display(m_screen);
if (display != NULL) {
// release keys that are still pressed
releaseKeys(display);
// no longer impervious to server grabs
XTestGrabControl(display, False);
// update
XSync(display, False);
}
}
// destroy window
if (m_window != None) {
m_screen->setWindow(None);
CDisplayLock display(m_screen);
if (display != NULL) {
XDestroyWindow(display, m_window);
}
m_window = None;
}
}
void
CXWindowsSecondaryScreen::showWindow()
{
// move hider window under the mouse (rather than moving the mouse
// somewhere else on the screen)
SInt32 x, y;
getCursorPos(x, y);
CDisplayLock display(m_screen);
XMoveWindow(display, m_window, x, y);
// raise and show the hider window. take activation.
// FIXME -- take focus?
XMapRaised(display, m_window);
}
void
CXWindowsSecondaryScreen::hideWindow()
{
assert(m_window != None);
CDisplayLock display(m_screen);
XUnmapWindow(display, m_window);
}
void
CXWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y)
{
CDisplayLock display(m_screen);
Display* pDisplay = display;
XTestFakeMotionEvent(display, DefaultScreen(pDisplay), x, y, CurrentTime);
XSync(display, False);
}
void
CXWindowsSecondaryScreen::setToggleState(KeyModifierMask mask)
{
CDisplayLock display(m_screen);
// toggle modifiers that don't match the desired state
unsigned int xMask = maskToX(mask);
if ((xMask & m_capsLockMask) != (m_mask & m_capsLockMask)) {
toggleKey(display, XK_Caps_Lock, m_capsLockMask);
}
if ((xMask & m_numLockMask) != (m_mask & m_numLockMask)) {
toggleKey(display, XK_Num_Lock, m_numLockMask);
}
if ((xMask & m_scrollLockMask) != (m_mask & m_scrollLockMask)) {
toggleKey(display, XK_Scroll_Lock, m_scrollLockMask);
}
}
KeyModifierMask
CXWindowsSecondaryScreen::getToggleState() const
{
KeyModifierMask mask = 0;
if ((m_mask & m_capsLockMask) != 0) {
mask |= KeyModifierCapsLock;
}
if ((m_mask & m_numLockMask) != 0) {
mask |= KeyModifierNumLock;
}
if ((m_mask & m_scrollLockMask) != 0) {
mask |= KeyModifierScrollLock;
}
return mask;
}
unsigned int
CXWindowsSecondaryScreen::mapButton(ButtonID id) const
{
if (id < 1 || id > m_buttons.size()) {
// out of range
return 0;
}
else if (m_buttons[id - 1] == 0) {
// button not mapped
return 0;
}
else {
return static_cast<unsigned int>(id);
}
}
KeyModifierMask
CXWindowsSecondaryScreen::mapKey(Keystrokes& keys, KeyCode& keycode,
KeyID id, KeyModifierMask mask, EKeyAction action) const
{
// note -- must have display locked on entry
// the system translates key events into characters depending
// on the modifier key state at the time of the event. to
// generate the right keysym we need to set the modifier key
// states appropriately.
//
// the mask passed by the caller is the desired mask. however,
// there may not be a keycode mapping to generate the desired
// keysym with that mask. we override the bits in the mask
// that cannot be accomodated.
// note if the key is "half-duplex"
const bool isHalfDuplex = ((id == kKeyCapsLock && m_capsLockHalfDuplex) ||
(id == kKeyNumLock && m_numLockHalfDuplex));
// ignore releases and repeats for half-duplex keys
if (isHalfDuplex && action != kPress) {
return m_mask;
}
// convert the id to a keysym and adjust the mask if necessary
unsigned int outMask = m_mask;
KeyCodeIndex keyIndex = findKey(id, outMask);
if (keyIndex == noKey()) {
// cannot convert id to keysym
LOG((CLOG_DEBUG2 "no keysym for key"));
return m_mask;
}
// get the keysym we're trying to generate and possible keycodes
KeySym keysym = keyIndex->first;
const KeyCodeMask& entry = keyIndex->second;
// we can choose any of the available keycode/modifier states to
// generate our keysym. the most desireable is the one most
// closely matching the input mask. determine the order we
// should try modifier states, from best match to worst. this
// doesn't concern itself with whether or not a given modifier
// state has an associated keycode. we'll just skip those later
// if necessary.
// default is none, shift, mode switch, shift + mode switch
unsigned int desired = maskToX(mask);
unsigned int index[4];
index[0] = 0;
index[1] = 1;
index[2] = 2;
index[3] = 3;
// if mode switch is active then 2 and 3 are better than 0 and 1
if (getBits(desired, m_modeSwitchMask) != 0) {
index[0] ^= 2;
index[1] ^= 2;
index[2] ^= 2;
index[3] ^= 2;
}
// if shift is active then 1 and 3 are better than 0 and 2. however,
// if the key is affected by NumLock and NumLock is active then 1 and
// 3 are better if shift is *not* down (because NumLock acts like
// shift for those keysyms and shift cancels NumLock). similarly for
// keys affected by CapsLock.
bool desireShift = (getBits(desired, ShiftMask) != 0);
bool invertShift = false;
LOG((CLOG_DEBUG2 "desire shift: %s", desireShift ? "yes" : "no"));
if (adjustForNumLock(keysym)) {
LOG((CLOG_DEBUG2 "num lock sensitive"));
if (m_numLockMask != 0) {
LOG((CLOG_DEBUG2 "we have num lock"));
if (getBits(desired, m_numLockMask) != 0) {
LOG((CLOG_DEBUG2 "num lock desired, invert shift"));
invertShift = true;
}
}
}
else if (adjustForCapsLock(keysym)) {
LOG((CLOG_DEBUG2 "caps lock sensitive"));
if (m_capsLockMask != 0) {
LOG((CLOG_DEBUG2 "we have caps lock"));
if (getBits(desired, m_capsLockMask) != 0) {
LOG((CLOG_DEBUG2 "caps lock desired, invert shift"));
invertShift = true;
}
}
}
if (desireShift != invertShift) {
index[0] ^= 1;
index[1] ^= 1;
index[2] ^= 1;
index[3] ^= 1;
}
// find the first modifier state with a keycode we can generate.
// note that if m_modeSwitchMask is 0 then we can't generate
// m_keycode[2] and m_keycode[3].
unsigned int bestIndex;
for (bestIndex = 0; bestIndex < 4; ++bestIndex) {
if (entry.m_keycode[index[bestIndex]] != 0) {
if (index[bestIndex] < 2 || m_modeSwitchMask != 0) {
bestIndex = index[bestIndex];
break;
}
}
}
if (bestIndex == 4) {
// no keycode/modifiers to generate the keysym
return m_mask;
}
// get the keycode
keycode = entry.m_keycode[bestIndex];
LOG((CLOG_DEBUG2 "bestIndex = %d, keycode = %d", bestIndex, keycode));
// note if the key is a modifier
ModifierMap::const_iterator modIndex = m_keycodeToModifier.find(keycode);
unsigned int modifierBit = 0;
if (modIndex != m_keycodeToModifier.end()) {
modifierBit = (1 << modIndex->second);
}
// if the key is a modifier and that modifier is already in the
// desired state then ignore the request since there's nothing
// to do. never ignore a toggle modifier on press or release,
// though.
if (modifierBit != 0) {
if (action == kRepeat) {
LOG((CLOG_DEBUG2 "ignore repeating modifier"));
return m_mask;
}
if (getBits(m_toggleModifierMask, modifierBit) == 0) {
if ((action == kPress && (m_mask & modifierBit) != 0) ||
(action == kRelease && (m_mask & modifierBit) == 0)) {
LOG((CLOG_DEBUG2 "modifier in proper state: 0x%04x", m_mask));
return m_mask;
}
}
}
// bestIndex tells us if shift and mode switch should be on or off,
// except if caps lock or num lock was down then we invert the sense
// of bestIndex's lowest bit.
// we must match both.
unsigned int required = ShiftMask | m_modeSwitchMask;
if (((bestIndex & 1) == 0) != invertShift) {
desired = clearBits(desired, ShiftMask);
}
else {
desired = setBits(desired, ShiftMask);
}
if ((bestIndex & 2) == 0) {
desired = clearBits(desired, m_modeSwitchMask);
}
else {
desired = setBits(desired, m_modeSwitchMask);
}
// if the key is a modifier then remove it from the desired mask.
// we'll be matching the modifiers in the desired mask then adding
// a key press or release for the keysym. if we don't clear the
// modifier bit from the desired mask we'll end up dealing with
// that key twice, once while matching modifiers and once while
// handling the keysym.
//
// note that instead of clearing the bit, we make it identical to
// the same bit in m_mask, meaning it's already in the right state.
desired = assignBits(desired, modifierBit, m_mask);
required = clearBits(required, modifierBit);
LOG((CLOG_DEBUG2 "desired = 0x%04x, current = 0x%04x", desired, m_mask));
// some modifiers never have an effect on keysym lookup. leave
// those modifiers alone by copying their state from m_mask to
// desired.
desired = assignBits(desired,
ControlMask |
m_altMask |
m_metaMask |
m_superMask |
m_scrollLockMask, m_mask);
// add the key events required to get to the modifier state
// necessary to generate an event yielding id. also save the
// key events required to restore the state. if the key is
// a modifier key then skip this because modifiers should not
// modify modifiers.
Keystrokes undo;
Keystroke keystroke;
if (desired != m_mask) {
for (unsigned int i = 0; i < 8; ++i) {
unsigned int bit = (1 << i);
if (getBits(desired, bit) != getBits(m_mask, bit)) {
LOG((CLOG_DEBUG2 "fix modifier %d", i));
// get the keycode we're using for this modifier. if
// there isn't one then bail if the modifier is required
// or ignore it if not required.
KeyCode modifierKey = m_modifierToKeycode[i];
if (modifierKey == 0) {
LOG((CLOG_DEBUG2 "no key mapped to modifier 0x%04x", bit));
if (getBits(required, bit) != 0) {
keys.clear();
return m_mask;
}
else {
continue;
}
}
keystroke.m_keycode = modifierKey;
keystroke.m_repeat = false;
if (getBits(desired, bit)) {
// modifier is not active but should be. if the
// modifier is a toggle then toggle it on with a
// press/release, otherwise activate it with a
// press. use the first keycode for the modifier.
LOG((CLOG_DEBUG2 "modifier 0x%04x is not active", bit));
if (getBits(m_toggleModifierMask, bit) != 0) {
LOG((CLOG_DEBUG2 "modifier 0x%04x is a toggle", bit));
if ((bit == m_capsLockMask && m_capsLockHalfDuplex) ||
(bit == m_numLockMask && m_numLockHalfDuplex)) {
keystroke.m_press = True;
keys.push_back(keystroke);
keystroke.m_press = False;
undo.push_back(keystroke);
}
else {
keystroke.m_press = True;
keys.push_back(keystroke);
keystroke.m_press = False;
keys.push_back(keystroke);
undo.push_back(keystroke);
keystroke.m_press = True;
undo.push_back(keystroke);
}
}
else {
keystroke.m_press = True;
keys.push_back(keystroke);
keystroke.m_press = False;
undo.push_back(keystroke);
}
}
else {
// modifier is active but should not be. if the
// modifier is a toggle then toggle it off with a
// press/release, otherwise deactivate it with a
// release. we must check each keycode for the
// modifier if not a toggle.
LOG((CLOG_DEBUG2 "modifier 0x%04x is active", bit));
if (getBits(m_toggleModifierMask, bit) != 0) {
LOG((CLOG_DEBUG2 "modifier 0x%04x is a toggle", bit));
if ((bit == m_capsLockMask && m_capsLockHalfDuplex) ||
(bit == m_numLockMask && m_numLockHalfDuplex)) {
keystroke.m_press = False;
keys.push_back(keystroke);
keystroke.m_press = True;
undo.push_back(keystroke);
}
else {
keystroke.m_press = True;
keys.push_back(keystroke);
keystroke.m_press = False;
keys.push_back(keystroke);
undo.push_back(keystroke);
keystroke.m_press = True;
undo.push_back(keystroke);
}
}
else {
for (unsigned int j = 0; j < m_keysPerModifier; ++j) {
const KeyCode key =
m_modifierToKeycodes[i * m_keysPerModifier + j];
if (key != 0 && m_keys[key]) {
keystroke.m_keycode = key;
keystroke.m_press = False;
keys.push_back(keystroke);
keystroke.m_press = True;
undo.push_back(keystroke);
}
}
}
}
}
}
}
// note if the press of a half-duplex key should be treated as a release
if (isHalfDuplex && getBits(m_mask, modifierBit) != 0) {
action = kRelease;
}
// add the key event
keystroke.m_keycode = keycode;
switch (action) {
case kPress:
keystroke.m_press = True;
keystroke.m_repeat = false;
keys.push_back(keystroke);
break;
case kRelease:
keystroke.m_press = False;
keystroke.m_repeat = false;
keys.push_back(keystroke);
break;
case kRepeat:
keystroke.m_press = False;
keystroke.m_repeat = true;
keys.push_back(keystroke);
keystroke.m_press = True;
keys.push_back(keystroke);
break;
}
// add key events to restore the modifier state. apply events in
// the reverse order that they're stored in undo.
while (!undo.empty()) {
keys.push_back(undo.back());
undo.pop_back();
}
// if the key is a modifier key then compute the modifier map after
// this key is pressed or released.
mask = m_mask;
if (modifierBit != 0) {
// can't be repeating if we've gotten here
assert(action != kRepeat);
// toggle keys modify the state on release. other keys set the
// bit on press and clear the bit on release. if half-duplex
// then toggle each time we get here.
if (getBits(m_toggleModifierMask, modifierBit) != 0) {
if (isHalfDuplex || action == kRelease) {
mask = flipBits(mask, modifierBit);
}
}
else if (action == kPress) {
mask = setBits(mask, modifierBit);
}
else if (action == kRelease) {
// can't reset bit until all keys that set it are released.
// scan those keys to see if any (except keycode) are pressed.
bool down = false;
for (unsigned int j = 0; !down && j < m_keysPerModifier; ++j) {
KeyCode modKeycode = m_modifierToKeycodes[modIndex->second *
m_keysPerModifier + j];
if (modKeycode != 0 && modKeycode != keycode) {
down = m_keys[modKeycode];
}
}
if (!down) {
mask = clearBits(mask, modifierBit);
}
}
}
LOG((CLOG_DEBUG2 "final mask: 0x%04x", mask));
return mask;
}
void
CXWindowsSecondaryScreen::doKeystrokes(const Keystrokes& keys, SInt32 count)
{
// do nothing if no keys or no repeats
if (count < 1 || keys.empty()) {
return;
}
// lock display
CDisplayLock display(m_screen);
// generate key events
for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) {
if (k->m_repeat) {
// repeat from here up to but not including the next key
// with m_repeat == false count times.
Keystrokes::const_iterator start = k;
for (; count > 0; --count) {
// send repeating events
for (k = start; k != keys.end() && k->m_repeat; ++k) {
XTestFakeKeyEvent(display,
k->m_keycode, k->m_press, CurrentTime);
}
}
// note -- k is now on the first non-repeat key after the
// repeat keys, exactly where we'd like to continue from.
}
else {
// send event
XTestFakeKeyEvent(display, k->m_keycode, k->m_press, CurrentTime);
// next key
++k;
}
}
// update
XSync(display, False);
}
unsigned int
CXWindowsSecondaryScreen::maskToX(KeyModifierMask inMask) const
{
unsigned int outMask = 0;
if (inMask & KeyModifierShift) {
outMask |= ShiftMask;
}
if (inMask & KeyModifierControl) {
outMask |= ControlMask;
}
if (inMask & KeyModifierAlt) {
outMask |= m_altMask;
}
if (inMask & KeyModifierMeta) {
outMask |= m_metaMask;
}
if (inMask & KeyModifierSuper) {
outMask |= m_superMask;
}
if (inMask & KeyModifierModeSwitch) {
outMask |= m_modeSwitchMask;
}
if (inMask & KeyModifierCapsLock) {
outMask |= m_capsLockMask;
}
if (inMask & KeyModifierNumLock) {
outMask |= m_numLockMask;
}
if (inMask & KeyModifierScrollLock) {
outMask |= m_scrollLockMask;
}
return outMask;
}
void
CXWindowsSecondaryScreen::releaseKeys(Display* display)
{
assert(display != NULL);
// key up for each key that's down
for (UInt32 i = 0; i < 256; ++i) {
if (m_keys[i]) {
XTestFakeKeyEvent(display, i, False, CurrentTime);
m_keys[i] = false;
}
}
}
void
CXWindowsSecondaryScreen::updateKeys()
{
CDisplayLock display(m_screen);
// query the button mapping
UInt32 numButtons = XGetPointerMapping(display, NULL, 0);
unsigned char* tmpButtons = new unsigned char[numButtons];
XGetPointerMapping(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;
// ask server which keys are pressed
char keys[32];
XQueryKeymap(display, keys);
// transfer to our state
for (UInt32 i = 0, j = 0; i < 32; j += 8, ++i) {
m_keys[j + 0] = ((keys[i] & 0x01) != 0);
m_keys[j + 1] = ((keys[i] & 0x02) != 0);
m_keys[j + 2] = ((keys[i] & 0x04) != 0);
m_keys[j + 3] = ((keys[i] & 0x08) != 0);
m_keys[j + 4] = ((keys[i] & 0x10) != 0);
m_keys[j + 5] = ((keys[i] & 0x20) != 0);
m_keys[j + 6] = ((keys[i] & 0x40) != 0);
m_keys[j + 7] = ((keys[i] & 0x80) != 0);
}
// update mappings and current modifiers
updateModifierMap(display);
updateKeycodeMap(display);
updateModifiers(display);
}
void
CXWindowsSecondaryScreen::updateModifiers(Display* display)
{
// query the pointer to get the keyboard state
Window root, window;
int xRoot, yRoot, xWindow, yWindow;
unsigned int state;
if (!XQueryPointer(display, m_window, &root, &window,
&xRoot, &yRoot, &xWindow, &yWindow, &state)) {
state = 0;
}
// update active modifier mask
m_mask = 0;
for (unsigned int i = 0; i < 8; ++i) {
const unsigned int bit = (1 << i);
if ((bit & m_toggleModifierMask) == 0) {
for (unsigned int j = 0; j < m_keysPerModifier; ++j) {
if (m_keys[m_modifierToKeycodes[i * m_keysPerModifier + j]])
m_mask |= bit;
}
}
else if ((bit & state) != 0) {
// toggle is on
m_mask |= bit;
}
}
}
void
CXWindowsSecondaryScreen::updateKeycodeMap(Display* display)
{
// there are up to 4 keysyms per keycode
static const unsigned int maxKeysyms = 4;
// table for counting 1 bits
static const int s_numBits[maxKeysyms] = { 0, 1, 1, 2 };
// get the number of keycodes
int minKeycode, maxKeycode;
XDisplayKeycodes(display, &minKeycode, &maxKeycode);
const int numKeycodes = maxKeycode - minKeycode + 1;
// get the keyboard mapping for all keys
int keysymsPerKeycode;
KeySym* keysyms = XGetKeyboardMapping(display,
minKeycode, numKeycodes,
&keysymsPerKeycode);
// we only understand up to maxKeysyms keysyms per keycodes
unsigned int numKeysyms = keysymsPerKeycode;
if (numKeysyms > maxKeysyms) {
numKeysyms = maxKeysyms;
}
// initialize
m_keycodeMap.clear();
// insert keys
for (int i = 0; i < numKeycodes; ++i) {
// compute mask over all mapped keysyms. if a keycode has, say,
// no shifted keysym then we can ignore the shift state when
// synthesizing an event to generate it.
unsigned int globalMask = 0;
for (unsigned int j = 0; j < numKeysyms; ++j) {
const KeySym keysym = keysyms[i * keysymsPerKeycode + j];
if (keysym != NoSymbol) {
globalMask |= j;
}
}
// map each keysym to it's keycode/modifier mask
for (unsigned int j = 0; j < numKeysyms; ++j) {
// get keysym
KeySym keysym = keysyms[i * keysymsPerKeycode + j];
// get modifier mask required for this keysym. note that
// a keysym of NoSymbol means that a keysym using fewer
// modifiers would be generated using these modifiers.
// for example, given
// keycode 86 = KP_Add
// then we'll generate KP_Add regardless of the modifiers.
// we add an entry for that keysym for these modifiers.
unsigned int index = j;
if (keysym == NoSymbol && (index == 1 || index == 3)) {
// shift doesn't matter
index = index - 1;
keysym = keysyms[i * keysymsPerKeycode + index];
}
if (keysym == NoSymbol && index == 2) {
// mode switch doesn't matter
index = 0;
keysym = keysyms[i * keysymsPerKeycode + index];
}
if (keysym == NoSymbol && index == 0) {
// no symbols at all for this keycode
continue;
}
// look it up, creating a new entry if necessary
KeyCodeMask& entry = m_keycodeMap[keysym];
// save keycode for keysym and modifiers
entry.m_keycode[j] = static_cast<KeyCode>(minKeycode + i);
}
}
// clean up
XFree(keysyms);
}
unsigned int
CXWindowsSecondaryScreen::indexToModifierMask(int index) const
{
assert(index >= 0 && index <= 3);
switch (index) {
case 0:
return 0;
case 1:
return ShiftMask | LockMask;
case 2:
return m_modeSwitchMask;
case 3:
return ShiftMask | LockMask | m_modeSwitchMask;
}
}
void
CXWindowsSecondaryScreen::updateModifierMap(Display* display)
{
// get modifier map from server
XModifierKeymap* keymap = XGetModifierMapping(display);
// initialize
m_modifierMask = 0;
m_toggleModifierMask = 0;
m_altMask = 0;
m_metaMask = 0;
m_superMask = 0;
m_modeSwitchMask = 0;
m_numLockMask = 0;
m_capsLockMask = 0;
m_scrollLockMask = 0;
m_keysPerModifier = keymap->max_keypermod;
m_modifierToKeycode.clear();
m_modifierToKeycode.resize(8);
m_modifierToKeycodes.clear();
m_modifierToKeycodes.resize(8 * m_keysPerModifier);
// set keycodes and masks
for (unsigned int i = 0; i < 8; ++i) {
const unsigned int bit = (1 << i);
for (unsigned int j = 0; j < m_keysPerModifier; ++j) {
KeyCode keycode = keymap->modifiermap[i * m_keysPerModifier + j];
// save in modifier to keycode
m_modifierToKeycodes[i * m_keysPerModifier + j] = keycode;
// no further interest in unmapped modifier
if (keycode == 0) {
continue;
}
// save keycode for modifier if we don't have one yet
if (m_modifierToKeycode[i] == 0) {
m_modifierToKeycode[i] = keycode;
}
// save in keycode to modifier
m_keycodeToModifier.insert(std::make_pair(keycode, i));
// save bit in all-modifiers mask
m_modifierMask |= bit;
// modifier is a toggle if the keysym is a toggle modifier
const KeySym keysym = XKeycodeToKeysym(display, keycode, 0);
if (isToggleKeysym(keysym)) {
m_toggleModifierMask |= bit;
}
// note mask for particular modifiers
switch (keysym) {
case XK_Alt_L:
case XK_Alt_R:
m_altMask |= bit;
break;
case XK_Meta_L:
case XK_Meta_R:
m_metaMask |= bit;
break;
case XK_Super_L:
case XK_Super_R:
m_superMask |= bit;
break;
case XK_Mode_switch:
m_modeSwitchMask |= bit;
break;
case XK_Num_Lock:
m_numLockMask |= bit;
break;
case XK_Caps_Lock:
m_capsLockMask |= bit;
break;
case XK_Scroll_Lock:
m_scrollLockMask |= bit;
}
}
}
XFreeModifiermap(keymap);
}
void
CXWindowsSecondaryScreen::toggleKey(Display* display,
KeySym keysym, unsigned int mask)
{
// lookup the keycode
KeyCodeMap::const_iterator index = m_keycodeMap.find(keysym);
if (index == m_keycodeMap.end()) {
return;
}
// FIXME -- which keycode?
KeyCode keycode = index->second.m_keycode[0];
// toggle the key
if ((keysym == XK_Caps_Lock && m_capsLockHalfDuplex) ||
(keysym == XK_Num_Lock && m_numLockHalfDuplex)) {
// "half-duplex" toggle
XTestFakeKeyEvent(display, keycode, (m_mask & mask) == 0, CurrentTime);
}
else {
// normal toggle
XTestFakeKeyEvent(display, keycode, True, CurrentTime);
XTestFakeKeyEvent(display, keycode, False, CurrentTime);
}
// toggle shadow state
m_mask ^= mask;
}
bool
CXWindowsSecondaryScreen::isToggleKeysym(KeySym key)
{
switch (key) {
case XK_Caps_Lock:
case XK_Shift_Lock:
case XK_Num_Lock:
case XK_Scroll_Lock:
return true;
default:
return false;
}
}
CXWindowsSecondaryScreen::KeyCodeIndex
CXWindowsSecondaryScreen::findKey(KeyID id, KeyModifierMask& mask) const
{
// convert id to keysym
KeySym keysym = NoSymbol;
switch (id & 0xffffff00) {
case 0x0000:
// Latin-1
keysym = static_cast<KeySym>(id);
break;
case 0xee00:
// ISO 9995 Function and Modifier Keys
if (id == kKeyLeftTab) {
keysym = XK_ISO_Left_Tab;
}
break;
case 0xef00:
// MISCELLANY
keysym = static_cast<KeySym>(id - 0xef00 + 0xff00);
break;
}
// fail if unknown key
if (keysym == NoSymbol) {
return m_keycodeMap.end();
}
// if kKeyTab is requested with shift active then try XK_ISO_Left_Tab
// instead. if that doesn't work, we'll fall back to XK_Tab with
// shift active. this is to handle primary screens that don't map
// XK_ISO_Left_Tab sending events to secondary screens that do.
if (keysym == XK_Tab && (mask & ShiftMask) != 0) {
keysym = XK_ISO_Left_Tab;
mask &= ~ShiftMask;
}
// find the keycodes that generate the keysym
KeyCodeIndex index = m_keycodeMap.find(keysym);
if (index == noKey() && keysym >= XK_a && keysym <= XK_z) {
// for an unfound lower case alpha, try the equivalent upper
// case (as some keymaps only include the upper case, notably
// Sun Solaris).
// Note that this depends on the keysyms for 'a' through 'z'
// and 'A' through 'Z' being contiguous. This is currently
// the case and extremely unlikely to change.
index = m_keycodeMap.find(keysym + XK_A - XK_a);
}
if (index == noKey()) {
// try backup keysym for certain keys (particularly the numpad
// keys since most laptops don't have a separate numpad and the
// numpad overlaying the main keyboard may not have movement
// key bindings).
switch (keysym) {
case XK_KP_Home:
keysym = XK_Home;
break;
case XK_KP_Left:
keysym = XK_Left;
break;
case XK_KP_Up:
keysym = XK_Up;
break;
case XK_KP_Right:
keysym = XK_Right;
break;
case XK_KP_Down:
keysym = XK_Down;
break;
case XK_KP_Prior:
keysym = XK_Prior;
break;
case XK_KP_Next:
keysym = XK_Next;
break;
case XK_KP_End:
keysym = XK_End;
break;
case XK_KP_Insert:
keysym = XK_Insert;
break;
case XK_KP_Delete:
keysym = XK_Delete;
break;
case XK_ISO_Left_Tab:
keysym = XK_Tab;
mask |= ShiftMask;
break;
default:
return index;
}
index = m_keycodeMap.find(keysym);
}
return index;
}
CXWindowsSecondaryScreen::KeyCodeIndex
CXWindowsSecondaryScreen::noKey() const
{
return m_keycodeMap.end();
}
bool
CXWindowsSecondaryScreen::adjustForNumLock(KeySym keysym) const
{
if (IsKeypadKey(keysym) || IsPrivateKeypadKey(keysym)) {
// it's NumLock sensitive
LOG((CLOG_DEBUG2 "keypad key: NumLock %s", ((m_mask & m_numLockMask) != 0) ? "active" : "inactive"));
return true;
}
return false;
}
bool
CXWindowsSecondaryScreen::adjustForCapsLock(KeySym keysym) const
{
KeySym lKey, uKey;
XConvertCase(keysym, &lKey, &uKey);
if (lKey != uKey) {
// it's CapsLock sensitive
LOG((CLOG_DEBUG2 "case convertible: CapsLock %s", ((m_mask & m_capsLockMask) != 0) ? "active" : "inactive"));
return true;
}
return false;
}
//
// CXWindowsSecondaryScreen::KeyCodeMask
//
CXWindowsSecondaryScreen::KeyCodeMask::KeyCodeMask()
{
m_keycode[0] = 0;
m_keycode[1] = 0;
m_keycode[2] = 0;
m_keycode[3] = 0;
}