Files
barrier/lib/platform/CXWindowsSecondaryScreen.cpp
crs 221628fd84 Checkpoint refactoring. CSecondaryScreen now does the work common
across platform secondary screens.  X11 screen was compiled and
tested but not the win23 screen.  Will next change inheritance
hierarchy.
2003-07-26 13:41:41 +00:00

1781 lines
51 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
# define XK_LATIN2
# define XK_LATIN3
# define XK_LATIN4
# define XK_LATIN8
# define XK_LATIN9
# 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
//
// CXWindowsSecondaryScreen
//
CXWindowsSecondaryScreen::KeySymsMap
CXWindowsSecondaryScreen::s_decomposedKeySyms;
CXWindowsSecondaryScreen::CXWindowsSecondaryScreen(IScreenReceiver* receiver) :
CSecondaryScreen(),
m_window(None),
m_xtestIsXineramaUnaware(true)
{
m_screen = new CXWindowsScreen(receiver, this);
// make sure decomposed keysym table is prepared
getDecomposedKeySymTable();
}
CXWindowsSecondaryScreen::~CXWindowsSecondaryScreen()
{
assert(m_window == None);
delete m_screen;
}
bool
CXWindowsSecondaryScreen::isAutoRepeating(SysKeyID sysKeyID) const
{
char bit = static_cast<char>(1 << (sysKeyID & 7));
return ((m_keyControl.auto_repeats[sysKeyID >> 3] & bit) != 0);
}
void
CXWindowsSecondaryScreen::flush()
{
CDisplayLock display(m_screen);
if (display != NULL) {
XFlush(display);
}
}
void
CXWindowsSecondaryScreen::resetOptions()
{
CSecondaryScreen::resetOptions();
m_xtestIsXineramaUnaware = true;
}
void
CXWindowsSecondaryScreen::setOptions(const COptionsList& options)
{
CSecondaryScreen::setOptions(options);
for (UInt32 i = 0, n = options.size(); i < n; i += 2) {
if (options[i] == kOptionXTestXineramaUnaware) {
m_xtestIsXineramaUnaware = (options[i + 1] != 0);
LOG((CLOG_DEBUG1 "XTest is Xinerama unaware %s", m_xtestIsXineramaUnaware ? "true" : "false"));
}
}
}
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
}
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()
{
{
// release keys that are still pressed
releaseKeys();
CDisplayLock display(m_screen);
if (display != NULL) {
// no longer impervious to server grabs
XTestGrabControl(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.
fakeMouseMove(x, y);
}
void
CXWindowsSecondaryScreen::hideWindow()
{
assert(m_window != None);
CDisplayLock display(m_screen);
XUnmapWindow(display, m_window);
}
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,
SysKeyID& keycode, KeyID id,
KeyModifierMask currentMask,
KeyModifierMask desiredMask, 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.
//
// desiredMask is the mask desired by the caller. 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.
// ignore releases and repeats for half-duplex keys
const bool isHalfDuplex = isKeyHalfDuplex(id);
if (isHalfDuplex && action != kPress) {
return currentMask;
}
// convert KeyID to a KeySym
KeySym keysym = keyIDToKeySym(id, desiredMask);
if (keysym == NoSymbol) {
// unknown key
LOG((CLOG_DEBUG2 "no keysym for id 0x%08x", id));
return currentMask;
}
// get the mapping for this keysym
KeySymIndex keyIndex = m_keysymMap.find(keysym);
// if the mapping isn't found and keysym is caps lock sensitive
// then convert the case of the keysym and try again.
if (keyIndex == m_keysymMap.end()) {
KeySym lKey, uKey;
XConvertCase(keysym, &lKey, &uKey);
if (lKey != uKey) {
if (lKey == keysym) {
keyIndex = m_keysymMap.find(uKey);
}
else {
keyIndex = m_keysymMap.find(lKey);
}
}
}
if (keyIndex != m_keysymMap.end()) {
// the keysym is mapped to some keycode. if it's 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.
const KeyMapping& keyMapping = keyIndex->second;
const KeyModifierMask modifierBit = keyMapping.m_modifierMask;
if (modifierBit != 0) {
if (action == kRepeat) {
LOG((CLOG_DEBUG2 "ignore repeating modifier"));
return currentMask;
}
if ((m_toggleModifierMask & modifierBit) == 0) {
if ((action == kPress && (currentMask & modifierBit) != 0) ||
(action == kRelease && (currentMask & modifierBit) == 0)) {
LOG((CLOG_DEBUG2 "modifier in proper state: 0x%04x", currentMask));
return currentMask;
}
}
}
// create the keystrokes for this keysym
KeyModifierMask mask;
if (!mapToKeystrokes(keys, keycode, mask,
keyIndex, currentMask, action, isHalfDuplex)) {
// failed to generate keystrokes
keys.clear();
return currentMask;
}
else {
// success
LOG((CLOG_DEBUG2 "new mask: 0x%04x", mask));
return mask;
}
}
// we can't find the keysym mapped to any keycode. this doesn't
// necessarily mean we can't generate the keysym, though. if the
// keysym can be created by combining keysyms then we may still
// be okay.
KeySyms decomposition;
if (decomposeKeySym(keysym, decomposition)) {
LOG((CLOG_DEBUG2 "decomposed keysym 0x%08x into %d keysyms", keysym, decomposition.size()));
// map each decomposed keysym to keystrokes. we want the mask
// and the keycode from the last keysym (which should be the
// only non-dead key). the dead keys are not sensitive to
// anything but shift and mode switch.
KeyModifierMask mask;
for (KeySyms::const_iterator i = decomposition.begin();
i != decomposition.end();) {
// increment the iterator
KeySyms::const_iterator next = i;
++next;
// lookup the key
keysym = *i;
keyIndex = m_keysymMap.find(keysym);
if (keyIndex == m_keysymMap.end()) {
// missing a required keysym
LOG((CLOG_DEBUG2 "no keycode for decomposed keysym 0x%08x", keysym));
keys.clear();
return currentMask;
}
// the keysym is mapped to some keycode
if (!mapToKeystrokes(keys, keycode, mask,
keyIndex, currentMask, action, isHalfDuplex)) {
// failed to generate keystrokes
keys.clear();
return currentMask;
}
// on to the next keysym
i = next;
}
LOG((CLOG_DEBUG2 "new mask: 0x%04x", mask));
return mask;
}
LOG((CLOG_DEBUG2 "no keycode for keysym"));
return currentMask;
}
KeyModifierMask
CXWindowsSecondaryScreen::getModifierKeyMask(SysKeyID keycode) const
{
KeyCodeToModifierMap::const_iterator i = m_keycodeToModifier.find(keycode);
if (i == m_keycodeToModifier.end()) {
return 0;
}
return m_modifierIndexToMask[i->second];
}
bool
CXWindowsSecondaryScreen::isModifierActive(SysKeyID keycode) const
{
// check if any keycode for this modifier is down. return false
// for toggle modifiers.
KeyCodeToModifierMap::const_iterator i = m_keycodeToModifier.find(keycode);
if (i != m_keycodeToModifier.end() &&
(m_modifierIndexToMask[i->second] & m_toggleModifierMask) != 0) {
const KeyCodes& keycodes = m_modifierKeycodes[i->second];
for (KeyCodes::const_iterator j = keycodes.begin();
j != keycodes.end(); ++j) {
if (isKeyDown(*j)) {
return true;
}
}
}
return false;
}
unsigned int
CXWindowsSecondaryScreen::findBestKeyIndex(KeySymIndex keyIndex,
KeyModifierMask /*currentMask*/) const
{
// there are up to 4 keycodes per keysym to choose from. the
// best choice is the one that requires the fewest adjustments
// to the modifier state. for example, the letter A normally
// requires shift + a. if shift isn't already down we'd have
// to synthesize a shift press before the a press. however,
// if A could also be created with some other keycode without
// shift then we'd prefer that when shift wasn't down.
//
// if the action is kRepeat or kRelease then we don't call this
// method since we just need to synthesize a key repeat/release
// on the same keycode that we pressed.
// XXX -- do this right
for (unsigned int i = 0; i < 4; ++i) {
if (keyIndex->second.m_keycode[i] != 0) {
return i;
}
}
assert(0 && "no keycode found for keysym");
return 0;
}
bool
CXWindowsSecondaryScreen::isShiftInverted(KeySymIndex keyIndex,
KeyModifierMask currentMask) const
{
// each keycode has up to 4 keysym associated with it, one each for:
// no modifiers, shift, mode switch, and shift and mode switch. if
// a keysym is modified by num lock and num lock is active then you
// get the shifted keysym when shift is not down and the unshifted
// keysym when it is. that is, num lock inverts the sense of the
// shift modifier when active. similarly for caps lock. this
// method returns true iff the sense of shift should be inverted
// for this key given a modifier state.
if (keyIndex->second.m_numLockSensitive) {
if ((currentMask & KeyModifierNumLock) != 0) {
return true;
}
}
// if a keysym is num lock sensitive it is never caps lock
// sensitive, thus the else here.
else if (keyIndex->second.m_capsLockSensitive) {
if ((currentMask & KeyModifierCapsLock) != 0) {
return true;
}
}
return false;
}
bool
CXWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys,
SysKeyID& keycode,
KeyModifierMask& finalMask,
KeySymIndex keyIndex,
KeyModifierMask currentMask,
EKeyAction action,
bool isHalfDuplex) const
{
// keyIndex must be valid
assert(keyIndex != m_keysymMap.end());
// get the keysym we're trying to generate and possible keycodes
const KeySym keysym = keyIndex->first;
const KeyMapping& mapping = keyIndex->second;
LOG((CLOG_DEBUG2 "keysym = 0x%08x", keysym));
// get the best keycode index for the keysym and modifiers. note
// that (bestIndex & 1) == 0 if the keycode is a shift modifier
// and (bestIndex & 2) == 0 if the keycode is a mode switch
// modifier. this is important later because we don't want
// adjustModifiers() to adjust a modifier if that's the key we're
// mapping.
unsigned int bestIndex = findBestKeyIndex(keyIndex, currentMask);
// get the keycode
keycode = mapping.m_keycode[bestIndex];
// flip low bit of bestIndex if shift is inverted. if there's a
// keycode for this new index then use it. otherwise use the old
// keycode. you'd think we should fail if there isn't a keycode
// for the new index but some keymaps only include the upper case
// keysyms (notably those on Sun Solaris) so to handle the missing
// lower case keysyms we just use the old keycode. note that
// isShiftInverted() will always return false for a shift modifier.
if (isShiftInverted(keyIndex, currentMask)) {
LOG((CLOG_DEBUG2 "shift is inverted"));
bestIndex ^= 1;
if (mapping.m_keycode[bestIndex] != 0) {
keycode = mapping.m_keycode[bestIndex];
}
}
LOG((CLOG_DEBUG2 "bestIndex = %d, keycode = %d", bestIndex, keycode));
// compute desired mask. the desired mask is the one that matches
// bestIndex, except if the key being synthesized is a shift key
// where we desire what we already have or if it's the mode switch
// key where we only desire to adjust shift. also, if the keycode
// is not sensitive to shift then don't adjust it, otherwise
// something like shift+home would become just home. similiarly
// for mode switch.
KeyModifierMask desiredMask = currentMask;
if (keyIndex->second.m_modifierMask != KeyModifierShift) {
if (keyIndex->second.m_shiftSensitive[bestIndex]) {
if ((bestIndex & 1) != 0) {
desiredMask |= KeyModifierShift;
}
else {
desiredMask &= ~KeyModifierShift;
}
}
if (keyIndex->second.m_modifierMask != KeyModifierModeSwitch) {
if (keyIndex->second.m_modeSwitchSensitive[bestIndex]) {
if ((bestIndex & 2) != 0) {
desiredMask |= KeyModifierModeSwitch;
}
else {
desiredMask &= ~KeyModifierModeSwitch;
}
}
}
}
// adjust the modifiers to match the desired modifiers
Keystrokes undo;
KeyModifierMask tmpMask = currentMask;
if (!adjustModifiers(keys, undo, tmpMask, desiredMask)) {
LOG((CLOG_DEBUG2 "failed to adjust modifiers"));
return false;
}
// note if the press of a half-duplex key should be treated as a release
if (isHalfDuplex && (currentMask & mapping.m_modifierMask) != 0) {
action = kRelease;
}
// add the key event
Keystroke keystroke;
keystroke.m_sysKeyID = 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;
}
// put undo keystrokes at end of keystrokes in reverse order
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.
finalMask = currentMask;
if (mapping.m_modifierMask != 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 ((m_toggleModifierMask & mapping.m_modifierMask) != 0) {
if (isHalfDuplex) {
finalMask ^= mapping.m_modifierMask;
}
}
else if (action == kPress) {
finalMask |= mapping.m_modifierMask;
}
}
return true;
}
bool
CXWindowsSecondaryScreen::adjustModifiers(Keystrokes& keys,
Keystrokes& undo,
KeyModifierMask& inOutMask,
KeyModifierMask desiredMask) const
{
// get mode switch set correctly. do this before shift because
// mode switch may be sensitive to the shift modifier and will
// set/reset it as necessary.
const bool wantModeSwitch = ((desiredMask & KeyModifierModeSwitch) != 0);
const bool haveModeSwitch = ((inOutMask & KeyModifierModeSwitch) != 0);
if (wantModeSwitch != haveModeSwitch) {
LOG((CLOG_DEBUG2 "fix mode switch"));
// adjust shift if necessary
KeySymIndex modeSwitchIndex = m_keysymMap.find(m_modeSwitchKeysym);
assert(modeSwitchIndex != m_keysymMap.end());
if (modeSwitchIndex->second.m_shiftSensitive[0]) {
const bool wantShift = false;
const bool haveShift = ((inOutMask & KeyModifierShift) != 0);
if (wantShift != haveShift) {
// add shift keystrokes
LOG((CLOG_DEBUG2 "fix shift for mode switch"));
if (!adjustModifier(keys, undo, m_shiftKeysym, wantShift)) {
return false;
}
inOutMask ^= KeyModifierShift;
}
}
// add mode switch keystrokes
if (!adjustModifier(keys, undo, m_modeSwitchKeysym, wantModeSwitch)) {
return false;
}
inOutMask ^= KeyModifierModeSwitch;
}
// get shift set correctly
const bool wantShift = ((desiredMask & KeyModifierShift) != 0);
const bool haveShift = ((inOutMask & KeyModifierShift) != 0);
if (wantShift != haveShift) {
// add shift keystrokes
LOG((CLOG_DEBUG2 "fix shift"));
if (!adjustModifier(keys, undo, m_shiftKeysym, wantShift)) {
return false;
}
inOutMask ^= KeyModifierShift;
}
return true;
}
bool
CXWindowsSecondaryScreen::adjustModifier(Keystrokes& keys,
Keystrokes& undo, KeySym keysym, bool desireActive) const
{
// this method generates keystrokes to change a modifier into the
// desired state. under X11, we only expect to adjust the shift
// and mode switch states. other modifiers don't affect keysym
// generation, except num lock and caps lock and we don't change
// those but instead just invert the handling of the shift key.
// we don't check here if the modifier is already in the desired
// state; the caller should do that.
// get the key mapping for keysym
KeySymIndex keyIndex = m_keysymMap.find(keysym);
if (keyIndex == m_keysymMap.end() || keyIndex->second.m_keycode[0] == 0) {
// no keycode for keysym or keycode is not a modifier
LOG((CLOG_DEBUG2 "no modifier for 0x%08x", keysym));
return false;
}
// this had better be a modifier
assert(keyIndex->second.m_modifierMask != 0);
// we do not handle toggle modifiers here. they never need to be
// adjusted
assert((keyIndex->second.m_modifierMask & m_toggleModifierMask) == 0);
// initialize keystroke
Keystroke keystroke;
keystroke.m_repeat = false;
// releasing a modifier is quite different from pressing one.
// when we release a modifier we have to release every keycode that
// is assigned to the modifier since the modifier is active if any
// one of them is down. when we press a modifier we just have to
// press one of those keycodes.
if (desireActive) {
// press
keystroke.m_sysKeyID = keyIndex->second.m_keycode[0];
keystroke.m_press = true;
keys.push_back(keystroke);
keystroke.m_press = false;
undo.push_back(keystroke);
}
else {
// release
KeyCodeToModifierMap::const_iterator index =
m_keycodeToModifier.find(keyIndex->second.m_keycode[0]);
if (index != m_keycodeToModifier.end()) {
const KeyCodes& keycodes = m_modifierKeycodes[index->second];
for (KeyCodes::const_iterator j = keycodes.begin();
j != keycodes.end(); ++j) {
if (isKeyDown(*j)) {
keystroke.m_sysKeyID = *j;
keystroke.m_press = false;
keys.push_back(keystroke);
keystroke.m_press = true;
undo.push_back(keystroke);
}
}
}
}
return true;
}
void
CXWindowsSecondaryScreen::fakeKeyEvent(SysKeyID keycode, bool press) const
{
CDisplayLock display(m_screen);
if (display != NULL) {
XTestFakeKeyEvent(display, keycode, press ? True : False, CurrentTime);
}
}
void
CXWindowsSecondaryScreen::fakeMouseButton(ButtonID button, bool press) const
{
const unsigned int xButton = mapButton(button);
if (xButton != 0) {
CDisplayLock display(m_screen);
if (display != NULL) {
XTestFakeButtonEvent(display, xButton,
press ? True : False, CurrentTime);
}
}
}
void
CXWindowsSecondaryScreen::fakeMouseMove(SInt32 x, SInt32 y) const
{
CDisplayLock display(m_screen);
if (m_xinerama && m_xtestIsXineramaUnaware) {
XWarpPointer(display, None, m_screen->getRoot(), 0, 0, 0, 0, x, y);
}
else {
Display* pDisplay = display;
XTestFakeMotionEvent(display, DefaultScreen(pDisplay),
x, y, CurrentTime);
}
}
void
CXWindowsSecondaryScreen::fakeMouseWheel(SInt32 delta) const
{
// 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);
}
}
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
updateKeysymMap(display);
}
void
CXWindowsSecondaryScreen::updateKeys(KeyState* keys)
{
CDisplayLock display(m_screen);
// ask server which keys are pressed
char xkeys[32];
XQueryKeymap(display, xkeys);
// transfer to our state
for (UInt32 i = 0, j = 0; i < 32; j += 8, ++i) {
keys[j + 0] = ((xkeys[i] & 0x01) != 0) ? kDown : 0;
keys[j + 1] = ((xkeys[i] & 0x02) != 0) ? kDown : 0;
keys[j + 2] = ((xkeys[i] & 0x04) != 0) ? kDown : 0;
keys[j + 3] = ((xkeys[i] & 0x08) != 0) ? kDown : 0;
keys[j + 4] = ((xkeys[i] & 0x10) != 0) ? kDown : 0;
keys[j + 5] = ((xkeys[i] & 0x20) != 0) ? kDown : 0;
keys[j + 6] = ((xkeys[i] & 0x40) != 0) ? kDown : 0;
keys[j + 7] = ((xkeys[i] & 0x80) != 0) ? kDown : 0;
}
// update mappings and current modifiers and mouse buttons
doUpdateKeys(display);
}
void
CXWindowsSecondaryScreen::updateKeysymMap(Display* display)
{
// there are up to 4 keysyms per keycode
static const unsigned int maxKeysyms = 4;
// 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;
}
// get modifier map from server
XModifierKeymap* modifiers = XGetModifierMapping(display);
// determine shift and mode switch sensitivity. a keysym is shift
// or mode switch sensitive if its keycode is. a keycode is mode
// mode switch sensitive if it has keysyms for indices 2 or 3.
// it's shift sensitive if the keysym for index 1 (if any) is
// different from the keysym for index 0 and, if the keysym for
// for index 3 (if any) is different from the keysym for index 2.
// that is, if shift changes the generated keysym for the keycode.
std::vector<bool> usesShift(numKeycodes);
std::vector<bool> usesModeSwitch(numKeycodes);
for (int i = 0; i < numKeycodes; ++i) {
// check mode switch first
if (numKeysyms > 2 &&
keysyms[i * keysymsPerKeycode + 2] != NoSymbol ||
keysyms[i * keysymsPerKeycode + 3] != NoSymbol) {
usesModeSwitch[i] = true;
}
// check index 0 with index 1 keysyms
if (keysyms[i * keysymsPerKeycode + 0] != NoSymbol &&
keysyms[i * keysymsPerKeycode + 1] != NoSymbol &&
keysyms[i * keysymsPerKeycode + 1] !=
keysyms[i * keysymsPerKeycode + 0]) {
usesShift[i] = true;
}
else if (numKeysyms >= 4 &&
keysyms[i * keysymsPerKeycode + 2] != NoSymbol &&
keysyms[i * keysymsPerKeycode + 3] != NoSymbol &&
keysyms[i * keysymsPerKeycode + 3] !=
keysyms[i * keysymsPerKeycode + 2]) {
usesShift[i] = true;
}
}
// initialize
m_keysymMap.clear();
int keysPerModifier = modifiers->max_keypermod;
// for each modifier keycode, get the index 0 keycode and add it to
// the keysym map. also collect all keycodes for each modifier.
m_keycodeToModifier.clear();
for (ModifierIndex i = 0; i < 8; ++i) {
// start with no keycodes for this modifier
m_modifierKeycodes[i].clear();
// no mask for this modifier
m_modifierIndexToMask[i] = 0;
// add each keycode for modifier
for (unsigned int j = 0; j < keysPerModifier; ++j) {
// get keycode and ignore unset keycodes
KeyCode keycode = modifiers->modifiermap[i * keysPerModifier + j];
if (keycode == 0) {
continue;
}
// save keycode for modifier and modifier for keycode
m_modifierKeycodes[i].push_back(keycode);
m_keycodeToModifier[keycode] = i;
// get keysym and get/create key mapping
const int keycodeIndex = keycode - minKeycode;
const KeySym keysym = keysyms[keycodeIndex *
keysymsPerKeycode + 0];
KeyMapping& mapping = m_keysymMap[keysym];
// skip if we already have a keycode for this index
if (mapping.m_keycode[0] != 0) {
continue;
}
// save modifier mask
m_modifierIndexToMask[i] = mapToModifierMask(i, keysym);
// fill in keysym info
mapping.m_keycode[0] = keycode;
mapping.m_shiftSensitive[0] = usesShift[keycodeIndex];
mapping.m_modeSwitchSensitive[0] = usesModeSwitch[keycodeIndex];
mapping.m_modifierMask = m_modifierIndexToMask[i];
mapping.m_capsLockSensitive = false;
mapping.m_numLockSensitive = false;
}
}
// create a convenient NoSymbol entry (if it doesn't exist yet).
// sometimes it's useful to handle NoSymbol like a normal keysym.
// remove any entry for NoSymbol. that keysym doesn't count.
{
KeyMapping& mapping = m_keysymMap[NoSymbol];
for (unsigned int i = 0; i < numKeysyms; ++i) {
mapping.m_keycode[i] = 0;
mapping.m_shiftSensitive[i] = false;
mapping.m_modeSwitchSensitive[i] = false;
}
mapping.m_modifierMask = 0;
mapping.m_capsLockSensitive = false;
mapping.m_numLockSensitive = false;
}
// add each keysym to the map, unless we've already inserted a key
// for that keysym index.
for (int i = 0; i < numKeycodes; ++i) {
for (unsigned int j = 0; j < numKeysyms; ++j) {
// lookup keysym
const KeySym keysym = keysyms[i * keysymsPerKeycode + j];
if (keysym == NoSymbol) {
continue;
}
KeyMapping& mapping = m_keysymMap[keysym];
// skip if we already have a keycode for this index
if (mapping.m_keycode[j] != 0) {
continue;
}
// fill in keysym info
if (mapping.m_keycode[0] == 0) {
mapping.m_modifierMask = 0;
}
mapping.m_keycode[j] = static_cast<KeyCode>(
minKeycode + i);
mapping.m_shiftSensitive[j] = usesShift[i];
mapping.m_modeSwitchSensitive[j] = usesModeSwitch[i];
mapping.m_numLockSensitive = adjustForNumLock(keysym);
mapping.m_capsLockSensitive = adjustForCapsLock(keysym);
}
}
// choose the keysym to use for each modifier. if the modifier
// isn't mapped then use NoSymbol. if a modifier has both left
// and right versions then (arbitrarily) prefer the left. also
// collect the available modifier bits.
struct CModifierBitInfo {
public:
KeySym CXWindowsSecondaryScreen::*m_keysym;
KeySym m_left;
KeySym m_right;
};
static const CModifierBitInfo s_modifierBitTable[] = {
{ &CXWindowsSecondaryScreen::m_shiftKeysym, XK_Shift_L, XK_Shift_R },
{ &CXWindowsSecondaryScreen::m_ctrlKeysym, XK_Control_L, XK_Control_R },
{ &CXWindowsSecondaryScreen::m_altKeysym, XK_Alt_L, XK_Alt_R },
{ &CXWindowsSecondaryScreen::m_metaKeysym, XK_Meta_L, XK_Meta_R },
{ &CXWindowsSecondaryScreen::m_superKeysym, XK_Super_L, XK_Super_R },
{ &CXWindowsSecondaryScreen::m_modeSwitchKeysym, XK_Mode_switch, NoSymbol },
{ &CXWindowsSecondaryScreen::m_numLockKeysym, XK_Num_Lock, NoSymbol },
{ &CXWindowsSecondaryScreen::m_capsLockKeysym, XK_Caps_Lock, NoSymbol },
{ &CXWindowsSecondaryScreen::m_scrollLockKeysym, XK_Scroll_Lock, NoSymbol }
};
m_modifierMask = 0;
m_toggleModifierMask = 0;
for (size_t i = 0; i < sizeof(s_modifierBitTable) /
sizeof(s_modifierBitTable[0]); ++i) {
const CModifierBitInfo& info = s_modifierBitTable[i];
// find available keysym
KeySymIndex keyIndex = m_keysymMap.find(info.m_left);
if (keyIndex == m_keysymMap.end() && info.m_right != NoSymbol) {
keyIndex = m_keysymMap.find(info.m_right);
}
if (keyIndex != m_keysymMap.end() &&
keyIndex->second.m_modifierMask != 0) {
this->*(info.m_keysym) = keyIndex->first;
}
else {
this->*(info.m_keysym) = NoSymbol;
continue;
}
// add modifier bit
m_modifierMask |= keyIndex->second.m_modifierMask;
if (isToggleKeysym(this->*(info.m_keysym))) {
m_toggleModifierMask |= keyIndex->second.m_modifierMask;
}
}
// if there's no mode switch key mapped then remove all keycodes
// that depend on it and no keycode can be mode switch sensitive.
if (m_modeSwitchKeysym == NoSymbol) {
LOG((CLOG_DEBUG2 "no mode switch in keymap"));
for (KeySymMap::iterator i = m_keysymMap.begin();
i != m_keysymMap.end(); ) {
i->second.m_keycode[2] = 0;
i->second.m_keycode[3] = 0;
i->second.m_modeSwitchSensitive[0] = false;
i->second.m_modeSwitchSensitive[1] = false;
i->second.m_modeSwitchSensitive[2] = false;
i->second.m_modeSwitchSensitive[3] = false;
// if this keysym no has no keycodes then remove it
// except for the NoSymbol keysym mapping.
if (i->second.m_keycode[0] == 0 && i->second.m_keycode[1] == 0) {
m_keysymMap.erase(i++);
}
else {
++i;
}
}
}
// clean up
XFree(keysyms);
XFreeModifiermap(modifiers);
}
KeyModifierMask
CXWindowsSecondaryScreen::getModifiers() const
{
CDisplayLock display(m_screen);
// 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
KeyModifierMask mask = 0;
for (ModifierIndex i = 0; i < 8; ++i) {
const KeyModifierMask bit = m_modifierIndexToMask[i];
if ((bit & m_toggleModifierMask) == 0) {
for (KeyCodes::const_iterator j = m_modifierKeycodes[i].begin();
j != m_modifierKeycodes[i].end(); ++j) {
// XXX -- is this right?
if (isKeyDown(*j)) {
mask |= bit;
break;
}
}
}
else if ((bit & state) != 0) {
// toggle is on
mask |= bit;
}
}
return mask;
}
CSecondaryScreen::SysKeyID
CXWindowsSecondaryScreen::getToggleSysKey(KeyID keyID) const
{
// convert KeyID to KeySym
KeySym keysym;
switch (keyID) {
case kKeyNumLock:
keysym = m_numLockKeysym;
break;
case kKeyCapsLock:
keysym = m_capsLockKeysym;
break;
case kKeyScrollLock:
keysym = m_scrollLockKeysym;
break;
default:
return 0;
}
// lookup the key mapping
KeySymIndex index = m_keysymMap.find(keysym);
if (index == m_keysymMap.end()) {
return 0;
}
return index->second.m_keycode[0];
}
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;
}
}
KeyModifierMask
CXWindowsSecondaryScreen::mapToModifierMask(
ModifierIndex i, KeySym keysym) const
{
// some modifier indices (0,1,2) are dedicated to particular uses,
// the rest depend on the keysyms bound.
switch (i) {
case 0:
return KeyModifierShift;
case 1:
return KeyModifierCapsLock;
case 2:
return KeyModifierControl;
default:
switch (keysym) {
case XK_Shift_L:
case XK_Shift_R:
return KeyModifierShift;
case XK_Control_L:
case XK_Control_R:
return KeyModifierControl;
case XK_Alt_L:
case XK_Alt_R:
return KeyModifierAlt;
case XK_Meta_L:
case XK_Meta_R:
return KeyModifierMeta;
case XK_Super_L:
case XK_Super_R:
return KeyModifierSuper;
case XK_Mode_switch:
return KeyModifierModeSwitch;
case XK_Caps_Lock:
return KeyModifierCapsLock;
case XK_Num_Lock:
return KeyModifierNumLock;
case XK_Scroll_Lock:
return KeyModifierScrollLock;
default:
return 0;
}
}
}
// 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
KeySym
CXWindowsSecondaryScreen::keyIDToKeySym(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:
return g_mapE000[id & 0xff];
#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
return static_cast<KeySym>(id);
}
else {
// lookup keysym in table
return CXWindowsUtil::mapUCS4ToKeySym(id);
}
// fail if unknown key
if (keysym == NoSymbol) {
return keysym;
}
// 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 & KeyModifierShift) != 0) {
keysym = XK_ISO_Left_Tab;
}
// some keysyms have emergency backups (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). figure out the emergency backup.
KeySym backupKeysym;
switch (keysym) {
case XK_KP_Home:
backupKeysym = XK_Home;
break;
case XK_KP_Left:
backupKeysym = XK_Left;
break;
case XK_KP_Up:
backupKeysym = XK_Up;
break;
case XK_KP_Right:
backupKeysym = XK_Right;
break;
case XK_KP_Down:
backupKeysym = XK_Down;
break;
case XK_KP_Prior:
backupKeysym = XK_Prior;
break;
case XK_KP_Next:
backupKeysym = XK_Next;
break;
case XK_KP_End:
backupKeysym = XK_End;
break;
case XK_KP_Insert:
backupKeysym = XK_Insert;
break;
case XK_KP_Delete:
backupKeysym = XK_Delete;
break;
case XK_ISO_Left_Tab:
backupKeysym = XK_Tab;
break;
default:
backupKeysym = keysym;
break;
}
// see if the keysym is assigned to any keycode. if not and the
// backup keysym is then use the backup keysym.
if (backupKeysym != keysym &&
m_keysymMap.find(keysym) == m_keysymMap.end() &&
m_keysymMap.find(backupKeysym) != m_keysymMap.end()) {
keysym = backupKeysym;
}
return keysym;
}
bool
CXWindowsSecondaryScreen::decomposeKeySym(KeySym keysym,
KeySyms& decomposed) const
{
// unfortunately, X11 doesn't appear to have any way of
// decomposing a keysym into its component keysyms. we'll
// use a lookup table for certain character sets.
const KeySymsMap& table = getDecomposedKeySymTable();
KeySymsMap::const_iterator i = table.find(keysym);
if (i == table.end()) {
return false;
}
decomposed = i->second;
return true;
}
bool
CXWindowsSecondaryScreen::adjustForNumLock(KeySym keysym) const
{
return (IsKeypadKey(keysym) || IsPrivateKeypadKey(keysym));
}
bool
CXWindowsSecondaryScreen::adjustForCapsLock(KeySym keysym) const
{
KeySym lKey, uKey;
XConvertCase(keysym, &lKey, &uKey);
return (lKey != uKey);
}
const CXWindowsSecondaryScreen::KeySymsMap&
CXWindowsSecondaryScreen::getDecomposedKeySymTable()
{
static const KeySym s_rawTable[] = {
// non-dead version of dead keys
XK_grave, XK_dead_grave, XK_space, 0,
XK_acute, XK_dead_acute, XK_space, 0,
XK_asciicircum, XK_dead_circumflex, XK_space, 0,
XK_asciitilde, XK_dead_tilde, XK_space, 0,
XK_cedilla, XK_dead_cedilla, XK_space, 0,
XK_ogonek, XK_dead_ogonek, XK_space, 0,
XK_caron, XK_dead_caron, XK_space, 0,
XK_abovedot, XK_dead_abovedot, XK_space, 0,
XK_doubleacute, XK_dead_doubleacute, XK_space, 0,
XK_breve, XK_dead_breve, XK_space, 0,
XK_macron, XK_dead_macron, XK_space, 0,
// Latin-1 (ISO 8859-1)
XK_Agrave, XK_dead_grave, XK_A, 0,
XK_Aacute, XK_dead_acute, XK_A, 0,
XK_Acircumflex, XK_dead_circumflex, XK_A, 0,
XK_Atilde, XK_dead_tilde, XK_A, 0,
XK_Adiaeresis, XK_dead_diaeresis, XK_A, 0,
XK_Aring, XK_dead_abovering, XK_A, 0,
XK_Ccedilla, XK_dead_cedilla, XK_C, 0,
XK_Egrave, XK_dead_grave, XK_E, 0,
XK_Eacute, XK_dead_acute, XK_E, 0,
XK_Ecircumflex, XK_dead_circumflex, XK_E, 0,
XK_Ediaeresis, XK_dead_diaeresis, XK_E, 0,
XK_Igrave, XK_dead_grave, XK_I, 0,
XK_Iacute, XK_dead_acute, XK_I, 0,
XK_Icircumflex, XK_dead_circumflex, XK_I, 0,
XK_Idiaeresis, XK_dead_diaeresis, XK_I, 0,
XK_Ntilde, XK_dead_tilde, XK_N, 0,
XK_Ograve, XK_dead_grave, XK_O, 0,
XK_Oacute, XK_dead_acute, XK_O, 0,
XK_Ocircumflex, XK_dead_circumflex, XK_O, 0,
XK_Otilde, XK_dead_tilde, XK_O, 0,
XK_Odiaeresis, XK_dead_diaeresis, XK_O, 0,
XK_Ugrave, XK_dead_grave, XK_U, 0,
XK_Uacute, XK_dead_acute, XK_U, 0,
XK_Ucircumflex, XK_dead_circumflex, XK_U, 0,
XK_Udiaeresis, XK_dead_diaeresis, XK_U, 0,
XK_Yacute, XK_dead_acute, XK_Y, 0,
XK_agrave, XK_dead_grave, XK_a, 0,
XK_aacute, XK_dead_acute, XK_a, 0,
XK_acircumflex, XK_dead_circumflex, XK_a, 0,
XK_atilde, XK_dead_tilde, XK_a, 0,
XK_adiaeresis, XK_dead_diaeresis, XK_a, 0,
XK_aring, XK_dead_abovering, XK_a, 0,
XK_ccedilla, XK_dead_cedilla, XK_c, 0,
XK_egrave, XK_dead_grave, XK_e, 0,
XK_eacute, XK_dead_acute, XK_e, 0,
XK_ecircumflex, XK_dead_circumflex, XK_e, 0,
XK_ediaeresis, XK_dead_diaeresis, XK_e, 0,
XK_igrave, XK_dead_grave, XK_i, 0,
XK_iacute, XK_dead_acute, XK_i, 0,
XK_icircumflex, XK_dead_circumflex, XK_i, 0,
XK_idiaeresis, XK_dead_diaeresis, XK_i, 0,
XK_ntilde, XK_dead_tilde, XK_n, 0,
XK_ograve, XK_dead_grave, XK_o, 0,
XK_oacute, XK_dead_acute, XK_o, 0,
XK_ocircumflex, XK_dead_circumflex, XK_o, 0,
XK_otilde, XK_dead_tilde, XK_o, 0,
XK_odiaeresis, XK_dead_diaeresis, XK_o, 0,
XK_ugrave, XK_dead_grave, XK_u, 0,
XK_uacute, XK_dead_acute, XK_u, 0,
XK_ucircumflex, XK_dead_circumflex, XK_u, 0,
XK_udiaeresis, XK_dead_diaeresis, XK_u, 0,
XK_yacute, XK_dead_acute, XK_y, 0,
XK_ydiaeresis, XK_dead_diaeresis, XK_y, 0,
// Latin-2 (ISO 8859-2)
XK_Aogonek, XK_dead_ogonek, XK_A, 0,
XK_Lcaron, XK_dead_caron, XK_L, 0,
XK_Sacute, XK_dead_acute, XK_S, 0,
XK_Scaron, XK_dead_caron, XK_S, 0,
XK_Scedilla, XK_dead_cedilla, XK_S, 0,
XK_Tcaron, XK_dead_caron, XK_T, 0,
XK_Zacute, XK_dead_acute, XK_Z, 0,
XK_Zcaron, XK_dead_caron, XK_Z, 0,
XK_Zabovedot, XK_dead_abovedot, XK_Z, 0,
XK_aogonek, XK_dead_ogonek, XK_a, 0,
XK_lcaron, XK_dead_caron, XK_l, 0,
XK_sacute, XK_dead_acute, XK_s, 0,
XK_scaron, XK_dead_caron, XK_s, 0,
XK_scedilla, XK_dead_cedilla, XK_s, 0,
XK_tcaron, XK_dead_caron, XK_t, 0,
XK_zacute, XK_dead_acute, XK_z, 0,
XK_zcaron, XK_dead_caron, XK_z, 0,
XK_zabovedot, XK_dead_abovedot, XK_z, 0,
XK_Racute, XK_dead_acute, XK_R, 0,
XK_Abreve, XK_dead_breve, XK_A, 0,
XK_Lacute, XK_dead_acute, XK_L, 0,
XK_Cacute, XK_dead_acute, XK_C, 0,
XK_Ccaron, XK_dead_caron, XK_C, 0,
XK_Eogonek, XK_dead_ogonek, XK_E, 0,
XK_Ecaron, XK_dead_caron, XK_E, 0,
XK_Dcaron, XK_dead_caron, XK_D, 0,
XK_Nacute, XK_dead_acute, XK_N, 0,
XK_Ncaron, XK_dead_caron, XK_N, 0,
XK_Odoubleacute, XK_dead_doubleacute, XK_O, 0,
XK_Rcaron, XK_dead_caron, XK_R, 0,
XK_Uring, XK_dead_abovering, XK_U, 0,
XK_Udoubleacute, XK_dead_doubleacute, XK_U, 0,
XK_Tcedilla, XK_dead_cedilla, XK_T, 0,
XK_racute, XK_dead_acute, XK_r, 0,
XK_abreve, XK_dead_breve, XK_a, 0,
XK_lacute, XK_dead_acute, XK_l, 0,
XK_cacute, XK_dead_acute, XK_c, 0,
XK_ccaron, XK_dead_caron, XK_c, 0,
XK_eogonek, XK_dead_ogonek, XK_e, 0,
XK_ecaron, XK_dead_caron, XK_e, 0,
XK_dcaron, XK_dead_caron, XK_d, 0,
XK_nacute, XK_dead_acute, XK_n, 0,
XK_ncaron, XK_dead_caron, XK_n, 0,
XK_odoubleacute, XK_dead_doubleacute, XK_o, 0,
XK_rcaron, XK_dead_caron, XK_r, 0,
XK_uring, XK_dead_abovering, XK_u, 0,
XK_udoubleacute, XK_dead_doubleacute, XK_u, 0,
XK_tcedilla, XK_dead_cedilla, XK_t, 0,
// Latin-3 (ISO 8859-3)
XK_Hcircumflex, XK_dead_circumflex, XK_H, 0,
XK_Iabovedot, XK_dead_abovedot, XK_I, 0,
XK_Gbreve, XK_dead_breve, XK_G, 0,
XK_Jcircumflex, XK_dead_circumflex, XK_J, 0,
XK_hcircumflex, XK_dead_circumflex, XK_h, 0,
XK_gbreve, XK_dead_breve, XK_g, 0,
XK_jcircumflex, XK_dead_circumflex, XK_j, 0,
XK_Cabovedot, XK_dead_abovedot, XK_C, 0,
XK_Ccircumflex, XK_dead_circumflex, XK_C, 0,
XK_Gabovedot, XK_dead_abovedot, XK_G, 0,
XK_Gcircumflex, XK_dead_circumflex, XK_G, 0,
XK_Ubreve, XK_dead_breve, XK_U, 0,
XK_Scircumflex, XK_dead_circumflex, XK_S, 0,
XK_cabovedot, XK_dead_abovedot, XK_c, 0,
XK_ccircumflex, XK_dead_circumflex, XK_c, 0,
XK_gabovedot, XK_dead_abovedot, XK_g, 0,
XK_gcircumflex, XK_dead_circumflex, XK_g, 0,
XK_ubreve, XK_dead_breve, XK_u, 0,
XK_scircumflex, XK_dead_circumflex, XK_s, 0,
// Latin-4 (ISO 8859-4)
XK_scircumflex, XK_dead_circumflex, XK_s, 0,
XK_Rcedilla, XK_dead_cedilla, XK_R, 0,
XK_Itilde, XK_dead_tilde, XK_I, 0,
XK_Lcedilla, XK_dead_cedilla, XK_L, 0,
XK_Emacron, XK_dead_macron, XK_E, 0,
XK_Gcedilla, XK_dead_cedilla, XK_G, 0,
XK_rcedilla, XK_dead_cedilla, XK_r, 0,
XK_itilde, XK_dead_tilde, XK_i, 0,
XK_lcedilla, XK_dead_cedilla, XK_l, 0,
XK_emacron, XK_dead_macron, XK_e, 0,
XK_gcedilla, XK_dead_cedilla, XK_g, 0,
XK_Amacron, XK_dead_macron, XK_A, 0,
XK_Iogonek, XK_dead_ogonek, XK_I, 0,
XK_Eabovedot, XK_dead_abovedot, XK_E, 0,
XK_Imacron, XK_dead_macron, XK_I, 0,
XK_Ncedilla, XK_dead_cedilla, XK_N, 0,
XK_Omacron, XK_dead_macron, XK_O, 0,
XK_Kcedilla, XK_dead_cedilla, XK_K, 0,
XK_Uogonek, XK_dead_ogonek, XK_U, 0,
XK_Utilde, XK_dead_tilde, XK_U, 0,
XK_Umacron, XK_dead_macron, XK_U, 0,
XK_amacron, XK_dead_macron, XK_a, 0,
XK_iogonek, XK_dead_ogonek, XK_i, 0,
XK_eabovedot, XK_dead_abovedot, XK_e, 0,
XK_imacron, XK_dead_macron, XK_i, 0,
XK_ncedilla, XK_dead_cedilla, XK_n, 0,
XK_omacron, XK_dead_macron, XK_o, 0,
XK_kcedilla, XK_dead_cedilla, XK_k, 0,
XK_uogonek, XK_dead_ogonek, XK_u, 0,
XK_utilde, XK_dead_tilde, XK_u, 0,
XK_umacron, XK_dead_macron, XK_u, 0,
// Latin-8 (ISO 8859-14)
#if defined(XK_Babovedot)
XK_Babovedot, XK_dead_abovedot, XK_B, 0,
XK_babovedot, XK_dead_abovedot, XK_b, 0,
XK_Dabovedot, XK_dead_abovedot, XK_D, 0,
XK_Wgrave, XK_dead_grave, XK_W, 0,
XK_Wacute, XK_dead_acute, XK_W, 0,
XK_dabovedot, XK_dead_abovedot, XK_d, 0,
XK_Ygrave, XK_dead_grave, XK_Y, 0,
XK_Fabovedot, XK_dead_abovedot, XK_F, 0,
XK_fabovedot, XK_dead_abovedot, XK_f, 0,
XK_Mabovedot, XK_dead_abovedot, XK_M, 0,
XK_mabovedot, XK_dead_abovedot, XK_m, 0,
XK_Pabovedot, XK_dead_abovedot, XK_P, 0,
XK_wgrave, XK_dead_grave, XK_w, 0,
XK_pabovedot, XK_dead_abovedot, XK_p, 0,
XK_wacute, XK_dead_acute, XK_w, 0,
XK_Sabovedot, XK_dead_abovedot, XK_S, 0,
XK_ygrave, XK_dead_grave, XK_y, 0,
XK_Wdiaeresis, XK_dead_diaeresis, XK_W, 0,
XK_wdiaeresis, XK_dead_diaeresis, XK_w, 0,
XK_sabovedot, XK_dead_abovedot, XK_s, 0,
XK_Wcircumflex, XK_dead_circumflex, XK_W, 0,
XK_Tabovedot, XK_dead_abovedot, XK_T, 0,
XK_Ycircumflex, XK_dead_circumflex, XK_Y, 0,
XK_wcircumflex, XK_dead_circumflex, XK_w, 0,
XK_tabovedot, XK_dead_abovedot, XK_t, 0,
XK_ycircumflex, XK_dead_circumflex, XK_y, 0,
#endif
// Latin-9 (ISO 8859-15)
#if defined(XK_Ydiaeresis)
XK_Ydiaeresis, XK_dead_diaeresis, XK_Y, 0,
#endif
// end of table
0
};
// fill table if not yet initialized
if (s_decomposedKeySyms.empty()) {
const KeySym* scan = s_rawTable;
while (*scan != 0) {
// add an entry for this keysym
KeySyms& entry = s_decomposedKeySyms[*scan];
// add the decomposed keysyms for the keysym
while (*++scan != 0) {
entry.push_back(*scan);
}
// skip end of entry marker
++scan;
}
}
return s_decomposedKeySyms;
}
//
// CXWindowsSecondaryScreen::KeyMapping
//
CXWindowsSecondaryScreen::KeyMapping::KeyMapping()
{
m_keycode[0] = 0;
m_keycode[1] = 0;
m_keycode[2] = 0;
m_keycode[3] = 0;
}