1721 lines
43 KiB
C++
1721 lines
43 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
|
|
# if HAVE_X11_EXTENSIONS_XINERAMA_H
|
|
// Xinerama.h may lack extern "C" for inclusion by C++
|
|
extern "C" {
|
|
# include <X11/extensions/Xinerama.h>
|
|
}
|
|
# endif
|
|
# if defined(HAVE_X11_XF86KEYSYM_H)
|
|
# include <X11/XF86keysym.h>
|
|
# endif
|
|
# if !defined(XF86XK_Launch0)
|
|
# define XF86XK_Launch0 0x1008FF40
|
|
# endif
|
|
# if !defined(XF86XK_Launch1)
|
|
# define XF86XK_Launch1 0x1008FF41
|
|
# 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_xtestIsXineramaUnaware(true)
|
|
{
|
|
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;
|
|
|
|
// check for ctrl+alt+del emulation
|
|
if (key == kKeyDelete &&
|
|
(mask & (KeyModifierControl | KeyModifierAlt)) ==
|
|
(KeyModifierControl | KeyModifierAlt)) {
|
|
LOG((CLOG_DEBUG "ctrl+alt+del emulation"));
|
|
// just pass the key through
|
|
}
|
|
|
|
// 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 this keycode shouldn't auto-repeat then ignore
|
|
if ((m_keyControl.auto_repeats[keycode >> 3] & (1 << (keycode & 7))) == 0) {
|
|
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;
|
|
}
|
|
|
|
// check for ctrl+alt+del emulation
|
|
if (key == kKeyDelete &&
|
|
(mask & (KeyModifierControl | KeyModifierAlt)) ==
|
|
(KeyModifierControl | KeyModifierAlt)) {
|
|
LOG((CLOG_DEBUG "ctrl+alt+del emulation"));
|
|
// just pass the key through
|
|
}
|
|
|
|
// 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(static_cast<ButtonID>(
|
|
(delta >= 0) ? -1 : -2));
|
|
if (xButton == 0) {
|
|
return;
|
|
}
|
|
|
|
// now use absolute value of delta
|
|
if (delta < 0) {
|
|
delta = -delta;
|
|
}
|
|
|
|
// send as many clicks as necessary
|
|
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;
|
|
m_xtestIsXineramaUnaware = true;
|
|
CSecondaryScreen::resetOptions();
|
|
}
|
|
|
|
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"));
|
|
}
|
|
else if (options[i] == kOptionXTestXineramaUnaware) {
|
|
m_xtestIsXineramaUnaware = (options[i + 1] != 0);
|
|
LOG((CLOG_DEBUG1 "XTest is Xinerama unaware %s", m_xtestIsXineramaUnaware ? "true" : "false"));
|
|
}
|
|
}
|
|
CSecondaryScreen::setOptions(options);
|
|
}
|
|
|
|
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);
|
|
|
|
// get the keyboard control state
|
|
CDisplayLock display(m_screen);
|
|
XGetKeyboardControl(display, &m_keyControl);
|
|
|
|
// check if xinerama is enabled and there is more than one screen
|
|
m_xinerama = false;
|
|
#if HAVE_X11_EXTENSIONS_XINERAMA_H
|
|
int eventBase, errorBase;
|
|
if (XineramaQueryExtension(display, &eventBase, &errorBase)) {
|
|
if (XineramaIsActive(display)) {
|
|
int numScreens;
|
|
XineramaScreenInfo* screens;
|
|
screens = XineramaQueryScreens(display, &numScreens);
|
|
if (screens != NULL) {
|
|
m_xinerama = (numScreens > 1);
|
|
XFree(screens);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
CXWindowsSecondaryScreen::onPreClose()
|
|
{
|
|
if (m_keyControl.global_auto_repeat == AutoRepeatModeOn) {
|
|
CDisplayLock display(m_screen);
|
|
XAutoRepeatOn(display);
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsSecondaryScreen::onPreEnter()
|
|
{
|
|
assert(m_window != None);
|
|
}
|
|
|
|
void
|
|
CXWindowsSecondaryScreen::onPostEnter()
|
|
{
|
|
assert(m_window != None);
|
|
|
|
// get the keyboard control state
|
|
CDisplayLock display(m_screen);
|
|
XGetKeyboardControl(display, &m_keyControl);
|
|
|
|
// turn off auto-repeat. we do this so fake key press events don't
|
|
// cause the local server to generate their own auto-repeats of
|
|
// those keys.
|
|
XAutoRepeatOff(display);
|
|
}
|
|
|
|
void
|
|
CXWindowsSecondaryScreen::onPreLeave()
|
|
{
|
|
assert(m_window != None);
|
|
|
|
// restore the previous keyboard auto-repeat state. if the user
|
|
// changed the auto-repeat configuration while on the client then
|
|
// that state is lost. that's because we can't get notified by
|
|
// the X server when the auto-repeat configuration is changed so
|
|
// we can't track the desired configuration.
|
|
if (m_keyControl.global_auto_repeat == AutoRepeatModeOn) {
|
|
CDisplayLock display(m_screen);
|
|
XAutoRepeatOn(display);
|
|
}
|
|
}
|
|
|
|
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(SInt32 x, SInt32 y)
|
|
{
|
|
{
|
|
CDisplayLock display(m_screen);
|
|
|
|
// move hider window under the given position
|
|
XMoveWindow(display, m_window, x, y);
|
|
|
|
// raise and show the hider window. take activation.
|
|
// FIXME -- take focus?
|
|
XMapRaised(display, m_window);
|
|
}
|
|
|
|
// now warp the mouse. we warp after showing the window so we're
|
|
// guaranteed to get the mouse leave event and to prevent the
|
|
// keyboard focus from changing under point-to-focus policies.
|
|
warpCursor(x, y);
|
|
}
|
|
|
|
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;
|
|
|
|
if (m_xinerama && m_xtestIsXineramaUnaware) {
|
|
XWarpPointer(display, None, m_screen->getRoot(), 0, 0, 0, 0, x, y);
|
|
}
|
|
else {
|
|
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
|
|
{
|
|
// map button -1 to button 4 (+wheel)
|
|
if (id == static_cast<ButtonID>(-1)) {
|
|
id = 4;
|
|
}
|
|
|
|
// map button -2 to button 5 (-wheel)
|
|
else if (id == static_cast<ButtonID>(-2)) {
|
|
id = 5;
|
|
}
|
|
|
|
// map buttons 4, 5, etc. to 6, 7, etc. to make room for buttons
|
|
// 4 and 5 used to simulate the mouse wheel.
|
|
else if (id >= 4) {
|
|
id += 2;
|
|
}
|
|
|
|
// check button is in legal range
|
|
if (id < 1 || id > m_buttons.size()) {
|
|
// out of range
|
|
return 0;
|
|
}
|
|
|
|
// map button
|
|
return static_cast<unsigned int>(m_buttons[id - 1]);
|
|
}
|
|
|
|
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;
|
|
LOG((CLOG_DEBUG2 "keysym is 0x%08x", keysym));
|
|
|
|
// note if the key is a modifier
|
|
unsigned int modifierBit;
|
|
unsigned int modifierIndex = keySymToModifierIndex(keysym);
|
|
if (modifierIndex != static_cast<unsigned int>(-1)) {
|
|
LOG((CLOG_DEBUG2 "keysym is modifier %d", modifierIndex));
|
|
modifierBit = (1 << modifierIndex);
|
|
}
|
|
else {
|
|
modifierBit = 0;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// sensitive notes the modifier keys that affect the synthesized
|
|
// key event. these modifiers must be in the expected state to
|
|
// get the correct keysym and we'll only try to match these
|
|
// modifiers.
|
|
//
|
|
// the shift and mode switch keys can modify any keycode. num
|
|
// lock and caps lock only affect certain keysyms and if a
|
|
// keysym is affected by num lock it is not affected by caps
|
|
// lock. no other modifiers have any effect.
|
|
//
|
|
// requested notes the modifiers requested by the server and
|
|
// desired notes the modifier state we ultimately want to match.
|
|
// only the bits in desired indicated by sensitive are relevant.
|
|
// we assign the num lock and caps lock bits here if relevant.
|
|
// we'll assign shift and mode switch later.
|
|
unsigned int sensitive = ShiftMask | m_modeSwitchMask;
|
|
unsigned int requested = maskToX(mask);
|
|
unsigned int desired = 0;
|
|
if (adjustForNumLock(keysym)) {
|
|
sensitive |= m_numLockMask;
|
|
desired = assignBits(desired, m_numLockMask, requested);
|
|
}
|
|
else if (adjustForCapsLock(keysym)) {
|
|
sensitive |= m_capsLockMask;
|
|
desired = assignBits(desired, m_capsLockMask, requested);
|
|
}
|
|
|
|
// we cannot be sensitive to the modifier we're pressing/releasing
|
|
sensitive = clearBits(sensitive, modifierBit);
|
|
|
|
// we can choose any of the available keycode/modifier states to
|
|
// generate our keysym. the most desireable is the one most
|
|
// closely matching the current 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 index[4];
|
|
index[0] = 0;
|
|
index[1] = 1;
|
|
index[2] = 2;
|
|
index[3] = 3;
|
|
|
|
// 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. none of this is necessary if the key
|
|
// is itself shift.
|
|
bool invertShift = false;
|
|
if (modifierBit != ShiftMask) {
|
|
bool desireShift = (getBits(m_mask, ShiftMask) != 0);
|
|
if ((sensitive & m_numLockMask) != 0) {
|
|
LOG((CLOG_DEBUG2 "num lock sensitive"));
|
|
if (getBits(m_mask, m_numLockMask) != 0) {
|
|
LOG((CLOG_DEBUG2 "num lock preferred, invert shift"));
|
|
invertShift = true;
|
|
}
|
|
}
|
|
else if ((sensitive & m_capsLockMask) != 0) {
|
|
LOG((CLOG_DEBUG2 "caps lock sensitive"));
|
|
if (getBits(m_mask, m_capsLockMask) != 0) {
|
|
LOG((CLOG_DEBUG2 "caps lock preferred, invert shift"));
|
|
invertShift = true;
|
|
}
|
|
}
|
|
if (desireShift != invertShift) {
|
|
LOG((CLOG_DEBUG2 "shift preferred"));
|
|
index[0] ^= 1;
|
|
index[1] ^= 1;
|
|
index[2] ^= 1;
|
|
index[3] ^= 1;
|
|
}
|
|
}
|
|
|
|
// if mode switch is active then 2 and 3 are better than 0 and 1,
|
|
// unless the key is itself mode switch.
|
|
if (modifierBit != m_modeSwitchMask &&
|
|
getBits(m_mask, m_modeSwitchMask) != 0) {
|
|
LOG((CLOG_DEBUG2 "mode switch preferred"));
|
|
index[0] ^= 2;
|
|
index[1] ^= 2;
|
|
index[2] ^= 2;
|
|
index[3] ^= 2;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
LOG((CLOG_DEBUG2 "skip index %d:%d because no mode-switch", bestIndex, index[bestIndex]));
|
|
}
|
|
}
|
|
if (bestIndex == 4) {
|
|
// no keycode/modifiers to generate the keysym
|
|
LOG((CLOG_DEBUG2 "no keycode for keysym"));
|
|
return m_mask;
|
|
}
|
|
|
|
// get the keycode
|
|
keycode = entry.m_keycode[bestIndex];
|
|
LOG((CLOG_DEBUG2 "bestIndex = %d, keycode = %d", bestIndex, keycode));
|
|
|
|
// FIXME -- can remove bits from sensitive if keycode doesn't have
|
|
// keysyms mapped to shift and/or mode switch.
|
|
|
|
// 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.
|
|
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);
|
|
}
|
|
|
|
// we now know what modifiers we want
|
|
LOG((CLOG_DEBUG2 "modifiers: sensitive = 0x%04x, desired = 0x%04x, current = 0x%04x", sensitive, desired, 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;
|
|
for (unsigned int i = 0; i < 8; ++i) {
|
|
// skip modifiers we don't care about
|
|
unsigned int bit = (1 << i);
|
|
if ((bit & sensitive) == 0) {
|
|
continue;
|
|
}
|
|
|
|
// skip modifiers that are correct
|
|
if (getBits(desired, bit) == getBits(m_mask, bit)) {
|
|
continue;
|
|
}
|
|
|
|
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));
|
|
keys.clear();
|
|
return m_mask;
|
|
}
|
|
|
|
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 but should be", 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[modifierIndex *
|
|
m_keysPerModifier + j];
|
|
if (modKeycode != 0 && modKeycode != keycode) {
|
|
down = m_keys[modKeycode];
|
|
}
|
|
}
|
|
if (!down) {
|
|
mask = clearBits(mask, modifierBit);
|
|
}
|
|
}
|
|
LOG((CLOG_DEBUG2 "new 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);
|
|
}
|
|
|
|
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_altIndex = static_cast<unsigned int>(-1);
|
|
m_metaIndex = static_cast<unsigned int>(-1);
|
|
m_superIndex = static_cast<unsigned int>(-1);
|
|
m_modeSwitchIndex = static_cast<unsigned int>(-1);
|
|
m_numLockIndex = static_cast<unsigned int>(-1);
|
|
m_capsLockIndex = static_cast<unsigned int>(-1);
|
|
m_scrollLockIndex = static_cast<unsigned int>(-1);
|
|
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 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_altIndex = i;
|
|
m_altMask |= bit;
|
|
break;
|
|
|
|
case XK_Meta_L:
|
|
case XK_Meta_R:
|
|
m_metaIndex = i;
|
|
m_metaMask |= bit;
|
|
break;
|
|
|
|
case XK_Super_L:
|
|
case XK_Super_R:
|
|
m_superIndex = i;
|
|
m_superMask |= bit;
|
|
break;
|
|
|
|
case XK_Mode_switch:
|
|
m_modeSwitchIndex = i;
|
|
m_modeSwitchMask |= bit;
|
|
break;
|
|
|
|
case XK_Num_Lock:
|
|
m_numLockIndex = i;
|
|
m_numLockMask |= bit;
|
|
break;
|
|
|
|
case XK_Caps_Lock:
|
|
m_capsLockIndex = i;
|
|
m_capsLockMask |= bit;
|
|
break;
|
|
|
|
case XK_Scroll_Lock:
|
|
m_scrollLockIndex = i;
|
|
m_scrollLockMask |= bit;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
XFreeModifiermap(keymap);
|
|
}
|
|
|
|
unsigned int
|
|
CXWindowsSecondaryScreen::keySymToModifierIndex(KeySym keysym) const
|
|
{
|
|
switch (keysym) {
|
|
case XK_Shift_L:
|
|
case XK_Shift_R:
|
|
return 0;
|
|
|
|
case XK_Control_L:
|
|
case XK_Control_R:
|
|
return 2;
|
|
|
|
case XK_Alt_L:
|
|
case XK_Alt_R:
|
|
return m_altIndex;
|
|
|
|
case XK_Meta_L:
|
|
case XK_Meta_R:
|
|
return m_metaIndex;
|
|
|
|
case XK_Super_L:
|
|
case XK_Super_R:
|
|
return m_superIndex;
|
|
|
|
case XK_Mode_switch:
|
|
return m_modeSwitchIndex;
|
|
|
|
case XK_Num_Lock:
|
|
return m_numLockIndex;
|
|
|
|
case XK_Caps_Lock:
|
|
return m_capsLockIndex;
|
|
|
|
case XK_Scroll_Lock:
|
|
return m_scrollLockIndex;
|
|
}
|
|
|
|
return static_cast<unsigned int>(-1);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// map special KeyID keys to KeySyms
|
|
#if defined(HAVE_X11_XF86KEYSYM_H)
|
|
static const KeySym g_mapE000[] =
|
|
{
|
|
/* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x18 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x50 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x60 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x68 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x78 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x88 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0x98 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xa0 */ 0, 0, 0, 0,
|
|
/* 0xa4 */ 0, 0,
|
|
/* 0xa6 */ XF86XK_Back, XF86XK_Forward,
|
|
/* 0xa8 */ XF86XK_Refresh, XF86XK_Stop,
|
|
/* 0xaa */ XF86XK_Search, XF86XK_Favorites,
|
|
/* 0xac */ XF86XK_HomePage, XF86XK_AudioMute,
|
|
/* 0xae */ XF86XK_AudioLowerVolume, XF86XK_AudioRaiseVolume,
|
|
/* 0xb0 */ XF86XK_AudioNext, XF86XK_AudioPrev,
|
|
/* 0xb2 */ XF86XK_AudioStop, XF86XK_AudioPlay,
|
|
/* 0xb4 */ XF86XK_Mail, XF86XK_AudioMedia,
|
|
/* 0xb6 */ XF86XK_Launch0, XF86XK_Launch1,
|
|
/* 0xb8 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xc0 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xc8 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xd0 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xe0 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xe8 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
#endif
|
|
|
|
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) {
|
|
#if defined(HAVE_X11_XF86KEYSYM_H)
|
|
case 0xe000:
|
|
keysym = g_mapE000[id & 0xff];
|
|
break;
|
|
#endif
|
|
|
|
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;
|
|
}
|