mirror of
https://github.com/debauchee/barrier.git
synced 2024-12-19 00:41:49 +03:00
11f90022e0
key handling to win32 on both client and server. It also changes the protocol and adds code to ensure every key pressed also gets released and that that doesn't get confused when the KeyID for the press is different from the KeyID of the release (or repeat).
1493 lines
37 KiB
C++
1493 lines
37 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
|
|
# 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, KeyButton button)
|
|
{
|
|
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;
|
|
m_fakeKeys[keycode] = true;
|
|
|
|
// note which server key generated this key
|
|
m_serverKeyMap[button] = keycode;
|
|
}
|
|
|
|
void
|
|
CXWindowsSecondaryScreen::keyRepeat(KeyID key,
|
|
KeyModifierMask mask, SInt32 count, KeyButton button)
|
|
{
|
|
Keystrokes keys;
|
|
KeyCode keycode;
|
|
|
|
// if we haven't seen this button go down then ignore it
|
|
ServerKeyMap::iterator index = m_serverKeyMap.find(button);
|
|
if (index == m_serverKeyMap.end()) {
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// if we've seen this button (and we should have) then make sure
|
|
// we release the same key we pressed when we saw it.
|
|
if (index != m_serverKeyMap.end() && keycode != index->second) {
|
|
// replace key up with previous keycode but leave key down
|
|
// alone so it uses the new keycode and store that keycode
|
|
// in the server key map.
|
|
for (Keystrokes::iterator index2 = keys.begin();
|
|
index2 != keys.end(); ++index2) {
|
|
if (index2->m_keycode == index->second) {
|
|
index2->m_keycode = index->second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// note that old key is now up
|
|
m_keys[index->second] = false;
|
|
m_fakeKeys[index->second] = false;
|
|
|
|
// map server key to new key
|
|
index->second = keycode;
|
|
|
|
// note that new key is now down
|
|
m_keys[index->second] = true;
|
|
m_fakeKeys[index->second] = true;
|
|
}
|
|
|
|
// generate key events
|
|
doKeystrokes(keys, count);
|
|
}
|
|
|
|
void
|
|
CXWindowsSecondaryScreen::keyUp(KeyID key,
|
|
KeyModifierMask mask, KeyButton button)
|
|
{
|
|
Keystrokes keys;
|
|
KeyCode keycode;
|
|
|
|
// if we haven't seen this button go down then ignore it
|
|
ServerKeyMap::iterator index = m_serverKeyMap.find(button);
|
|
if (index == m_serverKeyMap.end()) {
|
|
return;
|
|
}
|
|
|
|
// get the sequence of keys to simulate key release and the final
|
|
// modifier state.
|
|
m_mask = mapKey(keys, keycode, key, mask, kRelease);
|
|
|
|
// if there are no keys to generate then we should at least generate
|
|
// a key release for the key we pressed.
|
|
if (keys.empty()) {
|
|
Keystroke keystroke;
|
|
keycode = index->second;
|
|
keystroke.m_keycode = keycode;
|
|
keystroke.m_press = False;
|
|
keystroke.m_repeat = false;
|
|
keys.push_back(keystroke);
|
|
}
|
|
|
|
// if we've seen this button (and we should have) then make sure
|
|
// we release the same key we pressed when we saw it.
|
|
if (index != m_serverKeyMap.end() && keycode != index->second) {
|
|
// replace key up with previous keycode
|
|
for (Keystrokes::iterator index2 = keys.begin();
|
|
index2 != keys.end(); ++index2) {
|
|
if (index2->m_keycode == keycode) {
|
|
index2->m_keycode = index->second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// use old keycode
|
|
keycode = index->second;
|
|
}
|
|
|
|
// generate key events
|
|
doKeystrokes(keys, 1);
|
|
|
|
// note that key is now up
|
|
m_keys[keycode] = false;
|
|
m_fakeKeys[keycode] = false;
|
|
|
|
// remove server key from map
|
|
if (index != m_serverKeyMap.end()) {
|
|
m_serverKeyMap.erase(index);
|
|
}
|
|
}
|
|
|
|
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
|
|
CDisplayLock display(m_screen);
|
|
doUpdateKeys(display);
|
|
return true;
|
|
}
|
|
|
|
case LeaveNotify:
|
|
// mouse moved out of hider window somehow. hide the window.
|
|
hideWindow();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsSecondaryScreen::onOneShotTimerExpired(UInt32)
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
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
|
|
doReleaseKeys(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>(m_buttons[id - 1]);
|
|
}
|
|
}
|
|
|
|
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::doReleaseKeys(Display* display)
|
|
{
|
|
assert(display != NULL);
|
|
|
|
// key release for each key that we faked a press for
|
|
for (UInt32 i = 0; i < 256; ++i) {
|
|
if (m_fakeKeys[i]) {
|
|
XTestFakeKeyEvent(display, i, False, CurrentTime);
|
|
m_fakeKeys[i] = false;
|
|
m_keys[i] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsSecondaryScreen::doUpdateKeys(Display* display)
|
|
{
|
|
// 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;
|
|
|
|
// update mappings and current modifiers
|
|
updateModifierMap(display);
|
|
updateKeycodeMap(display);
|
|
updateModifiers(display);
|
|
}
|
|
|
|
void
|
|
CXWindowsSecondaryScreen::updateKeys()
|
|
{
|
|
CDisplayLock display(m_screen);
|
|
|
|
// 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);
|
|
}
|
|
|
|
// we've fake pressed no keys
|
|
m_fakeKeys.reset();
|
|
|
|
// update mappings and current modifiers and mouse buttons
|
|
doUpdateKeys(display);
|
|
}
|
|
|
|
void
|
|
CXWindowsSecondaryScreen::releaseKeys()
|
|
{
|
|
CDisplayLock display(m_screen);
|
|
if (display != NULL) {
|
|
doReleaseKeys(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;
|
|
if ((id & 0xfffff000) == 0xe000) {
|
|
// special character
|
|
switch (id & 0x0000ff00) {
|
|
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;
|
|
}
|
|
}
|
|
else if ((id >= 0x0020 && id <= 0x007e) ||
|
|
(id >= 0x00a0 && id <= 0x00ff)) {
|
|
// Latin-1 maps directly
|
|
keysym = static_cast<KeySym>(id);
|
|
}
|
|
else {
|
|
// lookup keysym in table
|
|
keysym = CXWindowsUtil::mapUCS4ToKeySym(id);
|
|
}
|
|
|
|
// 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()) {
|
|
// try upper/lower case (as some keymaps only include the
|
|
// upper case, notably Sun Solaris).
|
|
KeySym lower, upper;
|
|
XConvertCase(keysym, &lower, &upper);
|
|
if (lower != keysym)
|
|
index = m_keycodeMap.find(lower);
|
|
else if (upper != keysym)
|
|
index = m_keycodeMap.find(upper);
|
|
}
|
|
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;
|
|
}
|