• Main Page
  • Classes
  • Files
  • File List

CKeyMap.cpp

00001 /*
00002  * synergy -- mouse and keyboard sharing utility
00003  * Copyright (C) 2012 Bolton Software Ltd.
00004  * Copyright (C) 2005 Chris Schoeneman
00005  * 
00006  * This package is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * found in the file COPYING that should have accompanied this file.
00009  * 
00010  * This package is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00017  */
00018 
00019 #include "CKeyMap.h"
00020 #include "KeyTypes.h"
00021 #include "CLog.h"
00022 #include <assert.h>
00023 #include <cctype>
00024 #include <cstdlib>
00025 
00026 CKeyMap::CNameToKeyMap*         CKeyMap::s_nameToKeyMap      = NULL;
00027 CKeyMap::CNameToModifierMap*    CKeyMap::s_nameToModifierMap = NULL;
00028 CKeyMap::CKeyToNameMap*         CKeyMap::s_keyToNameMap      = NULL;
00029 CKeyMap::CModifierToNameMap*    CKeyMap::s_modifierToNameMap = NULL;
00030 
00031 CKeyMap::CKeyMap() :
00032     m_numGroups(0),
00033     m_composeAcrossGroups(false)
00034 {
00035     m_modifierKeyItem.m_id        = kKeyNone;
00036     m_modifierKeyItem.m_group     = 0;
00037     m_modifierKeyItem.m_button    = 0;
00038     m_modifierKeyItem.m_required  = 0;
00039     m_modifierKeyItem.m_sensitive = 0;
00040     m_modifierKeyItem.m_generates = 0;
00041     m_modifierKeyItem.m_dead      = false;
00042     m_modifierKeyItem.m_lock      = false;
00043     m_modifierKeyItem.m_client    = 0;
00044 }
00045 
00046 CKeyMap::~CKeyMap()
00047 {
00048     // do nothing
00049 }
00050 
00051 void
00052 CKeyMap::swap(CKeyMap& x)
00053 {
00054     m_keyIDMap.swap(x.m_keyIDMap);
00055     m_modifierKeys.swap(x.m_modifierKeys);
00056     m_halfDuplex.swap(x.m_halfDuplex);
00057     m_halfDuplexMods.swap(x.m_halfDuplexMods);
00058     SInt32 tmp1   = m_numGroups;
00059     m_numGroups   = x.m_numGroups;
00060     x.m_numGroups = tmp1;
00061     bool tmp2               = m_composeAcrossGroups;
00062     m_composeAcrossGroups   = x.m_composeAcrossGroups;
00063     x.m_composeAcrossGroups = tmp2;
00064 }
00065 
00066 void
00067 CKeyMap::addKeyEntry(const KeyItem& item)
00068 {
00069     // ignore kKeyNone
00070     if (item.m_id == kKeyNone) {
00071         return;
00072     }
00073 
00074     // resize number of groups for key
00075     SInt32 numGroups = item.m_group + 1;
00076     if (getNumGroups() > numGroups) {
00077         numGroups = getNumGroups();
00078     }
00079     KeyGroupTable& groupTable = m_keyIDMap[item.m_id];
00080     if (groupTable.size() < static_cast<size_t>(numGroups)) {
00081         groupTable.resize(numGroups);
00082     }
00083 
00084     // make a list from the item
00085     KeyItemList items;
00086     items.push_back(item);
00087 
00088     // set group and dead key flag on the item
00089     KeyItem& newItem = items.back();
00090     newItem.m_dead   = isDeadKey(item.m_id);
00091 
00092     // mask the required bits with the sensitive bits
00093     newItem.m_required &= newItem.m_sensitive;
00094 
00095     // see if we already have this item;  just return if so
00096     KeyEntryList& entries = groupTable[item.m_group];
00097     for (size_t i = 0, n = entries.size(); i < n; ++i) {
00098         if (entries[i].size() == 1 && newItem == entries[i][0]) {
00099             return;
00100         }
00101     }
00102 
00103     // add item list
00104     entries.push_back(items);
00105     LOG((CLOG_DEBUG5 "add key: %04x %d %03x %04x (%04x %04x %04x)%s", newItem.m_id, newItem.m_group, newItem.m_button, newItem.m_client, newItem.m_required, newItem.m_sensitive, newItem.m_generates, newItem.m_dead ? " dead" : ""));
00106 }
00107 
00108 void
00109 CKeyMap::addKeyAliasEntry(KeyID targetID, SInt32 group,
00110                 KeyModifierMask targetRequired,
00111                 KeyModifierMask targetSensitive,
00112                 KeyID sourceID,
00113                 KeyModifierMask sourceRequired,
00114                 KeyModifierMask sourceSensitive)
00115 {
00116     // if we can already generate the target as desired then we're done.
00117     if (findCompatibleKey(targetID, group, targetRequired,
00118                                 targetSensitive) != NULL) {
00119         return;
00120     }
00121 
00122     // find a compatible source, preferably in the same group
00123     for (SInt32 gd = 0, n = getNumGroups(); gd < n; ++gd) {
00124         SInt32 eg = getEffectiveGroup(group, gd);
00125         const KeyItemList* sourceEntry =
00126             findCompatibleKey(sourceID, eg,
00127                                 sourceRequired, sourceSensitive);
00128         if (sourceEntry != NULL && sourceEntry->size() == 1) {
00129             CKeyMap::KeyItem targetItem = sourceEntry->back();
00130             targetItem.m_id    = targetID;
00131             targetItem.m_group = eg;
00132             addKeyEntry(targetItem);
00133             break;
00134         }
00135     }
00136 }
00137 
00138 bool
00139 CKeyMap::addKeyCombinationEntry(KeyID id, SInt32 group,
00140                 const KeyID* keys, UInt32 numKeys)
00141 {
00142     // disallow kKeyNone
00143     if (id == kKeyNone) {
00144         return false;
00145     }
00146 
00147     SInt32 numGroups = group + 1;
00148     if (getNumGroups() > numGroups) {
00149         numGroups = getNumGroups();
00150     }
00151     KeyGroupTable& groupTable = m_keyIDMap[id];
00152     if (groupTable.size() < static_cast<size_t>(numGroups)) {
00153         groupTable.resize(numGroups);
00154     }
00155     if (!groupTable[group].empty()) {
00156         // key is already in the table
00157         return false;
00158     }
00159 
00160     // convert to buttons
00161     KeyItemList items;
00162     for (UInt32 i = 0; i < numKeys; ++i) {
00163         KeyIDMap::const_iterator gtIndex = m_keyIDMap.find(keys[i]);
00164         if (gtIndex == m_keyIDMap.end()) {
00165             return false;
00166         }
00167         const KeyGroupTable& groupTable = gtIndex->second;
00168 
00169         // if we allow group switching during composition then search all
00170         // groups for keys, otherwise search just the given group.
00171         SInt32 n = 1;
00172         if (m_composeAcrossGroups) {
00173             n = (SInt32)groupTable.size();
00174         }
00175 
00176         bool found = false;
00177         for (SInt32 gd = 0; gd < n && !found; ++gd) {
00178             SInt32 eg = (group + gd) % getNumGroups();
00179             const KeyEntryList& entries = groupTable[eg];
00180             for (size_t j = 0; j < entries.size(); ++j) {
00181                 if (entries[j].size() == 1) {
00182                     found = true;
00183                     items.push_back(entries[j][0]);
00184                     break;
00185                 }
00186             }
00187         }
00188         if (!found) {
00189             // required key is not in keyboard group
00190             return false;
00191         }
00192     }
00193 
00194     // add key
00195     groupTable[group].push_back(items);
00196     return true;
00197 }
00198 
00199 void
00200 CKeyMap::allowGroupSwitchDuringCompose()
00201 {
00202     m_composeAcrossGroups = true;
00203 }
00204 
00205 void
00206 CKeyMap::addHalfDuplexButton(KeyButton button)
00207 {
00208     m_halfDuplex.insert(button);
00209 }
00210 
00211 void
00212 CKeyMap::clearHalfDuplexModifiers()
00213 {
00214     m_halfDuplexMods.clear();
00215 }
00216 
00217 void
00218 CKeyMap::addHalfDuplexModifier(KeyID key)
00219 {
00220     m_halfDuplexMods.insert(key);
00221 }
00222 
00223 void
00224 CKeyMap::finish()
00225 {
00226     m_numGroups = findNumGroups();
00227 
00228     // make sure every key has the same number of groups
00229     for (KeyIDMap::iterator i = m_keyIDMap.begin();
00230                                 i != m_keyIDMap.end(); ++i) {
00231         i->second.resize(m_numGroups);
00232     }
00233 
00234     // compute keys that generate each modifier
00235     setModifierKeys();
00236 }
00237 
00238 void
00239 CKeyMap::foreachKey(ForeachKeyCallback cb, void* userData)
00240 {
00241     for (KeyIDMap::iterator i = m_keyIDMap.begin();
00242                                 i != m_keyIDMap.end(); ++i) {
00243         KeyGroupTable& groupTable = i->second;
00244         for (size_t group = 0; group < groupTable.size(); ++group) {
00245             KeyEntryList& entryList = groupTable[group];
00246             for (size_t j = 0; j < entryList.size(); ++j) {
00247                 KeyItemList& itemList = entryList[j];
00248                 for (size_t k = 0; k < itemList.size(); ++k) {
00249                     (*cb)(i->first, static_cast<SInt32>(group),
00250                                 itemList[k], userData);
00251                 }
00252             }
00253         }
00254     }
00255 }
00256 
00257 const CKeyMap::KeyItem*
00258 CKeyMap::mapKey(Keystrokes& keys, KeyID id, SInt32 group,
00259                 ModifierToKeys& activeModifiers,
00260                 KeyModifierMask& currentState,
00261                 KeyModifierMask desiredMask,
00262                 bool isAutoRepeat) const
00263 {
00264     LOG((CLOG_DEBUG1 "mapKey %04x (%d) with mask %04x, start state: %04x", id, id, desiredMask, currentState));
00265 
00266     // handle group change
00267     if (id == kKeyNextGroup) {
00268         keys.push_back(Keystroke(1, false, false));
00269         return NULL;
00270     }
00271     else if (id == kKeyPrevGroup) {
00272         keys.push_back(Keystroke(-1, false, false));
00273         return NULL;
00274     }
00275 
00276     const KeyItem* item;
00277     switch (id) {
00278     case kKeyShift_L:
00279     case kKeyShift_R:
00280     case kKeyControl_L:
00281     case kKeyControl_R:
00282     case kKeyAlt_L:
00283     case kKeyAlt_R:
00284     case kKeyMeta_L:
00285     case kKeyMeta_R:
00286     case kKeySuper_L:
00287     case kKeySuper_R:
00288     case kKeyAltGr:
00289     case kKeyCapsLock:
00290     case kKeyNumLock:
00291     case kKeyScrollLock:
00292         item = mapModifierKey(keys, id, group, activeModifiers,
00293                                 currentState, desiredMask, isAutoRepeat);
00294         break;
00295 
00296     case kKeySetModifiers:
00297         if (!keysForModifierState(0, group, activeModifiers, currentState,
00298                                 desiredMask, desiredMask, 0, keys)) {
00299             LOG((CLOG_DEBUG1 "unable to set modifiers %04x", desiredMask));
00300             return NULL;
00301         }
00302         return &m_modifierKeyItem;
00303 
00304     case kKeyClearModifiers:
00305         if (!keysForModifierState(0, group, activeModifiers, currentState,
00306                                 currentState & ~desiredMask,
00307                                 desiredMask, 0, keys)) {
00308             LOG((CLOG_DEBUG1 "unable to clear modifiers %04x", desiredMask));
00309             return NULL;
00310         }
00311         return &m_modifierKeyItem;
00312 
00313     default:
00314         if (isCommand(desiredMask)) {
00315             item = mapCommandKey(keys, id, group, activeModifiers,
00316                                 currentState, desiredMask, isAutoRepeat);
00317         }
00318         else {
00319             item = mapCharacterKey(keys, id, group, activeModifiers,
00320                                 currentState, desiredMask, isAutoRepeat);
00321         }
00322         break;
00323     }
00324 
00325     if (item != NULL) {
00326         LOG((CLOG_DEBUG1 "mapped to %03x, new state %04x", item->m_button, currentState));
00327     }
00328     return item;
00329 }
00330 
00331 SInt32
00332 CKeyMap::getNumGroups() const
00333 {
00334     return m_numGroups;
00335 }
00336 
00337 SInt32
00338 CKeyMap::getEffectiveGroup(SInt32 group, SInt32 offset) const
00339 {
00340     return (group + offset + getNumGroups()) % getNumGroups();
00341 }
00342 
00343 const CKeyMap::KeyItemList*
00344 CKeyMap::findCompatibleKey(KeyID id, SInt32 group,
00345                 KeyModifierMask required, KeyModifierMask sensitive) const
00346 {
00347     assert(group >= 0 && group < getNumGroups());
00348 
00349     KeyIDMap::const_iterator i = m_keyIDMap.find(id);
00350     if (i == m_keyIDMap.end()) {
00351         return NULL;
00352     }
00353 
00354     const KeyEntryList& entries = i->second[group];
00355     for (size_t j = 0; j < entries.size(); ++j) {
00356         if ((entries[j].back().m_sensitive & sensitive) == 0 ||
00357             (entries[j].back().m_required & sensitive) ==
00358                 (required & sensitive)) {
00359             return &entries[j];
00360         }
00361     }
00362 
00363     return NULL;
00364 }
00365 
00366 bool
00367 CKeyMap::isHalfDuplex(KeyID key, KeyButton button) const
00368 {
00369     return (m_halfDuplex.count(button) + m_halfDuplexMods.count(key) > 0);
00370 }
00371 
00372 bool
00373 CKeyMap::isCommand(KeyModifierMask mask) const
00374 {
00375     return ((mask & getCommandModifiers()) != 0);
00376 }
00377 
00378 KeyModifierMask
00379 CKeyMap::getCommandModifiers() const
00380 {
00381     // we currently treat ctrl, alt, meta and super as command modifiers.
00382     // some platforms may have a more limited set (OS X only needs Alt)
00383     // but this works anyway.
00384     return KeyModifierControl |
00385             KeyModifierAlt    |
00386             KeyModifierAltGr  |
00387             KeyModifierMeta   |
00388             KeyModifierSuper;
00389 }
00390 
00391 void
00392 CKeyMap::collectButtons(const ModifierToKeys& mods, ButtonToKeyMap& keys)
00393 {
00394     keys.clear();
00395     for (ModifierToKeys::const_iterator i = mods.begin();
00396                                 i != mods.end(); ++i) {
00397         keys.insert(std::make_pair(i->second.m_button, &i->second));
00398     }
00399 }
00400 
00401 void
00402 CKeyMap::initModifierKey(KeyItem& item)
00403 {
00404     item.m_generates = 0;
00405     item.m_lock      = false;
00406     switch (item.m_id) {
00407     case kKeyShift_L:
00408     case kKeyShift_R:
00409         item.m_generates = KeyModifierShift;
00410         break;
00411 
00412     case kKeyControl_L:
00413     case kKeyControl_R:
00414         item.m_generates = KeyModifierControl;
00415         break;
00416 
00417     case kKeyAlt_L:
00418     case kKeyAlt_R:
00419         item.m_generates = KeyModifierAlt;
00420         break;
00421 
00422     case kKeyMeta_L:
00423     case kKeyMeta_R:
00424         item.m_generates = KeyModifierMeta;
00425         break;
00426 
00427     case kKeySuper_L:
00428     case kKeySuper_R:
00429         item.m_generates = KeyModifierSuper;
00430         break;
00431 
00432     case kKeyAltGr:
00433         item.m_generates = KeyModifierAltGr;
00434         break;
00435 
00436     case kKeyCapsLock:
00437         item.m_generates = KeyModifierCapsLock;
00438         item.m_lock      = true;
00439         break;
00440 
00441     case kKeyNumLock:
00442         item.m_generates = KeyModifierNumLock;
00443         item.m_lock      = true;
00444         break;
00445 
00446     case kKeyScrollLock:
00447         item.m_generates = KeyModifierScrollLock;
00448         item.m_lock      = true;
00449         break;
00450 
00451     default:
00452         // not a modifier
00453         break;
00454     }
00455 }
00456 
00457 SInt32
00458 CKeyMap::findNumGroups() const
00459 {
00460     size_t max = 0;
00461     for (KeyIDMap::const_iterator i = m_keyIDMap.begin();
00462                                 i != m_keyIDMap.end(); ++i) {
00463         if (i->second.size() > max) {
00464             max = i->second.size();
00465         }
00466     }
00467     return static_cast<SInt32>(max);
00468 }
00469 
00470 void
00471 CKeyMap::setModifierKeys()
00472 {
00473     m_modifierKeys.clear();
00474     m_modifierKeys.resize(kKeyModifierNumBits * getNumGroups());
00475     for (KeyIDMap::const_iterator i = m_keyIDMap.begin();
00476                                 i != m_keyIDMap.end(); ++i) {
00477         const KeyGroupTable& groupTable = i->second;
00478         for (size_t g = 0; g < groupTable.size(); ++g) {
00479             const KeyEntryList& entries = groupTable[g];
00480             for (size_t j = 0; j < entries.size(); ++j) {
00481                 // skip multi-key sequences
00482                 if (entries[j].size() != 1) {
00483                     continue;
00484                 }
00485 
00486                 // skip keys that don't generate a modifier
00487                 const KeyItem& item = entries[j].back();
00488                 if (item.m_generates == 0) {
00489                     continue;
00490                 }
00491 
00492                 // add key to each indicated modifier in this group
00493                 for (SInt32 b = 0; b < kKeyModifierNumBits; ++b) {
00494                     // skip if item doesn't generate bit b
00495                     if (((1u << b) & item.m_generates) != 0) {
00496                         SInt32 mIndex = (SInt32)g * kKeyModifierNumBits + b;
00497                         m_modifierKeys[mIndex].push_back(&item);
00498                     }
00499                 }
00500             }
00501         }
00502     }
00503 }
00504 
00505 const CKeyMap::KeyItem*
00506 CKeyMap::mapCommandKey(Keystrokes& keys, KeyID id, SInt32 group,
00507                 ModifierToKeys& activeModifiers,
00508                 KeyModifierMask& currentState,
00509                 KeyModifierMask desiredMask,
00510                 bool isAutoRepeat) const
00511 {
00512     static const KeyModifierMask s_overrideModifiers = 0xffffu;
00513 
00514     // find KeySym in table
00515     KeyIDMap::const_iterator i = m_keyIDMap.find(id);
00516     if (i == m_keyIDMap.end()) {
00517         // unknown key
00518         LOG((CLOG_DEBUG1 "key %04x is not on keyboard", id));
00519         return NULL;
00520     }
00521     const KeyGroupTable& keyGroupTable = i->second;
00522 
00523     // find the first key that generates this KeyID
00524     const KeyItem* keyItem = NULL;
00525     SInt32 numGroups       = getNumGroups();
00526     for (SInt32 groupOffset = 0; groupOffset < numGroups; ++groupOffset) {
00527         SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset);
00528         const KeyEntryList& entryList = keyGroupTable[effectiveGroup];
00529         for (size_t i = 0; i < entryList.size(); ++i) {
00530             if (entryList[i].size() != 1) {
00531                 // ignore multikey entries
00532                 continue;
00533             }
00534 
00535             // only match based on shift;  we're after the right button
00536             // not the right character.  we'll use desiredMask as-is,
00537             // overriding the key's required modifiers, when synthesizing
00538             // this button.
00539             const KeyItem& item = entryList[i].back();
00540             if ((item.m_required & KeyModifierShift & desiredMask) ==
00541                 (item.m_sensitive & KeyModifierShift & desiredMask)) {
00542                 LOG((CLOG_DEBUG1 "found key in group %d", effectiveGroup));
00543                 keyItem = &item;
00544                 break;
00545             }
00546         }
00547         if (keyItem != NULL) {
00548             break;
00549         }
00550     }
00551     if (keyItem == NULL) {
00552         // no mapping for this keysym
00553         LOG((CLOG_DEBUG1 "no mapping for key %04x", id));
00554         return NULL;
00555     }
00556 
00557     // make working copy of modifiers
00558     ModifierToKeys newModifiers = activeModifiers;
00559     KeyModifierMask newState    = currentState;
00560     SInt32 newGroup             = group;
00561 
00562     // don't try to change CapsLock
00563     desiredMask = (desiredMask & ~KeyModifierCapsLock) |
00564                     (currentState & KeyModifierCapsLock);
00565 
00566     // add the key
00567     if (!keysForKeyItem(*keyItem, newGroup, newModifiers,
00568                             newState, desiredMask,
00569                             s_overrideModifiers, isAutoRepeat, keys)) {
00570         LOG((CLOG_DEBUG1 "can't map key"));
00571         keys.clear();
00572         return NULL;
00573     }
00574 
00575     // add keystrokes to restore modifier keys
00576     if (!keysToRestoreModifiers(*keyItem, group, newModifiers, newState,
00577                                 activeModifiers, keys)) {
00578         LOG((CLOG_DEBUG1 "failed to restore modifiers"));
00579         keys.clear();
00580         return NULL;
00581     }
00582 
00583     // add keystrokes to restore group
00584     if (newGroup != group) {
00585         keys.push_back(Keystroke(group, true, true));
00586     }
00587 
00588     // save new modifiers
00589     activeModifiers = newModifiers;
00590     currentState    = newState;
00591 
00592     return keyItem;
00593 }
00594 
00595 const CKeyMap::KeyItem*
00596 CKeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group,
00597                 ModifierToKeys& activeModifiers,
00598                 KeyModifierMask& currentState,
00599                 KeyModifierMask desiredMask,
00600                 bool isAutoRepeat) const
00601 {
00602     // find KeySym in table
00603     KeyIDMap::const_iterator i = m_keyIDMap.find(id);
00604     if (i == m_keyIDMap.end()) {
00605         // unknown key
00606         LOG((CLOG_DEBUG1 "key %04x is not on keyboard", id));
00607         return NULL;
00608     }
00609     const KeyGroupTable& keyGroupTable = i->second;
00610 
00611     // find best key in any group, starting with the active group
00612     SInt32 keyIndex  = -1;
00613     SInt32 numGroups = getNumGroups();
00614     SInt32 groupOffset;
00615     LOG((CLOG_DEBUG1 "find best:  %04x %04x", currentState, desiredMask));
00616     for (groupOffset = 0; groupOffset < numGroups; ++groupOffset) {
00617         SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset);
00618         keyIndex = findBestKey(keyGroupTable[effectiveGroup],
00619                                 currentState, desiredMask);
00620         if (keyIndex != -1) {
00621             LOG((CLOG_DEBUG1 "found key in group %d", effectiveGroup));
00622             break;
00623         }
00624     }
00625     if (keyIndex == -1) {
00626         // no mapping for this keysym
00627         LOG((CLOG_DEBUG1 "no mapping for key %04x", id));
00628         return NULL;
00629     }
00630 
00631     // get keys to press for key
00632     SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset);
00633     const KeyItemList& itemList = keyGroupTable[effectiveGroup][keyIndex];
00634     if (itemList.empty()) {
00635         return NULL;
00636     }
00637     const KeyItem& keyItem = itemList.back();
00638 
00639     // make working copy of modifiers
00640     ModifierToKeys newModifiers = activeModifiers;
00641     KeyModifierMask newState    = currentState;
00642     SInt32 newGroup             = group;
00643 
00644     // add each key
00645     for (size_t j = 0; j < itemList.size(); ++j) {
00646         if (!keysForKeyItem(itemList[j], newGroup, newModifiers,
00647                             newState, desiredMask,
00648                             0, isAutoRepeat, keys)) {
00649             LOG((CLOG_DEBUG1 "can't map key"));
00650             keys.clear();
00651             return NULL;
00652         }
00653     }
00654 
00655     // add keystrokes to restore modifier keys
00656     if (!keysToRestoreModifiers(keyItem, group, newModifiers, newState,
00657                                 activeModifiers, keys)) {
00658         LOG((CLOG_DEBUG1 "failed to restore modifiers"));
00659         keys.clear();
00660         return NULL;
00661     }
00662 
00663     // add keystrokes to restore group
00664     if (newGroup != group) {
00665         keys.push_back(Keystroke(group, true, true));
00666     }
00667 
00668     // save new modifiers
00669     activeModifiers = newModifiers;
00670     currentState    = newState;
00671 
00672     return &keyItem;
00673 }
00674 
00675 const CKeyMap::KeyItem*
00676 CKeyMap::mapModifierKey(Keystrokes& keys, KeyID id, SInt32 group,
00677                 ModifierToKeys& activeModifiers,
00678                 KeyModifierMask& currentState,
00679                 KeyModifierMask desiredMask,
00680                 bool isAutoRepeat) const
00681 {
00682     return mapCharacterKey(keys, id, group, activeModifiers,
00683                                 currentState, desiredMask, isAutoRepeat);
00684 }
00685 
00686 SInt32
00687 CKeyMap::findBestKey(const KeyEntryList& entryList,
00688                 KeyModifierMask /*currentState*/,
00689                 KeyModifierMask desiredState) const
00690 {
00691     // check for an item that can accommodate the desiredState exactly
00692     for (SInt32 i = 0; i < (SInt32)entryList.size(); ++i) {
00693         const KeyItem& item = entryList[i].back();
00694         if ((item.m_required & desiredState) ==
00695             (item.m_sensitive & desiredState)) {
00696             LOG((CLOG_DEBUG1 "best key index %d of %d (exact)", i, entryList.size()));
00697             return i;
00698         }
00699     }
00700 
00701     // choose the item that requires the fewest modifier changes
00702     SInt32 bestCount = 32;
00703     SInt32 bestIndex = -1;
00704     for (SInt32 i = 0; i < (SInt32)entryList.size(); ++i) {
00705         const KeyItem& item = entryList[i].back();
00706         KeyModifierMask change =
00707             ((item.m_required ^ desiredState) & item.m_sensitive);
00708         SInt32 n = getNumModifiers(change);
00709         if (n < bestCount) {
00710             bestCount = n;
00711             bestIndex = i;
00712         }
00713     }
00714     if (bestIndex != -1) {
00715         LOG((CLOG_DEBUG1 "best key index %d of %d (%d modifiers)", bestIndex, entryList.size(), bestCount));
00716     }
00717 
00718     return bestIndex;
00719 }
00720 
00721 
00722 const CKeyMap::KeyItem*
00723 CKeyMap::keyForModifier(KeyButton button, SInt32 group,
00724                 SInt32 modifierBit) const
00725 {
00726     assert(modifierBit >= 0 && modifierBit < kKeyModifierNumBits);
00727     assert(group >= 0 && group < getNumGroups());
00728 
00729     // find a key that generates the given modifier in the given group 
00730     // but doesn't use the given button, presumably because we're trying
00731     // to generate a KeyID that's only bound the the given button.
00732     // this is important when a shift button is modified by shift;  we
00733     // must use the other shift button to do the shifting.
00734     const ModifierKeyItemList& items =
00735         m_modifierKeys[group * kKeyModifierNumBits + modifierBit];
00736     for (ModifierKeyItemList::const_iterator i = items.begin();
00737                                 i != items.end(); ++i) {
00738         if ((*i)->m_button != button) {
00739             return (*i);
00740         }
00741     }
00742     return NULL;
00743 }
00744 
00745 bool
00746 CKeyMap::keysForKeyItem(const KeyItem& keyItem, SInt32& group,
00747                 ModifierToKeys& activeModifiers,
00748                 KeyModifierMask& currentState, KeyModifierMask desiredState,
00749                 KeyModifierMask overrideModifiers,
00750                 bool isAutoRepeat,
00751                 Keystrokes& keystrokes) const
00752 {
00753     static const KeyModifierMask s_notRequiredMask =
00754         KeyModifierAltGr | KeyModifierNumLock | KeyModifierScrollLock;
00755 
00756     // add keystrokes to adjust the group
00757     if (group != keyItem.m_group) {
00758         group = keyItem.m_group;
00759         keystrokes.push_back(Keystroke(group, true, false));
00760     }
00761 
00762     EKeystroke type;
00763     if (keyItem.m_dead) {
00764         // adjust modifiers for dead key
00765         if (!keysForModifierState(keyItem.m_button, group,
00766                                 activeModifiers, currentState,
00767                                 keyItem.m_required, keyItem.m_sensitive,
00768                                 0, keystrokes)) {
00769             LOG((CLOG_DEBUG1 "unable to match modifier state for dead key %d", keyItem.m_button));
00770             return false;
00771         }
00772 
00773         // press and release the dead key
00774         type = kKeystrokeClick;
00775     }
00776     else {
00777         // if this a command key then we don't have to match some of the
00778         // key's required modifiers.
00779         KeyModifierMask sensitive = keyItem.m_sensitive & ~overrideModifiers;
00780 
00781         // XXX -- must handle pressing a modifier.  in particular, if we want
00782         // to synthesize a KeyID on level 1 of a KeyButton that has Shift_L
00783         // mapped to level 0 then we must release that button if it's down
00784         // (in order to satisfy a shift modifier) then press a different
00785         // button (any other button) mapped to the shift modifier and then
00786         // the Shift_L button.
00787         // match key's required state
00788         LOG((CLOG_DEBUG1 "state: %04x,%04x,%04x", currentState, keyItem.m_required, sensitive));
00789         if (!keysForModifierState(keyItem.m_button, group,
00790                                 activeModifiers, currentState,
00791                                 keyItem.m_required, sensitive,
00792                                 0, keystrokes)) {
00793             LOG((CLOG_DEBUG1 "unable to match modifier state (%04x,%04x) for key %d", keyItem.m_required, keyItem.m_sensitive, keyItem.m_button));
00794             return false;
00795         }
00796 
00797         // match desiredState as closely as possible.  we must not
00798         // change any modifiers in keyItem.m_sensitive.  and if the key
00799         // is a modifier, we don't want to change that modifier.
00800         LOG((CLOG_DEBUG1 "desired state: %04x %04x,%04x,%04x", desiredState, currentState, keyItem.m_required, keyItem.m_sensitive));
00801         if (!keysForModifierState(keyItem.m_button, group,
00802                                 activeModifiers, currentState,
00803                                 desiredState,
00804                                 ~(sensitive | keyItem.m_generates),
00805                                 s_notRequiredMask, keystrokes)) {
00806             LOG((CLOG_DEBUG1 "unable to match desired modifier state (%04x,%04x) for key %d", desiredState, ~keyItem.m_sensitive & 0xffffu, keyItem.m_button));
00807             return false;
00808         }
00809 
00810         // repeat or press of key
00811         type = isAutoRepeat ? kKeystrokeRepeat : kKeystrokePress;
00812     }
00813     addKeystrokes(type, keyItem, activeModifiers, currentState, keystrokes);
00814 
00815     return true;
00816 }
00817 
00818 bool
00819 CKeyMap::keysToRestoreModifiers(const KeyItem& keyItem, SInt32,
00820                 ModifierToKeys& activeModifiers,
00821                 KeyModifierMask& currentState,
00822                 const ModifierToKeys& desiredModifiers,
00823                 Keystrokes& keystrokes) const
00824 {
00825     // XXX -- we're not considering modified modifiers here
00826 
00827     ModifierToKeys oldModifiers = activeModifiers;
00828 
00829     // get the pressed modifier buttons before and after
00830     ButtonToKeyMap oldKeys, newKeys;
00831     collectButtons(oldModifiers, oldKeys);
00832     collectButtons(desiredModifiers, newKeys);
00833 
00834     // release unwanted keys
00835     for (ModifierToKeys::const_iterator i = oldModifiers.begin();
00836                                 i != oldModifiers.end(); ++i) {
00837         KeyButton button = i->second.m_button;
00838         if (button != keyItem.m_button && newKeys.count(button) == 0) {
00839             EKeystroke type = kKeystrokeRelease;
00840             if (i->second.m_lock) {
00841                 type = kKeystrokeUnmodify;
00842             }
00843             addKeystrokes(type, i->second,
00844                                 activeModifiers, currentState, keystrokes);
00845         }
00846     }
00847 
00848     // press wanted keys
00849     for (ModifierToKeys::const_iterator i = desiredModifiers.begin();
00850                                 i != desiredModifiers.end(); ++i) {
00851         KeyButton button = i->second.m_button;
00852         if (button != keyItem.m_button && oldKeys.count(button) == 0) {
00853             EKeystroke type = kKeystrokePress;
00854             if (i->second.m_lock) {
00855                 type = kKeystrokeModify;
00856             }
00857             addKeystrokes(type, i->second,
00858                                 activeModifiers, currentState, keystrokes);
00859         }
00860     }
00861 
00862     return true;
00863 }
00864 
00865 bool
00866 CKeyMap::keysForModifierState(KeyButton button, SInt32 group,
00867                 ModifierToKeys& activeModifiers,
00868                 KeyModifierMask& currentState,
00869                 KeyModifierMask requiredState, KeyModifierMask sensitiveMask,
00870                 KeyModifierMask notRequiredMask,
00871                 Keystrokes& keystrokes) const
00872 {
00873     // compute which modifiers need changing
00874     KeyModifierMask flipMask = ((currentState ^ requiredState) & sensitiveMask);
00875     // if a modifier is not required then don't even try to match it.  if
00876     // we don't mask out notRequiredMask then we'll try to match those
00877     // modifiers but succeed if we can't.  however, this is known not
00878     // to work if the key itself is a modifier (the numlock toggle can
00879     // interfere) so we don't try to match at all.
00880     flipMask &= ~notRequiredMask;
00881     LOG((CLOG_DEBUG1 "flip: %04x (%04x vs %04x in %04x - %04x)", flipMask, currentState, requiredState, sensitiveMask & 0xffffu, notRequiredMask & 0xffffu));
00882     if (flipMask == 0) {
00883         return true;
00884     }
00885 
00886     // fix modifiers.  this is complicated by the fact that a modifier may
00887     // be sensitive to other modifiers!  (who thought that up?)
00888     //
00889     // we'll assume that modifiers with higher bits are affected by modifiers
00890     // with lower bits.  there's not much basis for that assumption except
00891     // that we're pretty sure shift isn't changed by other modifiers.
00892     for (SInt32 bit = kKeyModifierNumBits; bit-- > 0; ) {
00893         KeyModifierMask mask = (1u << bit);
00894         if ((flipMask & mask) == 0) {
00895             // modifier is already correct
00896             continue;
00897         }
00898 
00899         // do we want the modifier active or inactive?
00900         bool active = ((requiredState & mask) != 0);
00901 
00902         // get the KeyItem for the modifier in the group
00903         const KeyItem* keyItem = keyForModifier(button, group, bit);
00904         if (keyItem == NULL) {
00905             if ((mask & notRequiredMask) == 0) {
00906                 LOG((CLOG_DEBUG1 "no key for modifier %04x", mask));
00907                 return false;
00908             }
00909             else {
00910                 continue;
00911             }
00912         }
00913 
00914         // if this modifier is sensitive to modifiers then adjust those
00915         // modifiers.  also check if our assumption was correct.  note
00916         // that we only need to adjust the modifiers on key down.
00917         KeyModifierMask sensitive = keyItem->m_sensitive;
00918         if ((sensitive & mask) != 0) {
00919             // modifier is sensitive to itself.  that makes no sense
00920             // so ignore it.
00921             LOG((CLOG_DEBUG1 "modifier %04x modified by itself", mask));
00922             sensitive &= ~mask;
00923         }
00924         if (sensitive != 0) {
00925             if (sensitive > mask) {
00926                 // our assumption is incorrect
00927                 LOG((CLOG_DEBUG1 "modifier %04x modified by %04x", mask, sensitive));
00928                 return false;
00929             }
00930             if (active && !keysForModifierState(button, group,
00931                                 activeModifiers, currentState,
00932                                 keyItem->m_required, sensitive,
00933                                 notRequiredMask, keystrokes)) {
00934                 return false;
00935             }
00936             else if (!active) {
00937                 // release the modifier
00938                 // XXX -- this doesn't work!  if Alt and Meta are mapped
00939                 // to one key and we want to release Meta we can't do
00940                 // that without also releasing Alt.
00941                 // need to think about support for modified modifiers.
00942             }
00943         }
00944 
00945         // current state should match required state
00946         if ((currentState & sensitive) != (keyItem->m_required & sensitive)) {
00947             LOG((CLOG_DEBUG1 "unable to match modifier state for modifier %04x (%04x vs %04x in %04x)", mask, currentState, keyItem->m_required, sensitive));
00948             return false;
00949         }
00950 
00951         // add keystrokes
00952         EKeystroke type = active ? kKeystrokeModify : kKeystrokeUnmodify;
00953         addKeystrokes(type, *keyItem, activeModifiers, currentState,
00954                                 keystrokes);
00955     }
00956 
00957     return true;
00958 }
00959 
00960 void
00961 CKeyMap::addKeystrokes(EKeystroke type, const KeyItem& keyItem,
00962                 ModifierToKeys& activeModifiers,
00963                 KeyModifierMask& currentState,
00964                 Keystrokes& keystrokes) const
00965 {
00966     KeyButton button = keyItem.m_button;
00967     UInt32 data      = keyItem.m_client;
00968     switch (type) {
00969     case kKeystrokePress:
00970         keystrokes.push_back(Keystroke(button, true, false, data));
00971         if (keyItem.m_generates != 0) {
00972             if (!keyItem.m_lock || (currentState & keyItem.m_generates) == 0) {
00973                 // add modifier key and activate modifier
00974                 activeModifiers.insert(std::make_pair(
00975                                     keyItem.m_generates, keyItem));
00976                 currentState |= keyItem.m_generates;
00977             }
00978             else {
00979                 // deactivate locking modifier
00980                 activeModifiers.erase(keyItem.m_generates);
00981                 currentState &= ~keyItem.m_generates;
00982             }
00983         }
00984         break;
00985         
00986     case kKeystrokeRelease:
00987         keystrokes.push_back(Keystroke(button, false, false, data));
00988         if (keyItem.m_generates != 0 && !keyItem.m_lock) {
00989             // remove key from active modifiers
00990             std::pair<ModifierToKeys::iterator,
00991                         ModifierToKeys::iterator> range =
00992                 activeModifiers.equal_range(keyItem.m_generates);
00993             for (ModifierToKeys::iterator i = range.first;
00994                                 i != range.second; ++i) {
00995                 if (i->second.m_button == button) {
00996                     activeModifiers.erase(i);
00997                     break;
00998                 }
00999             }
01000 
01001             // if no more keys for this modifier then deactivate modifier
01002             if (activeModifiers.count(keyItem.m_generates) == 0) {
01003                 currentState &= ~keyItem.m_generates;
01004             }
01005         }
01006         break;
01007         
01008     case kKeystrokeRepeat:
01009         keystrokes.push_back(Keystroke(button, false, true, data));
01010         keystrokes.push_back(Keystroke(button,  true, true, data));
01011         // no modifier changes on key repeat
01012         break;
01013         
01014     case kKeystrokeClick:
01015         keystrokes.push_back(Keystroke(button,  true, false, data));
01016         keystrokes.push_back(Keystroke(button, false, false, data));
01017         // no modifier changes on key click
01018         break;
01019         
01020     case kKeystrokeModify:
01021     case kKeystrokeUnmodify:
01022         if (keyItem.m_lock) {
01023             // we assume there's just one button for this modifier
01024             if (m_halfDuplex.count(button) > 0) {
01025                 if (type == kKeystrokeModify) {
01026                     // turn half-duplex toggle on (press)
01027                     keystrokes.push_back(Keystroke(button,  true, false, data));
01028                 }
01029                 else {
01030                     // turn half-duplex toggle off (release)
01031                     keystrokes.push_back(Keystroke(button, false, false, data));
01032                 }
01033             }
01034             else {
01035                 // toggle (click)
01036                 keystrokes.push_back(Keystroke(button,  true, false, data));
01037                 keystrokes.push_back(Keystroke(button, false, false, data));
01038             }
01039         }
01040         else if (type == kKeystrokeModify) {
01041             // press modifier
01042             keystrokes.push_back(Keystroke(button, true, false, data));
01043         }
01044         else {
01045             // release all the keys that generate the modifier that are
01046             // currently down
01047             std::pair<ModifierToKeys::const_iterator,
01048                         ModifierToKeys::const_iterator> range =
01049                 activeModifiers.equal_range(keyItem.m_generates);
01050             for (ModifierToKeys::const_iterator i = range.first;
01051                                 i != range.second; ++i) {
01052                 keystrokes.push_back(Keystroke(i->second.m_button,
01053                                 false, false, i->second.m_client));
01054             }
01055         }
01056 
01057         if (type == kKeystrokeModify) {
01058             activeModifiers.insert(std::make_pair(
01059                                 keyItem.m_generates, keyItem));
01060             currentState |= keyItem.m_generates;
01061         }
01062         else {
01063             activeModifiers.erase(keyItem.m_generates);
01064             currentState &= ~keyItem.m_generates;
01065         }
01066         break;
01067     }
01068 }
01069 
01070 SInt32
01071 CKeyMap::getNumModifiers(KeyModifierMask state)
01072 {
01073     SInt32 n = 0;
01074     for (; state != 0; state >>= 1) {
01075         if ((state & 1) != 0) {
01076             ++n;
01077         }
01078     }
01079     return n;
01080 }
01081 
01082 bool
01083 CKeyMap::isDeadKey(KeyID key)
01084 {
01085     return (key == kKeyCompose || (key >= 0x0300 && key <= 0x036f));
01086 }
01087 
01088 KeyID
01089 CKeyMap::getDeadKey(KeyID key)
01090 {
01091     if (isDeadKey(key)) {
01092         // already dead
01093         return key;
01094     }
01095 
01096     switch (key) {
01097     case '`':
01098         return kKeyDeadGrave;
01099 
01100     case 0xb4u:
01101         return kKeyDeadAcute;
01102 
01103     case '^':
01104     case 0x2c6:
01105         return kKeyDeadCircumflex;
01106 
01107     case '~':
01108     case 0x2dcu:
01109         return kKeyDeadTilde;
01110 
01111     case 0xafu:
01112         return kKeyDeadMacron;
01113 
01114     case 0x2d8u:
01115         return kKeyDeadBreve;
01116 
01117     case 0x2d9u:
01118         return kKeyDeadAbovedot;
01119 
01120     case 0xa8u:
01121         return kKeyDeadDiaeresis;
01122 
01123     case 0xb0u:
01124     case 0x2dau:
01125         return kKeyDeadAbovering;
01126 
01127     case '\"':
01128     case 0x2ddu:
01129         return kKeyDeadDoubleacute;
01130 
01131     case 0x2c7u:
01132         return kKeyDeadCaron;
01133 
01134     case 0xb8u:
01135         return kKeyDeadCedilla;
01136 
01137     case 0x2dbu:
01138         return kKeyDeadOgonek;
01139 
01140     default:
01141         // unknown
01142         return kKeyNone;
01143     }
01144 }
01145 
01146 CString
01147 CKeyMap::formatKey(KeyID key, KeyModifierMask mask)
01148 {
01149     // initialize tables
01150     initKeyNameMaps();
01151 
01152     CString x;
01153     for (SInt32 i = 0; i < kKeyModifierNumBits; ++i) {
01154         KeyModifierMask mod = (1u << i);
01155         if ((mask & mod) != 0 && s_modifierToNameMap->count(mod) > 0) {
01156             x += s_modifierToNameMap->find(mod)->second;
01157             x += "+";
01158         }
01159     }
01160     if (key != kKeyNone) {
01161         if (s_keyToNameMap->count(key) > 0) {
01162             x += s_keyToNameMap->find(key)->second;
01163         }
01164         // XXX -- we're assuming ASCII here
01165         else if (key >= 33 && key < 127) {
01166             x += (char)key;
01167         }
01168         else {
01169             x += CStringUtil::print("\\u%04x", key);
01170         }
01171     }
01172     else if (!x.empty()) {
01173         // remove trailing '+'
01174         x.erase(x.size() - 1);
01175     }
01176     return x;
01177 }
01178 
01179 bool
01180 CKeyMap::parseKey(const CString& x, KeyID& key)
01181 {
01182     // initialize tables
01183     initKeyNameMaps();
01184 
01185     // parse the key
01186     key = kKeyNone;
01187     if (s_nameToKeyMap->count(x) > 0) {
01188         key = s_nameToKeyMap->find(x)->second;
01189     }
01190     // XXX -- we're assuming ASCII encoding here
01191     else if (x.size() == 1) {
01192         if (!isgraph(x[0])) {
01193             // unknown key
01194             return false;
01195         }
01196         key = (KeyID)x[0];
01197     }
01198     else if (x.size() == 6 && x[0] == '\\' && x[1] == 'u') {
01199         // escaped unicode (\uXXXX where XXXX is a hex number)
01200         char* end;
01201         key = (KeyID)strtol(x.c_str() + 2, &end, 16);
01202         if (*end != '\0') {
01203             return false;
01204         }
01205     }
01206     else if (!x.empty()) {
01207         // unknown key
01208         return false;
01209     }
01210 
01211     return true;
01212 }
01213 
01214 bool
01215 CKeyMap::parseModifiers(CString& x, KeyModifierMask& mask)
01216 {
01217     // initialize tables
01218     initKeyNameMaps();
01219 
01220     mask = 0;
01221     CString::size_type tb = x.find_first_not_of(" \t", 0);
01222     while (tb != CString::npos) {
01223         // get next component
01224         CString::size_type te = x.find_first_of(" \t+)", tb);
01225         if (te == CString::npos) {
01226             te = x.size();
01227         }
01228         CString c = x.substr(tb, te - tb);
01229         if (c.empty()) {
01230             // missing component
01231             return false;
01232         }
01233 
01234         if (s_nameToModifierMap->count(c) > 0) {
01235             KeyModifierMask mod = s_nameToModifierMap->find(c)->second;
01236             if ((mask & mod) != 0) {
01237                 // modifier appears twice
01238                 return false;
01239             }
01240             mask |= mod;
01241         }
01242         else {
01243             // unknown string
01244             x.erase(0, tb);
01245             CString::size_type tb = x.find_first_not_of(" \t");
01246             CString::size_type te = x.find_last_not_of(" \t");
01247             if (tb == CString::npos) {
01248                 x = "";
01249             }
01250             else {
01251                 x = x.substr(tb, te - tb + 1);
01252             }
01253             return true;
01254         }
01255 
01256         // check for '+' or end of string
01257         tb = x.find_first_not_of(" \t", te);
01258         if (tb != CString::npos) {
01259             if (x[tb] != '+') {
01260                 // expected '+'
01261                 return false;
01262             }
01263             tb = x.find_first_not_of(" \t", tb + 1);
01264         }
01265     }
01266 
01267     // parsed the whole thing
01268     x = "";
01269     return true;
01270 }
01271 
01272 void
01273 CKeyMap::initKeyNameMaps()
01274 {
01275     // initialize tables
01276     if (s_nameToKeyMap == NULL) {
01277         s_nameToKeyMap = new CNameToKeyMap;
01278         s_keyToNameMap = new CKeyToNameMap;
01279         for (const KeyNameMapEntry* i = kKeyNameMap; i->m_name != NULL; ++i) {
01280             (*s_nameToKeyMap)[i->m_name] = i->m_id;
01281             (*s_keyToNameMap)[i->m_id]   = i->m_name;
01282         }
01283     }
01284     if (s_nameToModifierMap == NULL) {
01285         s_nameToModifierMap = new CNameToModifierMap;
01286         s_modifierToNameMap = new CModifierToNameMap;
01287         for (const KeyModifierNameMapEntry* i = kModifierNameMap;
01288                                 i->m_name != NULL; ++i) {
01289             (*s_nameToModifierMap)[i->m_name] = i->m_mask;
01290             (*s_modifierToNameMap)[i->m_mask] = i->m_name;
01291         }
01292     }
01293 }
01294 
01295 
01296 //
01297 // CKeyMap::KeyItem
01298 //
01299 
01300 bool
01301 CKeyMap::KeyItem::operator==(const KeyItem& x) const
01302 {
01303     return (m_id        == x.m_id        &&
01304             m_group     == x.m_group     &&
01305             m_button    == x.m_button    &&
01306             m_required  == x.m_required  &&
01307             m_sensitive == x.m_sensitive &&
01308             m_generates == x.m_generates &&
01309             m_dead      == x.m_dead      &&
01310             m_lock      == x.m_lock      &&
01311             m_client    == x.m_client);
01312 }
01313 
01314 
01315 //
01316 // CKeyMap::Keystroke
01317 //
01318 
01319 CKeyMap::Keystroke::Keystroke(KeyButton button,
01320                 bool press, bool repeat, UInt32 data) :
01321     m_type(kButton)
01322 {
01323     m_data.m_button.m_button = button;
01324     m_data.m_button.m_press  = press;
01325     m_data.m_button.m_repeat = repeat;
01326     m_data.m_button.m_client = data;
01327 }
01328 
01329 CKeyMap::Keystroke::Keystroke(SInt32 group, bool absolute, bool restore) :
01330     m_type(kGroup)
01331 {
01332     m_data.m_group.m_group    = group;
01333     m_data.m_group.m_absolute = absolute;
01334     m_data.m_group.m_restore  = restore;
01335 }

Generated on Thu May 23 2013 00:00:03 for Synergy by  doxygen 1.7.1