1444c061aSmrg/*********************************************************** 2fdf6a26fSmrgCopyright (c) 1993, Oracle and/or its affiliates. 31477040fSmrg 41477040fSmrgPermission is hereby granted, free of charge, to any person obtaining a 51477040fSmrgcopy of this software and associated documentation files (the "Software"), 61477040fSmrgto deal in the Software without restriction, including without limitation 71477040fSmrgthe rights to use, copy, modify, merge, publish, distribute, sublicense, 81477040fSmrgand/or sell copies of the Software, and to permit persons to whom the 91477040fSmrgSoftware is furnished to do so, subject to the following conditions: 101477040fSmrg 111477040fSmrgThe above copyright notice and this permission notice (including the next 121477040fSmrgparagraph) shall be included in all copies or substantial portions of the 131477040fSmrgSoftware. 141477040fSmrg 151477040fSmrgTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 161477040fSmrgIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 171477040fSmrgFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 181477040fSmrgTHE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 191477040fSmrgLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 201477040fSmrgFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 211477040fSmrgDEALINGS IN THE SOFTWARE. 221477040fSmrg 231477040fSmrgCopyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts. 24444c061aSmrg 25444c061aSmrg All Rights Reserved 26444c061aSmrg 27444c061aSmrgPermission to use, copy, modify, and distribute this software and its 28444c061aSmrgdocumentation for any purpose and without fee is hereby granted, 29444c061aSmrgprovided that the above copyright notice appear in all copies and that 30444c061aSmrgboth that copyright notice and this permission notice appear in 311477040fSmrgsupporting documentation, and that the name of Digital not be 32444c061aSmrgused in advertising or publicity pertaining to distribution of the 33444c061aSmrgsoftware without specific, written prior permission. 34444c061aSmrg 35444c061aSmrgDIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 36444c061aSmrgALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL 37444c061aSmrgDIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 38444c061aSmrgANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 39444c061aSmrgWHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 40444c061aSmrgARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 41444c061aSmrgSOFTWARE. 42444c061aSmrg 43444c061aSmrg******************************************************************/ 44444c061aSmrg 45444c061aSmrg/* 46444c061aSmrg 47444c061aSmrgCopyright 1987, 1988, 1994, 1998 The Open Group 48444c061aSmrg 49444c061aSmrgPermission to use, copy, modify, distribute, and sell this software and its 50444c061aSmrgdocumentation for any purpose is hereby granted without fee, provided that 51444c061aSmrgthe above copyright notice appear in all copies and that both that 52444c061aSmrgcopyright notice and this permission notice appear in supporting 53444c061aSmrgdocumentation. 54444c061aSmrg 55444c061aSmrgThe above copyright notice and this permission notice shall be included in 56444c061aSmrgall copies or substantial portions of the Software. 57444c061aSmrg 58444c061aSmrgTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 59444c061aSmrgIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 60444c061aSmrgFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 61444c061aSmrgOPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 62444c061aSmrgAN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 63444c061aSmrgCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 64444c061aSmrg 65444c061aSmrgExcept as contained in this notice, the name of The Open Group shall not be 66444c061aSmrgused in advertising or otherwise to promote the sale, use or other dealings 67444c061aSmrgin this Software without prior written authorization from The Open Group. 68444c061aSmrg 69444c061aSmrg*/ 70444c061aSmrg 71444c061aSmrg#define XK_MISCELLANY 72444c061aSmrg#define XK_LATIN1 73444c061aSmrg#define XK_LATIN2 74444c061aSmrg#define XK_LATIN3 75444c061aSmrg#define XK_LATIN4 76444c061aSmrg 77444c061aSmrg#ifdef HAVE_CONFIG_H 78444c061aSmrg#include <config.h> 79444c061aSmrg#endif 80444c061aSmrg#include "IntrinsicI.h" 81444c061aSmrg#include <X11/keysymdef.h> 82444c061aSmrg#ifdef XKB 83444c061aSmrg#include <X11/XKBlib.h> 84444c061aSmrg#endif 85444c061aSmrg 86444c061aSmrg#define FLUSHKEYCACHE(ctx) \ 87a3bd7f05Smrg memset((void *)&ctx->keycache, 0, sizeof(TMKeyCache)) 88444c061aSmrg 89444c061aSmrg/* 90444c061aSmrg * The following array reorders the modifier bits so that the most common ones 91444c061aSmrg * (used by a translator) are in the top-most bits with respect to the size of 92444c061aSmrg * the keycache. The array currently just reverses the bits as a good guess. 93444c061aSmrg * This might be more trouble than it is worth, but it seems to help. 94444c061aSmrg */ 95444c061aSmrg 96444c061aSmrg#define FM(i) i >> (8 - TMKEYCACHELOG2) 97a3bd7f05Smrg 98a3bd7f05Smrg/* *INDENT-OFF* */ 99444c061aSmrgstatic const unsigned char modmix[256] = { 100444c061aSmrgFM(0x0f), FM(0x8f), FM(0x4f), FM(0xcf), FM(0x2f), FM(0xaf), FM(0x6f), FM(0xef), 101444c061aSmrgFM(0x1f), FM(0x9f), FM(0x5f), FM(0xdf), FM(0x3f), FM(0xbf), FM(0x7f), FM(0xff), 102444c061aSmrgFM(0x07), FM(0x87), FM(0x47), FM(0xc7), FM(0x27), FM(0xa7), FM(0x67), FM(0xe7), 103444c061aSmrgFM(0x17), FM(0x97), FM(0x57), FM(0xd7), FM(0x37), FM(0xb7), FM(0x77), FM(0xf7), 104444c061aSmrgFM(0x0b), FM(0x8b), FM(0x4b), FM(0xcb), FM(0x2b), FM(0xab), FM(0x6b), FM(0xeb), 105444c061aSmrgFM(0x1b), FM(0x9b), FM(0x5b), FM(0xdb), FM(0x3b), FM(0xbb), FM(0x7b), FM(0xfb), 106444c061aSmrgFM(0x03), FM(0x83), FM(0x43), FM(0xc3), FM(0x23), FM(0xa3), FM(0x63), FM(0xe3), 107444c061aSmrgFM(0x13), FM(0x93), FM(0x53), FM(0xd3), FM(0x33), FM(0xb3), FM(0x73), FM(0xf3), 108444c061aSmrgFM(0x0d), FM(0x8d), FM(0x4d), FM(0xcd), FM(0x2d), FM(0xad), FM(0x6d), FM(0xed), 109444c061aSmrgFM(0x1d), FM(0x9d), FM(0x5d), FM(0xdd), FM(0x3d), FM(0xbd), FM(0x7d), FM(0xfd), 110444c061aSmrgFM(0x05), FM(0x85), FM(0x45), FM(0xc5), FM(0x25), FM(0xa5), FM(0x65), FM(0xe5), 111444c061aSmrgFM(0x15), FM(0x95), FM(0x55), FM(0xd5), FM(0x35), FM(0xb5), FM(0x75), FM(0xf5), 112444c061aSmrgFM(0x09), FM(0x89), FM(0x49), FM(0xc9), FM(0x29), FM(0xa9), FM(0x69), FM(0xe9), 113444c061aSmrgFM(0x19), FM(0x99), FM(0x59), FM(0xd9), FM(0x39), FM(0xb9), FM(0x79), FM(0xf9), 114444c061aSmrgFM(0x01), FM(0x81), FM(0x41), FM(0xc1), FM(0x21), FM(0xa1), FM(0x61), FM(0xe1), 115444c061aSmrgFM(0x11), FM(0x91), FM(0x51), FM(0xd1), FM(0x31), FM(0xb1), FM(0x71), FM(0xf1), 116444c061aSmrgFM(0x00), FM(0x8e), FM(0x4e), FM(0xce), FM(0x2e), FM(0xae), FM(0x6e), FM(0xee), 117444c061aSmrgFM(0x1e), FM(0x9e), FM(0x5e), FM(0xde), FM(0x3e), FM(0xbe), FM(0x7e), FM(0xfe), 118444c061aSmrgFM(0x08), FM(0x88), FM(0x48), FM(0xc8), FM(0x28), FM(0xa8), FM(0x68), FM(0xe8), 119444c061aSmrgFM(0x18), FM(0x98), FM(0x58), FM(0xd8), FM(0x38), FM(0xb8), FM(0x78), FM(0xf8), 120444c061aSmrgFM(0x04), FM(0x84), FM(0x44), FM(0xc4), FM(0x24), FM(0xa4), FM(0x64), FM(0xe4), 121444c061aSmrgFM(0x14), FM(0x94), FM(0x54), FM(0xd4), FM(0x34), FM(0xb4), FM(0x74), FM(0xf4), 122444c061aSmrgFM(0x0c), FM(0x8c), FM(0x4c), FM(0xcc), FM(0x2c), FM(0xac), FM(0x6c), FM(0xec), 123444c061aSmrgFM(0x1c), FM(0x9c), FM(0x5c), FM(0xdc), FM(0x3c), FM(0xbc), FM(0x7c), FM(0xfc), 124444c061aSmrgFM(0x02), FM(0x82), FM(0x42), FM(0xc2), FM(0x22), FM(0xa2), FM(0x62), FM(0xe2), 125444c061aSmrgFM(0x12), FM(0x92), FM(0x52), FM(0xd2), FM(0x32), FM(0xb2), FM(0x72), FM(0xf2), 126444c061aSmrgFM(0x0a), FM(0x8a), FM(0x4a), FM(0xca), FM(0x2a), FM(0xaa), FM(0x6a), FM(0xea), 127444c061aSmrgFM(0x1a), FM(0x9a), FM(0x5a), FM(0xda), FM(0x3a), FM(0xba), FM(0x7a), FM(0xfa), 128444c061aSmrgFM(0x06), FM(0x86), FM(0x46), FM(0xc6), FM(0x26), FM(0xa6), FM(0x66), FM(0xe6), 129444c061aSmrgFM(0x16), FM(0x96), FM(0x56), FM(0xd6), FM(0x36), FM(0xb6), FM(0x76), FM(0xf6), 130444c061aSmrgFM(0x0e), FM(0x8e), FM(0x4e), FM(0xce), FM(0x2e), FM(0xae), FM(0x6e), FM(0xee), 131444c061aSmrgFM(0x1e), FM(0x9e), FM(0x5e), FM(0xde), FM(0x3e), FM(0xbe), FM(0x7e), FM(0xfe) 132444c061aSmrg}; 133a3bd7f05Smrg/* *INDENT-ON* */ 134444c061aSmrg#undef FM 135444c061aSmrg 136444c061aSmrg#define MOD_RETURN(ctx, key) (ctx)->keycache.modifiers_return[key] 137444c061aSmrg 138444c061aSmrg#define TRANSLATE(ctx,pd,dpy,key,mod,mod_ret,sym_ret) \ 139444c061aSmrg{ \ 1400568f49bSmrg int _i_ = (((key) - (TMLongCard) (pd)->min_keycode + modmix[(mod) & 0xff]) & \ 141a3bd7f05Smrg (TMKEYCACHESIZE-1)); \ 1422265a131Smrg if ((key) == 0) { /* Xlib XIM composed input */ \ 143a3bd7f05Smrg mod_ret = 0; \ 144a3bd7f05Smrg sym_ret = 0; \ 1452265a131Smrg } else if ( /* not Xlib XIM composed input */ \ 146a3bd7f05Smrg (ctx)->keycache.keycode[_i_] == (key) && \ 147a3bd7f05Smrg (ctx)->keycache.modifiers[_i_] == (mod)) { \ 148a3bd7f05Smrg mod_ret = MOD_RETURN(ctx, key); \ 149a3bd7f05Smrg sym_ret = (ctx)->keycache.keysym[_i_]; \ 150444c061aSmrg } else { \ 151a3bd7f05Smrg XtTranslateKeycode(dpy, (KeyCode) key, mod, &mod_ret, &sym_ret); \ 152a3bd7f05Smrg (ctx)->keycache.keycode[_i_] = (KeyCode) (key); \ 153a3bd7f05Smrg (ctx)->keycache.modifiers[_i_] = (unsigned char)(mod); \ 154a3bd7f05Smrg (ctx)->keycache.keysym[_i_] = sym_ret; \ 155a3bd7f05Smrg MOD_RETURN(ctx, key) = (unsigned char)mod_ret; \ 156444c061aSmrg } \ 157444c061aSmrg} 158444c061aSmrg 159444c061aSmrg#define UPDATE_CACHE(ctx, pd, key, mod, mod_ret, sym_ret) \ 160444c061aSmrg{ \ 1610568f49bSmrg int _i_ = (((key) - (TMLongCard) (pd)->min_keycode + modmix[(mod) & 0xff]) & \ 162a3bd7f05Smrg (TMKEYCACHESIZE-1)); \ 1630568f49bSmrg (ctx)->keycache.keycode[_i_] = (KeyCode) (key); \ 164444c061aSmrg (ctx)->keycache.modifiers[_i_] = (unsigned char)(mod); \ 165444c061aSmrg (ctx)->keycache.keysym[_i_] = sym_ret; \ 1660568f49bSmrg MOD_RETURN(ctx, key) = (unsigned char)(mod_ret); \ 167444c061aSmrg} 168444c061aSmrg 169444c061aSmrg/* usual number of expected keycodes in XtKeysymToKeycodeList */ 170444c061aSmrg#define KEYCODE_ARRAY_SIZE 10 171444c061aSmrg 172a3bd7f05SmrgBoolean 173a3bd7f05Smrg_XtComputeLateBindings(Display *dpy, 174a3bd7f05Smrg LateBindingsPtr lateModifiers, 175a3bd7f05Smrg Modifiers *computed, 176a3bd7f05Smrg Modifiers *computedMask) 177444c061aSmrg{ 178a3bd7f05Smrg int i, j, ref; 179a3bd7f05Smrg ModToKeysymTable *temp; 180444c061aSmrg XtPerDisplay perDisplay; 181444c061aSmrg KeySym tempKeysym = NoSymbol; 182444c061aSmrg 183444c061aSmrg perDisplay = _XtGetPerDisplay(dpy); 184444c061aSmrg if (perDisplay == NULL) { 185444c061aSmrg XtAppWarningMsg(XtDisplayToApplicationContext(dpy), 186a3bd7f05Smrg "displayError", "invalidDisplay", XtCXtToolkitError, 187a3bd7f05Smrg "Can't find display structure", NULL, NULL); 188a3bd7f05Smrg return FALSE; 189444c061aSmrg } 190444c061aSmrg _InitializeKeysymTables(dpy, perDisplay); 191a3bd7f05Smrg for (ref = 0; lateModifiers[ref].keysym; ref++) { 1920568f49bSmrg Boolean found = FALSE; 193a3bd7f05Smrg 194a3bd7f05Smrg for (i = 0; i < 8; i++) { 195444c061aSmrg temp = &(perDisplay->modsToKeysyms[i]); 196a3bd7f05Smrg for (j = 0; j < temp->count; j++) { 197a3bd7f05Smrg if (perDisplay->modKeysyms[temp->idx + j] == 198a3bd7f05Smrg lateModifiers[ref].keysym) { 199444c061aSmrg *computedMask = *computedMask | temp->mask; 200444c061aSmrg if (!lateModifiers[ref].knot) 201a3bd7f05Smrg *computed |= temp->mask; 202444c061aSmrg tempKeysym = lateModifiers[ref].keysym; 203a3bd7f05Smrg found = TRUE; 204a3bd7f05Smrg break; 205444c061aSmrg } 206444c061aSmrg } 207a3bd7f05Smrg if (found) 208a3bd7f05Smrg break; 209444c061aSmrg } 210a3bd7f05Smrg if (!found && !lateModifiers[ref].knot) 211444c061aSmrg if (!lateModifiers[ref].pair && (tempKeysym == NoSymbol)) 212444c061aSmrg return FALSE; 213444c061aSmrg /* if you didn't find the modifier and the modifier must be 214444c061aSmrg asserted then return FALSE. If you didn't find the modifier 215444c061aSmrg and the modifier must be off, then it is OK . Don't 216444c061aSmrg return FALSE if this is the first member of a pair or if 217444c061aSmrg it is the second member of a pair when the first member 218444c061aSmrg was bound to a modifier */ 219a3bd7f05Smrg if (!lateModifiers[ref].pair) 220a3bd7f05Smrg tempKeysym = NoSymbol; 221444c061aSmrg } 222444c061aSmrg return TRUE; 223444c061aSmrg} 224444c061aSmrg 225a3bd7f05Smrgvoid 226a3bd7f05Smrg_XtAllocTMContext(XtPerDisplay pd) 227444c061aSmrg{ 228444c061aSmrg TMKeyContext ctx; 229a3bd7f05Smrg 230a3bd7f05Smrg ctx = (TMKeyContext) _XtHeapAlloc(&pd->heap, sizeof(TMKeyContextRec)); 231444c061aSmrg ctx->event = NULL; 232444c061aSmrg ctx->serial = 0; 233444c061aSmrg ctx->keysym = NoSymbol; 234444c061aSmrg ctx->modifiers = 0; 235444c061aSmrg FLUSHKEYCACHE(ctx); 236444c061aSmrg pd->tm_context = ctx; 237444c061aSmrg} 238444c061aSmrg 239a3bd7f05Smrgstatic unsigned int 240a3bd7f05Smrgnum_bits(unsigned long mask) 241444c061aSmrg{ 242444c061aSmrg register unsigned long y; 243444c061aSmrg 244a3bd7f05Smrg y = (mask >> 1) & 033333333333; 245a3bd7f05Smrg y = mask - y - ((y >> 1) & 033333333333); 246444c061aSmrg return ((unsigned int) (((y + (y >> 3)) & 030707070707) % 077)); 247444c061aSmrg} 248444c061aSmrg 249a3bd7f05SmrgBoolean 250a3bd7f05Smrg_XtMatchUsingDontCareMods(TMTypeMatch typeMatch, 251a3bd7f05Smrg TMModifierMatch modMatch, 252a3bd7f05Smrg TMEventPtr eventSeq) 253444c061aSmrg{ 254444c061aSmrg Modifiers modifiers_return; 255444c061aSmrg KeySym keysym_return; 256444c061aSmrg Modifiers useful_mods; 257444c061aSmrg Modifiers computed = 0; 258444c061aSmrg Modifiers computedMask = 0; 259444c061aSmrg Boolean resolved = TRUE; 260444c061aSmrg Display *dpy = eventSeq->xev->xany.display; 261444c061aSmrg XtPerDisplay pd; 262444c061aSmrg 263444c061aSmrg if (modMatch->lateModifiers != NULL) 264a3bd7f05Smrg resolved = _XtComputeLateBindings(dpy, modMatch->lateModifiers, 265a3bd7f05Smrg &computed, &computedMask); 266a3bd7f05Smrg if (!resolved) 267a3bd7f05Smrg return FALSE; 2680568f49bSmrg computed = (Modifiers) (computed | modMatch->modifiers); 2690568f49bSmrg computedMask = (Modifiers) (computedMask | modMatch->modifierMask); /* gives do-care mask */ 270444c061aSmrg 271a3bd7f05Smrg if ((computed & computedMask) == (eventSeq->event.modifiers & computedMask)) { 272a3bd7f05Smrg TMKeyContext tm_context; 273a3bd7f05Smrg int num_modbits; 274a3bd7f05Smrg int i; 275a3bd7f05Smrg 276a3bd7f05Smrg pd = _XtGetPerDisplay(dpy); 277a3bd7f05Smrg tm_context = pd->tm_context; 278a3bd7f05Smrg TRANSLATE(tm_context, pd, dpy, (KeyCode) eventSeq->event.eventCode, 279a3bd7f05Smrg (unsigned) 0, modifiers_return, keysym_return); 280a3bd7f05Smrg 281a3bd7f05Smrg if ((keysym_return & typeMatch->eventCodeMask) == typeMatch->eventCode) { 282a3bd7f05Smrg tm_context->event = eventSeq->xev; 283a3bd7f05Smrg tm_context->serial = eventSeq->xev->xany.serial; 284a3bd7f05Smrg tm_context->keysym = keysym_return; 285a3bd7f05Smrg tm_context->modifiers = (Modifiers) 0; 286a3bd7f05Smrg return TRUE; 287a3bd7f05Smrg } 288444c061aSmrg useful_mods = ~computedMask & modifiers_return; 289a3bd7f05Smrg if (useful_mods == 0) 290a3bd7f05Smrg return FALSE; 291a3bd7f05Smrg 292a3bd7f05Smrg switch (num_modbits = (int) num_bits(useful_mods)) { 293a3bd7f05Smrg case 1: 294a3bd7f05Smrg case 8: 295a3bd7f05Smrg /* 296a3bd7f05Smrg * one modbit should never happen, in fact the implementation 297a3bd7f05Smrg * of XtTranslateKey and XmTranslateKey guarantee that it 298a3bd7f05Smrg * won't, so don't care if the loop is set up for the case 299a3bd7f05Smrg * when one modbit is set. 300a3bd7f05Smrg * The performance implications of all eight modbits being 301a3bd7f05Smrg * set is horrendous. This isn't a problem with Xt/Xaw based 302a3bd7f05Smrg * applications. We can only hope that Motif's virtual 303a3bd7f05Smrg * modifiers won't result in all eight modbits being set. 304a3bd7f05Smrg */ 305a3bd7f05Smrg for (i = (int) useful_mods; i > 0; i--) { 306a3bd7f05Smrg TRANSLATE(tm_context, pd, dpy, eventSeq->event.eventCode, 307a3bd7f05Smrg (Modifiers) i, modifiers_return, keysym_return); 308a3bd7f05Smrg if (keysym_return == 309a3bd7f05Smrg (typeMatch->eventCode & typeMatch->eventCodeMask)) { 310a3bd7f05Smrg tm_context->event = eventSeq->xev; 311a3bd7f05Smrg tm_context->serial = eventSeq->xev->xany.serial; 312a3bd7f05Smrg tm_context->keysym = keysym_return; 313a3bd7f05Smrg tm_context->modifiers = (Modifiers) i; 314a3bd7f05Smrg return TRUE; 315a3bd7f05Smrg } 316a3bd7f05Smrg } 317a3bd7f05Smrg break; 318a3bd7f05Smrg default: /* (2..7) */ 319a3bd7f05Smrg { 320a3bd7f05Smrg /* 321a3bd7f05Smrg * Only translate using combinations of the useful modifiers. 322a3bd7f05Smrg * to minimize the chance of invalidating the cache. 323a3bd7f05Smrg */ 324a3bd7f05Smrg static char pows[] = { 0, 1, 3, 7, 15, 31, 63, 127 }; 325a3bd7f05Smrg Modifiers tmod, mod_masks[8]; 326a3bd7f05Smrg int j; 327a3bd7f05Smrg 328a3bd7f05Smrg for (tmod = 1, i = 0; tmod <= (Mod5Mask << 1); tmod <<= 1) 329a3bd7f05Smrg if (tmod & useful_mods) 330a3bd7f05Smrg mod_masks[i++] = tmod; 331a3bd7f05Smrg for (j = (int) pows[num_modbits]; j > 0; j--) { 332a3bd7f05Smrg tmod = 0; 333a3bd7f05Smrg for (i = 0; i < num_modbits; i++) 334a3bd7f05Smrg if (j & (1 << i)) 335a3bd7f05Smrg tmod |= mod_masks[i]; 336a3bd7f05Smrg TRANSLATE(tm_context, pd, dpy, eventSeq->event.eventCode, 337a3bd7f05Smrg tmod, modifiers_return, keysym_return); 338a3bd7f05Smrg if (keysym_return == 339a3bd7f05Smrg (typeMatch->eventCode & typeMatch->eventCodeMask)) { 340a3bd7f05Smrg tm_context->event = eventSeq->xev; 341a3bd7f05Smrg tm_context->serial = eventSeq->xev->xany.serial; 342a3bd7f05Smrg tm_context->keysym = keysym_return; 343a3bd7f05Smrg tm_context->modifiers = (Modifiers) i; 344a3bd7f05Smrg return TRUE; 345a3bd7f05Smrg } 346a3bd7f05Smrg } 347a3bd7f05Smrg } 348a3bd7f05Smrg break; 349a3bd7f05Smrg } /* switch (num_modbits) */ 350444c061aSmrg } 351444c061aSmrg return FALSE; 352444c061aSmrg} 353444c061aSmrg 354a3bd7f05Smrgvoid 355a3bd7f05SmrgXtConvertCase(Display *dpy, 356a3bd7f05Smrg KeySym keysym, 357a3bd7f05Smrg KeySym *lower_return, 358a3bd7f05Smrg KeySym *upper_return) 359444c061aSmrg{ 360444c061aSmrg XtPerDisplay pd; 361444c061aSmrg CaseConverterPtr ptr; 362a3bd7f05Smrg 363444c061aSmrg DPY_TO_APPCON(dpy); 364444c061aSmrg 365444c061aSmrg LOCK_APP(app); 366444c061aSmrg pd = _XtGetPerDisplay(dpy); 367444c061aSmrg 368444c061aSmrg *lower_return = *upper_return = keysym; 369a3bd7f05Smrg for (ptr = pd->case_cvt; ptr; ptr = ptr->next) 370a3bd7f05Smrg if (ptr->start <= keysym && keysym <= ptr->stop) { 371a3bd7f05Smrg (*ptr->proc) (dpy, keysym, lower_return, upper_return); 372a3bd7f05Smrg return; 373a3bd7f05Smrg } 374444c061aSmrg XConvertCase(keysym, lower_return, upper_return); 375444c061aSmrg UNLOCK_APP(app); 376444c061aSmrg} 377444c061aSmrg 378a3bd7f05SmrgBoolean 379a3bd7f05Smrg_XtMatchUsingStandardMods(TMTypeMatch typeMatch, 380a3bd7f05Smrg TMModifierMatch modMatch, 381a3bd7f05Smrg TMEventPtr eventSeq) 382444c061aSmrg{ 383444c061aSmrg Modifiers modifiers_return; 384444c061aSmrg KeySym keysym_return; 385a3bd7f05Smrg Modifiers computed = 0; 386444c061aSmrg Modifiers computedMask = 0; 387444c061aSmrg Display *dpy = eventSeq->xev->xany.display; 388444c061aSmrg XtPerDisplay pd = _XtGetPerDisplay(dpy); 389444c061aSmrg TMKeyContext tm_context = pd->tm_context; 390444c061aSmrg Modifiers translateModifiers; 391444c061aSmrg 392444c061aSmrg /* To maximize cache utilization, we mask off nonstandard modifiers 393444c061aSmrg before cache lookup. For a given key translator, standard modifiers 394444c061aSmrg are constant per KeyCode. If a key translator uses no standard 395444c061aSmrg modifiers this implementation will never reference the cache. 396444c061aSmrg */ 397444c061aSmrg 398444c061aSmrg modifiers_return = MOD_RETURN(tm_context, eventSeq->event.eventCode); 399444c061aSmrg if (!modifiers_return) { 400a3bd7f05Smrg XtTranslateKeycode(dpy, (KeyCode) eventSeq->event.eventCode, 401a3bd7f05Smrg (Modifiers) eventSeq->event.modifiers, 402a3bd7f05Smrg &modifiers_return, &keysym_return); 403a3bd7f05Smrg translateModifiers = 404a3bd7f05Smrg (Modifiers) (eventSeq->event.modifiers & modifiers_return); 405a3bd7f05Smrg UPDATE_CACHE(tm_context, pd, eventSeq->event.eventCode, 406a3bd7f05Smrg translateModifiers, modifiers_return, keysym_return); 407a3bd7f05Smrg } 408a3bd7f05Smrg else { 409a3bd7f05Smrg translateModifiers = 410a3bd7f05Smrg (Modifiers) (eventSeq->event.modifiers & modifiers_return); 411a3bd7f05Smrg TRANSLATE(tm_context, pd, dpy, (KeyCode) eventSeq->event.eventCode, 412a3bd7f05Smrg translateModifiers, modifiers_return, keysym_return); 413444c061aSmrg } 414444c061aSmrg 415444c061aSmrg if ((typeMatch->eventCode & typeMatch->eventCodeMask) == 416a3bd7f05Smrg (keysym_return & typeMatch->eventCodeMask)) { 417a3bd7f05Smrg Boolean resolved = TRUE; 4180568f49bSmrg 419444c061aSmrg if (modMatch->lateModifiers != NULL) 420444c061aSmrg resolved = _XtComputeLateBindings(dpy, modMatch->lateModifiers, 421a3bd7f05Smrg &computed, &computedMask); 422a3bd7f05Smrg if (!resolved) 423a3bd7f05Smrg return FALSE; 4240568f49bSmrg computed = (Modifiers) (computed | modMatch->modifiers); 4250568f49bSmrg computedMask = (Modifiers) (computedMask | modMatch->modifierMask); 426444c061aSmrg 427444c061aSmrg if ((computed & computedMask) == 428a3bd7f05Smrg (eventSeq->event.modifiers & ~modifiers_return & computedMask)) { 429a3bd7f05Smrg tm_context->event = eventSeq->xev; 430a3bd7f05Smrg tm_context->serial = eventSeq->xev->xany.serial; 431a3bd7f05Smrg tm_context->keysym = keysym_return; 432a3bd7f05Smrg tm_context->modifiers = translateModifiers; 433a3bd7f05Smrg return TRUE; 434a3bd7f05Smrg } 435444c061aSmrg } 436444c061aSmrg return FALSE; 437444c061aSmrg} 438444c061aSmrg 439a3bd7f05Smrgvoid 440a3bd7f05Smrg_XtBuildKeysymTables(Display *dpy, register XtPerDisplay pd) 441444c061aSmrg{ 442444c061aSmrg ModToKeysymTable *table; 443a3bd7f05Smrg int maxCount, i, j, k, tempCount, idx; 444a3bd7f05Smrg KeySym keysym, tempKeysym; 445a3bd7f05Smrg XModifierKeymap *modKeymap; 446444c061aSmrg KeyCode keycode; 447a3bd7f05Smrg 448444c061aSmrg#define KeysymTableSize 16 449444c061aSmrg 450444c061aSmrg FLUSHKEYCACHE(pd->tm_context); 4510568f49bSmrg 452a3bd7f05Smrg XFree((char *) pd->keysyms); 453444c061aSmrg pd->keysyms_serial = NextRequest(dpy); 4540568f49bSmrg pd->keysyms = XGetKeyboardMapping(dpy, (KeyCode) pd->min_keycode, 455a3bd7f05Smrg pd->max_keycode - pd->min_keycode + 1, 456a3bd7f05Smrg &pd->keysyms_per_keycode); 457a3bd7f05Smrg XtFree((char *) pd->modKeysyms); 4580568f49bSmrg 459fdf6a26fSmrg pd->modKeysyms = XtMallocArray(KeysymTableSize, (Cardinal) sizeof(KeySym)); 460444c061aSmrg maxCount = KeysymTableSize; 461444c061aSmrg tempCount = 0; 462444c061aSmrg 463a3bd7f05Smrg XtFree((char *) pd->modsToKeysyms); 464fdf6a26fSmrg table = XtMallocArray(8, (Cardinal) sizeof(ModToKeysymTable)); 465444c061aSmrg pd->modsToKeysyms = table; 466444c061aSmrg 467444c061aSmrg table[0].mask = ShiftMask; 468444c061aSmrg table[1].mask = LockMask; 469444c061aSmrg table[2].mask = ControlMask; 470444c061aSmrg table[3].mask = Mod1Mask; 471444c061aSmrg table[4].mask = Mod2Mask; 472444c061aSmrg table[5].mask = Mod3Mask; 473444c061aSmrg table[6].mask = Mod4Mask; 474444c061aSmrg table[7].mask = Mod5Mask; 475444c061aSmrg tempKeysym = 0; 476444c061aSmrg 477444c061aSmrg modKeymap = XGetModifierMapping(dpy); 478a3bd7f05Smrg for (i = 0; i < 32; i++) 479a3bd7f05Smrg pd->isModifier[i] = 0; 480444c061aSmrg pd->mode_switch = 0; 481444c061aSmrg pd->num_lock = 0; 482a3bd7f05Smrg for (i = 0; i < 8; i++) { 483444c061aSmrg table[i].idx = tempCount; 484444c061aSmrg table[i].count = 0; 485a3bd7f05Smrg for (j = 0; j < modKeymap->max_keypermod; j++) { 486a3bd7f05Smrg keycode = modKeymap->modifiermap[i * modKeymap->max_keypermod + j]; 487444c061aSmrg if (keycode != 0) { 488a3bd7f05Smrg pd->isModifier[keycode >> 3] |= 489a3bd7f05Smrg (unsigned char) (1 << (keycode & 7)); 490a3bd7f05Smrg for (k = 0; k < pd->keysyms_per_keycode; k++) { 491a3bd7f05Smrg idx = ((keycode - pd->min_keycode) * 492a3bd7f05Smrg pd->keysyms_per_keycode) + k; 493444c061aSmrg keysym = pd->keysyms[idx]; 494a3bd7f05Smrg if ((keysym == XK_Mode_switch) && (i > 2)) 495a3bd7f05Smrg pd->mode_switch = 496a3bd7f05Smrg (pd->mode_switch | (Modifiers) (1 << i)); 497a3bd7f05Smrg if ((keysym == XK_Num_Lock) && (i > 2)) 498a3bd7f05Smrg pd->num_lock = (pd->num_lock | (Modifiers) (1 << i)); 499a3bd7f05Smrg if (keysym != 0 && keysym != tempKeysym) { 500a3bd7f05Smrg if (tempCount == maxCount) { 501444c061aSmrg maxCount += KeysymTableSize; 502fdf6a26fSmrg pd->modKeysyms = 503fdf6a26fSmrg XtReallocArray(pd->modKeysyms, 504fdf6a26fSmrg (Cardinal) maxCount, 505fdf6a26fSmrg (Cardinal) sizeof(KeySym)); 506444c061aSmrg } 507444c061aSmrg pd->modKeysyms[tempCount++] = keysym; 508444c061aSmrg table[i].count++; 509444c061aSmrg tempKeysym = keysym; 510444c061aSmrg } 511444c061aSmrg } 512444c061aSmrg } 513444c061aSmrg } 514444c061aSmrg } 515444c061aSmrg pd->lock_meaning = NoSymbol; 516444c061aSmrg for (i = 0; i < table[1].count; i++) { 517a3bd7f05Smrg keysym = pd->modKeysyms[table[1].idx + i]; 518a3bd7f05Smrg if (keysym == XK_Caps_Lock) { 519a3bd7f05Smrg pd->lock_meaning = XK_Caps_Lock; 520a3bd7f05Smrg break; 521a3bd7f05Smrg } 522a3bd7f05Smrg else if (keysym == XK_Shift_Lock) { 523a3bd7f05Smrg pd->lock_meaning = XK_Shift_Lock; 524a3bd7f05Smrg } 525444c061aSmrg } 526444c061aSmrg XFreeModifiermap(modKeymap); 527444c061aSmrg} 528444c061aSmrg 529a3bd7f05Smrgvoid 530a3bd7f05SmrgXtTranslateKeycode(Display *dpy, 531a3bd7f05Smrg _XtKeyCode keycode, 532a3bd7f05Smrg Modifiers modifiers, 533a3bd7f05Smrg Modifiers *modifiers_return, 534a3bd7f05Smrg KeySym *keysym_return) 535444c061aSmrg{ 536444c061aSmrg XtPerDisplay pd; 537a3bd7f05Smrg 538444c061aSmrg DPY_TO_APPCON(dpy); 539444c061aSmrg 540444c061aSmrg LOCK_APP(app); 541444c061aSmrg pd = _XtGetPerDisplay(dpy); 542444c061aSmrg _InitializeKeysymTables(dpy, pd); 543a3bd7f05Smrg (*pd->defaultKeycodeTranslator) (dpy, keycode, modifiers, modifiers_return, 544a3bd7f05Smrg keysym_return); 545444c061aSmrg UNLOCK_APP(app); 546444c061aSmrg} 547444c061aSmrg 548444c061aSmrg/* This code should match XTranslateKey (internal, sigh) in Xlib */ 549a3bd7f05Smrgvoid 550a3bd7f05SmrgXtTranslateKey(register Display *dpy, 551a3bd7f05Smrg _XtKeyCode keycode, 552a3bd7f05Smrg Modifiers modifiers, 553a3bd7f05Smrg Modifiers *modifiers_return, 554a3bd7f05Smrg KeySym *keysym_return) 555444c061aSmrg{ 556444c061aSmrg#ifndef XKB 557444c061aSmrg XtPerDisplay pd; 558444c061aSmrg int per; 559444c061aSmrg register KeySym *syms; 560444c061aSmrg KeySym sym, lsym, usym; 561a3bd7f05Smrg 562444c061aSmrg DPY_TO_APPCON(dpy); 563444c061aSmrg 564444c061aSmrg LOCK_APP(app); 565444c061aSmrg pd = _XtGetPerDisplay(dpy); 566a3bd7f05Smrg *modifiers_return = (ShiftMask | LockMask) | pd->mode_switch | pd->num_lock; 567a3bd7f05Smrg if (((int) keycode < pd->min_keycode) || ((int) keycode > pd->max_keycode)) { 568a3bd7f05Smrg *keysym_return = NoSymbol; 569a3bd7f05Smrg UNLOCK_APP(app); 570a3bd7f05Smrg return; 571444c061aSmrg } 572444c061aSmrg per = pd->keysyms_per_keycode; 573444c061aSmrg syms = &pd->keysyms[(keycode - pd->min_keycode) * per]; 574444c061aSmrg while ((per > 2) && (syms[per - 1] == NoSymbol)) 575a3bd7f05Smrg per--; 576444c061aSmrg if ((per > 2) && (modifiers & pd->mode_switch)) { 577a3bd7f05Smrg syms += 2; 578a3bd7f05Smrg per -= 2; 579444c061aSmrg } 580444c061aSmrg if ((modifiers & pd->num_lock) && 581a3bd7f05Smrg (per > 1 && (IsKeypadKey(syms[1]) || IsPrivateKeypadKey(syms[1])))) { 582a3bd7f05Smrg if ((modifiers & ShiftMask) || 583a3bd7f05Smrg ((modifiers & LockMask) && (pd->lock_meaning == XK_Shift_Lock))) 584a3bd7f05Smrg *keysym_return = syms[0]; 585a3bd7f05Smrg else 586a3bd7f05Smrg *keysym_return = syms[1]; 587a3bd7f05Smrg } 588a3bd7f05Smrg else if (!(modifiers & ShiftMask) && 589a3bd7f05Smrg (!(modifiers & LockMask) || (pd->lock_meaning == NoSymbol))) { 590a3bd7f05Smrg if ((per == 1) || (syms[1] == NoSymbol)) 591a3bd7f05Smrg XtConvertCase(dpy, syms[0], keysym_return, &usym); 592a3bd7f05Smrg else 593a3bd7f05Smrg *keysym_return = syms[0]; 594a3bd7f05Smrg } 595a3bd7f05Smrg else if (!(modifiers & LockMask) || (pd->lock_meaning != XK_Caps_Lock)) { 596a3bd7f05Smrg if ((per == 1) || ((usym = syms[1]) == NoSymbol)) 597a3bd7f05Smrg XtConvertCase(dpy, syms[0], &lsym, &usym); 598a3bd7f05Smrg *keysym_return = usym; 599a3bd7f05Smrg } 600a3bd7f05Smrg else { 601a3bd7f05Smrg if ((per == 1) || ((sym = syms[1]) == NoSymbol)) 602a3bd7f05Smrg sym = syms[0]; 603a3bd7f05Smrg XtConvertCase(dpy, sym, &lsym, &usym); 604a3bd7f05Smrg if (!(modifiers & ShiftMask) && (sym != syms[0]) && 605a3bd7f05Smrg ((sym != usym) || (lsym == usym))) 606a3bd7f05Smrg XtConvertCase(dpy, syms[0], &lsym, &usym); 607a3bd7f05Smrg *keysym_return = usym; 608444c061aSmrg } 609444c061aSmrg 610444c061aSmrg if (*keysym_return == XK_VoidSymbol) 611a3bd7f05Smrg *keysym_return = NoSymbol; 612444c061aSmrg UNLOCK_APP(app); 613444c061aSmrg#else 614a3bd7f05Smrg XkbLookupKeySym(dpy, (KeyCode) keycode, modifiers, modifiers_return, 615a3bd7f05Smrg keysym_return); 616444c061aSmrg#endif 617444c061aSmrg} 618444c061aSmrg 619a3bd7f05Smrgvoid 620a3bd7f05SmrgXtSetKeyTranslator(Display *dpy, XtKeyProc translator) 621444c061aSmrg{ 622444c061aSmrg XtPerDisplay pd; 623a3bd7f05Smrg 624444c061aSmrg DPY_TO_APPCON(dpy); 625444c061aSmrg 626444c061aSmrg LOCK_APP(app); 627444c061aSmrg pd = _XtGetPerDisplay(dpy); 628444c061aSmrg 629444c061aSmrg pd->defaultKeycodeTranslator = translator; 630444c061aSmrg FLUSHKEYCACHE(pd->tm_context); 631444c061aSmrg /* XXX should now redo grabs */ 632444c061aSmrg UNLOCK_APP(app); 633444c061aSmrg} 634444c061aSmrg 635a3bd7f05Smrgvoid 636a3bd7f05SmrgXtRegisterCaseConverter(Display *dpy, 637a3bd7f05Smrg XtCaseProc proc, 638a3bd7f05Smrg KeySym start, 639a3bd7f05Smrg KeySym stop) 640444c061aSmrg{ 641444c061aSmrg XtPerDisplay pd; 642444c061aSmrg CaseConverterPtr ptr, prev; 643a3bd7f05Smrg 644444c061aSmrg DPY_TO_APPCON(dpy); 645444c061aSmrg 646444c061aSmrg LOCK_APP(app); 647444c061aSmrg pd = _XtGetPerDisplay(dpy); 648444c061aSmrg 649444c061aSmrg ptr = (CaseConverterPtr) __XtMalloc(sizeof(CaseConverterRec)); 650444c061aSmrg ptr->start = start; 651444c061aSmrg ptr->stop = stop; 652444c061aSmrg ptr->proc = proc; 653444c061aSmrg ptr->next = pd->case_cvt; 654444c061aSmrg pd->case_cvt = ptr; 655444c061aSmrg 656444c061aSmrg /* Remove obsolete case converters from the list */ 657444c061aSmrg prev = ptr; 658a3bd7f05Smrg for (ptr = ptr->next; ptr; ptr = prev->next) { 659a3bd7f05Smrg if (start <= ptr->start && stop >= ptr->stop) { 660a3bd7f05Smrg prev->next = ptr->next; 661a3bd7f05Smrg XtFree((char *) ptr); 662a3bd7f05Smrg } 663a3bd7f05Smrg else 664a3bd7f05Smrg prev = ptr; 665444c061aSmrg } 666444c061aSmrg FLUSHKEYCACHE(pd->tm_context); 667444c061aSmrg /* XXX should now redo grabs */ 668444c061aSmrg UNLOCK_APP(app); 669444c061aSmrg} 670444c061aSmrg 671a3bd7f05SmrgKeySym * 672a3bd7f05SmrgXtGetKeysymTable(Display *dpy, 673a3bd7f05Smrg KeyCode *min_keycode_return, 674a3bd7f05Smrg int *keysyms_per_keycode_return) 675444c061aSmrg{ 676444c061aSmrg XtPerDisplay pd; 677a3bd7f05Smrg KeySym *retval; 678a3bd7f05Smrg 679444c061aSmrg DPY_TO_APPCON(dpy); 680444c061aSmrg 681444c061aSmrg LOCK_APP(app); 682444c061aSmrg pd = _XtGetPerDisplay(dpy); 683444c061aSmrg _InitializeKeysymTables(dpy, pd); 684a3bd7f05Smrg *min_keycode_return = (KeyCode) pd->min_keycode; /* %%% */ 685444c061aSmrg *keysyms_per_keycode_return = pd->keysyms_per_keycode; 686444c061aSmrg retval = pd->keysyms; 687444c061aSmrg UNLOCK_APP(app); 688444c061aSmrg return retval; 689444c061aSmrg} 690444c061aSmrg 691a3bd7f05Smrgvoid 692a3bd7f05SmrgXtKeysymToKeycodeList(Display *dpy, 693a3bd7f05Smrg KeySym keysym, 694a3bd7f05Smrg KeyCode **keycodes_return, 695a3bd7f05Smrg Cardinal *keycount_return) 696444c061aSmrg{ 697444c061aSmrg XtPerDisplay pd; 698444c061aSmrg unsigned keycode; 6990568f49bSmrg int per; 700444c061aSmrg register KeySym *syms; 701444c061aSmrg register int i, j; 702444c061aSmrg KeySym lsym, usym; 703444c061aSmrg unsigned maxcodes = 0; 704444c061aSmrg unsigned ncodes = 0; 705444c061aSmrg KeyCode *keycodes, *codeP = NULL; 706a3bd7f05Smrg 707444c061aSmrg DPY_TO_APPCON(dpy); 708444c061aSmrg 709444c061aSmrg LOCK_APP(app); 710444c061aSmrg pd = _XtGetPerDisplay(dpy); 711444c061aSmrg _InitializeKeysymTables(dpy, pd); 712444c061aSmrg keycodes = NULL; 713444c061aSmrg per = pd->keysyms_per_keycode; 714444c061aSmrg for (syms = pd->keysyms, keycode = (unsigned) pd->min_keycode; 715a3bd7f05Smrg (int) keycode <= pd->max_keycode; syms += per, keycode++) { 716a3bd7f05Smrg int match = 0; 717a3bd7f05Smrg 718a3bd7f05Smrg for (j = 0; j < per; j++) { 719a3bd7f05Smrg if (syms[j] == keysym) { 720a3bd7f05Smrg match = 1; 721a3bd7f05Smrg break; 722a3bd7f05Smrg } 723a3bd7f05Smrg } 724a3bd7f05Smrg if (!match) 725a3bd7f05Smrg for (i = 1; i < 5; i += 2) { 726a3bd7f05Smrg if ((per == i) || ((per > i) && (syms[i] == NoSymbol))) { 727a3bd7f05Smrg XtConvertCase(dpy, syms[i - 1], &lsym, &usym); 728a3bd7f05Smrg if ((lsym == keysym) || (usym == keysym)) { 729a3bd7f05Smrg match = 1; 730a3bd7f05Smrg break; 731a3bd7f05Smrg } 732a3bd7f05Smrg } 733a3bd7f05Smrg } 734a3bd7f05Smrg if (match) { 735a3bd7f05Smrg if (ncodes == maxcodes) { 736a3bd7f05Smrg KeyCode *old = keycodes; 737a3bd7f05Smrg 738a3bd7f05Smrg maxcodes += KEYCODE_ARRAY_SIZE; 739fdf6a26fSmrg keycodes = XtMallocArray(maxcodes, (Cardinal) sizeof(KeyCode)); 740a3bd7f05Smrg if (ncodes) { 741fdf6a26fSmrg (void) memcpy(keycodes, old, ncodes * sizeof(KeyCode)); 742a3bd7f05Smrg XtFree((char *) old); 743a3bd7f05Smrg } 744a3bd7f05Smrg codeP = &keycodes[ncodes]; 745a3bd7f05Smrg } 746a3bd7f05Smrg *codeP++ = (KeyCode) keycode; 747a3bd7f05Smrg ncodes++; 748a3bd7f05Smrg } 749444c061aSmrg } 750444c061aSmrg *keycodes_return = keycodes; 751444c061aSmrg *keycount_return = ncodes; 752444c061aSmrg UNLOCK_APP(app); 753444c061aSmrg} 754