Main Page | Class Hierarchy | Alphabetical List | Class List | File List | Class Members

CKeyMap.cpp

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

Generated on Fri Nov 6 00:21:14 2009 for synergy-plus by  doxygen 1.3.9.1