From 9076defaca9b25749f2526406b6c2d8756f97329 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 24 Mar 2017 10:49:00 +0100 Subject: [PATCH 01/20] Get parse.js in sync with generated code The generated keysymdef.js was recently converted to ES modules, but the generating script was overlooked. --- utils/parse.js | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/utils/parse.js b/utils/parse.js index fd79b12b..d2067db9 100644 --- a/utils/parse.js +++ b/utils/parse.js @@ -80,23 +80,20 @@ var out = "// This file describes mappings from Unicode codepoints to the keysym "// (and optionally, key names) expected by the RFB protocol\n" + "// How this file was generated:\n" + "// " + process.argv.join(" ") + "\n" + -"var keysyms = (function(){\n" + -" \"use strict\";\n" + -" var keynames = {keysyms};\n" + -" var codepoints = {codepoints};\n" + +"var keynames = {keysyms};\n" + +"var codepoints = {codepoints};\n" + "\n" + -" function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }\n" + -" return {\n" + -" fromUnicode : function(u) {\n" + -" var keysym = codepoints[u];\n" + -" if (keysym === undefined) {\n" + -" keysym = 0x01000000 | u;\n" + -" }\n" + -" return lookup(keysym);\n" + -" },\n" + -" lookup : lookup\n" + -" };\n" + -"})();\n"; +"function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }\n" + +"export default {\n" + +" fromUnicode : function(u) {\n" + +" var keysym = codepoints[u];\n" + +" if (keysym === undefined) {\n" + +" keysym = 0x01000000 | u;\n" + +" }\n" + +" return lookup(keysym);\n" + +" },\n" + +" lookup : lookup\n" + +"};\n"; out = out.replace('{keysyms}', use_keynames ? JSON.stringify(keysyms) : "null"); out = out.replace('{codepoints}', JSON.stringify(codepoints)); From 524d67f2834bfd41aad58b28a69d3c4d841f4ca2 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 24 Jan 2017 12:07:26 +0100 Subject: [PATCH 02/20] Remove keysym names from keysymdef.js They were incomplete and turned off in most cases so they served little use besides adding complexity. --- app/ui.js | 2 +- core/input/devices.js | 2 +- core/input/keysymdef.js | 7 +- core/input/util.js | 23 ++-- core/rfb.js | 2 +- tests/input.html | 4 - tests/test.helper.js | 62 +++++----- tests/test.keyboard.js | 246 ++++++++++++++++++++-------------------- tests/test.rfb.js | 3 +- utils/parse.js | 21 +--- 10 files changed, 174 insertions(+), 198 deletions(-) diff --git a/app/ui.js b/app/ui.js index 46826221..cd0901b3 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1519,7 +1519,7 @@ const UI = { UI.rfb.sendKey(KeyTable.XK_BackSpace); } for (i = newLen - inputs; i < newLen; i++) { - UI.rfb.sendKey(keysyms.fromUnicode(newValue.charCodeAt(i)).keysym); + UI.rfb.sendKey(keysyms.lookup(newValue.charCodeAt(i))); } // Control the text content length in the keyboardinput element diff --git a/core/input/devices.js b/core/input/devices.js index 22653e38..8aaabbd6 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -51,7 +51,7 @@ Keyboard.prototype = { _handleRfbEvent: function (e) { if (this._onKeyPress) { Log.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") + - ", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")"); + ", keysym: " + e.keysym); this._onKeyPress(e); } }, diff --git a/core/input/keysymdef.js b/core/input/keysymdef.js index 3d9cee39..a542f98c 100644 --- a/core/input/keysymdef.js +++ b/core/input/keysymdef.js @@ -3,17 +3,14 @@ // How this file was generated: // node /Users/jalf/dev/mi/novnc/utils/parse.js /opt/X11/include/X11/keysymdef.h -var keynames = null; var codepoints = {"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"60":60,"61":61,"62":62,"63":63,"64":64,"65":65,"66":66,"67":67,"68":68,"69":69,"70":70,"71":71,"72":72,"73":73,"74":74,"75":75,"76":76,"77":77,"78":78,"79":79,"80":80,"81":81,"82":82,"83":83,"84":84,"85":85,"86":86,"87":87,"88":88,"89":89,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"97":97,"98":98,"99":99,"100":100,"101":101,"102":102,"103":103,"104":104,"105":105,"106":106,"107":107,"108":108,"109":109,"110":110,"111":111,"112":112,"113":113,"114":114,"115":115,"116":116,"117":117,"118":118,"119":119,"120":120,"121":121,"122":122,"123":123,"124":124,"125":125,"126":126,"160":160,"161":161,"162":162,"163":163,"164":164,"165":165,"166":166,"167":167,"168":168,"169":169,"170":170,"171":171,"172":172,"173":173,"174":174,"175":175,"176":176,"177":177,"178":178,"179":179,"180":180,"181":181,"182":182,"183":183,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"190":190,"191":191,"192":192,"193":193,"194":194,"195":195,"196":196,"197":197,"198":198,"199":199,"200":200,"201":201,"202":202,"203":203,"204":204,"205":205,"206":206,"207":207,"208":208,"209":209,"210":210,"211":211,"212":212,"213":213,"214":214,"215":215,"216":216,"217":217,"218":218,"219":219,"220":220,"221":221,"222":222,"223":223,"224":224,"225":225,"226":226,"227":227,"228":228,"229":229,"230":230,"231":231,"232":232,"233":233,"234":234,"235":235,"236":236,"237":237,"238":238,"239":239,"240":240,"241":241,"242":242,"243":243,"244":244,"245":245,"246":246,"247":247,"248":248,"249":249,"250":250,"251":251,"252":252,"253":253,"254":254,"255":255,"256":960,"257":992,"258":451,"259":483,"260":417,"261":433,"262":454,"263":486,"264":710,"265":742,"266":709,"267":741,"268":456,"269":488,"270":463,"271":495,"272":464,"273":496,"274":938,"275":954,"278":972,"279":1004,"280":458,"281":490,"282":460,"283":492,"284":728,"285":760,"286":683,"287":699,"288":725,"289":757,"290":939,"291":955,"292":678,"293":694,"294":673,"295":689,"296":933,"297":949,"298":975,"299":1007,"300":16777516,"301":16777517,"302":967,"303":999,"304":681,"305":697,"308":684,"309":700,"310":979,"311":1011,"312":930,"313":453,"314":485,"315":934,"316":950,"317":421,"318":437,"321":419,"322":435,"323":465,"324":497,"325":977,"326":1009,"327":466,"328":498,"330":957,"331":959,"332":978,"333":1010,"336":469,"337":501,"338":5052,"339":5053,"340":448,"341":480,"342":931,"343":947,"344":472,"345":504,"346":422,"347":438,"348":734,"349":766,"350":426,"351":442,"352":425,"353":441,"354":478,"355":510,"356":427,"357":443,"358":940,"359":956,"360":989,"361":1021,"362":990,"363":1022,"364":733,"365":765,"366":473,"367":505,"368":475,"369":507,"370":985,"371":1017,"372":16777588,"373":16777589,"374":16777590,"375":16777591,"376":5054,"377":428,"378":444,"379":431,"380":447,"381":430,"382":446,"399":16777615,"402":2294,"415":16777631,"416":16777632,"417":16777633,"431":16777647,"432":16777648,"437":16777653,"438":16777654,"439":16777655,"466":16777681,"486":16777702,"487":16777703,"601":16777817,"629":16777845,"658":16777874,"711":439,"728":418,"729":511,"731":434,"733":445,"901":1966,"902":1953,"904":1954,"905":1955,"906":1956,"908":1959,"910":1960,"911":1963,"912":1974,"913":1985,"914":1986,"915":1987,"916":1988,"917":1989,"918":1990,"919":1991,"920":1992,"921":1993,"922":1994,"923":1995,"924":1996,"925":1997,"926":1998,"927":1999,"928":2000,"929":2001,"931":2002,"932":2004,"933":2005,"934":2006,"935":2007,"936":2008,"937":2009,"938":1957,"939":1961,"940":1969,"941":1970,"942":1971,"943":1972,"944":1978,"945":2017,"946":2018,"947":2019,"948":2020,"949":2021,"950":2022,"951":2023,"952":2024,"953":2025,"954":2026,"955":2027,"956":2028,"957":2029,"958":2030,"959":2031,"960":2032,"961":2033,"962":2035,"963":2034,"964":2036,"965":2037,"966":2038,"967":2039,"968":2040,"969":2041,"970":1973,"971":1977,"972":1975,"973":1976,"974":1979,"1025":1715,"1026":1713,"1027":1714,"1028":1716,"1029":1717,"1030":1718,"1031":1719,"1032":1720,"1033":1721,"1034":1722,"1035":1723,"1036":1724,"1038":1726,"1039":1727,"1040":1761,"1041":1762,"1042":1783,"1043":1767,"1044":1764,"1045":1765,"1046":1782,"1047":1786,"1048":1769,"1049":1770,"1050":1771,"1051":1772,"1052":1773,"1053":1774,"1054":1775,"1055":1776,"1056":1778,"1057":1779,"1058":1780,"1059":1781,"1060":1766,"1061":1768,"1062":1763,"1063":1790,"1064":1787,"1065":1789,"1066":1791,"1067":1785,"1068":1784,"1069":1788,"1070":1760,"1071":1777,"1072":1729,"1073":1730,"1074":1751,"1075":1735,"1076":1732,"1077":1733,"1078":1750,"1079":1754,"1080":1737,"1081":1738,"1082":1739,"1083":1740,"1084":1741,"1085":1742,"1086":1743,"1087":1744,"1088":1746,"1089":1747,"1090":1748,"1091":1749,"1092":1734,"1093":1736,"1094":1731,"1095":1758,"1096":1755,"1097":1757,"1098":1759,"1099":1753,"1100":1752,"1101":1756,"1102":1728,"1103":1745,"1105":1699,"1106":1697,"1107":1698,"1108":1700,"1109":1701,"1110":1702,"1111":1703,"1112":1704,"1113":1705,"1114":1706,"1115":1707,"1116":1708,"1118":1710,"1119":1711,"1168":1725,"1169":1709,"1170":16778386,"1171":16778387,"1174":16778390,"1175":16778391,"1178":16778394,"1179":16778395,"1180":16778396,"1181":16778397,"1186":16778402,"1187":16778403,"1198":16778414,"1199":16778415,"1200":16778416,"1201":16778417,"1202":16778418,"1203":16778419,"1206":16778422,"1207":16778423,"1208":16778424,"1209":16778425,"1210":16778426,"1211":16778427,"1240":16778456,"1241":16778457,"1250":16778466,"1251":16778467,"1256":16778472,"1257":16778473,"1262":16778478,"1263":16778479,"1329":16778545,"1330":16778546,"1331":16778547,"1332":16778548,"1333":16778549,"1334":16778550,"1335":16778551,"1336":16778552,"1337":16778553,"1338":16778554,"1339":16778555,"1340":16778556,"1341":16778557,"1342":16778558,"1343":16778559,"1344":16778560,"1345":16778561,"1346":16778562,"1347":16778563,"1348":16778564,"1349":16778565,"1350":16778566,"1351":16778567,"1352":16778568,"1353":16778569,"1354":16778570,"1355":16778571,"1356":16778572,"1357":16778573,"1358":16778574,"1359":16778575,"1360":16778576,"1361":16778577,"1362":16778578,"1363":16778579,"1364":16778580,"1365":16778581,"1366":16778582,"1370":16778586,"1371":16778587,"1372":16778588,"1373":16778589,"1374":16778590,"1377":16778593,"1378":16778594,"1379":16778595,"1380":16778596,"1381":16778597,"1382":16778598,"1383":16778599,"1384":16778600,"1385":16778601,"1386":16778602,"1387":16778603,"1388":16778604,"1389":16778605,"1390":16778606,"1391":16778607,"1392":16778608,"1393":16778609,"1394":16778610,"1395":16778611,"1396":16778612,"1397":16778613,"1398":16778614,"1399":16778615,"1400":16778616,"1401":16778617,"1402":16778618,"1403":16778619,"1404":16778620,"1405":16778621,"1406":16778622,"1407":16778623,"1408":16778624,"1409":16778625,"1410":16778626,"1411":16778627,"1412":16778628,"1413":16778629,"1414":16778630,"1415":16778631,"1417":16778633,"1418":16778634,"1488":3296,"1489":3297,"1490":3298,"1491":3299,"1492":3300,"1493":3301,"1494":3302,"1495":3303,"1496":3304,"1497":3305,"1498":3306,"1499":3307,"1500":3308,"1501":3309,"1502":3310,"1503":3311,"1504":3312,"1505":3313,"1506":3314,"1507":3315,"1508":3316,"1509":3317,"1510":3318,"1511":3319,"1512":3320,"1513":3321,"1514":3322,"1548":1452,"1563":1467,"1567":1471,"1569":1473,"1570":1474,"1571":1475,"1572":1476,"1573":1477,"1574":1478,"1575":1479,"1576":1480,"1577":1481,"1578":1482,"1579":1483,"1580":1484,"1581":1485,"1582":1486,"1583":1487,"1584":1488,"1585":1489,"1586":1490,"1587":1491,"1588":1492,"1589":1493,"1590":1494,"1591":1495,"1592":1496,"1593":1497,"1594":1498,"1600":1504,"1601":1505,"1602":1506,"1603":1507,"1604":1508,"1605":1509,"1606":1510,"1607":1511,"1608":1512,"1609":1513,"1610":1514,"1611":1515,"1612":1516,"1613":1517,"1614":1518,"1615":1519,"1616":1520,"1617":1521,"1618":1522,"1619":16778835,"1620":16778836,"1621":16778837,"1632":16778848,"1633":16778849,"1634":16778850,"1635":16778851,"1636":16778852,"1637":16778853,"1638":16778854,"1639":16778855,"1640":16778856,"1641":16778857,"1642":16778858,"1648":16778864,"1657":16778873,"1662":16778878,"1670":16778886,"1672":16778888,"1681":16778897,"1688":16778904,"1700":16778916,"1705":16778921,"1711":16778927,"1722":16778938,"1726":16778942,"1729":16778945,"1740":16778956,"1746":16778962,"1748":16778964,"1776":16778992,"1777":16778993,"1778":16778994,"1779":16778995,"1780":16778996,"1781":16778997,"1782":16778998,"1783":16778999,"1784":16779000,"1785":16779001,"3458":16780674,"3459":16780675,"3461":16780677,"3462":16780678,"3463":16780679,"3464":16780680,"3465":16780681,"3466":16780682,"3467":16780683,"3468":16780684,"3469":16780685,"3470":16780686,"3471":16780687,"3472":16780688,"3473":16780689,"3474":16780690,"3475":16780691,"3476":16780692,"3477":16780693,"3478":16780694,"3482":16780698,"3483":16780699,"3484":16780700,"3485":16780701,"3486":16780702,"3487":16780703,"3488":16780704,"3489":16780705,"3490":16780706,"3491":16780707,"3492":16780708,"3493":16780709,"3494":16780710,"3495":16780711,"3496":16780712,"3497":16780713,"3498":16780714,"3499":16780715,"3500":16780716,"3501":16780717,"3502":16780718,"3503":16780719,"3504":16780720,"3505":16780721,"3507":16780723,"3508":16780724,"3509":16780725,"3510":16780726,"3511":16780727,"3512":16780728,"3513":16780729,"3514":16780730,"3515":16780731,"3517":16780733,"3520":16780736,"3521":16780737,"3522":16780738,"3523":16780739,"3524":16780740,"3525":16780741,"3526":16780742,"3530":16780746,"3535":16780751,"3536":16780752,"3537":16780753,"3538":16780754,"3539":16780755,"3540":16780756,"3542":16780758,"3544":16780760,"3545":16780761,"3546":16780762,"3547":16780763,"3548":16780764,"3549":16780765,"3550":16780766,"3551":16780767,"3570":16780786,"3571":16780787,"3572":16780788,"3585":3489,"3586":3490,"3587":3491,"3588":3492,"3589":3493,"3590":3494,"3591":3495,"3592":3496,"3593":3497,"3594":3498,"3595":3499,"3596":3500,"3597":3501,"3598":3502,"3599":3503,"3600":3504,"3601":3505,"3602":3506,"3603":3507,"3604":3508,"3605":3509,"3606":3510,"3607":3511,"3608":3512,"3609":3513,"3610":3514,"3611":3515,"3612":3516,"3613":3517,"3614":3518,"3615":3519,"3616":3520,"3617":3521,"3618":3522,"3619":3523,"3620":3524,"3621":3525,"3622":3526,"3623":3527,"3624":3528,"3625":3529,"3626":3530,"3627":3531,"3628":3532,"3629":3533,"3630":3534,"3631":3535,"3632":3536,"3633":3537,"3634":3538,"3635":3539,"3636":3540,"3637":3541,"3638":3542,"3639":3543,"3640":3544,"3641":3545,"3642":3546,"3647":3551,"3648":3552,"3649":3553,"3650":3554,"3651":3555,"3652":3556,"3653":3557,"3654":3558,"3655":3559,"3656":3560,"3657":3561,"3658":3562,"3659":3563,"3660":3564,"3661":3565,"3664":3568,"3665":3569,"3666":3570,"3667":3571,"3668":3572,"3669":3573,"3670":3574,"3671":3575,"3672":3576,"3673":3577,"4304":16781520,"4305":16781521,"4306":16781522,"4307":16781523,"4308":16781524,"4309":16781525,"4310":16781526,"4311":16781527,"4312":16781528,"4313":16781529,"4314":16781530,"4315":16781531,"4316":16781532,"4317":16781533,"4318":16781534,"4319":16781535,"4320":16781536,"4321":16781537,"4322":16781538,"4323":16781539,"4324":16781540,"4325":16781541,"4326":16781542,"4327":16781543,"4328":16781544,"4329":16781545,"4330":16781546,"4331":16781547,"4332":16781548,"4333":16781549,"4334":16781550,"4335":16781551,"4336":16781552,"4337":16781553,"4338":16781554,"4339":16781555,"4340":16781556,"4341":16781557,"4342":16781558,"7682":16784898,"7683":16784899,"7690":16784906,"7691":16784907,"7710":16784926,"7711":16784927,"7734":16784950,"7735":16784951,"7744":16784960,"7745":16784961,"7766":16784982,"7767":16784983,"7776":16784992,"7777":16784993,"7786":16785002,"7787":16785003,"7808":16785024,"7809":16785025,"7810":16785026,"7811":16785027,"7812":16785028,"7813":16785029,"7818":16785034,"7819":16785035,"7840":16785056,"7841":16785057,"7842":16785058,"7843":16785059,"7844":16785060,"7845":16785061,"7846":16785062,"7847":16785063,"7848":16785064,"7849":16785065,"7850":16785066,"7851":16785067,"7852":16785068,"7853":16785069,"7854":16785070,"7855":16785071,"7856":16785072,"7857":16785073,"7858":16785074,"7859":16785075,"7860":16785076,"7861":16785077,"7862":16785078,"7863":16785079,"7864":16785080,"7865":16785081,"7866":16785082,"7867":16785083,"7868":16785084,"7869":16785085,"7870":16785086,"7871":16785087,"7872":16785088,"7873":16785089,"7874":16785090,"7875":16785091,"7876":16785092,"7877":16785093,"7878":16785094,"7879":16785095,"7880":16785096,"7881":16785097,"7882":16785098,"7883":16785099,"7884":16785100,"7885":16785101,"7886":16785102,"7887":16785103,"7888":16785104,"7889":16785105,"7890":16785106,"7891":16785107,"7892":16785108,"7893":16785109,"7894":16785110,"7895":16785111,"7896":16785112,"7897":16785113,"7898":16785114,"7899":16785115,"7900":16785116,"7901":16785117,"7902":16785118,"7903":16785119,"7904":16785120,"7905":16785121,"7906":16785122,"7907":16785123,"7908":16785124,"7909":16785125,"7910":16785126,"7911":16785127,"7912":16785128,"7913":16785129,"7914":16785130,"7915":16785131,"7916":16785132,"7917":16785133,"7918":16785134,"7919":16785135,"7920":16785136,"7921":16785137,"7922":16785138,"7923":16785139,"7924":16785140,"7925":16785141,"7926":16785142,"7927":16785143,"7928":16785144,"7929":16785145,"8194":2722,"8195":2721,"8196":2723,"8197":2724,"8199":2725,"8200":2726,"8201":2727,"8202":2728,"8210":2747,"8211":2730,"8212":2729,"8213":1967,"8215":3295,"8216":2768,"8217":2769,"8218":2813,"8220":2770,"8221":2771,"8222":2814,"8224":2801,"8225":2802,"8226":2790,"8229":2735,"8230":2734,"8240":2773,"8242":2774,"8243":2775,"8248":2812,"8254":1150,"8304":16785520,"8308":16785524,"8309":16785525,"8310":16785526,"8311":16785527,"8312":16785528,"8313":16785529,"8320":16785536,"8321":16785537,"8322":16785538,"8323":16785539,"8324":16785540,"8325":16785541,"8326":16785542,"8327":16785543,"8328":16785544,"8329":16785545,"8352":16785568,"8353":16785569,"8354":16785570,"8355":16785571,"8356":16785572,"8357":16785573,"8358":16785574,"8359":16785575,"8360":16785576,"8361":3839,"8362":16785578,"8363":16785579,"8364":8364,"8453":2744,"8470":1712,"8471":2811,"8478":2772,"8482":2761,"8531":2736,"8532":2737,"8533":2738,"8534":2739,"8535":2740,"8536":2741,"8537":2742,"8538":2743,"8539":2755,"8540":2756,"8541":2757,"8542":2758,"8592":2299,"8593":2300,"8594":2301,"8595":2302,"8658":2254,"8660":2253,"8706":2287,"8709":16785925,"8711":2245,"8712":16785928,"8713":16785929,"8715":16785931,"8728":3018,"8730":2262,"8731":16785947,"8732":16785948,"8733":2241,"8734":2242,"8743":2270,"8744":2271,"8745":2268,"8746":2269,"8747":2239,"8748":16785964,"8749":16785965,"8756":2240,"8757":16785973,"8764":2248,"8771":2249,"8773":16785992,"8775":16785991,"8800":2237,"8801":2255,"8802":16786018,"8803":16786019,"8804":2236,"8805":2238,"8834":2266,"8835":2267,"8866":3068,"8867":3036,"8868":3010,"8869":3022,"8968":3027,"8970":3012,"8981":2810,"8992":2212,"8993":2213,"9109":3020,"9115":2219,"9117":2220,"9118":2221,"9120":2222,"9121":2215,"9123":2216,"9124":2217,"9126":2218,"9128":2223,"9132":2224,"9143":2209,"9146":2543,"9147":2544,"9148":2546,"9149":2547,"9225":2530,"9226":2533,"9227":2537,"9228":2531,"9229":2532,"9251":2732,"9252":2536,"9472":2211,"9474":2214,"9484":2210,"9488":2539,"9492":2541,"9496":2538,"9500":2548,"9508":2549,"9516":2551,"9524":2550,"9532":2542,"9618":2529,"9642":2791,"9643":2785,"9644":2779,"9645":2786,"9646":2783,"9647":2767,"9650":2792,"9651":2787,"9654":2781,"9655":2765,"9660":2793,"9661":2788,"9664":2780,"9665":2764,"9670":2528,"9675":2766,"9679":2782,"9702":2784,"9734":2789,"9742":2809,"9747":2762,"9756":2794,"9758":2795,"9792":2808,"9794":2807,"9827":2796,"9829":2798,"9830":2797,"9837":2806,"9839":2805,"10003":2803,"10007":2804,"10013":2777,"10016":2800,"10216":2748,"10217":2750,"10240":16787456,"10241":16787457,"10242":16787458,"10243":16787459,"10244":16787460,"10245":16787461,"10246":16787462,"10247":16787463,"10248":16787464,"10249":16787465,"10250":16787466,"10251":16787467,"10252":16787468,"10253":16787469,"10254":16787470,"10255":16787471,"10256":16787472,"10257":16787473,"10258":16787474,"10259":16787475,"10260":16787476,"10261":16787477,"10262":16787478,"10263":16787479,"10264":16787480,"10265":16787481,"10266":16787482,"10267":16787483,"10268":16787484,"10269":16787485,"10270":16787486,"10271":16787487,"10272":16787488,"10273":16787489,"10274":16787490,"10275":16787491,"10276":16787492,"10277":16787493,"10278":16787494,"10279":16787495,"10280":16787496,"10281":16787497,"10282":16787498,"10283":16787499,"10284":16787500,"10285":16787501,"10286":16787502,"10287":16787503,"10288":16787504,"10289":16787505,"10290":16787506,"10291":16787507,"10292":16787508,"10293":16787509,"10294":16787510,"10295":16787511,"10296":16787512,"10297":16787513,"10298":16787514,"10299":16787515,"10300":16787516,"10301":16787517,"10302":16787518,"10303":16787519,"10304":16787520,"10305":16787521,"10306":16787522,"10307":16787523,"10308":16787524,"10309":16787525,"10310":16787526,"10311":16787527,"10312":16787528,"10313":16787529,"10314":16787530,"10315":16787531,"10316":16787532,"10317":16787533,"10318":16787534,"10319":16787535,"10320":16787536,"10321":16787537,"10322":16787538,"10323":16787539,"10324":16787540,"10325":16787541,"10326":16787542,"10327":16787543,"10328":16787544,"10329":16787545,"10330":16787546,"10331":16787547,"10332":16787548,"10333":16787549,"10334":16787550,"10335":16787551,"10336":16787552,"10337":16787553,"10338":16787554,"10339":16787555,"10340":16787556,"10341":16787557,"10342":16787558,"10343":16787559,"10344":16787560,"10345":16787561,"10346":16787562,"10347":16787563,"10348":16787564,"10349":16787565,"10350":16787566,"10351":16787567,"10352":16787568,"10353":16787569,"10354":16787570,"10355":16787571,"10356":16787572,"10357":16787573,"10358":16787574,"10359":16787575,"10360":16787576,"10361":16787577,"10362":16787578,"10363":16787579,"10364":16787580,"10365":16787581,"10366":16787582,"10367":16787583,"10368":16787584,"10369":16787585,"10370":16787586,"10371":16787587,"10372":16787588,"10373":16787589,"10374":16787590,"10375":16787591,"10376":16787592,"10377":16787593,"10378":16787594,"10379":16787595,"10380":16787596,"10381":16787597,"10382":16787598,"10383":16787599,"10384":16787600,"10385":16787601,"10386":16787602,"10387":16787603,"10388":16787604,"10389":16787605,"10390":16787606,"10391":16787607,"10392":16787608,"10393":16787609,"10394":16787610,"10395":16787611,"10396":16787612,"10397":16787613,"10398":16787614,"10399":16787615,"10400":16787616,"10401":16787617,"10402":16787618,"10403":16787619,"10404":16787620,"10405":16787621,"10406":16787622,"10407":16787623,"10408":16787624,"10409":16787625,"10410":16787626,"10411":16787627,"10412":16787628,"10413":16787629,"10414":16787630,"10415":16787631,"10416":16787632,"10417":16787633,"10418":16787634,"10419":16787635,"10420":16787636,"10421":16787637,"10422":16787638,"10423":16787639,"10424":16787640,"10425":16787641,"10426":16787642,"10427":16787643,"10428":16787644,"10429":16787645,"10430":16787646,"10431":16787647,"10432":16787648,"10433":16787649,"10434":16787650,"10435":16787651,"10436":16787652,"10437":16787653,"10438":16787654,"10439":16787655,"10440":16787656,"10441":16787657,"10442":16787658,"10443":16787659,"10444":16787660,"10445":16787661,"10446":16787662,"10447":16787663,"10448":16787664,"10449":16787665,"10450":16787666,"10451":16787667,"10452":16787668,"10453":16787669,"10454":16787670,"10455":16787671,"10456":16787672,"10457":16787673,"10458":16787674,"10459":16787675,"10460":16787676,"10461":16787677,"10462":16787678,"10463":16787679,"10464":16787680,"10465":16787681,"10466":16787682,"10467":16787683,"10468":16787684,"10469":16787685,"10470":16787686,"10471":16787687,"10472":16787688,"10473":16787689,"10474":16787690,"10475":16787691,"10476":16787692,"10477":16787693,"10478":16787694,"10479":16787695,"10480":16787696,"10481":16787697,"10482":16787698,"10483":16787699,"10484":16787700,"10485":16787701,"10486":16787702,"10487":16787703,"10488":16787704,"10489":16787705,"10490":16787706,"10491":16787707,"10492":16787708,"10493":16787709,"10494":16787710,"10495":16787711,"12289":1188,"12290":1185,"12300":1186,"12301":1187,"12443":1246,"12444":1247,"12449":1191,"12450":1201,"12451":1192,"12452":1202,"12453":1193,"12454":1203,"12455":1194,"12456":1204,"12457":1195,"12458":1205,"12459":1206,"12461":1207,"12463":1208,"12465":1209,"12467":1210,"12469":1211,"12471":1212,"12473":1213,"12475":1214,"12477":1215,"12479":1216,"12481":1217,"12483":1199,"12484":1218,"12486":1219,"12488":1220,"12490":1221,"12491":1222,"12492":1223,"12493":1224,"12494":1225,"12495":1226,"12498":1227,"12501":1228,"12504":1229,"12507":1230,"12510":1231,"12511":1232,"12512":1233,"12513":1234,"12514":1235,"12515":1196,"12516":1236,"12517":1197,"12518":1237,"12519":1198,"12520":1238,"12521":1239,"12522":1240,"12523":1241,"12524":1242,"12525":1243,"12527":1244,"12530":1190,"12531":1245,"12539":1189,"12540":1200}; -function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; } export default { - fromUnicode : function(u) { + lookup : function(u) { var keysym = codepoints[u]; if (keysym === undefined) { keysym = 0x01000000 | u; } - return lookup(keysym); + return keysym; }, - lookup : lookup }; diff --git a/core/input/util.js b/core/input/util.js index b1e63711..39ac905e 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -93,7 +93,7 @@ export function ModifierSync(charModifier) { function sync(evt, keysym) { var result = []; function syncKey(keysym) { - return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'}; + return {keysym: keysym, type: state[keysym] ? 'keydown' : 'keyup'}; } if (evt.ctrlKey !== undefined && @@ -124,8 +124,7 @@ export function ModifierSync(charModifier) { return result; } function syncKeyEvent(evt, down) { - var obj = getKeysym(evt); - var keysym = obj ? obj.keysym : null; + var keysym = getKeysym(evt); // first, apply the event itself, if relevant if (keysym !== null && state[keysym] !== undefined) { @@ -177,17 +176,17 @@ export function getKeysym(evt){ codepoint = evt.keyCode; } if (codepoint) { - return keysyms.fromUnicode(substituteCodepoint(codepoint)); + return keysyms.lookup(substituteCodepoint(codepoint)); } // we could check evt.key here. // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list, // so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key // so we don't *need* it yet if (evt.keyCode) { - return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey)); + return keysymFromKeyCode(evt.keyCode, evt.shiftKey); } if (evt.which) { - return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey)); + return keysymFromKeyCode(evt.which, evt.shiftKey); } return null; } @@ -453,7 +452,7 @@ export function KeyEventDecoder (modifierState, next) { if (active && keysym) { var isCharModifier = false; for (var i = 0; i < active.length; ++i) { - if (active[i] === keysym.keysym) { + if (active[i] === keysym) { isCharModifier = true; } } @@ -527,7 +526,7 @@ export function VerifyCharModifier (next) { // Firefox sends keypress even when no char is generated. // so, if keypress keysym is the same as we'd have guessed from keydown, // the modifier didn't have any effect, and should not be escaped - if (queue[0].escape && (!cur.keysym || cur.keysym.keysym !== queue[0].keysym.keysym)) { + if (queue[0].escape && (!cur.keysym || cur.keysym !== queue[0].keysym)) { cur.escape = queue[0].escape; } cur.keysym = queue[0].keysym; @@ -570,7 +569,7 @@ export function TrackKeyState (next) { if (evt.keysym) { // make sure last event contains this keysym (a single "logical" keyevent // can cause multiple key events to be sent to the VNC server) - last.keysyms[evt.keysym.keysym] = evt.keysym; + last.keysyms[evt.keysym] = evt.keysym; last.ignoreKeyPress = true; next(evt); } @@ -587,7 +586,7 @@ export function TrackKeyState (next) { // If we didn't expect a keypress, and already sent a keydown to the VNC server // based on the keydown, make sure to skip this event. if (evt.keysym && !last.ignoreKeyPress) { - last.keysyms[evt.keysym.keysym] = evt.keysym; + last.keysyms[evt.keysym] = evt.keysym; evt.type = 'keydown'; next(evt); } @@ -646,14 +645,14 @@ export function EscapeModifiers (next) { } // undo modifiers for (var i = 0; i < evt.escape.length; ++i) { - next({type: 'keyup', keyId: 0, keysym: keysyms.lookup(evt.escape[i])}); + next({type: 'keyup', keyId: 0, keysym: evt.escape[i]}); } // send the character event next(evt); // redo modifiers /* jshint shadow: true */ for (var i = 0; i < evt.escape.length; ++i) { - next({type: 'keydown', keyId: 0, keysym: keysyms.lookup(evt.escape[i])}); + next({type: 'keydown', keyId: 0, keysym: evt.escape[i]}); } /* jshint shadow: false */ }; diff --git a/core/rfb.js b/core/rfb.js index 669c817e..9afe9e59 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -659,7 +659,7 @@ RFB.prototype = { Log.Error('Unable to find a xt scancode for code = ' + keyevent.code); } } else { - keysym = keyevent.keysym.keysym; + keysym = keyevent.keysym; RFB.messages.keyEvent(this._sock, keysym, down); } }, diff --git a/tests/input.html b/tests/input.html index 0938a4ab..febee5fd 100644 --- a/tests/input.html +++ b/tests/input.html @@ -63,11 +63,7 @@ function rfbKeyPress(keysym, down) { var d = down ? "down" : " up "; - var key = keysyms.lookup(keysym); var msg = "RFB keypress " + d + " keysym: " + keysym; - if (key && key.keyname) { - msg += " key name: " + key.keyname; - } message(msg); } function rawKey(e) { diff --git a/tests/test.helper.js b/tests/test.helper.js index 12232d82..c3c34626 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -23,29 +23,29 @@ describe('Helpers', function() { }); }); - describe('keysyms.fromUnicode', function() { + describe('keysyms.lookup', function() { it('should map ASCII characters to keysyms', function() { - expect(keysyms.fromUnicode('a'.charCodeAt())).to.have.property('keysym', 0x61); - expect(keysyms.fromUnicode('A'.charCodeAt())).to.have.property('keysym', 0x41); + expect(keysyms.lookup('a'.charCodeAt())).to.be.equal(0x61); + expect(keysyms.lookup('A'.charCodeAt())).to.be.equal(0x41); }); it('should map Latin-1 characters to keysyms', function() { - expect(keysyms.fromUnicode('ø'.charCodeAt())).to.have.property('keysym', 0xf8); + expect(keysyms.lookup('ø'.charCodeAt())).to.be.equal(0xf8); - expect(keysyms.fromUnicode('é'.charCodeAt())).to.have.property('keysym', 0xe9); + expect(keysyms.lookup('é'.charCodeAt())).to.be.equal(0xe9); }); it('should map characters that are in Windows-1252 but not in Latin-1 to keysyms', function() { - expect(keysyms.fromUnicode('Š'.charCodeAt())).to.have.property('keysym', 0x01a9); + expect(keysyms.lookup('Š'.charCodeAt())).to.be.equal(0x01a9); }); it('should map characters which aren\'t in Latin1 *or* Windows-1252 to keysyms', function() { - expect(keysyms.fromUnicode('ŵ'.charCodeAt())).to.have.property('keysym', 0x1000175); + expect(keysyms.lookup('ŵ'.charCodeAt())).to.be.equal(0x1000175); }); it('should map unknown codepoints to the Unicode range', function() { - expect(keysyms.fromUnicode('\n'.charCodeAt())).to.have.property('keysym', 0x100000a); - expect(keysyms.fromUnicode('\u262D'.charCodeAt())).to.have.property('keysym', 0x100262d); + expect(keysyms.lookup('\n'.charCodeAt())).to.be.equal(0x100000a); + expect(keysyms.lookup('\u262D'.charCodeAt())).to.be.equal(0x100262d); }); // This requires very recent versions of most browsers... skipping for now it.skip('should map UCS-4 codepoints to the Unicode range', function() { - //expect(keysyms.fromUnicode('\u{1F686}'.codePointAt())).to.have.property('keysym', 0x101f686); + //expect(keysyms.lookup('\u{1F686}'.codePointAt())).to.be.equal(0x101f686); }); }); @@ -81,23 +81,23 @@ describe('Helpers', function() { describe('getKeysym', function() { it('should prefer char', function() { - expect(KeyboardUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x61); + expect(KeyboardUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61); }); it('should use charCode if no char', function() { - expect(KeyboardUtil.getKeysym({char : '', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9); - expect(KeyboardUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9); - expect(KeyboardUtil.getKeysym({char : 'hello', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9); + expect(KeyboardUtil.getKeysym({char : '', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); + expect(KeyboardUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); + expect(KeyboardUtil.getKeysym({char : 'hello', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); }); it('should use keyCode if no charCode', function() { - expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: false})).to.have.property('keysym', 0x62); - expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: true})).to.have.property('keysym', 0x42); + expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: false})).to.be.equal(0x62); + expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: true})).to.be.equal(0x42); }); it('should use which if no keyCode', function() { - expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: false})).to.have.property('keysym', 0x63); - expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: true})).to.have.property('keysym', 0x43); + expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: false})).to.be.equal(0x63); + expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: true})).to.be.equal(0x43); }); it('should substitute where applicable', function() { - expect(KeyboardUtil.getKeysym({char : 'Ș'})).to.have.property('keysym', 0x1aa); + expect(KeyboardUtil.getKeysym({char : 'Ș'})).to.be.equal(0x1aa); }); }); @@ -151,13 +151,13 @@ describe('Helpers', function() { expect(sync.keydown({ keyCode: 0x41, ctrlKey: true, - })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keydown'}]); + })).to.be.deep.equal([{keysym: 0xffe3, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ keyCode: 0x41, ctrlKey: false - })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keyup'}]); + })).to.be.deep.equal([{keysym: 0xffe3, type: 'keyup'}]); }); }); describe('Toggle Alt', function() { @@ -166,13 +166,13 @@ describe('Helpers', function() { expect(sync.keydown({ keyCode: 0x41, altKey: true, - })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]); + })).to.be.deep.equal([{keysym: 0xffe9, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ keyCode: 0x41, altKey: false - })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keyup'}]); + })).to.be.deep.equal([{keysym: 0xffe9, type: 'keyup'}]); }); }); describe('Toggle AltGr', function() { @@ -181,13 +181,13 @@ describe('Helpers', function() { expect(sync.keydown({ keyCode: 0x41, altGraphKey: true, - })).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keydown'}]); + })).to.be.deep.equal([{keysym: 0xfe03, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ keyCode: 0x41, altGraphKey: false - })).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keyup'}]); + })).to.be.deep.equal([{keysym: 0xfe03, type: 'keyup'}]); }); }); describe('Toggle Shift', function() { @@ -196,13 +196,13 @@ describe('Helpers', function() { expect(sync.keydown({ keyCode: 0x41, shiftKey: true, - })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe1), type: 'keydown'}]); + })).to.be.deep.equal([{keysym: 0xffe1, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ keyCode: 0x41, shiftKey: false - })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe1), type: 'keyup'}]); + })).to.be.deep.equal([{keysym: 0xffe1, type: 'keyup'}]); }); }); describe('Toggle Meta', function() { @@ -211,13 +211,13 @@ describe('Helpers', function() { expect(sync.keydown({ keyCode: 0x41, metaKey: true, - })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe7), type: 'keydown'}]); + })).to.be.deep.equal([{keysym: 0xffe7, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ keyCode: 0x41, metaKey: false - })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe7), type: 'keyup'}]); + })).to.be.deep.equal([{keysym: 0xffe7, type: 'keyup'}]); }); }); describe('Modifier keyevents', function() { @@ -245,14 +245,14 @@ describe('Helpers', function() { expect(KeyboardUtil.ModifierSync().keydown({ keyCode: 0x11, altKey: true - })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]); + })).to.be.deep.equal([{keysym: 0xffe9, type: 'keydown'}]); }) }); describe('sync modifiers on non-key events', function() { it('should generate sync events when receiving non-keyboard events', function() { expect(KeyboardUtil.ModifierSync().syncAny({ altKey: true - })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]); + })).to.be.deep.equal([{keysym: 0xffe9, type: 'keydown'}]); }); }); describe('do not treat shift as a modifier key', function() { diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index b68c96d9..dc8f0dbc 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -16,7 +16,7 @@ describe('Key Event Pipeline Stages', function() { }); it('should pass the right keysym through', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt.keysym).to.be.deep.equal(keysyms.lookup(0x61)); + expect(evt.keysym).to.be.deep.equal(0x61); done(); }).keypress({keyCode: 0x41}); }); @@ -39,11 +39,11 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { switch (count) { case 0: // fake a ctrl keydown - expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xffe3), type: 'keydown'}); + expect(evt).to.be.deep.equal({keysym: 0xffe3, type: 'keydown'}); ++count; break; case 1: - expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown', keysym: keysyms.lookup(0x61)}); + expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown', keysym: 0x61}); done(); break; } @@ -57,13 +57,13 @@ describe('Key Event Pipeline Stages', function() { }); it('should forward keyup events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'}); + expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keyup'}); done(); }).keyup({keyCode: 0x41}); }); it('should forward keypress events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'}); + expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keypress'}); done(); }).keypress({keyCode: 0x41}); }); @@ -72,7 +72,7 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { switch (count) { case 0: // fake altgr - expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xfe03), type: 'keydown'}); + expect(evt).to.be.deep.equal({keysym: 0xfe03, type: 'keydown'}); ++count; break; case 1: // stall before processing the 'a' keydown @@ -83,7 +83,7 @@ describe('Key Event Pipeline Stages', function() { expect(evt).to.be.deep.equal({ type: 'keydown', keyId: 0x41, - keysym: keysyms.lookup(0x61) + keysym: 0x61 }); done(); @@ -197,7 +197,7 @@ describe('Key Event Pipeline Stages', function() { expect(evt).to.be.deep.equal({ type: 'keypress', keyId: 'A'.charCodeAt(), - keysym: keysyms.lookup('a'.charCodeAt()), + keysym: 'a'.charCodeAt(), escape: [0xfe03] }); done(); @@ -220,7 +220,7 @@ describe('Key Event Pipeline Stages', function() { expect(evt).to.be.deep.equal({ type: 'keypress', keyId: 'A'.charCodeAt(), - keysym: keysyms.lookup('a'.charCodeAt()), + keysym: 'a'.charCodeAt(), escape: [0xffe9, 0xffe3] }); done(); @@ -252,7 +252,7 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { switch (times_called++) { case 1: - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'}); + expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keydown'}); break; } }).keydown({keyCode: 0x41, ctrlKey: true}); @@ -263,7 +263,7 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { switch (times_called++) { case 2: - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'}); + expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keydown'}); break; } }).keydown({keyCode: 0x41, altGraphKey: true}); @@ -271,21 +271,21 @@ describe('Key Event Pipeline Stages', function() { }); it('should not remove keysym from keydown if key is noncharacter', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt, 'bacobjpace').to.be.deep.equal({keyId: 0x09, keysym: keysyms.lookup(0xff09), type: 'keydown'}); + expect(evt, 'bacobjpace').to.be.deep.equal({keyId: 0x09, keysym: 0xff09, type: 'keydown'}); }).keydown({keyCode: 0x09}); KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt, 'ctrl').to.be.deep.equal({keyId: 0x11, keysym: keysyms.lookup(0xffe3), type: 'keydown'}); + expect(evt, 'ctrl').to.be.deep.equal({keyId: 0x11, keysym: 0xffe3, type: 'keydown'}); }).keydown({keyCode: 0x11}); }); it('should never remove keysym from keypress', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'}); + expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keypress'}); }).keypress({keyCode: 0x41}); }); it('should never remove keysym from keyup', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'}); + expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keyup'}); }).keyup({keyCode: 0x41}); }); }); @@ -296,31 +296,31 @@ describe('Key Event Pipeline Stages', function() { describe('Verify that char modifiers are active', function() { it('should pass keydown events through if there is no stall', function(done) { var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)}); + expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x41}); done(); - })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)}); + })({type: 'keydown', keyId: 0x41, keysym: 0x41}); }); it('should pass keyup events through if there is no stall', function(done) { var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)}); + expect(evt).to.deep.equal({type: 'keyup', keyId: 0x41, keysym: 0x41}); done(); - })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)}); + })({type: 'keyup', keyId: 0x41, keysym: 0x41}); }); it('should pass keypress events through if there is no stall', function(done) { var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)}); + expect(evt).to.deep.equal({type: 'keypress', keyId: 0x41, keysym: 0x41}); done(); - })({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)}); + })({type: 'keypress', keyId: 0x41, keysym: 0x41}); }); it('should not pass stall events through', function(done){ var obj = KeyboardUtil.VerifyCharModifier(function(evt){ // should only be called once, for the keydown - expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)}); + expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x41}); done(); }); obj({type: 'stall'}); - obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)}); + obj({type: 'keydown', keyId: 0x41, keysym: 0x41}); }); it('should merge keydown and keypress events if they come after a stall', function(done) { var next_called = false; @@ -328,13 +328,13 @@ describe('Key Event Pipeline Stages', function() { // should only be called once, for the keydown expect(next_called).to.be.false; next_called = true; - expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44)}); + expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x44}); done(); }); obj({type: 'stall'}); - obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); - obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)}); + obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); + obj({type: 'keypress', keyId: 0x43, keysym: 0x44}); expect(next_called).to.be.false; }); it('should preserve modifier attribute when merging if keysyms differ', function(done) { @@ -343,13 +343,13 @@ describe('Key Event Pipeline Stages', function() { // should only be called once, for the keydown expect(next_called).to.be.false; next_called = true; - expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44), escape: [0xffe3]}); + expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x44, escape: [0xffe3]}); done(); }); obj({type: 'stall'}); - obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); - obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44), escape: [0xffe3]}); + obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); + obj({type: 'keypress', keyId: 0x43, keysym: 0x44, escape: [0xffe3]}); expect(next_called).to.be.false; }); it('should not preserve modifier attribute when merging if keysyms are the same', function() { @@ -358,18 +358,18 @@ describe('Key Event Pipeline Stages', function() { }); obj({type: 'stall'}); - obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); - obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x42), escape: [0xffe3]}); + obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); + obj({type: 'keypress', keyId: 0x43, keysym: 0x42, escape: [0xffe3]}); }); it('should not merge keydown and keypress events if there is no stall', function(done) { var times_called = 0; var obj = KeyboardUtil.VerifyCharModifier(function(evt){ switch(times_called) { case 0: - expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); + expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42}); break; case 1: - expect(evt).to.deep.equal({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)}); + expect(evt).to.deep.equal({type: 'keypress', keyId: 0x43, keysym: 0x44}); done(); break; } @@ -377,21 +377,21 @@ describe('Key Event Pipeline Stages', function() { ++times_called; }); - obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); - obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)}); + obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); + obj({type: 'keypress', keyId: 0x43, keysym: 0x44}); }); it('should not merge keydown and keypress events if separated by another event', function(done) { var times_called = 0; var obj = KeyboardUtil.VerifyCharModifier(function(evt){ switch(times_called) { case 0: - expect(evt,1).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); + expect(evt,1).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42}); break; case 1: - expect(evt,2).to.deep.equal({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)}); + expect(evt,2).to.deep.equal({type: 'keyup', keyId: 0x43, keysym: 0x44}); break; case 2: - expect(evt,3).to.deep.equal({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)}); + expect(evt,3).to.deep.equal({type: 'keypress', keyId: 0x45, keysym: 0x46}); done(); break; } @@ -400,9 +400,9 @@ describe('Key Event Pipeline Stages', function() { }); obj({type: 'stall'}); - obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); - obj({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)}); - obj({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)}); + obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); + obj({type: 'keyup', keyId: 0x43, keysym: 0x44}); + obj({type: 'keypress', keyId: 0x45, keysym: 0x46}); }); }); @@ -421,19 +421,19 @@ describe('Key Event Pipeline Stages', function() { ++times_called; if (elem.type == 'keyup') { expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym.keysym]; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; + delete keysymsdown[evt.keysym]; } else { expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; } elem = null; }); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}; - keysymsdown[keysyms.lookup(0x42).keysym] = true; + elem = {type: 'keydown', keyId: 0x41, keysym: 0x42}; + keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; elem = {type: 'keyup', keyId: 0x41}; @@ -449,19 +449,19 @@ describe('Key Event Pipeline Stages', function() { ++times_called; if (elem.type == 'keyup') { expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym.keysym]; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; + delete keysymsdown[evt.keysym]; } else { expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; } elem = null; }); expect(elem).to.be.null; - elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)}; - keysymsdown[keysyms.lookup(0x42).keysym] = true; + elem = {type: 'keypress', keyId: 0x41, keysym: 0x42}; + keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; elem = {type: 'keyup', keyId: 0x41}; @@ -478,23 +478,23 @@ describe('Key Event Pipeline Stages', function() { ++times_called; if (elem.type == 'keyup') { expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym.keysym]; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; + delete keysymsdown[evt.keysym]; } else { expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; elem = null; } }); expect(elem).to.be.null; - elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)}; - keysymsdown[keysyms.lookup(0x42).keysym] = true; + elem = {type: 'keypress', keyId: 0x41, keysym: 0x42}; + keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)}; - keysymsdown[keysyms.lookup(0x43).keysym] = true; + elem = {type: 'keypress', keyId: 0x41, keysym: 0x43}; + keysymsdown[0x43] = true; obj(elem); expect(elem).to.be.null; elem = {type: 'keyup', keyId: 0x41}; @@ -510,23 +510,23 @@ describe('Key Event Pipeline Stages', function() { ++times_called; if (elem.type == 'keyup') { expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym.keysym]; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; + delete keysymsdown[evt.keysym]; } else { expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; elem = null; } }); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)}; - keysymsdown[keysyms.lookup(0x42).keysym] = true; + elem = {type: 'keydown', keyId: 0, keysym: 0x42}; + keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)}; - keysymsdown[keysyms.lookup(0x43).keysym] = true; + elem = {type: 'keydown', keyId: 0, keysym: 0x43}; + keysymsdown[0x43] = true; obj(elem); expect(times_called).to.be.equal(2); expect(elem).to.be.null; @@ -546,23 +546,23 @@ describe('Key Event Pipeline Stages', function() { ++times_called; if (elem.type == 'keyup') { expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym.keysym]; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; + delete keysymsdown[evt.keysym]; } else { expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; elem = null; } }); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)}; - keysymsdown[keysyms.lookup(0x42).keysym] = true; + elem = {type: 'keydown', keyId: 0, keysym: 0x42}; + keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)}; - keysymsdown[keysyms.lookup(0x42).keysym] = true; + elem = {type: 'keydown', keyId: 0, keysym: 0x42}; + keysymsdown[0x42] = true; obj(elem); expect(times_called).to.be.equal(2); expect(elem).to.be.null; @@ -579,23 +579,23 @@ describe('Key Event Pipeline Stages', function() { ++times_called; if (elem.type == 'keyup') { expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym.keysym]; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; + delete keysymsdown[evt.keysym]; } else { expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; elem = null; } }); expect(elem).to.be.null; - elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)}; - keysymsdown[keysyms.lookup(0x42).keysym] = true; + elem = {type: 'keypress', keyId: 0x41, keysym: 0x42}; + keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keypress', keyId: 0x42, keysym: keysyms.lookup(0x43)}; - keysymsdown[keysyms.lookup(0x43).keysym] = true; + elem = {type: 'keypress', keyId: 0x42, keysym: 0x43}; + keysymsdown[0x43] = true; obj(elem); expect(elem).to.be.null; elem = {type: 'keyup', keyId: 0x41}; @@ -614,23 +614,23 @@ describe('Key Event Pipeline Stages', function() { ++times_called; if (elem.type == 'keyup') { expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym.keysym]; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; + delete keysymsdown[evt.keysym]; } else { expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; elem = null; } }); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)}; - keysymsdown[keysyms.lookup(0x42).keysym] = true; + elem = {type: 'keydown', keyId: 0, keysym: 0x42}; + keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x43)}; - keysymsdown[keysyms.lookup(0x43).keysym] = true; + elem = {type: 'keydown', keyId: 0x42, keysym: 0x43}; + keysymsdown[0x43] = true; obj(elem); expect(elem).to.be.null; expect(times_called).to.be.equal(2); @@ -650,23 +650,23 @@ describe('Key Event Pipeline Stages', function() { ++times_called; if (elem.type == 'keyup') { expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym.keysym]; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; + delete keysymsdown[evt.keysym]; } else { expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; + expect (keysymsdown[evt.keysym]).to.not.be.undefined; elem = null; } }); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}; - keysymsdown[keysyms.lookup(0x42).keysym] = true; + elem = {type: 'keydown', keyId: 0x41, keysym: 0x42}; + keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)}; - keysymsdown[keysyms.lookup(0x43).keysym] = true; + elem = {type: 'keydown', keyId: 0, keysym: 0x43}; + keysymsdown[0x43] = true; obj(elem); expect(elem).to.be.null; elem = {type: 'keyup', keyId: 0x41}; @@ -686,14 +686,14 @@ describe('Key Event Pipeline Stages', function() { expect(evt.type).to.be.equal('keydown'); break; case 3: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x42, keysym: keysyms.lookup(0x62)}); + expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x42, keysym: 0x62}); break; } }); - obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)}); - obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)}); - obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)}); + obj({type: 'keydown', keyId: 0x41, keysym: 0x61}); + obj({type: 'keydown', keyId: 0x42, keysym: 0x62}); + obj({type: 'keydown', keyId: 0x43, keysym: 0x63}); obj({type: 'keyup', keyId: 0x42}); expect(times_called).to.equal(4); }); @@ -707,14 +707,14 @@ describe('Key Event Pipeline Stages', function() { expect(evt.type).to.be.equal('keydown'); break; case 3: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x61)}); + expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0x61}); break; } }); - obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x61)}); - obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x62)}); - obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x63)}); + obj({type: 'keydown', keyId: 0, keysym: 0x61}); + obj({type: 'keydown', keyId: 0, keysym: 0x62}); + obj({type: 'keydown', keyId: 0x41, keysym: 0x63}); obj({type: 'keyup', keyId: 0x0}); expect(times_called).to.equal(4); }); @@ -728,14 +728,14 @@ describe('Key Event Pipeline Stages', function() { expect(evt.type).to.be.equal('keydown'); break; case 3: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x44, keysym: keysyms.lookup(0x63)}); + expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x44, keysym: 0x63}); break; } }); - obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)}); - obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)}); - obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)}); + obj({type: 'keydown', keyId: 0x41, keysym: 0x61}); + obj({type: 'keydown', keyId: 0x42, keysym: 0x62}); + obj({type: 'keydown', keyId: 0x43, keysym: 0x63}); obj({type: 'keyup', keyId: 0x44}); expect(times_called).to.equal(4); }); @@ -747,9 +747,9 @@ describe('Key Event Pipeline Stages', function() { ++times_called; }); - obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); + obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); expect(times_called).to.be.equal(1); - obj({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)}); + obj({type: 'keypress', keyId: 0x41, keysym: 0x43}); }); }); describe('releaseAll', function() { @@ -766,15 +766,15 @@ describe('Key Event Pipeline Stages', function() { var obj = KeyboardUtil.TrackKeyState(function(evt) { switch (times_called++) { case 2: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x41)}); + expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0x41}); break; case 3: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x42)}); + expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0x42}); break; } }); - obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)}); - obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x42)}); + obj({type: 'keydown', keyId: 0x41, keysym: 0x41}); + obj({type: 'keydown', keyId: 0x42, keysym: 0x42}); expect(times_called).to.be.equal(2); obj({type: 'releaseall'}); expect(times_called).to.be.equal(4); @@ -792,8 +792,8 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.EscapeModifiers(function(evt) { expect(times_called).to.be.equal(0); ++times_called; - expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); - })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); + expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42}); + })({type: 'keydown', keyId: 0x41, keysym: 0x42}); expect(times_called).to.be.equal(1); }); it('should generate fake undo/redo events when a char modifier is down', function() { @@ -801,22 +801,22 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.EscapeModifiers(function(evt) { switch(times_called++) { case 0: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe9)}); + expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0xffe9}); break; case 1: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe3)}); + expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0xffe3}); break; case 2: - expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]}); + expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42, escape: [0xffe9, 0xffe3]}); break; case 3: - expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe9)}); + expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: 0xffe9}); break; case 4: - expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe3)}); + expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: 0xffe3}); break; } - })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]}); + })({type: 'keydown', keyId: 0x41, keysym: 0x42, escape: [0xffe9, 0xffe3]}); expect(times_called).to.be.equal(5); }); }); @@ -826,8 +826,8 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.EscapeModifiers(function(evt) { expect(times_called).to.be.equal(0); ++times_called; - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]}); - })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]}); + expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: 0x42, escape: [0xfe03]}); + })({type: 'keyup', keyId: 0x41, keysym: 0x42, escape: [0xfe03]}); expect(times_called).to.be.equal(1); }); it('should pass through when a char modifier is not down', function() { @@ -835,8 +835,8 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.EscapeModifiers(function(evt) { expect(times_called).to.be.equal(0); ++times_called; - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)}); - })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)}); + expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: 0x42}); + })({type: 'keyup', keyId: 0x41, keysym: 0x42}); expect(times_called).to.be.equal(1); }); }); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index dd658e04..c8035f40 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -2017,8 +2017,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send a key message on a key press', function () { var keyevent = {}; keyevent.type = 'keydown'; - keyevent.keysym = {}; - keyevent.keysym.keysym = 1234; + keyevent.keysym = 1234; client._keyboard._onKeyPress(keyevent); var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(key_msg, 1234, 1); diff --git a/utils/parse.js b/utils/parse.js index d2067db9..970a1de6 100644 --- a/utils/parse.js +++ b/utils/parse.js @@ -4,7 +4,6 @@ var fs = require('fs'); var show_help = process.argv.length === 2; -var use_keynames = false; var filename; for (var i = 2; i < process.argv.length; ++i) { @@ -13,10 +12,6 @@ for (var i = 2; i < process.argv.length; ++i) { case "-h": show_help = true; break; - case "--debug-names": - case "-d": - use_keynames = true; - break; case "--file": case "-f": default: @@ -33,14 +28,10 @@ if (show_help) { console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings"); console.log("Usage: node parse.js [options] filename:"); console.log(" -h [ --help ] Produce this help message"); - console.log(" -d [ --debug-names ] Preserve keysym names for debugging (Increases file size by ~40KB)"); console.log(" filename The keysymdef.h file to parse"); return; } -// Set this to false to omit key names from the generated keysymdef.js -// This reduces the file size by around 40kb, but may hinder debugging - var buf = fs.readFileSync(filename); var str = buf.toString('utf8'); @@ -48,7 +39,6 @@ var re = /^\#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/) var arr = str.split('\n'); -var keysyms = {}; var codepoints = {}; for (var i = 0; i < arr.length; ++i) { @@ -58,8 +48,6 @@ for (var i = 0; i < arr.length; ++i) { var keysym = parseInt(result[2], 16); var remainder = result[3]; - keysyms[keysym] = keyname; - var unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder); if (unicodeRes) { var unicode = parseInt(unicodeRes[1], 16); @@ -80,21 +68,18 @@ var out = "// This file describes mappings from Unicode codepoints to the keysym "// (and optionally, key names) expected by the RFB protocol\n" + "// How this file was generated:\n" + "// " + process.argv.join(" ") + "\n" + -"var keynames = {keysyms};\n" + +"\n" + "var codepoints = {codepoints};\n" + "\n" + -"function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }\n" + "export default {\n" + -" fromUnicode : function(u) {\n" + +" lookup : function(u) {\n" + " var keysym = codepoints[u];\n" + " if (keysym === undefined) {\n" + " keysym = 0x01000000 | u;\n" + " }\n" + -" return lookup(keysym);\n" + +" return keysym;\n" + " },\n" + -" lookup : lookup\n" + "};\n"; -out = out.replace('{keysyms}', use_keynames ? JSON.stringify(keysyms) : "null"); out = out.replace('{codepoints}', JSON.stringify(codepoints)); fs.writeFileSync("keysymdef.js", out); From 041568bd3110d01582a031a1c8467323b72b279b Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 24 Jan 2017 12:39:21 +0100 Subject: [PATCH 03/20] Clean up keysymdef --- core/input/keysymdef.js | 1568 ++++++++++++++++++++++++++- utils/{parse.js => genkeysymdef.js} | 55 +- 2 files changed, 1602 insertions(+), 21 deletions(-) rename utils/{parse.js => genkeysymdef.js} (62%) mode change 100644 => 100755 diff --git a/core/input/keysymdef.js b/core/input/keysymdef.js index a542f98c..8032ad64 100644 --- a/core/input/keysymdef.js +++ b/core/input/keysymdef.js @@ -1,9 +1,1567 @@ -// This file describes mappings from Unicode codepoints to the keysym values -// (and optionally, key names) expected by the RFB protocol -// How this file was generated: -// node /Users/jalf/dev/mi/novnc/utils/parse.js /opt/X11/include/X11/keysymdef.h +/* + * Mapping from Unicode codepoints to X11/RFB keysyms + * + * This file was automatically generated from keysymdef.h + * DO NOT EDIT! + */ -var codepoints = {"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"60":60,"61":61,"62":62,"63":63,"64":64,"65":65,"66":66,"67":67,"68":68,"69":69,"70":70,"71":71,"72":72,"73":73,"74":74,"75":75,"76":76,"77":77,"78":78,"79":79,"80":80,"81":81,"82":82,"83":83,"84":84,"85":85,"86":86,"87":87,"88":88,"89":89,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"97":97,"98":98,"99":99,"100":100,"101":101,"102":102,"103":103,"104":104,"105":105,"106":106,"107":107,"108":108,"109":109,"110":110,"111":111,"112":112,"113":113,"114":114,"115":115,"116":116,"117":117,"118":118,"119":119,"120":120,"121":121,"122":122,"123":123,"124":124,"125":125,"126":126,"160":160,"161":161,"162":162,"163":163,"164":164,"165":165,"166":166,"167":167,"168":168,"169":169,"170":170,"171":171,"172":172,"173":173,"174":174,"175":175,"176":176,"177":177,"178":178,"179":179,"180":180,"181":181,"182":182,"183":183,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"190":190,"191":191,"192":192,"193":193,"194":194,"195":195,"196":196,"197":197,"198":198,"199":199,"200":200,"201":201,"202":202,"203":203,"204":204,"205":205,"206":206,"207":207,"208":208,"209":209,"210":210,"211":211,"212":212,"213":213,"214":214,"215":215,"216":216,"217":217,"218":218,"219":219,"220":220,"221":221,"222":222,"223":223,"224":224,"225":225,"226":226,"227":227,"228":228,"229":229,"230":230,"231":231,"232":232,"233":233,"234":234,"235":235,"236":236,"237":237,"238":238,"239":239,"240":240,"241":241,"242":242,"243":243,"244":244,"245":245,"246":246,"247":247,"248":248,"249":249,"250":250,"251":251,"252":252,"253":253,"254":254,"255":255,"256":960,"257":992,"258":451,"259":483,"260":417,"261":433,"262":454,"263":486,"264":710,"265":742,"266":709,"267":741,"268":456,"269":488,"270":463,"271":495,"272":464,"273":496,"274":938,"275":954,"278":972,"279":1004,"280":458,"281":490,"282":460,"283":492,"284":728,"285":760,"286":683,"287":699,"288":725,"289":757,"290":939,"291":955,"292":678,"293":694,"294":673,"295":689,"296":933,"297":949,"298":975,"299":1007,"300":16777516,"301":16777517,"302":967,"303":999,"304":681,"305":697,"308":684,"309":700,"310":979,"311":1011,"312":930,"313":453,"314":485,"315":934,"316":950,"317":421,"318":437,"321":419,"322":435,"323":465,"324":497,"325":977,"326":1009,"327":466,"328":498,"330":957,"331":959,"332":978,"333":1010,"336":469,"337":501,"338":5052,"339":5053,"340":448,"341":480,"342":931,"343":947,"344":472,"345":504,"346":422,"347":438,"348":734,"349":766,"350":426,"351":442,"352":425,"353":441,"354":478,"355":510,"356":427,"357":443,"358":940,"359":956,"360":989,"361":1021,"362":990,"363":1022,"364":733,"365":765,"366":473,"367":505,"368":475,"369":507,"370":985,"371":1017,"372":16777588,"373":16777589,"374":16777590,"375":16777591,"376":5054,"377":428,"378":444,"379":431,"380":447,"381":430,"382":446,"399":16777615,"402":2294,"415":16777631,"416":16777632,"417":16777633,"431":16777647,"432":16777648,"437":16777653,"438":16777654,"439":16777655,"466":16777681,"486":16777702,"487":16777703,"601":16777817,"629":16777845,"658":16777874,"711":439,"728":418,"729":511,"731":434,"733":445,"901":1966,"902":1953,"904":1954,"905":1955,"906":1956,"908":1959,"910":1960,"911":1963,"912":1974,"913":1985,"914":1986,"915":1987,"916":1988,"917":1989,"918":1990,"919":1991,"920":1992,"921":1993,"922":1994,"923":1995,"924":1996,"925":1997,"926":1998,"927":1999,"928":2000,"929":2001,"931":2002,"932":2004,"933":2005,"934":2006,"935":2007,"936":2008,"937":2009,"938":1957,"939":1961,"940":1969,"941":1970,"942":1971,"943":1972,"944":1978,"945":2017,"946":2018,"947":2019,"948":2020,"949":2021,"950":2022,"951":2023,"952":2024,"953":2025,"954":2026,"955":2027,"956":2028,"957":2029,"958":2030,"959":2031,"960":2032,"961":2033,"962":2035,"963":2034,"964":2036,"965":2037,"966":2038,"967":2039,"968":2040,"969":2041,"970":1973,"971":1977,"972":1975,"973":1976,"974":1979,"1025":1715,"1026":1713,"1027":1714,"1028":1716,"1029":1717,"1030":1718,"1031":1719,"1032":1720,"1033":1721,"1034":1722,"1035":1723,"1036":1724,"1038":1726,"1039":1727,"1040":1761,"1041":1762,"1042":1783,"1043":1767,"1044":1764,"1045":1765,"1046":1782,"1047":1786,"1048":1769,"1049":1770,"1050":1771,"1051":1772,"1052":1773,"1053":1774,"1054":1775,"1055":1776,"1056":1778,"1057":1779,"1058":1780,"1059":1781,"1060":1766,"1061":1768,"1062":1763,"1063":1790,"1064":1787,"1065":1789,"1066":1791,"1067":1785,"1068":1784,"1069":1788,"1070":1760,"1071":1777,"1072":1729,"1073":1730,"1074":1751,"1075":1735,"1076":1732,"1077":1733,"1078":1750,"1079":1754,"1080":1737,"1081":1738,"1082":1739,"1083":1740,"1084":1741,"1085":1742,"1086":1743,"1087":1744,"1088":1746,"1089":1747,"1090":1748,"1091":1749,"1092":1734,"1093":1736,"1094":1731,"1095":1758,"1096":1755,"1097":1757,"1098":1759,"1099":1753,"1100":1752,"1101":1756,"1102":1728,"1103":1745,"1105":1699,"1106":1697,"1107":1698,"1108":1700,"1109":1701,"1110":1702,"1111":1703,"1112":1704,"1113":1705,"1114":1706,"1115":1707,"1116":1708,"1118":1710,"1119":1711,"1168":1725,"1169":1709,"1170":16778386,"1171":16778387,"1174":16778390,"1175":16778391,"1178":16778394,"1179":16778395,"1180":16778396,"1181":16778397,"1186":16778402,"1187":16778403,"1198":16778414,"1199":16778415,"1200":16778416,"1201":16778417,"1202":16778418,"1203":16778419,"1206":16778422,"1207":16778423,"1208":16778424,"1209":16778425,"1210":16778426,"1211":16778427,"1240":16778456,"1241":16778457,"1250":16778466,"1251":16778467,"1256":16778472,"1257":16778473,"1262":16778478,"1263":16778479,"1329":16778545,"1330":16778546,"1331":16778547,"1332":16778548,"1333":16778549,"1334":16778550,"1335":16778551,"1336":16778552,"1337":16778553,"1338":16778554,"1339":16778555,"1340":16778556,"1341":16778557,"1342":16778558,"1343":16778559,"1344":16778560,"1345":16778561,"1346":16778562,"1347":16778563,"1348":16778564,"1349":16778565,"1350":16778566,"1351":16778567,"1352":16778568,"1353":16778569,"1354":16778570,"1355":16778571,"1356":16778572,"1357":16778573,"1358":16778574,"1359":16778575,"1360":16778576,"1361":16778577,"1362":16778578,"1363":16778579,"1364":16778580,"1365":16778581,"1366":16778582,"1370":16778586,"1371":16778587,"1372":16778588,"1373":16778589,"1374":16778590,"1377":16778593,"1378":16778594,"1379":16778595,"1380":16778596,"1381":16778597,"1382":16778598,"1383":16778599,"1384":16778600,"1385":16778601,"1386":16778602,"1387":16778603,"1388":16778604,"1389":16778605,"1390":16778606,"1391":16778607,"1392":16778608,"1393":16778609,"1394":16778610,"1395":16778611,"1396":16778612,"1397":16778613,"1398":16778614,"1399":16778615,"1400":16778616,"1401":16778617,"1402":16778618,"1403":16778619,"1404":16778620,"1405":16778621,"1406":16778622,"1407":16778623,"1408":16778624,"1409":16778625,"1410":16778626,"1411":16778627,"1412":16778628,"1413":16778629,"1414":16778630,"1415":16778631,"1417":16778633,"1418":16778634,"1488":3296,"1489":3297,"1490":3298,"1491":3299,"1492":3300,"1493":3301,"1494":3302,"1495":3303,"1496":3304,"1497":3305,"1498":3306,"1499":3307,"1500":3308,"1501":3309,"1502":3310,"1503":3311,"1504":3312,"1505":3313,"1506":3314,"1507":3315,"1508":3316,"1509":3317,"1510":3318,"1511":3319,"1512":3320,"1513":3321,"1514":3322,"1548":1452,"1563":1467,"1567":1471,"1569":1473,"1570":1474,"1571":1475,"1572":1476,"1573":1477,"1574":1478,"1575":1479,"1576":1480,"1577":1481,"1578":1482,"1579":1483,"1580":1484,"1581":1485,"1582":1486,"1583":1487,"1584":1488,"1585":1489,"1586":1490,"1587":1491,"1588":1492,"1589":1493,"1590":1494,"1591":1495,"1592":1496,"1593":1497,"1594":1498,"1600":1504,"1601":1505,"1602":1506,"1603":1507,"1604":1508,"1605":1509,"1606":1510,"1607":1511,"1608":1512,"1609":1513,"1610":1514,"1611":1515,"1612":1516,"1613":1517,"1614":1518,"1615":1519,"1616":1520,"1617":1521,"1618":1522,"1619":16778835,"1620":16778836,"1621":16778837,"1632":16778848,"1633":16778849,"1634":16778850,"1635":16778851,"1636":16778852,"1637":16778853,"1638":16778854,"1639":16778855,"1640":16778856,"1641":16778857,"1642":16778858,"1648":16778864,"1657":16778873,"1662":16778878,"1670":16778886,"1672":16778888,"1681":16778897,"1688":16778904,"1700":16778916,"1705":16778921,"1711":16778927,"1722":16778938,"1726":16778942,"1729":16778945,"1740":16778956,"1746":16778962,"1748":16778964,"1776":16778992,"1777":16778993,"1778":16778994,"1779":16778995,"1780":16778996,"1781":16778997,"1782":16778998,"1783":16778999,"1784":16779000,"1785":16779001,"3458":16780674,"3459":16780675,"3461":16780677,"3462":16780678,"3463":16780679,"3464":16780680,"3465":16780681,"3466":16780682,"3467":16780683,"3468":16780684,"3469":16780685,"3470":16780686,"3471":16780687,"3472":16780688,"3473":16780689,"3474":16780690,"3475":16780691,"3476":16780692,"3477":16780693,"3478":16780694,"3482":16780698,"3483":16780699,"3484":16780700,"3485":16780701,"3486":16780702,"3487":16780703,"3488":16780704,"3489":16780705,"3490":16780706,"3491":16780707,"3492":16780708,"3493":16780709,"3494":16780710,"3495":16780711,"3496":16780712,"3497":16780713,"3498":16780714,"3499":16780715,"3500":16780716,"3501":16780717,"3502":16780718,"3503":16780719,"3504":16780720,"3505":16780721,"3507":16780723,"3508":16780724,"3509":16780725,"3510":16780726,"3511":16780727,"3512":16780728,"3513":16780729,"3514":16780730,"3515":16780731,"3517":16780733,"3520":16780736,"3521":16780737,"3522":16780738,"3523":16780739,"3524":16780740,"3525":16780741,"3526":16780742,"3530":16780746,"3535":16780751,"3536":16780752,"3537":16780753,"3538":16780754,"3539":16780755,"3540":16780756,"3542":16780758,"3544":16780760,"3545":16780761,"3546":16780762,"3547":16780763,"3548":16780764,"3549":16780765,"3550":16780766,"3551":16780767,"3570":16780786,"3571":16780787,"3572":16780788,"3585":3489,"3586":3490,"3587":3491,"3588":3492,"3589":3493,"3590":3494,"3591":3495,"3592":3496,"3593":3497,"3594":3498,"3595":3499,"3596":3500,"3597":3501,"3598":3502,"3599":3503,"3600":3504,"3601":3505,"3602":3506,"3603":3507,"3604":3508,"3605":3509,"3606":3510,"3607":3511,"3608":3512,"3609":3513,"3610":3514,"3611":3515,"3612":3516,"3613":3517,"3614":3518,"3615":3519,"3616":3520,"3617":3521,"3618":3522,"3619":3523,"3620":3524,"3621":3525,"3622":3526,"3623":3527,"3624":3528,"3625":3529,"3626":3530,"3627":3531,"3628":3532,"3629":3533,"3630":3534,"3631":3535,"3632":3536,"3633":3537,"3634":3538,"3635":3539,"3636":3540,"3637":3541,"3638":3542,"3639":3543,"3640":3544,"3641":3545,"3642":3546,"3647":3551,"3648":3552,"3649":3553,"3650":3554,"3651":3555,"3652":3556,"3653":3557,"3654":3558,"3655":3559,"3656":3560,"3657":3561,"3658":3562,"3659":3563,"3660":3564,"3661":3565,"3664":3568,"3665":3569,"3666":3570,"3667":3571,"3668":3572,"3669":3573,"3670":3574,"3671":3575,"3672":3576,"3673":3577,"4304":16781520,"4305":16781521,"4306":16781522,"4307":16781523,"4308":16781524,"4309":16781525,"4310":16781526,"4311":16781527,"4312":16781528,"4313":16781529,"4314":16781530,"4315":16781531,"4316":16781532,"4317":16781533,"4318":16781534,"4319":16781535,"4320":16781536,"4321":16781537,"4322":16781538,"4323":16781539,"4324":16781540,"4325":16781541,"4326":16781542,"4327":16781543,"4328":16781544,"4329":16781545,"4330":16781546,"4331":16781547,"4332":16781548,"4333":16781549,"4334":16781550,"4335":16781551,"4336":16781552,"4337":16781553,"4338":16781554,"4339":16781555,"4340":16781556,"4341":16781557,"4342":16781558,"7682":16784898,"7683":16784899,"7690":16784906,"7691":16784907,"7710":16784926,"7711":16784927,"7734":16784950,"7735":16784951,"7744":16784960,"7745":16784961,"7766":16784982,"7767":16784983,"7776":16784992,"7777":16784993,"7786":16785002,"7787":16785003,"7808":16785024,"7809":16785025,"7810":16785026,"7811":16785027,"7812":16785028,"7813":16785029,"7818":16785034,"7819":16785035,"7840":16785056,"7841":16785057,"7842":16785058,"7843":16785059,"7844":16785060,"7845":16785061,"7846":16785062,"7847":16785063,"7848":16785064,"7849":16785065,"7850":16785066,"7851":16785067,"7852":16785068,"7853":16785069,"7854":16785070,"7855":16785071,"7856":16785072,"7857":16785073,"7858":16785074,"7859":16785075,"7860":16785076,"7861":16785077,"7862":16785078,"7863":16785079,"7864":16785080,"7865":16785081,"7866":16785082,"7867":16785083,"7868":16785084,"7869":16785085,"7870":16785086,"7871":16785087,"7872":16785088,"7873":16785089,"7874":16785090,"7875":16785091,"7876":16785092,"7877":16785093,"7878":16785094,"7879":16785095,"7880":16785096,"7881":16785097,"7882":16785098,"7883":16785099,"7884":16785100,"7885":16785101,"7886":16785102,"7887":16785103,"7888":16785104,"7889":16785105,"7890":16785106,"7891":16785107,"7892":16785108,"7893":16785109,"7894":16785110,"7895":16785111,"7896":16785112,"7897":16785113,"7898":16785114,"7899":16785115,"7900":16785116,"7901":16785117,"7902":16785118,"7903":16785119,"7904":16785120,"7905":16785121,"7906":16785122,"7907":16785123,"7908":16785124,"7909":16785125,"7910":16785126,"7911":16785127,"7912":16785128,"7913":16785129,"7914":16785130,"7915":16785131,"7916":16785132,"7917":16785133,"7918":16785134,"7919":16785135,"7920":16785136,"7921":16785137,"7922":16785138,"7923":16785139,"7924":16785140,"7925":16785141,"7926":16785142,"7927":16785143,"7928":16785144,"7929":16785145,"8194":2722,"8195":2721,"8196":2723,"8197":2724,"8199":2725,"8200":2726,"8201":2727,"8202":2728,"8210":2747,"8211":2730,"8212":2729,"8213":1967,"8215":3295,"8216":2768,"8217":2769,"8218":2813,"8220":2770,"8221":2771,"8222":2814,"8224":2801,"8225":2802,"8226":2790,"8229":2735,"8230":2734,"8240":2773,"8242":2774,"8243":2775,"8248":2812,"8254":1150,"8304":16785520,"8308":16785524,"8309":16785525,"8310":16785526,"8311":16785527,"8312":16785528,"8313":16785529,"8320":16785536,"8321":16785537,"8322":16785538,"8323":16785539,"8324":16785540,"8325":16785541,"8326":16785542,"8327":16785543,"8328":16785544,"8329":16785545,"8352":16785568,"8353":16785569,"8354":16785570,"8355":16785571,"8356":16785572,"8357":16785573,"8358":16785574,"8359":16785575,"8360":16785576,"8361":3839,"8362":16785578,"8363":16785579,"8364":8364,"8453":2744,"8470":1712,"8471":2811,"8478":2772,"8482":2761,"8531":2736,"8532":2737,"8533":2738,"8534":2739,"8535":2740,"8536":2741,"8537":2742,"8538":2743,"8539":2755,"8540":2756,"8541":2757,"8542":2758,"8592":2299,"8593":2300,"8594":2301,"8595":2302,"8658":2254,"8660":2253,"8706":2287,"8709":16785925,"8711":2245,"8712":16785928,"8713":16785929,"8715":16785931,"8728":3018,"8730":2262,"8731":16785947,"8732":16785948,"8733":2241,"8734":2242,"8743":2270,"8744":2271,"8745":2268,"8746":2269,"8747":2239,"8748":16785964,"8749":16785965,"8756":2240,"8757":16785973,"8764":2248,"8771":2249,"8773":16785992,"8775":16785991,"8800":2237,"8801":2255,"8802":16786018,"8803":16786019,"8804":2236,"8805":2238,"8834":2266,"8835":2267,"8866":3068,"8867":3036,"8868":3010,"8869":3022,"8968":3027,"8970":3012,"8981":2810,"8992":2212,"8993":2213,"9109":3020,"9115":2219,"9117":2220,"9118":2221,"9120":2222,"9121":2215,"9123":2216,"9124":2217,"9126":2218,"9128":2223,"9132":2224,"9143":2209,"9146":2543,"9147":2544,"9148":2546,"9149":2547,"9225":2530,"9226":2533,"9227":2537,"9228":2531,"9229":2532,"9251":2732,"9252":2536,"9472":2211,"9474":2214,"9484":2210,"9488":2539,"9492":2541,"9496":2538,"9500":2548,"9508":2549,"9516":2551,"9524":2550,"9532":2542,"9618":2529,"9642":2791,"9643":2785,"9644":2779,"9645":2786,"9646":2783,"9647":2767,"9650":2792,"9651":2787,"9654":2781,"9655":2765,"9660":2793,"9661":2788,"9664":2780,"9665":2764,"9670":2528,"9675":2766,"9679":2782,"9702":2784,"9734":2789,"9742":2809,"9747":2762,"9756":2794,"9758":2795,"9792":2808,"9794":2807,"9827":2796,"9829":2798,"9830":2797,"9837":2806,"9839":2805,"10003":2803,"10007":2804,"10013":2777,"10016":2800,"10216":2748,"10217":2750,"10240":16787456,"10241":16787457,"10242":16787458,"10243":16787459,"10244":16787460,"10245":16787461,"10246":16787462,"10247":16787463,"10248":16787464,"10249":16787465,"10250":16787466,"10251":16787467,"10252":16787468,"10253":16787469,"10254":16787470,"10255":16787471,"10256":16787472,"10257":16787473,"10258":16787474,"10259":16787475,"10260":16787476,"10261":16787477,"10262":16787478,"10263":16787479,"10264":16787480,"10265":16787481,"10266":16787482,"10267":16787483,"10268":16787484,"10269":16787485,"10270":16787486,"10271":16787487,"10272":16787488,"10273":16787489,"10274":16787490,"10275":16787491,"10276":16787492,"10277":16787493,"10278":16787494,"10279":16787495,"10280":16787496,"10281":16787497,"10282":16787498,"10283":16787499,"10284":16787500,"10285":16787501,"10286":16787502,"10287":16787503,"10288":16787504,"10289":16787505,"10290":16787506,"10291":16787507,"10292":16787508,"10293":16787509,"10294":16787510,"10295":16787511,"10296":16787512,"10297":16787513,"10298":16787514,"10299":16787515,"10300":16787516,"10301":16787517,"10302":16787518,"10303":16787519,"10304":16787520,"10305":16787521,"10306":16787522,"10307":16787523,"10308":16787524,"10309":16787525,"10310":16787526,"10311":16787527,"10312":16787528,"10313":16787529,"10314":16787530,"10315":16787531,"10316":16787532,"10317":16787533,"10318":16787534,"10319":16787535,"10320":16787536,"10321":16787537,"10322":16787538,"10323":16787539,"10324":16787540,"10325":16787541,"10326":16787542,"10327":16787543,"10328":16787544,"10329":16787545,"10330":16787546,"10331":16787547,"10332":16787548,"10333":16787549,"10334":16787550,"10335":16787551,"10336":16787552,"10337":16787553,"10338":16787554,"10339":16787555,"10340":16787556,"10341":16787557,"10342":16787558,"10343":16787559,"10344":16787560,"10345":16787561,"10346":16787562,"10347":16787563,"10348":16787564,"10349":16787565,"10350":16787566,"10351":16787567,"10352":16787568,"10353":16787569,"10354":16787570,"10355":16787571,"10356":16787572,"10357":16787573,"10358":16787574,"10359":16787575,"10360":16787576,"10361":16787577,"10362":16787578,"10363":16787579,"10364":16787580,"10365":16787581,"10366":16787582,"10367":16787583,"10368":16787584,"10369":16787585,"10370":16787586,"10371":16787587,"10372":16787588,"10373":16787589,"10374":16787590,"10375":16787591,"10376":16787592,"10377":16787593,"10378":16787594,"10379":16787595,"10380":16787596,"10381":16787597,"10382":16787598,"10383":16787599,"10384":16787600,"10385":16787601,"10386":16787602,"10387":16787603,"10388":16787604,"10389":16787605,"10390":16787606,"10391":16787607,"10392":16787608,"10393":16787609,"10394":16787610,"10395":16787611,"10396":16787612,"10397":16787613,"10398":16787614,"10399":16787615,"10400":16787616,"10401":16787617,"10402":16787618,"10403":16787619,"10404":16787620,"10405":16787621,"10406":16787622,"10407":16787623,"10408":16787624,"10409":16787625,"10410":16787626,"10411":16787627,"10412":16787628,"10413":16787629,"10414":16787630,"10415":16787631,"10416":16787632,"10417":16787633,"10418":16787634,"10419":16787635,"10420":16787636,"10421":16787637,"10422":16787638,"10423":16787639,"10424":16787640,"10425":16787641,"10426":16787642,"10427":16787643,"10428":16787644,"10429":16787645,"10430":16787646,"10431":16787647,"10432":16787648,"10433":16787649,"10434":16787650,"10435":16787651,"10436":16787652,"10437":16787653,"10438":16787654,"10439":16787655,"10440":16787656,"10441":16787657,"10442":16787658,"10443":16787659,"10444":16787660,"10445":16787661,"10446":16787662,"10447":16787663,"10448":16787664,"10449":16787665,"10450":16787666,"10451":16787667,"10452":16787668,"10453":16787669,"10454":16787670,"10455":16787671,"10456":16787672,"10457":16787673,"10458":16787674,"10459":16787675,"10460":16787676,"10461":16787677,"10462":16787678,"10463":16787679,"10464":16787680,"10465":16787681,"10466":16787682,"10467":16787683,"10468":16787684,"10469":16787685,"10470":16787686,"10471":16787687,"10472":16787688,"10473":16787689,"10474":16787690,"10475":16787691,"10476":16787692,"10477":16787693,"10478":16787694,"10479":16787695,"10480":16787696,"10481":16787697,"10482":16787698,"10483":16787699,"10484":16787700,"10485":16787701,"10486":16787702,"10487":16787703,"10488":16787704,"10489":16787705,"10490":16787706,"10491":16787707,"10492":16787708,"10493":16787709,"10494":16787710,"10495":16787711,"12289":1188,"12290":1185,"12300":1186,"12301":1187,"12443":1246,"12444":1247,"12449":1191,"12450":1201,"12451":1192,"12452":1202,"12453":1193,"12454":1203,"12455":1194,"12456":1204,"12457":1195,"12458":1205,"12459":1206,"12461":1207,"12463":1208,"12465":1209,"12467":1210,"12469":1211,"12471":1212,"12473":1213,"12475":1214,"12477":1215,"12479":1216,"12481":1217,"12483":1199,"12484":1218,"12486":1219,"12488":1220,"12490":1221,"12491":1222,"12492":1223,"12493":1224,"12494":1225,"12495":1226,"12498":1227,"12501":1228,"12504":1229,"12507":1230,"12510":1231,"12511":1232,"12512":1233,"12513":1234,"12514":1235,"12515":1196,"12516":1236,"12517":1197,"12518":1237,"12519":1198,"12520":1238,"12521":1239,"12522":1240,"12523":1241,"12524":1242,"12525":1243,"12527":1244,"12530":1190,"12531":1245,"12539":1189,"12540":1200}; +/* Functions at the bottom */ + +var codepoints = { + 0x0020: 0x0020, // XK_space + 0x0021: 0x0021, // XK_exclam + 0x0022: 0x0022, // XK_quotedbl + 0x0023: 0x0023, // XK_numbersign + 0x0024: 0x0024, // XK_dollar + 0x0025: 0x0025, // XK_percent + 0x0026: 0x0026, // XK_ampersand + 0x0027: 0x0027, // XK_apostrophe + 0x0028: 0x0028, // XK_parenleft + 0x0029: 0x0029, // XK_parenright + 0x002a: 0x002a, // XK_asterisk + 0x002b: 0x002b, // XK_plus + 0x002c: 0x002c, // XK_comma + 0x002d: 0x002d, // XK_minus + 0x002e: 0x002e, // XK_period + 0x002f: 0x002f, // XK_slash + 0x0030: 0x0030, // XK_0 + 0x0031: 0x0031, // XK_1 + 0x0032: 0x0032, // XK_2 + 0x0033: 0x0033, // XK_3 + 0x0034: 0x0034, // XK_4 + 0x0035: 0x0035, // XK_5 + 0x0036: 0x0036, // XK_6 + 0x0037: 0x0037, // XK_7 + 0x0038: 0x0038, // XK_8 + 0x0039: 0x0039, // XK_9 + 0x003a: 0x003a, // XK_colon + 0x003b: 0x003b, // XK_semicolon + 0x003c: 0x003c, // XK_less + 0x003d: 0x003d, // XK_equal + 0x003e: 0x003e, // XK_greater + 0x003f: 0x003f, // XK_question + 0x0040: 0x0040, // XK_at + 0x0041: 0x0041, // XK_A + 0x0042: 0x0042, // XK_B + 0x0043: 0x0043, // XK_C + 0x0044: 0x0044, // XK_D + 0x0045: 0x0045, // XK_E + 0x0046: 0x0046, // XK_F + 0x0047: 0x0047, // XK_G + 0x0048: 0x0048, // XK_H + 0x0049: 0x0049, // XK_I + 0x004a: 0x004a, // XK_J + 0x004b: 0x004b, // XK_K + 0x004c: 0x004c, // XK_L + 0x004d: 0x004d, // XK_M + 0x004e: 0x004e, // XK_N + 0x004f: 0x004f, // XK_O + 0x0050: 0x0050, // XK_P + 0x0051: 0x0051, // XK_Q + 0x0052: 0x0052, // XK_R + 0x0053: 0x0053, // XK_S + 0x0054: 0x0054, // XK_T + 0x0055: 0x0055, // XK_U + 0x0056: 0x0056, // XK_V + 0x0057: 0x0057, // XK_W + 0x0058: 0x0058, // XK_X + 0x0059: 0x0059, // XK_Y + 0x005a: 0x005a, // XK_Z + 0x005b: 0x005b, // XK_bracketleft + 0x005c: 0x005c, // XK_backslash + 0x005d: 0x005d, // XK_bracketright + 0x005e: 0x005e, // XK_asciicircum + 0x005f: 0x005f, // XK_underscore + 0x0060: 0x0060, // XK_grave + 0x0061: 0x0061, // XK_a + 0x0062: 0x0062, // XK_b + 0x0063: 0x0063, // XK_c + 0x0064: 0x0064, // XK_d + 0x0065: 0x0065, // XK_e + 0x0066: 0x0066, // XK_f + 0x0067: 0x0067, // XK_g + 0x0068: 0x0068, // XK_h + 0x0069: 0x0069, // XK_i + 0x006a: 0x006a, // XK_j + 0x006b: 0x006b, // XK_k + 0x006c: 0x006c, // XK_l + 0x006d: 0x006d, // XK_m + 0x006e: 0x006e, // XK_n + 0x006f: 0x006f, // XK_o + 0x0070: 0x0070, // XK_p + 0x0071: 0x0071, // XK_q + 0x0072: 0x0072, // XK_r + 0x0073: 0x0073, // XK_s + 0x0074: 0x0074, // XK_t + 0x0075: 0x0075, // XK_u + 0x0076: 0x0076, // XK_v + 0x0077: 0x0077, // XK_w + 0x0078: 0x0078, // XK_x + 0x0079: 0x0079, // XK_y + 0x007a: 0x007a, // XK_z + 0x007b: 0x007b, // XK_braceleft + 0x007c: 0x007c, // XK_bar + 0x007d: 0x007d, // XK_braceright + 0x007e: 0x007e, // XK_asciitilde + 0x00a0: 0x00a0, // XK_nobreakspace + 0x00a1: 0x00a1, // XK_exclamdown + 0x00a2: 0x00a2, // XK_cent + 0x00a3: 0x00a3, // XK_sterling + 0x00a4: 0x00a4, // XK_currency + 0x00a5: 0x00a5, // XK_yen + 0x00a6: 0x00a6, // XK_brokenbar + 0x00a7: 0x00a7, // XK_section + 0x00a8: 0x00a8, // XK_diaeresis + 0x00a9: 0x00a9, // XK_copyright + 0x00aa: 0x00aa, // XK_ordfeminine + 0x00ab: 0x00ab, // XK_guillemotleft + 0x00ac: 0x00ac, // XK_notsign + 0x00ad: 0x00ad, // XK_hyphen + 0x00ae: 0x00ae, // XK_registered + 0x00af: 0x00af, // XK_macron + 0x00b0: 0x00b0, // XK_degree + 0x00b1: 0x00b1, // XK_plusminus + 0x00b2: 0x00b2, // XK_twosuperior + 0x00b3: 0x00b3, // XK_threesuperior + 0x00b4: 0x00b4, // XK_acute + 0x00b5: 0x00b5, // XK_mu + 0x00b6: 0x00b6, // XK_paragraph + 0x00b7: 0x00b7, // XK_periodcentered + 0x00b8: 0x00b8, // XK_cedilla + 0x00b9: 0x00b9, // XK_onesuperior + 0x00ba: 0x00ba, // XK_masculine + 0x00bb: 0x00bb, // XK_guillemotright + 0x00bc: 0x00bc, // XK_onequarter + 0x00bd: 0x00bd, // XK_onehalf + 0x00be: 0x00be, // XK_threequarters + 0x00bf: 0x00bf, // XK_questiondown + 0x00c0: 0x00c0, // XK_Agrave + 0x00c1: 0x00c1, // XK_Aacute + 0x00c2: 0x00c2, // XK_Acircumflex + 0x00c3: 0x00c3, // XK_Atilde + 0x00c4: 0x00c4, // XK_Adiaeresis + 0x00c5: 0x00c5, // XK_Aring + 0x00c6: 0x00c6, // XK_AE + 0x00c7: 0x00c7, // XK_Ccedilla + 0x00c8: 0x00c8, // XK_Egrave + 0x00c9: 0x00c9, // XK_Eacute + 0x00ca: 0x00ca, // XK_Ecircumflex + 0x00cb: 0x00cb, // XK_Ediaeresis + 0x00cc: 0x00cc, // XK_Igrave + 0x00cd: 0x00cd, // XK_Iacute + 0x00ce: 0x00ce, // XK_Icircumflex + 0x00cf: 0x00cf, // XK_Idiaeresis + 0x00d0: 0x00d0, // XK_ETH + 0x00d1: 0x00d1, // XK_Ntilde + 0x00d2: 0x00d2, // XK_Ograve + 0x00d3: 0x00d3, // XK_Oacute + 0x00d4: 0x00d4, // XK_Ocircumflex + 0x00d5: 0x00d5, // XK_Otilde + 0x00d6: 0x00d6, // XK_Odiaeresis + 0x00d7: 0x00d7, // XK_multiply + 0x00d8: 0x00d8, // XK_Oslash + 0x00d9: 0x00d9, // XK_Ugrave + 0x00da: 0x00da, // XK_Uacute + 0x00db: 0x00db, // XK_Ucircumflex + 0x00dc: 0x00dc, // XK_Udiaeresis + 0x00dd: 0x00dd, // XK_Yacute + 0x00de: 0x00de, // XK_THORN + 0x00df: 0x00df, // XK_ssharp + 0x00e0: 0x00e0, // XK_agrave + 0x00e1: 0x00e1, // XK_aacute + 0x00e2: 0x00e2, // XK_acircumflex + 0x00e3: 0x00e3, // XK_atilde + 0x00e4: 0x00e4, // XK_adiaeresis + 0x00e5: 0x00e5, // XK_aring + 0x00e6: 0x00e6, // XK_ae + 0x00e7: 0x00e7, // XK_ccedilla + 0x00e8: 0x00e8, // XK_egrave + 0x00e9: 0x00e9, // XK_eacute + 0x00ea: 0x00ea, // XK_ecircumflex + 0x00eb: 0x00eb, // XK_ediaeresis + 0x00ec: 0x00ec, // XK_igrave + 0x00ed: 0x00ed, // XK_iacute + 0x00ee: 0x00ee, // XK_icircumflex + 0x00ef: 0x00ef, // XK_idiaeresis + 0x00f0: 0x00f0, // XK_eth + 0x00f1: 0x00f1, // XK_ntilde + 0x00f2: 0x00f2, // XK_ograve + 0x00f3: 0x00f3, // XK_oacute + 0x00f4: 0x00f4, // XK_ocircumflex + 0x00f5: 0x00f5, // XK_otilde + 0x00f6: 0x00f6, // XK_odiaeresis + 0x00f7: 0x00f7, // XK_division + 0x00f8: 0x00f8, // XK_oslash + 0x00f9: 0x00f9, // XK_ugrave + 0x00fa: 0x00fa, // XK_uacute + 0x00fb: 0x00fb, // XK_ucircumflex + 0x00fc: 0x00fc, // XK_udiaeresis + 0x00fd: 0x00fd, // XK_yacute + 0x00fe: 0x00fe, // XK_thorn + 0x00ff: 0x00ff, // XK_ydiaeresis + 0x0100: 0x03c0, // XK_Amacron + 0x0101: 0x03e0, // XK_amacron + 0x0102: 0x01c3, // XK_Abreve + 0x0103: 0x01e3, // XK_abreve + 0x0104: 0x01a1, // XK_Aogonek + 0x0105: 0x01b1, // XK_aogonek + 0x0106: 0x01c6, // XK_Cacute + 0x0107: 0x01e6, // XK_cacute + 0x0108: 0x02c6, // XK_Ccircumflex + 0x0109: 0x02e6, // XK_ccircumflex + 0x010a: 0x02c5, // XK_Cabovedot + 0x010b: 0x02e5, // XK_cabovedot + 0x010c: 0x01c8, // XK_Ccaron + 0x010d: 0x01e8, // XK_ccaron + 0x010e: 0x01cf, // XK_Dcaron + 0x010f: 0x01ef, // XK_dcaron + 0x0110: 0x01d0, // XK_Dstroke + 0x0111: 0x01f0, // XK_dstroke + 0x0112: 0x03aa, // XK_Emacron + 0x0113: 0x03ba, // XK_emacron + 0x0116: 0x03cc, // XK_Eabovedot + 0x0117: 0x03ec, // XK_eabovedot + 0x0118: 0x01ca, // XK_Eogonek + 0x0119: 0x01ea, // XK_eogonek + 0x011a: 0x01cc, // XK_Ecaron + 0x011b: 0x01ec, // XK_ecaron + 0x011c: 0x02d8, // XK_Gcircumflex + 0x011d: 0x02f8, // XK_gcircumflex + 0x011e: 0x02ab, // XK_Gbreve + 0x011f: 0x02bb, // XK_gbreve + 0x0120: 0x02d5, // XK_Gabovedot + 0x0121: 0x02f5, // XK_gabovedot + 0x0122: 0x03ab, // XK_Gcedilla + 0x0123: 0x03bb, // XK_gcedilla + 0x0124: 0x02a6, // XK_Hcircumflex + 0x0125: 0x02b6, // XK_hcircumflex + 0x0126: 0x02a1, // XK_Hstroke + 0x0127: 0x02b1, // XK_hstroke + 0x0128: 0x03a5, // XK_Itilde + 0x0129: 0x03b5, // XK_itilde + 0x012a: 0x03cf, // XK_Imacron + 0x012b: 0x03ef, // XK_imacron + 0x012c: 0x100012c, // XK_Ibreve + 0x012d: 0x100012d, // XK_ibreve + 0x012e: 0x03c7, // XK_Iogonek + 0x012f: 0x03e7, // XK_iogonek + 0x0130: 0x02a9, // XK_Iabovedot + 0x0131: 0x02b9, // XK_idotless + 0x0134: 0x02ac, // XK_Jcircumflex + 0x0135: 0x02bc, // XK_jcircumflex + 0x0136: 0x03d3, // XK_Kcedilla + 0x0137: 0x03f3, // XK_kcedilla + 0x0138: 0x03a2, // XK_kra + 0x0139: 0x01c5, // XK_Lacute + 0x013a: 0x01e5, // XK_lacute + 0x013b: 0x03a6, // XK_Lcedilla + 0x013c: 0x03b6, // XK_lcedilla + 0x013d: 0x01a5, // XK_Lcaron + 0x013e: 0x01b5, // XK_lcaron + 0x0141: 0x01a3, // XK_Lstroke + 0x0142: 0x01b3, // XK_lstroke + 0x0143: 0x01d1, // XK_Nacute + 0x0144: 0x01f1, // XK_nacute + 0x0145: 0x03d1, // XK_Ncedilla + 0x0146: 0x03f1, // XK_ncedilla + 0x0147: 0x01d2, // XK_Ncaron + 0x0148: 0x01f2, // XK_ncaron + 0x014a: 0x03bd, // XK_ENG + 0x014b: 0x03bf, // XK_eng + 0x014c: 0x03d2, // XK_Omacron + 0x014d: 0x03f2, // XK_omacron + 0x0150: 0x01d5, // XK_Odoubleacute + 0x0151: 0x01f5, // XK_odoubleacute + 0x0152: 0x13bc, // XK_OE + 0x0153: 0x13bd, // XK_oe + 0x0154: 0x01c0, // XK_Racute + 0x0155: 0x01e0, // XK_racute + 0x0156: 0x03a3, // XK_Rcedilla + 0x0157: 0x03b3, // XK_rcedilla + 0x0158: 0x01d8, // XK_Rcaron + 0x0159: 0x01f8, // XK_rcaron + 0x015a: 0x01a6, // XK_Sacute + 0x015b: 0x01b6, // XK_sacute + 0x015c: 0x02de, // XK_Scircumflex + 0x015d: 0x02fe, // XK_scircumflex + 0x015e: 0x01aa, // XK_Scedilla + 0x015f: 0x01ba, // XK_scedilla + 0x0160: 0x01a9, // XK_Scaron + 0x0161: 0x01b9, // XK_scaron + 0x0162: 0x01de, // XK_Tcedilla + 0x0163: 0x01fe, // XK_tcedilla + 0x0164: 0x01ab, // XK_Tcaron + 0x0165: 0x01bb, // XK_tcaron + 0x0166: 0x03ac, // XK_Tslash + 0x0167: 0x03bc, // XK_tslash + 0x0168: 0x03dd, // XK_Utilde + 0x0169: 0x03fd, // XK_utilde + 0x016a: 0x03de, // XK_Umacron + 0x016b: 0x03fe, // XK_umacron + 0x016c: 0x02dd, // XK_Ubreve + 0x016d: 0x02fd, // XK_ubreve + 0x016e: 0x01d9, // XK_Uring + 0x016f: 0x01f9, // XK_uring + 0x0170: 0x01db, // XK_Udoubleacute + 0x0171: 0x01fb, // XK_udoubleacute + 0x0172: 0x03d9, // XK_Uogonek + 0x0173: 0x03f9, // XK_uogonek + 0x0174: 0x1000174, // XK_Wcircumflex + 0x0175: 0x1000175, // XK_wcircumflex + 0x0176: 0x1000176, // XK_Ycircumflex + 0x0177: 0x1000177, // XK_ycircumflex + 0x0178: 0x13be, // XK_Ydiaeresis + 0x0179: 0x01ac, // XK_Zacute + 0x017a: 0x01bc, // XK_zacute + 0x017b: 0x01af, // XK_Zabovedot + 0x017c: 0x01bf, // XK_zabovedot + 0x017d: 0x01ae, // XK_Zcaron + 0x017e: 0x01be, // XK_zcaron + 0x018f: 0x100018f, // XK_SCHWA + 0x0192: 0x08f6, // XK_function + 0x019f: 0x100019f, // XK_Obarred + 0x01a0: 0x10001a0, // XK_Ohorn + 0x01a1: 0x10001a1, // XK_ohorn + 0x01af: 0x10001af, // XK_Uhorn + 0x01b0: 0x10001b0, // XK_uhorn + 0x01b5: 0x10001b5, // XK_Zstroke + 0x01b6: 0x10001b6, // XK_zstroke + 0x01b7: 0x10001b7, // XK_EZH + 0x01d2: 0x10001d1, // XK_Ocaron + 0x01e6: 0x10001e6, // XK_Gcaron + 0x01e7: 0x10001e7, // XK_gcaron + 0x0259: 0x1000259, // XK_schwa + 0x0275: 0x1000275, // XK_obarred + 0x0292: 0x1000292, // XK_ezh + 0x02c7: 0x01b7, // XK_caron + 0x02d8: 0x01a2, // XK_breve + 0x02d9: 0x01ff, // XK_abovedot + 0x02db: 0x01b2, // XK_ogonek + 0x02dd: 0x01bd, // XK_doubleacute + 0x0385: 0x07ae, // XK_Greek_accentdieresis + 0x0386: 0x07a1, // XK_Greek_ALPHAaccent + 0x0388: 0x07a2, // XK_Greek_EPSILONaccent + 0x0389: 0x07a3, // XK_Greek_ETAaccent + 0x038a: 0x07a4, // XK_Greek_IOTAaccent + 0x038c: 0x07a7, // XK_Greek_OMICRONaccent + 0x038e: 0x07a8, // XK_Greek_UPSILONaccent + 0x038f: 0x07ab, // XK_Greek_OMEGAaccent + 0x0390: 0x07b6, // XK_Greek_iotaaccentdieresis + 0x0391: 0x07c1, // XK_Greek_ALPHA + 0x0392: 0x07c2, // XK_Greek_BETA + 0x0393: 0x07c3, // XK_Greek_GAMMA + 0x0394: 0x07c4, // XK_Greek_DELTA + 0x0395: 0x07c5, // XK_Greek_EPSILON + 0x0396: 0x07c6, // XK_Greek_ZETA + 0x0397: 0x07c7, // XK_Greek_ETA + 0x0398: 0x07c8, // XK_Greek_THETA + 0x0399: 0x07c9, // XK_Greek_IOTA + 0x039a: 0x07ca, // XK_Greek_KAPPA + 0x039b: 0x07cb, // XK_Greek_LAMDA + 0x039c: 0x07cc, // XK_Greek_MU + 0x039d: 0x07cd, // XK_Greek_NU + 0x039e: 0x07ce, // XK_Greek_XI + 0x039f: 0x07cf, // XK_Greek_OMICRON + 0x03a0: 0x07d0, // XK_Greek_PI + 0x03a1: 0x07d1, // XK_Greek_RHO + 0x03a3: 0x07d2, // XK_Greek_SIGMA + 0x03a4: 0x07d4, // XK_Greek_TAU + 0x03a5: 0x07d5, // XK_Greek_UPSILON + 0x03a6: 0x07d6, // XK_Greek_PHI + 0x03a7: 0x07d7, // XK_Greek_CHI + 0x03a8: 0x07d8, // XK_Greek_PSI + 0x03a9: 0x07d9, // XK_Greek_OMEGA + 0x03aa: 0x07a5, // XK_Greek_IOTAdieresis + 0x03ab: 0x07a9, // XK_Greek_UPSILONdieresis + 0x03ac: 0x07b1, // XK_Greek_alphaaccent + 0x03ad: 0x07b2, // XK_Greek_epsilonaccent + 0x03ae: 0x07b3, // XK_Greek_etaaccent + 0x03af: 0x07b4, // XK_Greek_iotaaccent + 0x03b0: 0x07ba, // XK_Greek_upsilonaccentdieresis + 0x03b1: 0x07e1, // XK_Greek_alpha + 0x03b2: 0x07e2, // XK_Greek_beta + 0x03b3: 0x07e3, // XK_Greek_gamma + 0x03b4: 0x07e4, // XK_Greek_delta + 0x03b5: 0x07e5, // XK_Greek_epsilon + 0x03b6: 0x07e6, // XK_Greek_zeta + 0x03b7: 0x07e7, // XK_Greek_eta + 0x03b8: 0x07e8, // XK_Greek_theta + 0x03b9: 0x07e9, // XK_Greek_iota + 0x03ba: 0x07ea, // XK_Greek_kappa + 0x03bb: 0x07eb, // XK_Greek_lamda + 0x03bc: 0x07ec, // XK_Greek_mu + 0x03bd: 0x07ed, // XK_Greek_nu + 0x03be: 0x07ee, // XK_Greek_xi + 0x03bf: 0x07ef, // XK_Greek_omicron + 0x03c0: 0x07f0, // XK_Greek_pi + 0x03c1: 0x07f1, // XK_Greek_rho + 0x03c2: 0x07f3, // XK_Greek_finalsmallsigma + 0x03c3: 0x07f2, // XK_Greek_sigma + 0x03c4: 0x07f4, // XK_Greek_tau + 0x03c5: 0x07f5, // XK_Greek_upsilon + 0x03c6: 0x07f6, // XK_Greek_phi + 0x03c7: 0x07f7, // XK_Greek_chi + 0x03c8: 0x07f8, // XK_Greek_psi + 0x03c9: 0x07f9, // XK_Greek_omega + 0x03ca: 0x07b5, // XK_Greek_iotadieresis + 0x03cb: 0x07b9, // XK_Greek_upsilondieresis + 0x03cc: 0x07b7, // XK_Greek_omicronaccent + 0x03cd: 0x07b8, // XK_Greek_upsilonaccent + 0x03ce: 0x07bb, // XK_Greek_omegaaccent + 0x0401: 0x06b3, // XK_Cyrillic_IO + 0x0402: 0x06b1, // XK_Serbian_DJE + 0x0403: 0x06b2, // XK_Macedonia_GJE + 0x0404: 0x06b4, // XK_Ukrainian_IE + 0x0405: 0x06b5, // XK_Macedonia_DSE + 0x0406: 0x06b6, // XK_Ukrainian_I + 0x0407: 0x06b7, // XK_Ukrainian_YI + 0x0408: 0x06b8, // XK_Cyrillic_JE + 0x0409: 0x06b9, // XK_Cyrillic_LJE + 0x040a: 0x06ba, // XK_Cyrillic_NJE + 0x040b: 0x06bb, // XK_Serbian_TSHE + 0x040c: 0x06bc, // XK_Macedonia_KJE + 0x040e: 0x06be, // XK_Byelorussian_SHORTU + 0x040f: 0x06bf, // XK_Cyrillic_DZHE + 0x0410: 0x06e1, // XK_Cyrillic_A + 0x0411: 0x06e2, // XK_Cyrillic_BE + 0x0412: 0x06f7, // XK_Cyrillic_VE + 0x0413: 0x06e7, // XK_Cyrillic_GHE + 0x0414: 0x06e4, // XK_Cyrillic_DE + 0x0415: 0x06e5, // XK_Cyrillic_IE + 0x0416: 0x06f6, // XK_Cyrillic_ZHE + 0x0417: 0x06fa, // XK_Cyrillic_ZE + 0x0418: 0x06e9, // XK_Cyrillic_I + 0x0419: 0x06ea, // XK_Cyrillic_SHORTI + 0x041a: 0x06eb, // XK_Cyrillic_KA + 0x041b: 0x06ec, // XK_Cyrillic_EL + 0x041c: 0x06ed, // XK_Cyrillic_EM + 0x041d: 0x06ee, // XK_Cyrillic_EN + 0x041e: 0x06ef, // XK_Cyrillic_O + 0x041f: 0x06f0, // XK_Cyrillic_PE + 0x0420: 0x06f2, // XK_Cyrillic_ER + 0x0421: 0x06f3, // XK_Cyrillic_ES + 0x0422: 0x06f4, // XK_Cyrillic_TE + 0x0423: 0x06f5, // XK_Cyrillic_U + 0x0424: 0x06e6, // XK_Cyrillic_EF + 0x0425: 0x06e8, // XK_Cyrillic_HA + 0x0426: 0x06e3, // XK_Cyrillic_TSE + 0x0427: 0x06fe, // XK_Cyrillic_CHE + 0x0428: 0x06fb, // XK_Cyrillic_SHA + 0x0429: 0x06fd, // XK_Cyrillic_SHCHA + 0x042a: 0x06ff, // XK_Cyrillic_HARDSIGN + 0x042b: 0x06f9, // XK_Cyrillic_YERU + 0x042c: 0x06f8, // XK_Cyrillic_SOFTSIGN + 0x042d: 0x06fc, // XK_Cyrillic_E + 0x042e: 0x06e0, // XK_Cyrillic_YU + 0x042f: 0x06f1, // XK_Cyrillic_YA + 0x0430: 0x06c1, // XK_Cyrillic_a + 0x0431: 0x06c2, // XK_Cyrillic_be + 0x0432: 0x06d7, // XK_Cyrillic_ve + 0x0433: 0x06c7, // XK_Cyrillic_ghe + 0x0434: 0x06c4, // XK_Cyrillic_de + 0x0435: 0x06c5, // XK_Cyrillic_ie + 0x0436: 0x06d6, // XK_Cyrillic_zhe + 0x0437: 0x06da, // XK_Cyrillic_ze + 0x0438: 0x06c9, // XK_Cyrillic_i + 0x0439: 0x06ca, // XK_Cyrillic_shorti + 0x043a: 0x06cb, // XK_Cyrillic_ka + 0x043b: 0x06cc, // XK_Cyrillic_el + 0x043c: 0x06cd, // XK_Cyrillic_em + 0x043d: 0x06ce, // XK_Cyrillic_en + 0x043e: 0x06cf, // XK_Cyrillic_o + 0x043f: 0x06d0, // XK_Cyrillic_pe + 0x0440: 0x06d2, // XK_Cyrillic_er + 0x0441: 0x06d3, // XK_Cyrillic_es + 0x0442: 0x06d4, // XK_Cyrillic_te + 0x0443: 0x06d5, // XK_Cyrillic_u + 0x0444: 0x06c6, // XK_Cyrillic_ef + 0x0445: 0x06c8, // XK_Cyrillic_ha + 0x0446: 0x06c3, // XK_Cyrillic_tse + 0x0447: 0x06de, // XK_Cyrillic_che + 0x0448: 0x06db, // XK_Cyrillic_sha + 0x0449: 0x06dd, // XK_Cyrillic_shcha + 0x044a: 0x06df, // XK_Cyrillic_hardsign + 0x044b: 0x06d9, // XK_Cyrillic_yeru + 0x044c: 0x06d8, // XK_Cyrillic_softsign + 0x044d: 0x06dc, // XK_Cyrillic_e + 0x044e: 0x06c0, // XK_Cyrillic_yu + 0x044f: 0x06d1, // XK_Cyrillic_ya + 0x0451: 0x06a3, // XK_Cyrillic_io + 0x0452: 0x06a1, // XK_Serbian_dje + 0x0453: 0x06a2, // XK_Macedonia_gje + 0x0454: 0x06a4, // XK_Ukrainian_ie + 0x0455: 0x06a5, // XK_Macedonia_dse + 0x0456: 0x06a6, // XK_Ukrainian_i + 0x0457: 0x06a7, // XK_Ukrainian_yi + 0x0458: 0x06a8, // XK_Cyrillic_je + 0x0459: 0x06a9, // XK_Cyrillic_lje + 0x045a: 0x06aa, // XK_Cyrillic_nje + 0x045b: 0x06ab, // XK_Serbian_tshe + 0x045c: 0x06ac, // XK_Macedonia_kje + 0x045e: 0x06ae, // XK_Byelorussian_shortu + 0x045f: 0x06af, // XK_Cyrillic_dzhe + 0x0490: 0x06bd, // XK_Ukrainian_GHE_WITH_UPTURN + 0x0491: 0x06ad, // XK_Ukrainian_ghe_with_upturn + 0x0492: 0x1000492, // XK_Cyrillic_GHE_bar + 0x0493: 0x1000493, // XK_Cyrillic_ghe_bar + 0x0496: 0x1000496, // XK_Cyrillic_ZHE_descender + 0x0497: 0x1000497, // XK_Cyrillic_zhe_descender + 0x049a: 0x100049a, // XK_Cyrillic_KA_descender + 0x049b: 0x100049b, // XK_Cyrillic_ka_descender + 0x049c: 0x100049c, // XK_Cyrillic_KA_vertstroke + 0x049d: 0x100049d, // XK_Cyrillic_ka_vertstroke + 0x04a2: 0x10004a2, // XK_Cyrillic_EN_descender + 0x04a3: 0x10004a3, // XK_Cyrillic_en_descender + 0x04ae: 0x10004ae, // XK_Cyrillic_U_straight + 0x04af: 0x10004af, // XK_Cyrillic_u_straight + 0x04b0: 0x10004b0, // XK_Cyrillic_U_straight_bar + 0x04b1: 0x10004b1, // XK_Cyrillic_u_straight_bar + 0x04b2: 0x10004b2, // XK_Cyrillic_HA_descender + 0x04b3: 0x10004b3, // XK_Cyrillic_ha_descender + 0x04b6: 0x10004b6, // XK_Cyrillic_CHE_descender + 0x04b7: 0x10004b7, // XK_Cyrillic_che_descender + 0x04b8: 0x10004b8, // XK_Cyrillic_CHE_vertstroke + 0x04b9: 0x10004b9, // XK_Cyrillic_che_vertstroke + 0x04ba: 0x10004ba, // XK_Cyrillic_SHHA + 0x04bb: 0x10004bb, // XK_Cyrillic_shha + 0x04d8: 0x10004d8, // XK_Cyrillic_SCHWA + 0x04d9: 0x10004d9, // XK_Cyrillic_schwa + 0x04e2: 0x10004e2, // XK_Cyrillic_I_macron + 0x04e3: 0x10004e3, // XK_Cyrillic_i_macron + 0x04e8: 0x10004e8, // XK_Cyrillic_O_bar + 0x04e9: 0x10004e9, // XK_Cyrillic_o_bar + 0x04ee: 0x10004ee, // XK_Cyrillic_U_macron + 0x04ef: 0x10004ef, // XK_Cyrillic_u_macron + 0x0531: 0x1000531, // XK_Armenian_AYB + 0x0532: 0x1000532, // XK_Armenian_BEN + 0x0533: 0x1000533, // XK_Armenian_GIM + 0x0534: 0x1000534, // XK_Armenian_DA + 0x0535: 0x1000535, // XK_Armenian_YECH + 0x0536: 0x1000536, // XK_Armenian_ZA + 0x0537: 0x1000537, // XK_Armenian_E + 0x0538: 0x1000538, // XK_Armenian_AT + 0x0539: 0x1000539, // XK_Armenian_TO + 0x053a: 0x100053a, // XK_Armenian_ZHE + 0x053b: 0x100053b, // XK_Armenian_INI + 0x053c: 0x100053c, // XK_Armenian_LYUN + 0x053d: 0x100053d, // XK_Armenian_KHE + 0x053e: 0x100053e, // XK_Armenian_TSA + 0x053f: 0x100053f, // XK_Armenian_KEN + 0x0540: 0x1000540, // XK_Armenian_HO + 0x0541: 0x1000541, // XK_Armenian_DZA + 0x0542: 0x1000542, // XK_Armenian_GHAT + 0x0543: 0x1000543, // XK_Armenian_TCHE + 0x0544: 0x1000544, // XK_Armenian_MEN + 0x0545: 0x1000545, // XK_Armenian_HI + 0x0546: 0x1000546, // XK_Armenian_NU + 0x0547: 0x1000547, // XK_Armenian_SHA + 0x0548: 0x1000548, // XK_Armenian_VO + 0x0549: 0x1000549, // XK_Armenian_CHA + 0x054a: 0x100054a, // XK_Armenian_PE + 0x054b: 0x100054b, // XK_Armenian_JE + 0x054c: 0x100054c, // XK_Armenian_RA + 0x054d: 0x100054d, // XK_Armenian_SE + 0x054e: 0x100054e, // XK_Armenian_VEV + 0x054f: 0x100054f, // XK_Armenian_TYUN + 0x0550: 0x1000550, // XK_Armenian_RE + 0x0551: 0x1000551, // XK_Armenian_TSO + 0x0552: 0x1000552, // XK_Armenian_VYUN + 0x0553: 0x1000553, // XK_Armenian_PYUR + 0x0554: 0x1000554, // XK_Armenian_KE + 0x0555: 0x1000555, // XK_Armenian_O + 0x0556: 0x1000556, // XK_Armenian_FE + 0x055a: 0x100055a, // XK_Armenian_apostrophe + 0x055b: 0x100055b, // XK_Armenian_accent + 0x055c: 0x100055c, // XK_Armenian_exclam + 0x055d: 0x100055d, // XK_Armenian_separation_mark + 0x055e: 0x100055e, // XK_Armenian_question + 0x0561: 0x1000561, // XK_Armenian_ayb + 0x0562: 0x1000562, // XK_Armenian_ben + 0x0563: 0x1000563, // XK_Armenian_gim + 0x0564: 0x1000564, // XK_Armenian_da + 0x0565: 0x1000565, // XK_Armenian_yech + 0x0566: 0x1000566, // XK_Armenian_za + 0x0567: 0x1000567, // XK_Armenian_e + 0x0568: 0x1000568, // XK_Armenian_at + 0x0569: 0x1000569, // XK_Armenian_to + 0x056a: 0x100056a, // XK_Armenian_zhe + 0x056b: 0x100056b, // XK_Armenian_ini + 0x056c: 0x100056c, // XK_Armenian_lyun + 0x056d: 0x100056d, // XK_Armenian_khe + 0x056e: 0x100056e, // XK_Armenian_tsa + 0x056f: 0x100056f, // XK_Armenian_ken + 0x0570: 0x1000570, // XK_Armenian_ho + 0x0571: 0x1000571, // XK_Armenian_dza + 0x0572: 0x1000572, // XK_Armenian_ghat + 0x0573: 0x1000573, // XK_Armenian_tche + 0x0574: 0x1000574, // XK_Armenian_men + 0x0575: 0x1000575, // XK_Armenian_hi + 0x0576: 0x1000576, // XK_Armenian_nu + 0x0577: 0x1000577, // XK_Armenian_sha + 0x0578: 0x1000578, // XK_Armenian_vo + 0x0579: 0x1000579, // XK_Armenian_cha + 0x057a: 0x100057a, // XK_Armenian_pe + 0x057b: 0x100057b, // XK_Armenian_je + 0x057c: 0x100057c, // XK_Armenian_ra + 0x057d: 0x100057d, // XK_Armenian_se + 0x057e: 0x100057e, // XK_Armenian_vev + 0x057f: 0x100057f, // XK_Armenian_tyun + 0x0580: 0x1000580, // XK_Armenian_re + 0x0581: 0x1000581, // XK_Armenian_tso + 0x0582: 0x1000582, // XK_Armenian_vyun + 0x0583: 0x1000583, // XK_Armenian_pyur + 0x0584: 0x1000584, // XK_Armenian_ke + 0x0585: 0x1000585, // XK_Armenian_o + 0x0586: 0x1000586, // XK_Armenian_fe + 0x0587: 0x1000587, // XK_Armenian_ligature_ew + 0x0589: 0x1000589, // XK_Armenian_full_stop + 0x058a: 0x100058a, // XK_Armenian_hyphen + 0x05d0: 0x0ce0, // XK_hebrew_aleph + 0x05d1: 0x0ce1, // XK_hebrew_bet + 0x05d2: 0x0ce2, // XK_hebrew_gimel + 0x05d3: 0x0ce3, // XK_hebrew_dalet + 0x05d4: 0x0ce4, // XK_hebrew_he + 0x05d5: 0x0ce5, // XK_hebrew_waw + 0x05d6: 0x0ce6, // XK_hebrew_zain + 0x05d7: 0x0ce7, // XK_hebrew_chet + 0x05d8: 0x0ce8, // XK_hebrew_tet + 0x05d9: 0x0ce9, // XK_hebrew_yod + 0x05da: 0x0cea, // XK_hebrew_finalkaph + 0x05db: 0x0ceb, // XK_hebrew_kaph + 0x05dc: 0x0cec, // XK_hebrew_lamed + 0x05dd: 0x0ced, // XK_hebrew_finalmem + 0x05de: 0x0cee, // XK_hebrew_mem + 0x05df: 0x0cef, // XK_hebrew_finalnun + 0x05e0: 0x0cf0, // XK_hebrew_nun + 0x05e1: 0x0cf1, // XK_hebrew_samech + 0x05e2: 0x0cf2, // XK_hebrew_ayin + 0x05e3: 0x0cf3, // XK_hebrew_finalpe + 0x05e4: 0x0cf4, // XK_hebrew_pe + 0x05e5: 0x0cf5, // XK_hebrew_finalzade + 0x05e6: 0x0cf6, // XK_hebrew_zade + 0x05e7: 0x0cf7, // XK_hebrew_qoph + 0x05e8: 0x0cf8, // XK_hebrew_resh + 0x05e9: 0x0cf9, // XK_hebrew_shin + 0x05ea: 0x0cfa, // XK_hebrew_taw + 0x060c: 0x05ac, // XK_Arabic_comma + 0x061b: 0x05bb, // XK_Arabic_semicolon + 0x061f: 0x05bf, // XK_Arabic_question_mark + 0x0621: 0x05c1, // XK_Arabic_hamza + 0x0622: 0x05c2, // XK_Arabic_maddaonalef + 0x0623: 0x05c3, // XK_Arabic_hamzaonalef + 0x0624: 0x05c4, // XK_Arabic_hamzaonwaw + 0x0625: 0x05c5, // XK_Arabic_hamzaunderalef + 0x0626: 0x05c6, // XK_Arabic_hamzaonyeh + 0x0627: 0x05c7, // XK_Arabic_alef + 0x0628: 0x05c8, // XK_Arabic_beh + 0x0629: 0x05c9, // XK_Arabic_tehmarbuta + 0x062a: 0x05ca, // XK_Arabic_teh + 0x062b: 0x05cb, // XK_Arabic_theh + 0x062c: 0x05cc, // XK_Arabic_jeem + 0x062d: 0x05cd, // XK_Arabic_hah + 0x062e: 0x05ce, // XK_Arabic_khah + 0x062f: 0x05cf, // XK_Arabic_dal + 0x0630: 0x05d0, // XK_Arabic_thal + 0x0631: 0x05d1, // XK_Arabic_ra + 0x0632: 0x05d2, // XK_Arabic_zain + 0x0633: 0x05d3, // XK_Arabic_seen + 0x0634: 0x05d4, // XK_Arabic_sheen + 0x0635: 0x05d5, // XK_Arabic_sad + 0x0636: 0x05d6, // XK_Arabic_dad + 0x0637: 0x05d7, // XK_Arabic_tah + 0x0638: 0x05d8, // XK_Arabic_zah + 0x0639: 0x05d9, // XK_Arabic_ain + 0x063a: 0x05da, // XK_Arabic_ghain + 0x0640: 0x05e0, // XK_Arabic_tatweel + 0x0641: 0x05e1, // XK_Arabic_feh + 0x0642: 0x05e2, // XK_Arabic_qaf + 0x0643: 0x05e3, // XK_Arabic_kaf + 0x0644: 0x05e4, // XK_Arabic_lam + 0x0645: 0x05e5, // XK_Arabic_meem + 0x0646: 0x05e6, // XK_Arabic_noon + 0x0647: 0x05e7, // XK_Arabic_ha + 0x0648: 0x05e8, // XK_Arabic_waw + 0x0649: 0x05e9, // XK_Arabic_alefmaksura + 0x064a: 0x05ea, // XK_Arabic_yeh + 0x064b: 0x05eb, // XK_Arabic_fathatan + 0x064c: 0x05ec, // XK_Arabic_dammatan + 0x064d: 0x05ed, // XK_Arabic_kasratan + 0x064e: 0x05ee, // XK_Arabic_fatha + 0x064f: 0x05ef, // XK_Arabic_damma + 0x0650: 0x05f0, // XK_Arabic_kasra + 0x0651: 0x05f1, // XK_Arabic_shadda + 0x0652: 0x05f2, // XK_Arabic_sukun + 0x0653: 0x1000653, // XK_Arabic_madda_above + 0x0654: 0x1000654, // XK_Arabic_hamza_above + 0x0655: 0x1000655, // XK_Arabic_hamza_below + 0x0660: 0x1000660, // XK_Arabic_0 + 0x0661: 0x1000661, // XK_Arabic_1 + 0x0662: 0x1000662, // XK_Arabic_2 + 0x0663: 0x1000663, // XK_Arabic_3 + 0x0664: 0x1000664, // XK_Arabic_4 + 0x0665: 0x1000665, // XK_Arabic_5 + 0x0666: 0x1000666, // XK_Arabic_6 + 0x0667: 0x1000667, // XK_Arabic_7 + 0x0668: 0x1000668, // XK_Arabic_8 + 0x0669: 0x1000669, // XK_Arabic_9 + 0x066a: 0x100066a, // XK_Arabic_percent + 0x0670: 0x1000670, // XK_Arabic_superscript_alef + 0x0679: 0x1000679, // XK_Arabic_tteh + 0x067e: 0x100067e, // XK_Arabic_peh + 0x0686: 0x1000686, // XK_Arabic_tcheh + 0x0688: 0x1000688, // XK_Arabic_ddal + 0x0691: 0x1000691, // XK_Arabic_rreh + 0x0698: 0x1000698, // XK_Arabic_jeh + 0x06a4: 0x10006a4, // XK_Arabic_veh + 0x06a9: 0x10006a9, // XK_Arabic_keheh + 0x06af: 0x10006af, // XK_Arabic_gaf + 0x06ba: 0x10006ba, // XK_Arabic_noon_ghunna + 0x06be: 0x10006be, // XK_Arabic_heh_doachashmee + 0x06c1: 0x10006c1, // XK_Arabic_heh_goal + 0x06cc: 0x10006cc, // XK_Farsi_yeh + 0x06d2: 0x10006d2, // XK_Arabic_yeh_baree + 0x06d4: 0x10006d4, // XK_Arabic_fullstop + 0x06f0: 0x10006f0, // XK_Farsi_0 + 0x06f1: 0x10006f1, // XK_Farsi_1 + 0x06f2: 0x10006f2, // XK_Farsi_2 + 0x06f3: 0x10006f3, // XK_Farsi_3 + 0x06f4: 0x10006f4, // XK_Farsi_4 + 0x06f5: 0x10006f5, // XK_Farsi_5 + 0x06f6: 0x10006f6, // XK_Farsi_6 + 0x06f7: 0x10006f7, // XK_Farsi_7 + 0x06f8: 0x10006f8, // XK_Farsi_8 + 0x06f9: 0x10006f9, // XK_Farsi_9 + 0x0d82: 0x1000d82, // XK_Sinh_ng + 0x0d83: 0x1000d83, // XK_Sinh_h2 + 0x0d85: 0x1000d85, // XK_Sinh_a + 0x0d86: 0x1000d86, // XK_Sinh_aa + 0x0d87: 0x1000d87, // XK_Sinh_ae + 0x0d88: 0x1000d88, // XK_Sinh_aee + 0x0d89: 0x1000d89, // XK_Sinh_i + 0x0d8a: 0x1000d8a, // XK_Sinh_ii + 0x0d8b: 0x1000d8b, // XK_Sinh_u + 0x0d8c: 0x1000d8c, // XK_Sinh_uu + 0x0d8d: 0x1000d8d, // XK_Sinh_ri + 0x0d8e: 0x1000d8e, // XK_Sinh_rii + 0x0d8f: 0x1000d8f, // XK_Sinh_lu + 0x0d90: 0x1000d90, // XK_Sinh_luu + 0x0d91: 0x1000d91, // XK_Sinh_e + 0x0d92: 0x1000d92, // XK_Sinh_ee + 0x0d93: 0x1000d93, // XK_Sinh_ai + 0x0d94: 0x1000d94, // XK_Sinh_o + 0x0d95: 0x1000d95, // XK_Sinh_oo + 0x0d96: 0x1000d96, // XK_Sinh_au + 0x0d9a: 0x1000d9a, // XK_Sinh_ka + 0x0d9b: 0x1000d9b, // XK_Sinh_kha + 0x0d9c: 0x1000d9c, // XK_Sinh_ga + 0x0d9d: 0x1000d9d, // XK_Sinh_gha + 0x0d9e: 0x1000d9e, // XK_Sinh_ng2 + 0x0d9f: 0x1000d9f, // XK_Sinh_nga + 0x0da0: 0x1000da0, // XK_Sinh_ca + 0x0da1: 0x1000da1, // XK_Sinh_cha + 0x0da2: 0x1000da2, // XK_Sinh_ja + 0x0da3: 0x1000da3, // XK_Sinh_jha + 0x0da4: 0x1000da4, // XK_Sinh_nya + 0x0da5: 0x1000da5, // XK_Sinh_jnya + 0x0da6: 0x1000da6, // XK_Sinh_nja + 0x0da7: 0x1000da7, // XK_Sinh_tta + 0x0da8: 0x1000da8, // XK_Sinh_ttha + 0x0da9: 0x1000da9, // XK_Sinh_dda + 0x0daa: 0x1000daa, // XK_Sinh_ddha + 0x0dab: 0x1000dab, // XK_Sinh_nna + 0x0dac: 0x1000dac, // XK_Sinh_ndda + 0x0dad: 0x1000dad, // XK_Sinh_tha + 0x0dae: 0x1000dae, // XK_Sinh_thha + 0x0daf: 0x1000daf, // XK_Sinh_dha + 0x0db0: 0x1000db0, // XK_Sinh_dhha + 0x0db1: 0x1000db1, // XK_Sinh_na + 0x0db3: 0x1000db3, // XK_Sinh_ndha + 0x0db4: 0x1000db4, // XK_Sinh_pa + 0x0db5: 0x1000db5, // XK_Sinh_pha + 0x0db6: 0x1000db6, // XK_Sinh_ba + 0x0db7: 0x1000db7, // XK_Sinh_bha + 0x0db8: 0x1000db8, // XK_Sinh_ma + 0x0db9: 0x1000db9, // XK_Sinh_mba + 0x0dba: 0x1000dba, // XK_Sinh_ya + 0x0dbb: 0x1000dbb, // XK_Sinh_ra + 0x0dbd: 0x1000dbd, // XK_Sinh_la + 0x0dc0: 0x1000dc0, // XK_Sinh_va + 0x0dc1: 0x1000dc1, // XK_Sinh_sha + 0x0dc2: 0x1000dc2, // XK_Sinh_ssha + 0x0dc3: 0x1000dc3, // XK_Sinh_sa + 0x0dc4: 0x1000dc4, // XK_Sinh_ha + 0x0dc5: 0x1000dc5, // XK_Sinh_lla + 0x0dc6: 0x1000dc6, // XK_Sinh_fa + 0x0dca: 0x1000dca, // XK_Sinh_al + 0x0dcf: 0x1000dcf, // XK_Sinh_aa2 + 0x0dd0: 0x1000dd0, // XK_Sinh_ae2 + 0x0dd1: 0x1000dd1, // XK_Sinh_aee2 + 0x0dd2: 0x1000dd2, // XK_Sinh_i2 + 0x0dd3: 0x1000dd3, // XK_Sinh_ii2 + 0x0dd4: 0x1000dd4, // XK_Sinh_u2 + 0x0dd6: 0x1000dd6, // XK_Sinh_uu2 + 0x0dd8: 0x1000dd8, // XK_Sinh_ru2 + 0x0dd9: 0x1000dd9, // XK_Sinh_e2 + 0x0dda: 0x1000dda, // XK_Sinh_ee2 + 0x0ddb: 0x1000ddb, // XK_Sinh_ai2 + 0x0ddc: 0x1000ddc, // XK_Sinh_o2 + 0x0ddd: 0x1000ddd, // XK_Sinh_oo2 + 0x0dde: 0x1000dde, // XK_Sinh_au2 + 0x0ddf: 0x1000ddf, // XK_Sinh_lu2 + 0x0df2: 0x1000df2, // XK_Sinh_ruu2 + 0x0df3: 0x1000df3, // XK_Sinh_luu2 + 0x0df4: 0x1000df4, // XK_Sinh_kunddaliya + 0x0e01: 0x0da1, // XK_Thai_kokai + 0x0e02: 0x0da2, // XK_Thai_khokhai + 0x0e03: 0x0da3, // XK_Thai_khokhuat + 0x0e04: 0x0da4, // XK_Thai_khokhwai + 0x0e05: 0x0da5, // XK_Thai_khokhon + 0x0e06: 0x0da6, // XK_Thai_khorakhang + 0x0e07: 0x0da7, // XK_Thai_ngongu + 0x0e08: 0x0da8, // XK_Thai_chochan + 0x0e09: 0x0da9, // XK_Thai_choching + 0x0e0a: 0x0daa, // XK_Thai_chochang + 0x0e0b: 0x0dab, // XK_Thai_soso + 0x0e0c: 0x0dac, // XK_Thai_chochoe + 0x0e0d: 0x0dad, // XK_Thai_yoying + 0x0e0e: 0x0dae, // XK_Thai_dochada + 0x0e0f: 0x0daf, // XK_Thai_topatak + 0x0e10: 0x0db0, // XK_Thai_thothan + 0x0e11: 0x0db1, // XK_Thai_thonangmontho + 0x0e12: 0x0db2, // XK_Thai_thophuthao + 0x0e13: 0x0db3, // XK_Thai_nonen + 0x0e14: 0x0db4, // XK_Thai_dodek + 0x0e15: 0x0db5, // XK_Thai_totao + 0x0e16: 0x0db6, // XK_Thai_thothung + 0x0e17: 0x0db7, // XK_Thai_thothahan + 0x0e18: 0x0db8, // XK_Thai_thothong + 0x0e19: 0x0db9, // XK_Thai_nonu + 0x0e1a: 0x0dba, // XK_Thai_bobaimai + 0x0e1b: 0x0dbb, // XK_Thai_popla + 0x0e1c: 0x0dbc, // XK_Thai_phophung + 0x0e1d: 0x0dbd, // XK_Thai_fofa + 0x0e1e: 0x0dbe, // XK_Thai_phophan + 0x0e1f: 0x0dbf, // XK_Thai_fofan + 0x0e20: 0x0dc0, // XK_Thai_phosamphao + 0x0e21: 0x0dc1, // XK_Thai_moma + 0x0e22: 0x0dc2, // XK_Thai_yoyak + 0x0e23: 0x0dc3, // XK_Thai_rorua + 0x0e24: 0x0dc4, // XK_Thai_ru + 0x0e25: 0x0dc5, // XK_Thai_loling + 0x0e26: 0x0dc6, // XK_Thai_lu + 0x0e27: 0x0dc7, // XK_Thai_wowaen + 0x0e28: 0x0dc8, // XK_Thai_sosala + 0x0e29: 0x0dc9, // XK_Thai_sorusi + 0x0e2a: 0x0dca, // XK_Thai_sosua + 0x0e2b: 0x0dcb, // XK_Thai_hohip + 0x0e2c: 0x0dcc, // XK_Thai_lochula + 0x0e2d: 0x0dcd, // XK_Thai_oang + 0x0e2e: 0x0dce, // XK_Thai_honokhuk + 0x0e2f: 0x0dcf, // XK_Thai_paiyannoi + 0x0e30: 0x0dd0, // XK_Thai_saraa + 0x0e31: 0x0dd1, // XK_Thai_maihanakat + 0x0e32: 0x0dd2, // XK_Thai_saraaa + 0x0e33: 0x0dd3, // XK_Thai_saraam + 0x0e34: 0x0dd4, // XK_Thai_sarai + 0x0e35: 0x0dd5, // XK_Thai_saraii + 0x0e36: 0x0dd6, // XK_Thai_saraue + 0x0e37: 0x0dd7, // XK_Thai_sarauee + 0x0e38: 0x0dd8, // XK_Thai_sarau + 0x0e39: 0x0dd9, // XK_Thai_sarauu + 0x0e3a: 0x0dda, // XK_Thai_phinthu + 0x0e3f: 0x0ddf, // XK_Thai_baht + 0x0e40: 0x0de0, // XK_Thai_sarae + 0x0e41: 0x0de1, // XK_Thai_saraae + 0x0e42: 0x0de2, // XK_Thai_sarao + 0x0e43: 0x0de3, // XK_Thai_saraaimaimuan + 0x0e44: 0x0de4, // XK_Thai_saraaimaimalai + 0x0e45: 0x0de5, // XK_Thai_lakkhangyao + 0x0e46: 0x0de6, // XK_Thai_maiyamok + 0x0e47: 0x0de7, // XK_Thai_maitaikhu + 0x0e48: 0x0de8, // XK_Thai_maiek + 0x0e49: 0x0de9, // XK_Thai_maitho + 0x0e4a: 0x0dea, // XK_Thai_maitri + 0x0e4b: 0x0deb, // XK_Thai_maichattawa + 0x0e4c: 0x0dec, // XK_Thai_thanthakhat + 0x0e4d: 0x0ded, // XK_Thai_nikhahit + 0x0e50: 0x0df0, // XK_Thai_leksun + 0x0e51: 0x0df1, // XK_Thai_leknung + 0x0e52: 0x0df2, // XK_Thai_leksong + 0x0e53: 0x0df3, // XK_Thai_leksam + 0x0e54: 0x0df4, // XK_Thai_leksi + 0x0e55: 0x0df5, // XK_Thai_lekha + 0x0e56: 0x0df6, // XK_Thai_lekhok + 0x0e57: 0x0df7, // XK_Thai_lekchet + 0x0e58: 0x0df8, // XK_Thai_lekpaet + 0x0e59: 0x0df9, // XK_Thai_lekkao + 0x10d0: 0x10010d0, // XK_Georgian_an + 0x10d1: 0x10010d1, // XK_Georgian_ban + 0x10d2: 0x10010d2, // XK_Georgian_gan + 0x10d3: 0x10010d3, // XK_Georgian_don + 0x10d4: 0x10010d4, // XK_Georgian_en + 0x10d5: 0x10010d5, // XK_Georgian_vin + 0x10d6: 0x10010d6, // XK_Georgian_zen + 0x10d7: 0x10010d7, // XK_Georgian_tan + 0x10d8: 0x10010d8, // XK_Georgian_in + 0x10d9: 0x10010d9, // XK_Georgian_kan + 0x10da: 0x10010da, // XK_Georgian_las + 0x10db: 0x10010db, // XK_Georgian_man + 0x10dc: 0x10010dc, // XK_Georgian_nar + 0x10dd: 0x10010dd, // XK_Georgian_on + 0x10de: 0x10010de, // XK_Georgian_par + 0x10df: 0x10010df, // XK_Georgian_zhar + 0x10e0: 0x10010e0, // XK_Georgian_rae + 0x10e1: 0x10010e1, // XK_Georgian_san + 0x10e2: 0x10010e2, // XK_Georgian_tar + 0x10e3: 0x10010e3, // XK_Georgian_un + 0x10e4: 0x10010e4, // XK_Georgian_phar + 0x10e5: 0x10010e5, // XK_Georgian_khar + 0x10e6: 0x10010e6, // XK_Georgian_ghan + 0x10e7: 0x10010e7, // XK_Georgian_qar + 0x10e8: 0x10010e8, // XK_Georgian_shin + 0x10e9: 0x10010e9, // XK_Georgian_chin + 0x10ea: 0x10010ea, // XK_Georgian_can + 0x10eb: 0x10010eb, // XK_Georgian_jil + 0x10ec: 0x10010ec, // XK_Georgian_cil + 0x10ed: 0x10010ed, // XK_Georgian_char + 0x10ee: 0x10010ee, // XK_Georgian_xan + 0x10ef: 0x10010ef, // XK_Georgian_jhan + 0x10f0: 0x10010f0, // XK_Georgian_hae + 0x10f1: 0x10010f1, // XK_Georgian_he + 0x10f2: 0x10010f2, // XK_Georgian_hie + 0x10f3: 0x10010f3, // XK_Georgian_we + 0x10f4: 0x10010f4, // XK_Georgian_har + 0x10f5: 0x10010f5, // XK_Georgian_hoe + 0x10f6: 0x10010f6, // XK_Georgian_fi + 0x1e02: 0x1001e02, // XK_Babovedot + 0x1e03: 0x1001e03, // XK_babovedot + 0x1e0a: 0x1001e0a, // XK_Dabovedot + 0x1e0b: 0x1001e0b, // XK_dabovedot + 0x1e1e: 0x1001e1e, // XK_Fabovedot + 0x1e1f: 0x1001e1f, // XK_fabovedot + 0x1e36: 0x1001e36, // XK_Lbelowdot + 0x1e37: 0x1001e37, // XK_lbelowdot + 0x1e40: 0x1001e40, // XK_Mabovedot + 0x1e41: 0x1001e41, // XK_mabovedot + 0x1e56: 0x1001e56, // XK_Pabovedot + 0x1e57: 0x1001e57, // XK_pabovedot + 0x1e60: 0x1001e60, // XK_Sabovedot + 0x1e61: 0x1001e61, // XK_sabovedot + 0x1e6a: 0x1001e6a, // XK_Tabovedot + 0x1e6b: 0x1001e6b, // XK_tabovedot + 0x1e80: 0x1001e80, // XK_Wgrave + 0x1e81: 0x1001e81, // XK_wgrave + 0x1e82: 0x1001e82, // XK_Wacute + 0x1e83: 0x1001e83, // XK_wacute + 0x1e84: 0x1001e84, // XK_Wdiaeresis + 0x1e85: 0x1001e85, // XK_wdiaeresis + 0x1e8a: 0x1001e8a, // XK_Xabovedot + 0x1e8b: 0x1001e8b, // XK_xabovedot + 0x1ea0: 0x1001ea0, // XK_Abelowdot + 0x1ea1: 0x1001ea1, // XK_abelowdot + 0x1ea2: 0x1001ea2, // XK_Ahook + 0x1ea3: 0x1001ea3, // XK_ahook + 0x1ea4: 0x1001ea4, // XK_Acircumflexacute + 0x1ea5: 0x1001ea5, // XK_acircumflexacute + 0x1ea6: 0x1001ea6, // XK_Acircumflexgrave + 0x1ea7: 0x1001ea7, // XK_acircumflexgrave + 0x1ea8: 0x1001ea8, // XK_Acircumflexhook + 0x1ea9: 0x1001ea9, // XK_acircumflexhook + 0x1eaa: 0x1001eaa, // XK_Acircumflextilde + 0x1eab: 0x1001eab, // XK_acircumflextilde + 0x1eac: 0x1001eac, // XK_Acircumflexbelowdot + 0x1ead: 0x1001ead, // XK_acircumflexbelowdot + 0x1eae: 0x1001eae, // XK_Abreveacute + 0x1eaf: 0x1001eaf, // XK_abreveacute + 0x1eb0: 0x1001eb0, // XK_Abrevegrave + 0x1eb1: 0x1001eb1, // XK_abrevegrave + 0x1eb2: 0x1001eb2, // XK_Abrevehook + 0x1eb3: 0x1001eb3, // XK_abrevehook + 0x1eb4: 0x1001eb4, // XK_Abrevetilde + 0x1eb5: 0x1001eb5, // XK_abrevetilde + 0x1eb6: 0x1001eb6, // XK_Abrevebelowdot + 0x1eb7: 0x1001eb7, // XK_abrevebelowdot + 0x1eb8: 0x1001eb8, // XK_Ebelowdot + 0x1eb9: 0x1001eb9, // XK_ebelowdot + 0x1eba: 0x1001eba, // XK_Ehook + 0x1ebb: 0x1001ebb, // XK_ehook + 0x1ebc: 0x1001ebc, // XK_Etilde + 0x1ebd: 0x1001ebd, // XK_etilde + 0x1ebe: 0x1001ebe, // XK_Ecircumflexacute + 0x1ebf: 0x1001ebf, // XK_ecircumflexacute + 0x1ec0: 0x1001ec0, // XK_Ecircumflexgrave + 0x1ec1: 0x1001ec1, // XK_ecircumflexgrave + 0x1ec2: 0x1001ec2, // XK_Ecircumflexhook + 0x1ec3: 0x1001ec3, // XK_ecircumflexhook + 0x1ec4: 0x1001ec4, // XK_Ecircumflextilde + 0x1ec5: 0x1001ec5, // XK_ecircumflextilde + 0x1ec6: 0x1001ec6, // XK_Ecircumflexbelowdot + 0x1ec7: 0x1001ec7, // XK_ecircumflexbelowdot + 0x1ec8: 0x1001ec8, // XK_Ihook + 0x1ec9: 0x1001ec9, // XK_ihook + 0x1eca: 0x1001eca, // XK_Ibelowdot + 0x1ecb: 0x1001ecb, // XK_ibelowdot + 0x1ecc: 0x1001ecc, // XK_Obelowdot + 0x1ecd: 0x1001ecd, // XK_obelowdot + 0x1ece: 0x1001ece, // XK_Ohook + 0x1ecf: 0x1001ecf, // XK_ohook + 0x1ed0: 0x1001ed0, // XK_Ocircumflexacute + 0x1ed1: 0x1001ed1, // XK_ocircumflexacute + 0x1ed2: 0x1001ed2, // XK_Ocircumflexgrave + 0x1ed3: 0x1001ed3, // XK_ocircumflexgrave + 0x1ed4: 0x1001ed4, // XK_Ocircumflexhook + 0x1ed5: 0x1001ed5, // XK_ocircumflexhook + 0x1ed6: 0x1001ed6, // XK_Ocircumflextilde + 0x1ed7: 0x1001ed7, // XK_ocircumflextilde + 0x1ed8: 0x1001ed8, // XK_Ocircumflexbelowdot + 0x1ed9: 0x1001ed9, // XK_ocircumflexbelowdot + 0x1eda: 0x1001eda, // XK_Ohornacute + 0x1edb: 0x1001edb, // XK_ohornacute + 0x1edc: 0x1001edc, // XK_Ohorngrave + 0x1edd: 0x1001edd, // XK_ohorngrave + 0x1ede: 0x1001ede, // XK_Ohornhook + 0x1edf: 0x1001edf, // XK_ohornhook + 0x1ee0: 0x1001ee0, // XK_Ohorntilde + 0x1ee1: 0x1001ee1, // XK_ohorntilde + 0x1ee2: 0x1001ee2, // XK_Ohornbelowdot + 0x1ee3: 0x1001ee3, // XK_ohornbelowdot + 0x1ee4: 0x1001ee4, // XK_Ubelowdot + 0x1ee5: 0x1001ee5, // XK_ubelowdot + 0x1ee6: 0x1001ee6, // XK_Uhook + 0x1ee7: 0x1001ee7, // XK_uhook + 0x1ee8: 0x1001ee8, // XK_Uhornacute + 0x1ee9: 0x1001ee9, // XK_uhornacute + 0x1eea: 0x1001eea, // XK_Uhorngrave + 0x1eeb: 0x1001eeb, // XK_uhorngrave + 0x1eec: 0x1001eec, // XK_Uhornhook + 0x1eed: 0x1001eed, // XK_uhornhook + 0x1eee: 0x1001eee, // XK_Uhorntilde + 0x1eef: 0x1001eef, // XK_uhorntilde + 0x1ef0: 0x1001ef0, // XK_Uhornbelowdot + 0x1ef1: 0x1001ef1, // XK_uhornbelowdot + 0x1ef2: 0x1001ef2, // XK_Ygrave + 0x1ef3: 0x1001ef3, // XK_ygrave + 0x1ef4: 0x1001ef4, // XK_Ybelowdot + 0x1ef5: 0x1001ef5, // XK_ybelowdot + 0x1ef6: 0x1001ef6, // XK_Yhook + 0x1ef7: 0x1001ef7, // XK_yhook + 0x1ef8: 0x1001ef8, // XK_Ytilde + 0x1ef9: 0x1001ef9, // XK_ytilde + 0x2002: 0x0aa2, // XK_enspace + 0x2003: 0x0aa1, // XK_emspace + 0x2004: 0x0aa3, // XK_em3space + 0x2005: 0x0aa4, // XK_em4space + 0x2007: 0x0aa5, // XK_digitspace + 0x2008: 0x0aa6, // XK_punctspace + 0x2009: 0x0aa7, // XK_thinspace + 0x200a: 0x0aa8, // XK_hairspace + 0x2012: 0x0abb, // XK_figdash + 0x2013: 0x0aaa, // XK_endash + 0x2014: 0x0aa9, // XK_emdash + 0x2015: 0x07af, // XK_Greek_horizbar + 0x2017: 0x0cdf, // XK_hebrew_doublelowline + 0x2018: 0x0ad0, // XK_leftsinglequotemark + 0x2019: 0x0ad1, // XK_rightsinglequotemark + 0x201a: 0x0afd, // XK_singlelowquotemark + 0x201c: 0x0ad2, // XK_leftdoublequotemark + 0x201d: 0x0ad3, // XK_rightdoublequotemark + 0x201e: 0x0afe, // XK_doublelowquotemark + 0x2020: 0x0af1, // XK_dagger + 0x2021: 0x0af2, // XK_doubledagger + 0x2022: 0x0ae6, // XK_enfilledcircbullet + 0x2025: 0x0aaf, // XK_doubbaselinedot + 0x2026: 0x0aae, // XK_ellipsis + 0x2030: 0x0ad5, // XK_permille + 0x2032: 0x0ad6, // XK_minutes + 0x2033: 0x0ad7, // XK_seconds + 0x2038: 0x0afc, // XK_caret + 0x203e: 0x047e, // XK_overline + 0x2070: 0x1002070, // XK_zerosuperior + 0x2074: 0x1002074, // XK_foursuperior + 0x2075: 0x1002075, // XK_fivesuperior + 0x2076: 0x1002076, // XK_sixsuperior + 0x2077: 0x1002077, // XK_sevensuperior + 0x2078: 0x1002078, // XK_eightsuperior + 0x2079: 0x1002079, // XK_ninesuperior + 0x2080: 0x1002080, // XK_zerosubscript + 0x2081: 0x1002081, // XK_onesubscript + 0x2082: 0x1002082, // XK_twosubscript + 0x2083: 0x1002083, // XK_threesubscript + 0x2084: 0x1002084, // XK_foursubscript + 0x2085: 0x1002085, // XK_fivesubscript + 0x2086: 0x1002086, // XK_sixsubscript + 0x2087: 0x1002087, // XK_sevensubscript + 0x2088: 0x1002088, // XK_eightsubscript + 0x2089: 0x1002089, // XK_ninesubscript + 0x20a0: 0x10020a0, // XK_EcuSign + 0x20a1: 0x10020a1, // XK_ColonSign + 0x20a2: 0x10020a2, // XK_CruzeiroSign + 0x20a3: 0x10020a3, // XK_FFrancSign + 0x20a4: 0x10020a4, // XK_LiraSign + 0x20a5: 0x10020a5, // XK_MillSign + 0x20a6: 0x10020a6, // XK_NairaSign + 0x20a7: 0x10020a7, // XK_PesetaSign + 0x20a8: 0x10020a8, // XK_RupeeSign + 0x20a9: 0x0eff, // XK_Korean_Won + 0x20aa: 0x10020aa, // XK_NewSheqelSign + 0x20ab: 0x10020ab, // XK_DongSign + 0x20ac: 0x20ac, // XK_EuroSign + 0x2105: 0x0ab8, // XK_careof + 0x2116: 0x06b0, // XK_numerosign + 0x2117: 0x0afb, // XK_phonographcopyright + 0x211e: 0x0ad4, // XK_prescription + 0x2122: 0x0ac9, // XK_trademark + 0x2153: 0x0ab0, // XK_onethird + 0x2154: 0x0ab1, // XK_twothirds + 0x2155: 0x0ab2, // XK_onefifth + 0x2156: 0x0ab3, // XK_twofifths + 0x2157: 0x0ab4, // XK_threefifths + 0x2158: 0x0ab5, // XK_fourfifths + 0x2159: 0x0ab6, // XK_onesixth + 0x215a: 0x0ab7, // XK_fivesixths + 0x215b: 0x0ac3, // XK_oneeighth + 0x215c: 0x0ac4, // XK_threeeighths + 0x215d: 0x0ac5, // XK_fiveeighths + 0x215e: 0x0ac6, // XK_seveneighths + 0x2190: 0x08fb, // XK_leftarrow + 0x2191: 0x08fc, // XK_uparrow + 0x2192: 0x08fd, // XK_rightarrow + 0x2193: 0x08fe, // XK_downarrow + 0x21d2: 0x08ce, // XK_implies + 0x21d4: 0x08cd, // XK_ifonlyif + 0x2202: 0x08ef, // XK_partialderivative + 0x2205: 0x1002205, // XK_emptyset + 0x2207: 0x08c5, // XK_nabla + 0x2208: 0x1002208, // XK_elementof + 0x2209: 0x1002209, // XK_notelementof + 0x220b: 0x100220b, // XK_containsas + 0x2218: 0x0bca, // XK_jot + 0x221a: 0x08d6, // XK_radical + 0x221b: 0x100221b, // XK_cuberoot + 0x221c: 0x100221c, // XK_fourthroot + 0x221d: 0x08c1, // XK_variation + 0x221e: 0x08c2, // XK_infinity + 0x2227: 0x08de, // XK_logicaland + 0x2228: 0x08df, // XK_logicalor + 0x2229: 0x08dc, // XK_intersection + 0x222a: 0x08dd, // XK_union + 0x222b: 0x08bf, // XK_integral + 0x222c: 0x100222c, // XK_dintegral + 0x222d: 0x100222d, // XK_tintegral + 0x2234: 0x08c0, // XK_therefore + 0x2235: 0x1002235, // XK_because + 0x223c: 0x08c8, // XK_approximate + 0x2243: 0x08c9, // XK_similarequal + 0x2245: 0x1002248, // XK_approxeq + 0x2247: 0x1002247, // XK_notapproxeq + 0x2260: 0x08bd, // XK_notequal + 0x2261: 0x08cf, // XK_identical + 0x2262: 0x1002262, // XK_notidentical + 0x2263: 0x1002263, // XK_stricteq + 0x2264: 0x08bc, // XK_lessthanequal + 0x2265: 0x08be, // XK_greaterthanequal + 0x2282: 0x08da, // XK_includedin + 0x2283: 0x08db, // XK_includes + 0x22a2: 0x0bfc, // XK_righttack + 0x22a3: 0x0bdc, // XK_lefttack + 0x22a4: 0x0bc2, // XK_downtack + 0x22a5: 0x0bce, // XK_uptack + 0x2308: 0x0bd3, // XK_upstile + 0x230a: 0x0bc4, // XK_downstile + 0x2315: 0x0afa, // XK_telephonerecorder + 0x2320: 0x08a4, // XK_topintegral + 0x2321: 0x08a5, // XK_botintegral + 0x2395: 0x0bcc, // XK_quad + 0x239b: 0x08ab, // XK_topleftparens + 0x239d: 0x08ac, // XK_botleftparens + 0x239e: 0x08ad, // XK_toprightparens + 0x23a0: 0x08ae, // XK_botrightparens + 0x23a1: 0x08a7, // XK_topleftsqbracket + 0x23a3: 0x08a8, // XK_botleftsqbracket + 0x23a4: 0x08a9, // XK_toprightsqbracket + 0x23a6: 0x08aa, // XK_botrightsqbracket + 0x23a8: 0x08af, // XK_leftmiddlecurlybrace + 0x23ac: 0x08b0, // XK_rightmiddlecurlybrace + 0x23b7: 0x08a1, // XK_leftradical + 0x23ba: 0x09ef, // XK_horizlinescan1 + 0x23bb: 0x09f0, // XK_horizlinescan3 + 0x23bc: 0x09f2, // XK_horizlinescan7 + 0x23bd: 0x09f3, // XK_horizlinescan9 + 0x2409: 0x09e2, // XK_ht + 0x240a: 0x09e5, // XK_lf + 0x240b: 0x09e9, // XK_vt + 0x240c: 0x09e3, // XK_ff + 0x240d: 0x09e4, // XK_cr + 0x2423: 0x0aac, // XK_signifblank + 0x2424: 0x09e8, // XK_nl + 0x2500: 0x08a3, // XK_horizconnector + 0x2502: 0x08a6, // XK_vertconnector + 0x250c: 0x08a2, // XK_topleftradical + 0x2510: 0x09eb, // XK_uprightcorner + 0x2514: 0x09ed, // XK_lowleftcorner + 0x2518: 0x09ea, // XK_lowrightcorner + 0x251c: 0x09f4, // XK_leftt + 0x2524: 0x09f5, // XK_rightt + 0x252c: 0x09f7, // XK_topt + 0x2534: 0x09f6, // XK_bott + 0x253c: 0x09ee, // XK_crossinglines + 0x2592: 0x09e1, // XK_checkerboard + 0x25aa: 0x0ae7, // XK_enfilledsqbullet + 0x25ab: 0x0ae1, // XK_enopensquarebullet + 0x25ac: 0x0adb, // XK_filledrectbullet + 0x25ad: 0x0ae2, // XK_openrectbullet + 0x25ae: 0x0adf, // XK_emfilledrect + 0x25af: 0x0acf, // XK_emopenrectangle + 0x25b2: 0x0ae8, // XK_filledtribulletup + 0x25b3: 0x0ae3, // XK_opentribulletup + 0x25b6: 0x0add, // XK_filledrighttribullet + 0x25b7: 0x0acd, // XK_rightopentriangle + 0x25bc: 0x0ae9, // XK_filledtribulletdown + 0x25bd: 0x0ae4, // XK_opentribulletdown + 0x25c0: 0x0adc, // XK_filledlefttribullet + 0x25c1: 0x0acc, // XK_leftopentriangle + 0x25c6: 0x09e0, // XK_soliddiamond + 0x25cb: 0x0ace, // XK_emopencircle + 0x25cf: 0x0ade, // XK_emfilledcircle + 0x25e6: 0x0ae0, // XK_enopencircbullet + 0x2606: 0x0ae5, // XK_openstar + 0x260e: 0x0af9, // XK_telephone + 0x2613: 0x0aca, // XK_signaturemark + 0x261c: 0x0aea, // XK_leftpointer + 0x261e: 0x0aeb, // XK_rightpointer + 0x2640: 0x0af8, // XK_femalesymbol + 0x2642: 0x0af7, // XK_malesymbol + 0x2663: 0x0aec, // XK_club + 0x2665: 0x0aee, // XK_heart + 0x2666: 0x0aed, // XK_diamond + 0x266d: 0x0af6, // XK_musicalflat + 0x266f: 0x0af5, // XK_musicalsharp + 0x2713: 0x0af3, // XK_checkmark + 0x2717: 0x0af4, // XK_ballotcross + 0x271d: 0x0ad9, // XK_latincross + 0x2720: 0x0af0, // XK_maltesecross + 0x27e8: 0x0abc, // XK_leftanglebracket + 0x27e9: 0x0abe, // XK_rightanglebracket + 0x2800: 0x1002800, // XK_braille_blank + 0x2801: 0x1002801, // XK_braille_dots_1 + 0x2802: 0x1002802, // XK_braille_dots_2 + 0x2803: 0x1002803, // XK_braille_dots_12 + 0x2804: 0x1002804, // XK_braille_dots_3 + 0x2805: 0x1002805, // XK_braille_dots_13 + 0x2806: 0x1002806, // XK_braille_dots_23 + 0x2807: 0x1002807, // XK_braille_dots_123 + 0x2808: 0x1002808, // XK_braille_dots_4 + 0x2809: 0x1002809, // XK_braille_dots_14 + 0x280a: 0x100280a, // XK_braille_dots_24 + 0x280b: 0x100280b, // XK_braille_dots_124 + 0x280c: 0x100280c, // XK_braille_dots_34 + 0x280d: 0x100280d, // XK_braille_dots_134 + 0x280e: 0x100280e, // XK_braille_dots_234 + 0x280f: 0x100280f, // XK_braille_dots_1234 + 0x2810: 0x1002810, // XK_braille_dots_5 + 0x2811: 0x1002811, // XK_braille_dots_15 + 0x2812: 0x1002812, // XK_braille_dots_25 + 0x2813: 0x1002813, // XK_braille_dots_125 + 0x2814: 0x1002814, // XK_braille_dots_35 + 0x2815: 0x1002815, // XK_braille_dots_135 + 0x2816: 0x1002816, // XK_braille_dots_235 + 0x2817: 0x1002817, // XK_braille_dots_1235 + 0x2818: 0x1002818, // XK_braille_dots_45 + 0x2819: 0x1002819, // XK_braille_dots_145 + 0x281a: 0x100281a, // XK_braille_dots_245 + 0x281b: 0x100281b, // XK_braille_dots_1245 + 0x281c: 0x100281c, // XK_braille_dots_345 + 0x281d: 0x100281d, // XK_braille_dots_1345 + 0x281e: 0x100281e, // XK_braille_dots_2345 + 0x281f: 0x100281f, // XK_braille_dots_12345 + 0x2820: 0x1002820, // XK_braille_dots_6 + 0x2821: 0x1002821, // XK_braille_dots_16 + 0x2822: 0x1002822, // XK_braille_dots_26 + 0x2823: 0x1002823, // XK_braille_dots_126 + 0x2824: 0x1002824, // XK_braille_dots_36 + 0x2825: 0x1002825, // XK_braille_dots_136 + 0x2826: 0x1002826, // XK_braille_dots_236 + 0x2827: 0x1002827, // XK_braille_dots_1236 + 0x2828: 0x1002828, // XK_braille_dots_46 + 0x2829: 0x1002829, // XK_braille_dots_146 + 0x282a: 0x100282a, // XK_braille_dots_246 + 0x282b: 0x100282b, // XK_braille_dots_1246 + 0x282c: 0x100282c, // XK_braille_dots_346 + 0x282d: 0x100282d, // XK_braille_dots_1346 + 0x282e: 0x100282e, // XK_braille_dots_2346 + 0x282f: 0x100282f, // XK_braille_dots_12346 + 0x2830: 0x1002830, // XK_braille_dots_56 + 0x2831: 0x1002831, // XK_braille_dots_156 + 0x2832: 0x1002832, // XK_braille_dots_256 + 0x2833: 0x1002833, // XK_braille_dots_1256 + 0x2834: 0x1002834, // XK_braille_dots_356 + 0x2835: 0x1002835, // XK_braille_dots_1356 + 0x2836: 0x1002836, // XK_braille_dots_2356 + 0x2837: 0x1002837, // XK_braille_dots_12356 + 0x2838: 0x1002838, // XK_braille_dots_456 + 0x2839: 0x1002839, // XK_braille_dots_1456 + 0x283a: 0x100283a, // XK_braille_dots_2456 + 0x283b: 0x100283b, // XK_braille_dots_12456 + 0x283c: 0x100283c, // XK_braille_dots_3456 + 0x283d: 0x100283d, // XK_braille_dots_13456 + 0x283e: 0x100283e, // XK_braille_dots_23456 + 0x283f: 0x100283f, // XK_braille_dots_123456 + 0x2840: 0x1002840, // XK_braille_dots_7 + 0x2841: 0x1002841, // XK_braille_dots_17 + 0x2842: 0x1002842, // XK_braille_dots_27 + 0x2843: 0x1002843, // XK_braille_dots_127 + 0x2844: 0x1002844, // XK_braille_dots_37 + 0x2845: 0x1002845, // XK_braille_dots_137 + 0x2846: 0x1002846, // XK_braille_dots_237 + 0x2847: 0x1002847, // XK_braille_dots_1237 + 0x2848: 0x1002848, // XK_braille_dots_47 + 0x2849: 0x1002849, // XK_braille_dots_147 + 0x284a: 0x100284a, // XK_braille_dots_247 + 0x284b: 0x100284b, // XK_braille_dots_1247 + 0x284c: 0x100284c, // XK_braille_dots_347 + 0x284d: 0x100284d, // XK_braille_dots_1347 + 0x284e: 0x100284e, // XK_braille_dots_2347 + 0x284f: 0x100284f, // XK_braille_dots_12347 + 0x2850: 0x1002850, // XK_braille_dots_57 + 0x2851: 0x1002851, // XK_braille_dots_157 + 0x2852: 0x1002852, // XK_braille_dots_257 + 0x2853: 0x1002853, // XK_braille_dots_1257 + 0x2854: 0x1002854, // XK_braille_dots_357 + 0x2855: 0x1002855, // XK_braille_dots_1357 + 0x2856: 0x1002856, // XK_braille_dots_2357 + 0x2857: 0x1002857, // XK_braille_dots_12357 + 0x2858: 0x1002858, // XK_braille_dots_457 + 0x2859: 0x1002859, // XK_braille_dots_1457 + 0x285a: 0x100285a, // XK_braille_dots_2457 + 0x285b: 0x100285b, // XK_braille_dots_12457 + 0x285c: 0x100285c, // XK_braille_dots_3457 + 0x285d: 0x100285d, // XK_braille_dots_13457 + 0x285e: 0x100285e, // XK_braille_dots_23457 + 0x285f: 0x100285f, // XK_braille_dots_123457 + 0x2860: 0x1002860, // XK_braille_dots_67 + 0x2861: 0x1002861, // XK_braille_dots_167 + 0x2862: 0x1002862, // XK_braille_dots_267 + 0x2863: 0x1002863, // XK_braille_dots_1267 + 0x2864: 0x1002864, // XK_braille_dots_367 + 0x2865: 0x1002865, // XK_braille_dots_1367 + 0x2866: 0x1002866, // XK_braille_dots_2367 + 0x2867: 0x1002867, // XK_braille_dots_12367 + 0x2868: 0x1002868, // XK_braille_dots_467 + 0x2869: 0x1002869, // XK_braille_dots_1467 + 0x286a: 0x100286a, // XK_braille_dots_2467 + 0x286b: 0x100286b, // XK_braille_dots_12467 + 0x286c: 0x100286c, // XK_braille_dots_3467 + 0x286d: 0x100286d, // XK_braille_dots_13467 + 0x286e: 0x100286e, // XK_braille_dots_23467 + 0x286f: 0x100286f, // XK_braille_dots_123467 + 0x2870: 0x1002870, // XK_braille_dots_567 + 0x2871: 0x1002871, // XK_braille_dots_1567 + 0x2872: 0x1002872, // XK_braille_dots_2567 + 0x2873: 0x1002873, // XK_braille_dots_12567 + 0x2874: 0x1002874, // XK_braille_dots_3567 + 0x2875: 0x1002875, // XK_braille_dots_13567 + 0x2876: 0x1002876, // XK_braille_dots_23567 + 0x2877: 0x1002877, // XK_braille_dots_123567 + 0x2878: 0x1002878, // XK_braille_dots_4567 + 0x2879: 0x1002879, // XK_braille_dots_14567 + 0x287a: 0x100287a, // XK_braille_dots_24567 + 0x287b: 0x100287b, // XK_braille_dots_124567 + 0x287c: 0x100287c, // XK_braille_dots_34567 + 0x287d: 0x100287d, // XK_braille_dots_134567 + 0x287e: 0x100287e, // XK_braille_dots_234567 + 0x287f: 0x100287f, // XK_braille_dots_1234567 + 0x2880: 0x1002880, // XK_braille_dots_8 + 0x2881: 0x1002881, // XK_braille_dots_18 + 0x2882: 0x1002882, // XK_braille_dots_28 + 0x2883: 0x1002883, // XK_braille_dots_128 + 0x2884: 0x1002884, // XK_braille_dots_38 + 0x2885: 0x1002885, // XK_braille_dots_138 + 0x2886: 0x1002886, // XK_braille_dots_238 + 0x2887: 0x1002887, // XK_braille_dots_1238 + 0x2888: 0x1002888, // XK_braille_dots_48 + 0x2889: 0x1002889, // XK_braille_dots_148 + 0x288a: 0x100288a, // XK_braille_dots_248 + 0x288b: 0x100288b, // XK_braille_dots_1248 + 0x288c: 0x100288c, // XK_braille_dots_348 + 0x288d: 0x100288d, // XK_braille_dots_1348 + 0x288e: 0x100288e, // XK_braille_dots_2348 + 0x288f: 0x100288f, // XK_braille_dots_12348 + 0x2890: 0x1002890, // XK_braille_dots_58 + 0x2891: 0x1002891, // XK_braille_dots_158 + 0x2892: 0x1002892, // XK_braille_dots_258 + 0x2893: 0x1002893, // XK_braille_dots_1258 + 0x2894: 0x1002894, // XK_braille_dots_358 + 0x2895: 0x1002895, // XK_braille_dots_1358 + 0x2896: 0x1002896, // XK_braille_dots_2358 + 0x2897: 0x1002897, // XK_braille_dots_12358 + 0x2898: 0x1002898, // XK_braille_dots_458 + 0x2899: 0x1002899, // XK_braille_dots_1458 + 0x289a: 0x100289a, // XK_braille_dots_2458 + 0x289b: 0x100289b, // XK_braille_dots_12458 + 0x289c: 0x100289c, // XK_braille_dots_3458 + 0x289d: 0x100289d, // XK_braille_dots_13458 + 0x289e: 0x100289e, // XK_braille_dots_23458 + 0x289f: 0x100289f, // XK_braille_dots_123458 + 0x28a0: 0x10028a0, // XK_braille_dots_68 + 0x28a1: 0x10028a1, // XK_braille_dots_168 + 0x28a2: 0x10028a2, // XK_braille_dots_268 + 0x28a3: 0x10028a3, // XK_braille_dots_1268 + 0x28a4: 0x10028a4, // XK_braille_dots_368 + 0x28a5: 0x10028a5, // XK_braille_dots_1368 + 0x28a6: 0x10028a6, // XK_braille_dots_2368 + 0x28a7: 0x10028a7, // XK_braille_dots_12368 + 0x28a8: 0x10028a8, // XK_braille_dots_468 + 0x28a9: 0x10028a9, // XK_braille_dots_1468 + 0x28aa: 0x10028aa, // XK_braille_dots_2468 + 0x28ab: 0x10028ab, // XK_braille_dots_12468 + 0x28ac: 0x10028ac, // XK_braille_dots_3468 + 0x28ad: 0x10028ad, // XK_braille_dots_13468 + 0x28ae: 0x10028ae, // XK_braille_dots_23468 + 0x28af: 0x10028af, // XK_braille_dots_123468 + 0x28b0: 0x10028b0, // XK_braille_dots_568 + 0x28b1: 0x10028b1, // XK_braille_dots_1568 + 0x28b2: 0x10028b2, // XK_braille_dots_2568 + 0x28b3: 0x10028b3, // XK_braille_dots_12568 + 0x28b4: 0x10028b4, // XK_braille_dots_3568 + 0x28b5: 0x10028b5, // XK_braille_dots_13568 + 0x28b6: 0x10028b6, // XK_braille_dots_23568 + 0x28b7: 0x10028b7, // XK_braille_dots_123568 + 0x28b8: 0x10028b8, // XK_braille_dots_4568 + 0x28b9: 0x10028b9, // XK_braille_dots_14568 + 0x28ba: 0x10028ba, // XK_braille_dots_24568 + 0x28bb: 0x10028bb, // XK_braille_dots_124568 + 0x28bc: 0x10028bc, // XK_braille_dots_34568 + 0x28bd: 0x10028bd, // XK_braille_dots_134568 + 0x28be: 0x10028be, // XK_braille_dots_234568 + 0x28bf: 0x10028bf, // XK_braille_dots_1234568 + 0x28c0: 0x10028c0, // XK_braille_dots_78 + 0x28c1: 0x10028c1, // XK_braille_dots_178 + 0x28c2: 0x10028c2, // XK_braille_dots_278 + 0x28c3: 0x10028c3, // XK_braille_dots_1278 + 0x28c4: 0x10028c4, // XK_braille_dots_378 + 0x28c5: 0x10028c5, // XK_braille_dots_1378 + 0x28c6: 0x10028c6, // XK_braille_dots_2378 + 0x28c7: 0x10028c7, // XK_braille_dots_12378 + 0x28c8: 0x10028c8, // XK_braille_dots_478 + 0x28c9: 0x10028c9, // XK_braille_dots_1478 + 0x28ca: 0x10028ca, // XK_braille_dots_2478 + 0x28cb: 0x10028cb, // XK_braille_dots_12478 + 0x28cc: 0x10028cc, // XK_braille_dots_3478 + 0x28cd: 0x10028cd, // XK_braille_dots_13478 + 0x28ce: 0x10028ce, // XK_braille_dots_23478 + 0x28cf: 0x10028cf, // XK_braille_dots_123478 + 0x28d0: 0x10028d0, // XK_braille_dots_578 + 0x28d1: 0x10028d1, // XK_braille_dots_1578 + 0x28d2: 0x10028d2, // XK_braille_dots_2578 + 0x28d3: 0x10028d3, // XK_braille_dots_12578 + 0x28d4: 0x10028d4, // XK_braille_dots_3578 + 0x28d5: 0x10028d5, // XK_braille_dots_13578 + 0x28d6: 0x10028d6, // XK_braille_dots_23578 + 0x28d7: 0x10028d7, // XK_braille_dots_123578 + 0x28d8: 0x10028d8, // XK_braille_dots_4578 + 0x28d9: 0x10028d9, // XK_braille_dots_14578 + 0x28da: 0x10028da, // XK_braille_dots_24578 + 0x28db: 0x10028db, // XK_braille_dots_124578 + 0x28dc: 0x10028dc, // XK_braille_dots_34578 + 0x28dd: 0x10028dd, // XK_braille_dots_134578 + 0x28de: 0x10028de, // XK_braille_dots_234578 + 0x28df: 0x10028df, // XK_braille_dots_1234578 + 0x28e0: 0x10028e0, // XK_braille_dots_678 + 0x28e1: 0x10028e1, // XK_braille_dots_1678 + 0x28e2: 0x10028e2, // XK_braille_dots_2678 + 0x28e3: 0x10028e3, // XK_braille_dots_12678 + 0x28e4: 0x10028e4, // XK_braille_dots_3678 + 0x28e5: 0x10028e5, // XK_braille_dots_13678 + 0x28e6: 0x10028e6, // XK_braille_dots_23678 + 0x28e7: 0x10028e7, // XK_braille_dots_123678 + 0x28e8: 0x10028e8, // XK_braille_dots_4678 + 0x28e9: 0x10028e9, // XK_braille_dots_14678 + 0x28ea: 0x10028ea, // XK_braille_dots_24678 + 0x28eb: 0x10028eb, // XK_braille_dots_124678 + 0x28ec: 0x10028ec, // XK_braille_dots_34678 + 0x28ed: 0x10028ed, // XK_braille_dots_134678 + 0x28ee: 0x10028ee, // XK_braille_dots_234678 + 0x28ef: 0x10028ef, // XK_braille_dots_1234678 + 0x28f0: 0x10028f0, // XK_braille_dots_5678 + 0x28f1: 0x10028f1, // XK_braille_dots_15678 + 0x28f2: 0x10028f2, // XK_braille_dots_25678 + 0x28f3: 0x10028f3, // XK_braille_dots_125678 + 0x28f4: 0x10028f4, // XK_braille_dots_35678 + 0x28f5: 0x10028f5, // XK_braille_dots_135678 + 0x28f6: 0x10028f6, // XK_braille_dots_235678 + 0x28f7: 0x10028f7, // XK_braille_dots_1235678 + 0x28f8: 0x10028f8, // XK_braille_dots_45678 + 0x28f9: 0x10028f9, // XK_braille_dots_145678 + 0x28fa: 0x10028fa, // XK_braille_dots_245678 + 0x28fb: 0x10028fb, // XK_braille_dots_1245678 + 0x28fc: 0x10028fc, // XK_braille_dots_345678 + 0x28fd: 0x10028fd, // XK_braille_dots_1345678 + 0x28fe: 0x10028fe, // XK_braille_dots_2345678 + 0x28ff: 0x10028ff, // XK_braille_dots_12345678 + 0x3001: 0x04a4, // XK_kana_comma + 0x3002: 0x04a1, // XK_kana_fullstop + 0x300c: 0x04a2, // XK_kana_openingbracket + 0x300d: 0x04a3, // XK_kana_closingbracket + 0x309b: 0x04de, // XK_voicedsound + 0x309c: 0x04df, // XK_semivoicedsound + 0x30a1: 0x04a7, // XK_kana_a + 0x30a2: 0x04b1, // XK_kana_A + 0x30a3: 0x04a8, // XK_kana_i + 0x30a4: 0x04b2, // XK_kana_I + 0x30a5: 0x04a9, // XK_kana_u + 0x30a6: 0x04b3, // XK_kana_U + 0x30a7: 0x04aa, // XK_kana_e + 0x30a8: 0x04b4, // XK_kana_E + 0x30a9: 0x04ab, // XK_kana_o + 0x30aa: 0x04b5, // XK_kana_O + 0x30ab: 0x04b6, // XK_kana_KA + 0x30ad: 0x04b7, // XK_kana_KI + 0x30af: 0x04b8, // XK_kana_KU + 0x30b1: 0x04b9, // XK_kana_KE + 0x30b3: 0x04ba, // XK_kana_KO + 0x30b5: 0x04bb, // XK_kana_SA + 0x30b7: 0x04bc, // XK_kana_SHI + 0x30b9: 0x04bd, // XK_kana_SU + 0x30bb: 0x04be, // XK_kana_SE + 0x30bd: 0x04bf, // XK_kana_SO + 0x30bf: 0x04c0, // XK_kana_TA + 0x30c1: 0x04c1, // XK_kana_CHI + 0x30c3: 0x04af, // XK_kana_tsu + 0x30c4: 0x04c2, // XK_kana_TSU + 0x30c6: 0x04c3, // XK_kana_TE + 0x30c8: 0x04c4, // XK_kana_TO + 0x30ca: 0x04c5, // XK_kana_NA + 0x30cb: 0x04c6, // XK_kana_NI + 0x30cc: 0x04c7, // XK_kana_NU + 0x30cd: 0x04c8, // XK_kana_NE + 0x30ce: 0x04c9, // XK_kana_NO + 0x30cf: 0x04ca, // XK_kana_HA + 0x30d2: 0x04cb, // XK_kana_HI + 0x30d5: 0x04cc, // XK_kana_FU + 0x30d8: 0x04cd, // XK_kana_HE + 0x30db: 0x04ce, // XK_kana_HO + 0x30de: 0x04cf, // XK_kana_MA + 0x30df: 0x04d0, // XK_kana_MI + 0x30e0: 0x04d1, // XK_kana_MU + 0x30e1: 0x04d2, // XK_kana_ME + 0x30e2: 0x04d3, // XK_kana_MO + 0x30e3: 0x04ac, // XK_kana_ya + 0x30e4: 0x04d4, // XK_kana_YA + 0x30e5: 0x04ad, // XK_kana_yu + 0x30e6: 0x04d5, // XK_kana_YU + 0x30e7: 0x04ae, // XK_kana_yo + 0x30e8: 0x04d6, // XK_kana_YO + 0x30e9: 0x04d7, // XK_kana_RA + 0x30ea: 0x04d8, // XK_kana_RI + 0x30eb: 0x04d9, // XK_kana_RU + 0x30ec: 0x04da, // XK_kana_RE + 0x30ed: 0x04db, // XK_kana_RO + 0x30ef: 0x04dc, // XK_kana_WA + 0x30f2: 0x04a6, // XK_kana_WO + 0x30f3: 0x04dd, // XK_kana_N + 0x30fb: 0x04a5, // XK_kana_conjunctive + 0x30fc: 0x04b0, // XK_prolongedsound +}; export default { lookup : function(u) { diff --git a/utils/parse.js b/utils/genkeysymdef.js old mode 100644 new mode 100755 similarity index 62% rename from utils/parse.js rename to utils/genkeysymdef.js index 970a1de6..42dddb7e --- a/utils/parse.js +++ b/utils/genkeysymdef.js @@ -1,4 +1,11 @@ -// Utility to parse keysymdef.h to produce mappings from Unicode codepoints to keysyms +#!/usr/bin/env node +/* + * genkeysymdef: X11 keysymdef.h to JavaScript converter + * Copyright 2013 jalf + * Copyright 2017 Pierre Ossman for Cendio AB + * Licensed under MPL 2.0 (see LICENSE.txt) + */ + "use strict"; var fs = require('fs'); @@ -51,25 +58,42 @@ for (var i = 0; i < arr.length; ++i) { var unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder); if (unicodeRes) { var unicode = parseInt(unicodeRes[1], 16); + // The first entry is the preferred one if (!codepoints[unicode]){ - codepoints[unicode] = keysym; + codepoints[unicode] = { keysym: keysym, name: keyname }; } } - else { - console.log("no unicode codepoint found:", arr[i]); - } - } - else { - console.log("line is not a keysym:", arr[i]); } } -var out = "// This file describes mappings from Unicode codepoints to the keysym values\n" + -"// (and optionally, key names) expected by the RFB protocol\n" + -"// How this file was generated:\n" + -"// " + process.argv.join(" ") + "\n" + +var out = +"/*\n" + +" * Mapping from Unicode codepoints to X11/RFB keysyms\n" + +" *\n" + +" * This file was automatically generated from keysymdef.h\n" + +" * DO NOT EDIT!\n" + +" */\n" + "\n" + -"var codepoints = {codepoints};\n" + +"/* Functions at the bottom */\n" + +"\n" + +"var codepoints = {\n"; + +function toHex(num) { + var s = num.toString(16); + if (s.length < 4) { + s = ("0000" + s).slice(-4); + } + return "0x" + s; +}; + +for (var codepoint in codepoints) { + out += " " + toHex(parseInt(codepoint)) + ": " + + toHex(codepoints[codepoint].keysym) + + ", // XK_" + codepoints[codepoint].name + "\n"; +} + +out += +"};\n" + "\n" + "export default {\n" + " lookup : function(u) {\n" + @@ -79,7 +103,6 @@ var out = "// This file describes mappings from Unicode codepoints to the keysym " }\n" + " return keysym;\n" + " },\n" + -"};\n"; -out = out.replace('{codepoints}', JSON.stringify(codepoints)); +"};"; -fs.writeFileSync("keysymdef.js", out); +console.log(out); From 278a5e7fbd922e930c4c9df8f50b545ede17e39f Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 24 Jan 2017 12:49:29 +0100 Subject: [PATCH 04/20] Simplify keysymdef.js Some Unicode to Keysym mappings can be computed and can therefore be left out of the huge lookup table. --- core/input/keysymdef.js | 910 +--------------------------------------- tests/test.helper.js | 2 +- utils/genkeysymdef.js | 30 +- 3 files changed, 38 insertions(+), 904 deletions(-) diff --git a/core/input/keysymdef.js b/core/input/keysymdef.js index 8032ad64..95922b30 100644 --- a/core/input/keysymdef.js +++ b/core/input/keysymdef.js @@ -8,197 +8,6 @@ /* Functions at the bottom */ var codepoints = { - 0x0020: 0x0020, // XK_space - 0x0021: 0x0021, // XK_exclam - 0x0022: 0x0022, // XK_quotedbl - 0x0023: 0x0023, // XK_numbersign - 0x0024: 0x0024, // XK_dollar - 0x0025: 0x0025, // XK_percent - 0x0026: 0x0026, // XK_ampersand - 0x0027: 0x0027, // XK_apostrophe - 0x0028: 0x0028, // XK_parenleft - 0x0029: 0x0029, // XK_parenright - 0x002a: 0x002a, // XK_asterisk - 0x002b: 0x002b, // XK_plus - 0x002c: 0x002c, // XK_comma - 0x002d: 0x002d, // XK_minus - 0x002e: 0x002e, // XK_period - 0x002f: 0x002f, // XK_slash - 0x0030: 0x0030, // XK_0 - 0x0031: 0x0031, // XK_1 - 0x0032: 0x0032, // XK_2 - 0x0033: 0x0033, // XK_3 - 0x0034: 0x0034, // XK_4 - 0x0035: 0x0035, // XK_5 - 0x0036: 0x0036, // XK_6 - 0x0037: 0x0037, // XK_7 - 0x0038: 0x0038, // XK_8 - 0x0039: 0x0039, // XK_9 - 0x003a: 0x003a, // XK_colon - 0x003b: 0x003b, // XK_semicolon - 0x003c: 0x003c, // XK_less - 0x003d: 0x003d, // XK_equal - 0x003e: 0x003e, // XK_greater - 0x003f: 0x003f, // XK_question - 0x0040: 0x0040, // XK_at - 0x0041: 0x0041, // XK_A - 0x0042: 0x0042, // XK_B - 0x0043: 0x0043, // XK_C - 0x0044: 0x0044, // XK_D - 0x0045: 0x0045, // XK_E - 0x0046: 0x0046, // XK_F - 0x0047: 0x0047, // XK_G - 0x0048: 0x0048, // XK_H - 0x0049: 0x0049, // XK_I - 0x004a: 0x004a, // XK_J - 0x004b: 0x004b, // XK_K - 0x004c: 0x004c, // XK_L - 0x004d: 0x004d, // XK_M - 0x004e: 0x004e, // XK_N - 0x004f: 0x004f, // XK_O - 0x0050: 0x0050, // XK_P - 0x0051: 0x0051, // XK_Q - 0x0052: 0x0052, // XK_R - 0x0053: 0x0053, // XK_S - 0x0054: 0x0054, // XK_T - 0x0055: 0x0055, // XK_U - 0x0056: 0x0056, // XK_V - 0x0057: 0x0057, // XK_W - 0x0058: 0x0058, // XK_X - 0x0059: 0x0059, // XK_Y - 0x005a: 0x005a, // XK_Z - 0x005b: 0x005b, // XK_bracketleft - 0x005c: 0x005c, // XK_backslash - 0x005d: 0x005d, // XK_bracketright - 0x005e: 0x005e, // XK_asciicircum - 0x005f: 0x005f, // XK_underscore - 0x0060: 0x0060, // XK_grave - 0x0061: 0x0061, // XK_a - 0x0062: 0x0062, // XK_b - 0x0063: 0x0063, // XK_c - 0x0064: 0x0064, // XK_d - 0x0065: 0x0065, // XK_e - 0x0066: 0x0066, // XK_f - 0x0067: 0x0067, // XK_g - 0x0068: 0x0068, // XK_h - 0x0069: 0x0069, // XK_i - 0x006a: 0x006a, // XK_j - 0x006b: 0x006b, // XK_k - 0x006c: 0x006c, // XK_l - 0x006d: 0x006d, // XK_m - 0x006e: 0x006e, // XK_n - 0x006f: 0x006f, // XK_o - 0x0070: 0x0070, // XK_p - 0x0071: 0x0071, // XK_q - 0x0072: 0x0072, // XK_r - 0x0073: 0x0073, // XK_s - 0x0074: 0x0074, // XK_t - 0x0075: 0x0075, // XK_u - 0x0076: 0x0076, // XK_v - 0x0077: 0x0077, // XK_w - 0x0078: 0x0078, // XK_x - 0x0079: 0x0079, // XK_y - 0x007a: 0x007a, // XK_z - 0x007b: 0x007b, // XK_braceleft - 0x007c: 0x007c, // XK_bar - 0x007d: 0x007d, // XK_braceright - 0x007e: 0x007e, // XK_asciitilde - 0x00a0: 0x00a0, // XK_nobreakspace - 0x00a1: 0x00a1, // XK_exclamdown - 0x00a2: 0x00a2, // XK_cent - 0x00a3: 0x00a3, // XK_sterling - 0x00a4: 0x00a4, // XK_currency - 0x00a5: 0x00a5, // XK_yen - 0x00a6: 0x00a6, // XK_brokenbar - 0x00a7: 0x00a7, // XK_section - 0x00a8: 0x00a8, // XK_diaeresis - 0x00a9: 0x00a9, // XK_copyright - 0x00aa: 0x00aa, // XK_ordfeminine - 0x00ab: 0x00ab, // XK_guillemotleft - 0x00ac: 0x00ac, // XK_notsign - 0x00ad: 0x00ad, // XK_hyphen - 0x00ae: 0x00ae, // XK_registered - 0x00af: 0x00af, // XK_macron - 0x00b0: 0x00b0, // XK_degree - 0x00b1: 0x00b1, // XK_plusminus - 0x00b2: 0x00b2, // XK_twosuperior - 0x00b3: 0x00b3, // XK_threesuperior - 0x00b4: 0x00b4, // XK_acute - 0x00b5: 0x00b5, // XK_mu - 0x00b6: 0x00b6, // XK_paragraph - 0x00b7: 0x00b7, // XK_periodcentered - 0x00b8: 0x00b8, // XK_cedilla - 0x00b9: 0x00b9, // XK_onesuperior - 0x00ba: 0x00ba, // XK_masculine - 0x00bb: 0x00bb, // XK_guillemotright - 0x00bc: 0x00bc, // XK_onequarter - 0x00bd: 0x00bd, // XK_onehalf - 0x00be: 0x00be, // XK_threequarters - 0x00bf: 0x00bf, // XK_questiondown - 0x00c0: 0x00c0, // XK_Agrave - 0x00c1: 0x00c1, // XK_Aacute - 0x00c2: 0x00c2, // XK_Acircumflex - 0x00c3: 0x00c3, // XK_Atilde - 0x00c4: 0x00c4, // XK_Adiaeresis - 0x00c5: 0x00c5, // XK_Aring - 0x00c6: 0x00c6, // XK_AE - 0x00c7: 0x00c7, // XK_Ccedilla - 0x00c8: 0x00c8, // XK_Egrave - 0x00c9: 0x00c9, // XK_Eacute - 0x00ca: 0x00ca, // XK_Ecircumflex - 0x00cb: 0x00cb, // XK_Ediaeresis - 0x00cc: 0x00cc, // XK_Igrave - 0x00cd: 0x00cd, // XK_Iacute - 0x00ce: 0x00ce, // XK_Icircumflex - 0x00cf: 0x00cf, // XK_Idiaeresis - 0x00d0: 0x00d0, // XK_ETH - 0x00d1: 0x00d1, // XK_Ntilde - 0x00d2: 0x00d2, // XK_Ograve - 0x00d3: 0x00d3, // XK_Oacute - 0x00d4: 0x00d4, // XK_Ocircumflex - 0x00d5: 0x00d5, // XK_Otilde - 0x00d6: 0x00d6, // XK_Odiaeresis - 0x00d7: 0x00d7, // XK_multiply - 0x00d8: 0x00d8, // XK_Oslash - 0x00d9: 0x00d9, // XK_Ugrave - 0x00da: 0x00da, // XK_Uacute - 0x00db: 0x00db, // XK_Ucircumflex - 0x00dc: 0x00dc, // XK_Udiaeresis - 0x00dd: 0x00dd, // XK_Yacute - 0x00de: 0x00de, // XK_THORN - 0x00df: 0x00df, // XK_ssharp - 0x00e0: 0x00e0, // XK_agrave - 0x00e1: 0x00e1, // XK_aacute - 0x00e2: 0x00e2, // XK_acircumflex - 0x00e3: 0x00e3, // XK_atilde - 0x00e4: 0x00e4, // XK_adiaeresis - 0x00e5: 0x00e5, // XK_aring - 0x00e6: 0x00e6, // XK_ae - 0x00e7: 0x00e7, // XK_ccedilla - 0x00e8: 0x00e8, // XK_egrave - 0x00e9: 0x00e9, // XK_eacute - 0x00ea: 0x00ea, // XK_ecircumflex - 0x00eb: 0x00eb, // XK_ediaeresis - 0x00ec: 0x00ec, // XK_igrave - 0x00ed: 0x00ed, // XK_iacute - 0x00ee: 0x00ee, // XK_icircumflex - 0x00ef: 0x00ef, // XK_idiaeresis - 0x00f0: 0x00f0, // XK_eth - 0x00f1: 0x00f1, // XK_ntilde - 0x00f2: 0x00f2, // XK_ograve - 0x00f3: 0x00f3, // XK_oacute - 0x00f4: 0x00f4, // XK_ocircumflex - 0x00f5: 0x00f5, // XK_otilde - 0x00f6: 0x00f6, // XK_odiaeresis - 0x00f7: 0x00f7, // XK_division - 0x00f8: 0x00f8, // XK_oslash - 0x00f9: 0x00f9, // XK_ugrave - 0x00fa: 0x00fa, // XK_uacute - 0x00fb: 0x00fb, // XK_ucircumflex - 0x00fc: 0x00fc, // XK_udiaeresis - 0x00fd: 0x00fd, // XK_yacute - 0x00fe: 0x00fe, // XK_thorn - 0x00ff: 0x00ff, // XK_ydiaeresis 0x0100: 0x03c0, // XK_Amacron 0x0101: 0x03e0, // XK_amacron 0x0102: 0x01c3, // XK_Abreve @@ -241,8 +50,6 @@ var codepoints = { 0x0129: 0x03b5, // XK_itilde 0x012a: 0x03cf, // XK_Imacron 0x012b: 0x03ef, // XK_imacron - 0x012c: 0x100012c, // XK_Ibreve - 0x012d: 0x100012d, // XK_ibreve 0x012e: 0x03c7, // XK_Iogonek 0x012f: 0x03e7, // XK_iogonek 0x0130: 0x02a9, // XK_Iabovedot @@ -306,10 +113,6 @@ var codepoints = { 0x0171: 0x01fb, // XK_udoubleacute 0x0172: 0x03d9, // XK_Uogonek 0x0173: 0x03f9, // XK_uogonek - 0x0174: 0x1000174, // XK_Wcircumflex - 0x0175: 0x1000175, // XK_wcircumflex - 0x0176: 0x1000176, // XK_Ycircumflex - 0x0177: 0x1000177, // XK_ycircumflex 0x0178: 0x13be, // XK_Ydiaeresis 0x0179: 0x01ac, // XK_Zacute 0x017a: 0x01bc, // XK_zacute @@ -317,22 +120,8 @@ var codepoints = { 0x017c: 0x01bf, // XK_zabovedot 0x017d: 0x01ae, // XK_Zcaron 0x017e: 0x01be, // XK_zcaron - 0x018f: 0x100018f, // XK_SCHWA 0x0192: 0x08f6, // XK_function - 0x019f: 0x100019f, // XK_Obarred - 0x01a0: 0x10001a0, // XK_Ohorn - 0x01a1: 0x10001a1, // XK_ohorn - 0x01af: 0x10001af, // XK_Uhorn - 0x01b0: 0x10001b0, // XK_uhorn - 0x01b5: 0x10001b5, // XK_Zstroke - 0x01b6: 0x10001b6, // XK_zstroke - 0x01b7: 0x10001b7, // XK_EZH 0x01d2: 0x10001d1, // XK_Ocaron - 0x01e6: 0x10001e6, // XK_Gcaron - 0x01e7: 0x10001e7, // XK_gcaron - 0x0259: 0x1000259, // XK_schwa - 0x0275: 0x1000275, // XK_obarred - 0x0292: 0x1000292, // XK_ezh 0x02c7: 0x01b7, // XK_caron 0x02d8: 0x01a2, // XK_breve 0x02d9: 0x01ff, // XK_abovedot @@ -502,120 +291,6 @@ var codepoints = { 0x045f: 0x06af, // XK_Cyrillic_dzhe 0x0490: 0x06bd, // XK_Ukrainian_GHE_WITH_UPTURN 0x0491: 0x06ad, // XK_Ukrainian_ghe_with_upturn - 0x0492: 0x1000492, // XK_Cyrillic_GHE_bar - 0x0493: 0x1000493, // XK_Cyrillic_ghe_bar - 0x0496: 0x1000496, // XK_Cyrillic_ZHE_descender - 0x0497: 0x1000497, // XK_Cyrillic_zhe_descender - 0x049a: 0x100049a, // XK_Cyrillic_KA_descender - 0x049b: 0x100049b, // XK_Cyrillic_ka_descender - 0x049c: 0x100049c, // XK_Cyrillic_KA_vertstroke - 0x049d: 0x100049d, // XK_Cyrillic_ka_vertstroke - 0x04a2: 0x10004a2, // XK_Cyrillic_EN_descender - 0x04a3: 0x10004a3, // XK_Cyrillic_en_descender - 0x04ae: 0x10004ae, // XK_Cyrillic_U_straight - 0x04af: 0x10004af, // XK_Cyrillic_u_straight - 0x04b0: 0x10004b0, // XK_Cyrillic_U_straight_bar - 0x04b1: 0x10004b1, // XK_Cyrillic_u_straight_bar - 0x04b2: 0x10004b2, // XK_Cyrillic_HA_descender - 0x04b3: 0x10004b3, // XK_Cyrillic_ha_descender - 0x04b6: 0x10004b6, // XK_Cyrillic_CHE_descender - 0x04b7: 0x10004b7, // XK_Cyrillic_che_descender - 0x04b8: 0x10004b8, // XK_Cyrillic_CHE_vertstroke - 0x04b9: 0x10004b9, // XK_Cyrillic_che_vertstroke - 0x04ba: 0x10004ba, // XK_Cyrillic_SHHA - 0x04bb: 0x10004bb, // XK_Cyrillic_shha - 0x04d8: 0x10004d8, // XK_Cyrillic_SCHWA - 0x04d9: 0x10004d9, // XK_Cyrillic_schwa - 0x04e2: 0x10004e2, // XK_Cyrillic_I_macron - 0x04e3: 0x10004e3, // XK_Cyrillic_i_macron - 0x04e8: 0x10004e8, // XK_Cyrillic_O_bar - 0x04e9: 0x10004e9, // XK_Cyrillic_o_bar - 0x04ee: 0x10004ee, // XK_Cyrillic_U_macron - 0x04ef: 0x10004ef, // XK_Cyrillic_u_macron - 0x0531: 0x1000531, // XK_Armenian_AYB - 0x0532: 0x1000532, // XK_Armenian_BEN - 0x0533: 0x1000533, // XK_Armenian_GIM - 0x0534: 0x1000534, // XK_Armenian_DA - 0x0535: 0x1000535, // XK_Armenian_YECH - 0x0536: 0x1000536, // XK_Armenian_ZA - 0x0537: 0x1000537, // XK_Armenian_E - 0x0538: 0x1000538, // XK_Armenian_AT - 0x0539: 0x1000539, // XK_Armenian_TO - 0x053a: 0x100053a, // XK_Armenian_ZHE - 0x053b: 0x100053b, // XK_Armenian_INI - 0x053c: 0x100053c, // XK_Armenian_LYUN - 0x053d: 0x100053d, // XK_Armenian_KHE - 0x053e: 0x100053e, // XK_Armenian_TSA - 0x053f: 0x100053f, // XK_Armenian_KEN - 0x0540: 0x1000540, // XK_Armenian_HO - 0x0541: 0x1000541, // XK_Armenian_DZA - 0x0542: 0x1000542, // XK_Armenian_GHAT - 0x0543: 0x1000543, // XK_Armenian_TCHE - 0x0544: 0x1000544, // XK_Armenian_MEN - 0x0545: 0x1000545, // XK_Armenian_HI - 0x0546: 0x1000546, // XK_Armenian_NU - 0x0547: 0x1000547, // XK_Armenian_SHA - 0x0548: 0x1000548, // XK_Armenian_VO - 0x0549: 0x1000549, // XK_Armenian_CHA - 0x054a: 0x100054a, // XK_Armenian_PE - 0x054b: 0x100054b, // XK_Armenian_JE - 0x054c: 0x100054c, // XK_Armenian_RA - 0x054d: 0x100054d, // XK_Armenian_SE - 0x054e: 0x100054e, // XK_Armenian_VEV - 0x054f: 0x100054f, // XK_Armenian_TYUN - 0x0550: 0x1000550, // XK_Armenian_RE - 0x0551: 0x1000551, // XK_Armenian_TSO - 0x0552: 0x1000552, // XK_Armenian_VYUN - 0x0553: 0x1000553, // XK_Armenian_PYUR - 0x0554: 0x1000554, // XK_Armenian_KE - 0x0555: 0x1000555, // XK_Armenian_O - 0x0556: 0x1000556, // XK_Armenian_FE - 0x055a: 0x100055a, // XK_Armenian_apostrophe - 0x055b: 0x100055b, // XK_Armenian_accent - 0x055c: 0x100055c, // XK_Armenian_exclam - 0x055d: 0x100055d, // XK_Armenian_separation_mark - 0x055e: 0x100055e, // XK_Armenian_question - 0x0561: 0x1000561, // XK_Armenian_ayb - 0x0562: 0x1000562, // XK_Armenian_ben - 0x0563: 0x1000563, // XK_Armenian_gim - 0x0564: 0x1000564, // XK_Armenian_da - 0x0565: 0x1000565, // XK_Armenian_yech - 0x0566: 0x1000566, // XK_Armenian_za - 0x0567: 0x1000567, // XK_Armenian_e - 0x0568: 0x1000568, // XK_Armenian_at - 0x0569: 0x1000569, // XK_Armenian_to - 0x056a: 0x100056a, // XK_Armenian_zhe - 0x056b: 0x100056b, // XK_Armenian_ini - 0x056c: 0x100056c, // XK_Armenian_lyun - 0x056d: 0x100056d, // XK_Armenian_khe - 0x056e: 0x100056e, // XK_Armenian_tsa - 0x056f: 0x100056f, // XK_Armenian_ken - 0x0570: 0x1000570, // XK_Armenian_ho - 0x0571: 0x1000571, // XK_Armenian_dza - 0x0572: 0x1000572, // XK_Armenian_ghat - 0x0573: 0x1000573, // XK_Armenian_tche - 0x0574: 0x1000574, // XK_Armenian_men - 0x0575: 0x1000575, // XK_Armenian_hi - 0x0576: 0x1000576, // XK_Armenian_nu - 0x0577: 0x1000577, // XK_Armenian_sha - 0x0578: 0x1000578, // XK_Armenian_vo - 0x0579: 0x1000579, // XK_Armenian_cha - 0x057a: 0x100057a, // XK_Armenian_pe - 0x057b: 0x100057b, // XK_Armenian_je - 0x057c: 0x100057c, // XK_Armenian_ra - 0x057d: 0x100057d, // XK_Armenian_se - 0x057e: 0x100057e, // XK_Armenian_vev - 0x057f: 0x100057f, // XK_Armenian_tyun - 0x0580: 0x1000580, // XK_Armenian_re - 0x0581: 0x1000581, // XK_Armenian_tso - 0x0582: 0x1000582, // XK_Armenian_vyun - 0x0583: 0x1000583, // XK_Armenian_pyur - 0x0584: 0x1000584, // XK_Armenian_ke - 0x0585: 0x1000585, // XK_Armenian_o - 0x0586: 0x1000586, // XK_Armenian_fe - 0x0587: 0x1000587, // XK_Armenian_ligature_ew - 0x0589: 0x1000589, // XK_Armenian_full_stop - 0x058a: 0x100058a, // XK_Armenian_hyphen 0x05d0: 0x0ce0, // XK_hebrew_aleph 0x05d1: 0x0ce1, // XK_hebrew_bet 0x05d2: 0x0ce2, // XK_hebrew_gimel @@ -691,126 +366,6 @@ var codepoints = { 0x0650: 0x05f0, // XK_Arabic_kasra 0x0651: 0x05f1, // XK_Arabic_shadda 0x0652: 0x05f2, // XK_Arabic_sukun - 0x0653: 0x1000653, // XK_Arabic_madda_above - 0x0654: 0x1000654, // XK_Arabic_hamza_above - 0x0655: 0x1000655, // XK_Arabic_hamza_below - 0x0660: 0x1000660, // XK_Arabic_0 - 0x0661: 0x1000661, // XK_Arabic_1 - 0x0662: 0x1000662, // XK_Arabic_2 - 0x0663: 0x1000663, // XK_Arabic_3 - 0x0664: 0x1000664, // XK_Arabic_4 - 0x0665: 0x1000665, // XK_Arabic_5 - 0x0666: 0x1000666, // XK_Arabic_6 - 0x0667: 0x1000667, // XK_Arabic_7 - 0x0668: 0x1000668, // XK_Arabic_8 - 0x0669: 0x1000669, // XK_Arabic_9 - 0x066a: 0x100066a, // XK_Arabic_percent - 0x0670: 0x1000670, // XK_Arabic_superscript_alef - 0x0679: 0x1000679, // XK_Arabic_tteh - 0x067e: 0x100067e, // XK_Arabic_peh - 0x0686: 0x1000686, // XK_Arabic_tcheh - 0x0688: 0x1000688, // XK_Arabic_ddal - 0x0691: 0x1000691, // XK_Arabic_rreh - 0x0698: 0x1000698, // XK_Arabic_jeh - 0x06a4: 0x10006a4, // XK_Arabic_veh - 0x06a9: 0x10006a9, // XK_Arabic_keheh - 0x06af: 0x10006af, // XK_Arabic_gaf - 0x06ba: 0x10006ba, // XK_Arabic_noon_ghunna - 0x06be: 0x10006be, // XK_Arabic_heh_doachashmee - 0x06c1: 0x10006c1, // XK_Arabic_heh_goal - 0x06cc: 0x10006cc, // XK_Farsi_yeh - 0x06d2: 0x10006d2, // XK_Arabic_yeh_baree - 0x06d4: 0x10006d4, // XK_Arabic_fullstop - 0x06f0: 0x10006f0, // XK_Farsi_0 - 0x06f1: 0x10006f1, // XK_Farsi_1 - 0x06f2: 0x10006f2, // XK_Farsi_2 - 0x06f3: 0x10006f3, // XK_Farsi_3 - 0x06f4: 0x10006f4, // XK_Farsi_4 - 0x06f5: 0x10006f5, // XK_Farsi_5 - 0x06f6: 0x10006f6, // XK_Farsi_6 - 0x06f7: 0x10006f7, // XK_Farsi_7 - 0x06f8: 0x10006f8, // XK_Farsi_8 - 0x06f9: 0x10006f9, // XK_Farsi_9 - 0x0d82: 0x1000d82, // XK_Sinh_ng - 0x0d83: 0x1000d83, // XK_Sinh_h2 - 0x0d85: 0x1000d85, // XK_Sinh_a - 0x0d86: 0x1000d86, // XK_Sinh_aa - 0x0d87: 0x1000d87, // XK_Sinh_ae - 0x0d88: 0x1000d88, // XK_Sinh_aee - 0x0d89: 0x1000d89, // XK_Sinh_i - 0x0d8a: 0x1000d8a, // XK_Sinh_ii - 0x0d8b: 0x1000d8b, // XK_Sinh_u - 0x0d8c: 0x1000d8c, // XK_Sinh_uu - 0x0d8d: 0x1000d8d, // XK_Sinh_ri - 0x0d8e: 0x1000d8e, // XK_Sinh_rii - 0x0d8f: 0x1000d8f, // XK_Sinh_lu - 0x0d90: 0x1000d90, // XK_Sinh_luu - 0x0d91: 0x1000d91, // XK_Sinh_e - 0x0d92: 0x1000d92, // XK_Sinh_ee - 0x0d93: 0x1000d93, // XK_Sinh_ai - 0x0d94: 0x1000d94, // XK_Sinh_o - 0x0d95: 0x1000d95, // XK_Sinh_oo - 0x0d96: 0x1000d96, // XK_Sinh_au - 0x0d9a: 0x1000d9a, // XK_Sinh_ka - 0x0d9b: 0x1000d9b, // XK_Sinh_kha - 0x0d9c: 0x1000d9c, // XK_Sinh_ga - 0x0d9d: 0x1000d9d, // XK_Sinh_gha - 0x0d9e: 0x1000d9e, // XK_Sinh_ng2 - 0x0d9f: 0x1000d9f, // XK_Sinh_nga - 0x0da0: 0x1000da0, // XK_Sinh_ca - 0x0da1: 0x1000da1, // XK_Sinh_cha - 0x0da2: 0x1000da2, // XK_Sinh_ja - 0x0da3: 0x1000da3, // XK_Sinh_jha - 0x0da4: 0x1000da4, // XK_Sinh_nya - 0x0da5: 0x1000da5, // XK_Sinh_jnya - 0x0da6: 0x1000da6, // XK_Sinh_nja - 0x0da7: 0x1000da7, // XK_Sinh_tta - 0x0da8: 0x1000da8, // XK_Sinh_ttha - 0x0da9: 0x1000da9, // XK_Sinh_dda - 0x0daa: 0x1000daa, // XK_Sinh_ddha - 0x0dab: 0x1000dab, // XK_Sinh_nna - 0x0dac: 0x1000dac, // XK_Sinh_ndda - 0x0dad: 0x1000dad, // XK_Sinh_tha - 0x0dae: 0x1000dae, // XK_Sinh_thha - 0x0daf: 0x1000daf, // XK_Sinh_dha - 0x0db0: 0x1000db0, // XK_Sinh_dhha - 0x0db1: 0x1000db1, // XK_Sinh_na - 0x0db3: 0x1000db3, // XK_Sinh_ndha - 0x0db4: 0x1000db4, // XK_Sinh_pa - 0x0db5: 0x1000db5, // XK_Sinh_pha - 0x0db6: 0x1000db6, // XK_Sinh_ba - 0x0db7: 0x1000db7, // XK_Sinh_bha - 0x0db8: 0x1000db8, // XK_Sinh_ma - 0x0db9: 0x1000db9, // XK_Sinh_mba - 0x0dba: 0x1000dba, // XK_Sinh_ya - 0x0dbb: 0x1000dbb, // XK_Sinh_ra - 0x0dbd: 0x1000dbd, // XK_Sinh_la - 0x0dc0: 0x1000dc0, // XK_Sinh_va - 0x0dc1: 0x1000dc1, // XK_Sinh_sha - 0x0dc2: 0x1000dc2, // XK_Sinh_ssha - 0x0dc3: 0x1000dc3, // XK_Sinh_sa - 0x0dc4: 0x1000dc4, // XK_Sinh_ha - 0x0dc5: 0x1000dc5, // XK_Sinh_lla - 0x0dc6: 0x1000dc6, // XK_Sinh_fa - 0x0dca: 0x1000dca, // XK_Sinh_al - 0x0dcf: 0x1000dcf, // XK_Sinh_aa2 - 0x0dd0: 0x1000dd0, // XK_Sinh_ae2 - 0x0dd1: 0x1000dd1, // XK_Sinh_aee2 - 0x0dd2: 0x1000dd2, // XK_Sinh_i2 - 0x0dd3: 0x1000dd3, // XK_Sinh_ii2 - 0x0dd4: 0x1000dd4, // XK_Sinh_u2 - 0x0dd6: 0x1000dd6, // XK_Sinh_uu2 - 0x0dd8: 0x1000dd8, // XK_Sinh_ru2 - 0x0dd9: 0x1000dd9, // XK_Sinh_e2 - 0x0dda: 0x1000dda, // XK_Sinh_ee2 - 0x0ddb: 0x1000ddb, // XK_Sinh_ai2 - 0x0ddc: 0x1000ddc, // XK_Sinh_o2 - 0x0ddd: 0x1000ddd, // XK_Sinh_oo2 - 0x0dde: 0x1000dde, // XK_Sinh_au2 - 0x0ddf: 0x1000ddf, // XK_Sinh_lu2 - 0x0df2: 0x1000df2, // XK_Sinh_ruu2 - 0x0df3: 0x1000df3, // XK_Sinh_luu2 - 0x0df4: 0x1000df4, // XK_Sinh_kunddaliya 0x0e01: 0x0da1, // XK_Thai_kokai 0x0e02: 0x0da2, // XK_Thai_khokhai 0x0e03: 0x0da3, // XK_Thai_khokhuat @@ -894,159 +449,6 @@ var codepoints = { 0x0e57: 0x0df7, // XK_Thai_lekchet 0x0e58: 0x0df8, // XK_Thai_lekpaet 0x0e59: 0x0df9, // XK_Thai_lekkao - 0x10d0: 0x10010d0, // XK_Georgian_an - 0x10d1: 0x10010d1, // XK_Georgian_ban - 0x10d2: 0x10010d2, // XK_Georgian_gan - 0x10d3: 0x10010d3, // XK_Georgian_don - 0x10d4: 0x10010d4, // XK_Georgian_en - 0x10d5: 0x10010d5, // XK_Georgian_vin - 0x10d6: 0x10010d6, // XK_Georgian_zen - 0x10d7: 0x10010d7, // XK_Georgian_tan - 0x10d8: 0x10010d8, // XK_Georgian_in - 0x10d9: 0x10010d9, // XK_Georgian_kan - 0x10da: 0x10010da, // XK_Georgian_las - 0x10db: 0x10010db, // XK_Georgian_man - 0x10dc: 0x10010dc, // XK_Georgian_nar - 0x10dd: 0x10010dd, // XK_Georgian_on - 0x10de: 0x10010de, // XK_Georgian_par - 0x10df: 0x10010df, // XK_Georgian_zhar - 0x10e0: 0x10010e0, // XK_Georgian_rae - 0x10e1: 0x10010e1, // XK_Georgian_san - 0x10e2: 0x10010e2, // XK_Georgian_tar - 0x10e3: 0x10010e3, // XK_Georgian_un - 0x10e4: 0x10010e4, // XK_Georgian_phar - 0x10e5: 0x10010e5, // XK_Georgian_khar - 0x10e6: 0x10010e6, // XK_Georgian_ghan - 0x10e7: 0x10010e7, // XK_Georgian_qar - 0x10e8: 0x10010e8, // XK_Georgian_shin - 0x10e9: 0x10010e9, // XK_Georgian_chin - 0x10ea: 0x10010ea, // XK_Georgian_can - 0x10eb: 0x10010eb, // XK_Georgian_jil - 0x10ec: 0x10010ec, // XK_Georgian_cil - 0x10ed: 0x10010ed, // XK_Georgian_char - 0x10ee: 0x10010ee, // XK_Georgian_xan - 0x10ef: 0x10010ef, // XK_Georgian_jhan - 0x10f0: 0x10010f0, // XK_Georgian_hae - 0x10f1: 0x10010f1, // XK_Georgian_he - 0x10f2: 0x10010f2, // XK_Georgian_hie - 0x10f3: 0x10010f3, // XK_Georgian_we - 0x10f4: 0x10010f4, // XK_Georgian_har - 0x10f5: 0x10010f5, // XK_Georgian_hoe - 0x10f6: 0x10010f6, // XK_Georgian_fi - 0x1e02: 0x1001e02, // XK_Babovedot - 0x1e03: 0x1001e03, // XK_babovedot - 0x1e0a: 0x1001e0a, // XK_Dabovedot - 0x1e0b: 0x1001e0b, // XK_dabovedot - 0x1e1e: 0x1001e1e, // XK_Fabovedot - 0x1e1f: 0x1001e1f, // XK_fabovedot - 0x1e36: 0x1001e36, // XK_Lbelowdot - 0x1e37: 0x1001e37, // XK_lbelowdot - 0x1e40: 0x1001e40, // XK_Mabovedot - 0x1e41: 0x1001e41, // XK_mabovedot - 0x1e56: 0x1001e56, // XK_Pabovedot - 0x1e57: 0x1001e57, // XK_pabovedot - 0x1e60: 0x1001e60, // XK_Sabovedot - 0x1e61: 0x1001e61, // XK_sabovedot - 0x1e6a: 0x1001e6a, // XK_Tabovedot - 0x1e6b: 0x1001e6b, // XK_tabovedot - 0x1e80: 0x1001e80, // XK_Wgrave - 0x1e81: 0x1001e81, // XK_wgrave - 0x1e82: 0x1001e82, // XK_Wacute - 0x1e83: 0x1001e83, // XK_wacute - 0x1e84: 0x1001e84, // XK_Wdiaeresis - 0x1e85: 0x1001e85, // XK_wdiaeresis - 0x1e8a: 0x1001e8a, // XK_Xabovedot - 0x1e8b: 0x1001e8b, // XK_xabovedot - 0x1ea0: 0x1001ea0, // XK_Abelowdot - 0x1ea1: 0x1001ea1, // XK_abelowdot - 0x1ea2: 0x1001ea2, // XK_Ahook - 0x1ea3: 0x1001ea3, // XK_ahook - 0x1ea4: 0x1001ea4, // XK_Acircumflexacute - 0x1ea5: 0x1001ea5, // XK_acircumflexacute - 0x1ea6: 0x1001ea6, // XK_Acircumflexgrave - 0x1ea7: 0x1001ea7, // XK_acircumflexgrave - 0x1ea8: 0x1001ea8, // XK_Acircumflexhook - 0x1ea9: 0x1001ea9, // XK_acircumflexhook - 0x1eaa: 0x1001eaa, // XK_Acircumflextilde - 0x1eab: 0x1001eab, // XK_acircumflextilde - 0x1eac: 0x1001eac, // XK_Acircumflexbelowdot - 0x1ead: 0x1001ead, // XK_acircumflexbelowdot - 0x1eae: 0x1001eae, // XK_Abreveacute - 0x1eaf: 0x1001eaf, // XK_abreveacute - 0x1eb0: 0x1001eb0, // XK_Abrevegrave - 0x1eb1: 0x1001eb1, // XK_abrevegrave - 0x1eb2: 0x1001eb2, // XK_Abrevehook - 0x1eb3: 0x1001eb3, // XK_abrevehook - 0x1eb4: 0x1001eb4, // XK_Abrevetilde - 0x1eb5: 0x1001eb5, // XK_abrevetilde - 0x1eb6: 0x1001eb6, // XK_Abrevebelowdot - 0x1eb7: 0x1001eb7, // XK_abrevebelowdot - 0x1eb8: 0x1001eb8, // XK_Ebelowdot - 0x1eb9: 0x1001eb9, // XK_ebelowdot - 0x1eba: 0x1001eba, // XK_Ehook - 0x1ebb: 0x1001ebb, // XK_ehook - 0x1ebc: 0x1001ebc, // XK_Etilde - 0x1ebd: 0x1001ebd, // XK_etilde - 0x1ebe: 0x1001ebe, // XK_Ecircumflexacute - 0x1ebf: 0x1001ebf, // XK_ecircumflexacute - 0x1ec0: 0x1001ec0, // XK_Ecircumflexgrave - 0x1ec1: 0x1001ec1, // XK_ecircumflexgrave - 0x1ec2: 0x1001ec2, // XK_Ecircumflexhook - 0x1ec3: 0x1001ec3, // XK_ecircumflexhook - 0x1ec4: 0x1001ec4, // XK_Ecircumflextilde - 0x1ec5: 0x1001ec5, // XK_ecircumflextilde - 0x1ec6: 0x1001ec6, // XK_Ecircumflexbelowdot - 0x1ec7: 0x1001ec7, // XK_ecircumflexbelowdot - 0x1ec8: 0x1001ec8, // XK_Ihook - 0x1ec9: 0x1001ec9, // XK_ihook - 0x1eca: 0x1001eca, // XK_Ibelowdot - 0x1ecb: 0x1001ecb, // XK_ibelowdot - 0x1ecc: 0x1001ecc, // XK_Obelowdot - 0x1ecd: 0x1001ecd, // XK_obelowdot - 0x1ece: 0x1001ece, // XK_Ohook - 0x1ecf: 0x1001ecf, // XK_ohook - 0x1ed0: 0x1001ed0, // XK_Ocircumflexacute - 0x1ed1: 0x1001ed1, // XK_ocircumflexacute - 0x1ed2: 0x1001ed2, // XK_Ocircumflexgrave - 0x1ed3: 0x1001ed3, // XK_ocircumflexgrave - 0x1ed4: 0x1001ed4, // XK_Ocircumflexhook - 0x1ed5: 0x1001ed5, // XK_ocircumflexhook - 0x1ed6: 0x1001ed6, // XK_Ocircumflextilde - 0x1ed7: 0x1001ed7, // XK_ocircumflextilde - 0x1ed8: 0x1001ed8, // XK_Ocircumflexbelowdot - 0x1ed9: 0x1001ed9, // XK_ocircumflexbelowdot - 0x1eda: 0x1001eda, // XK_Ohornacute - 0x1edb: 0x1001edb, // XK_ohornacute - 0x1edc: 0x1001edc, // XK_Ohorngrave - 0x1edd: 0x1001edd, // XK_ohorngrave - 0x1ede: 0x1001ede, // XK_Ohornhook - 0x1edf: 0x1001edf, // XK_ohornhook - 0x1ee0: 0x1001ee0, // XK_Ohorntilde - 0x1ee1: 0x1001ee1, // XK_ohorntilde - 0x1ee2: 0x1001ee2, // XK_Ohornbelowdot - 0x1ee3: 0x1001ee3, // XK_ohornbelowdot - 0x1ee4: 0x1001ee4, // XK_Ubelowdot - 0x1ee5: 0x1001ee5, // XK_ubelowdot - 0x1ee6: 0x1001ee6, // XK_Uhook - 0x1ee7: 0x1001ee7, // XK_uhook - 0x1ee8: 0x1001ee8, // XK_Uhornacute - 0x1ee9: 0x1001ee9, // XK_uhornacute - 0x1eea: 0x1001eea, // XK_Uhorngrave - 0x1eeb: 0x1001eeb, // XK_uhorngrave - 0x1eec: 0x1001eec, // XK_Uhornhook - 0x1eed: 0x1001eed, // XK_uhornhook - 0x1eee: 0x1001eee, // XK_Uhorntilde - 0x1eef: 0x1001eef, // XK_uhorntilde - 0x1ef0: 0x1001ef0, // XK_Uhornbelowdot - 0x1ef1: 0x1001ef1, // XK_uhornbelowdot - 0x1ef2: 0x1001ef2, // XK_Ygrave - 0x1ef3: 0x1001ef3, // XK_ygrave - 0x1ef4: 0x1001ef4, // XK_Ybelowdot - 0x1ef5: 0x1001ef5, // XK_ybelowdot - 0x1ef6: 0x1001ef6, // XK_Yhook - 0x1ef7: 0x1001ef7, // XK_yhook - 0x1ef8: 0x1001ef8, // XK_Ytilde - 0x1ef9: 0x1001ef9, // XK_ytilde 0x2002: 0x0aa2, // XK_enspace 0x2003: 0x0aa1, // XK_emspace 0x2004: 0x0aa3, // XK_em3space @@ -1076,35 +478,7 @@ var codepoints = { 0x2033: 0x0ad7, // XK_seconds 0x2038: 0x0afc, // XK_caret 0x203e: 0x047e, // XK_overline - 0x2070: 0x1002070, // XK_zerosuperior - 0x2074: 0x1002074, // XK_foursuperior - 0x2075: 0x1002075, // XK_fivesuperior - 0x2076: 0x1002076, // XK_sixsuperior - 0x2077: 0x1002077, // XK_sevensuperior - 0x2078: 0x1002078, // XK_eightsuperior - 0x2079: 0x1002079, // XK_ninesuperior - 0x2080: 0x1002080, // XK_zerosubscript - 0x2081: 0x1002081, // XK_onesubscript - 0x2082: 0x1002082, // XK_twosubscript - 0x2083: 0x1002083, // XK_threesubscript - 0x2084: 0x1002084, // XK_foursubscript - 0x2085: 0x1002085, // XK_fivesubscript - 0x2086: 0x1002086, // XK_sixsubscript - 0x2087: 0x1002087, // XK_sevensubscript - 0x2088: 0x1002088, // XK_eightsubscript - 0x2089: 0x1002089, // XK_ninesubscript - 0x20a0: 0x10020a0, // XK_EcuSign - 0x20a1: 0x10020a1, // XK_ColonSign - 0x20a2: 0x10020a2, // XK_CruzeiroSign - 0x20a3: 0x10020a3, // XK_FFrancSign - 0x20a4: 0x10020a4, // XK_LiraSign - 0x20a5: 0x10020a5, // XK_MillSign - 0x20a6: 0x10020a6, // XK_NairaSign - 0x20a7: 0x10020a7, // XK_PesetaSign - 0x20a8: 0x10020a8, // XK_RupeeSign 0x20a9: 0x0eff, // XK_Korean_Won - 0x20aa: 0x10020aa, // XK_NewSheqelSign - 0x20ab: 0x10020ab, // XK_DongSign 0x20ac: 0x20ac, // XK_EuroSign 0x2105: 0x0ab8, // XK_careof 0x2116: 0x06b0, // XK_numerosign @@ -1130,15 +504,9 @@ var codepoints = { 0x21d2: 0x08ce, // XK_implies 0x21d4: 0x08cd, // XK_ifonlyif 0x2202: 0x08ef, // XK_partialderivative - 0x2205: 0x1002205, // XK_emptyset 0x2207: 0x08c5, // XK_nabla - 0x2208: 0x1002208, // XK_elementof - 0x2209: 0x1002209, // XK_notelementof - 0x220b: 0x100220b, // XK_containsas 0x2218: 0x0bca, // XK_jot 0x221a: 0x08d6, // XK_radical - 0x221b: 0x100221b, // XK_cuberoot - 0x221c: 0x100221c, // XK_fourthroot 0x221d: 0x08c1, // XK_variation 0x221e: 0x08c2, // XK_infinity 0x2227: 0x08de, // XK_logicaland @@ -1146,18 +514,12 @@ var codepoints = { 0x2229: 0x08dc, // XK_intersection 0x222a: 0x08dd, // XK_union 0x222b: 0x08bf, // XK_integral - 0x222c: 0x100222c, // XK_dintegral - 0x222d: 0x100222d, // XK_tintegral 0x2234: 0x08c0, // XK_therefore - 0x2235: 0x1002235, // XK_because 0x223c: 0x08c8, // XK_approximate 0x2243: 0x08c9, // XK_similarequal 0x2245: 0x1002248, // XK_approxeq - 0x2247: 0x1002247, // XK_notapproxeq 0x2260: 0x08bd, // XK_notequal 0x2261: 0x08cf, // XK_identical - 0x2262: 0x1002262, // XK_notidentical - 0x2263: 0x1002263, // XK_stricteq 0x2264: 0x08bc, // XK_lessthanequal 0x2265: 0x08be, // XK_greaterthanequal 0x2282: 0x08da, // XK_includedin @@ -1242,262 +604,6 @@ var codepoints = { 0x2720: 0x0af0, // XK_maltesecross 0x27e8: 0x0abc, // XK_leftanglebracket 0x27e9: 0x0abe, // XK_rightanglebracket - 0x2800: 0x1002800, // XK_braille_blank - 0x2801: 0x1002801, // XK_braille_dots_1 - 0x2802: 0x1002802, // XK_braille_dots_2 - 0x2803: 0x1002803, // XK_braille_dots_12 - 0x2804: 0x1002804, // XK_braille_dots_3 - 0x2805: 0x1002805, // XK_braille_dots_13 - 0x2806: 0x1002806, // XK_braille_dots_23 - 0x2807: 0x1002807, // XK_braille_dots_123 - 0x2808: 0x1002808, // XK_braille_dots_4 - 0x2809: 0x1002809, // XK_braille_dots_14 - 0x280a: 0x100280a, // XK_braille_dots_24 - 0x280b: 0x100280b, // XK_braille_dots_124 - 0x280c: 0x100280c, // XK_braille_dots_34 - 0x280d: 0x100280d, // XK_braille_dots_134 - 0x280e: 0x100280e, // XK_braille_dots_234 - 0x280f: 0x100280f, // XK_braille_dots_1234 - 0x2810: 0x1002810, // XK_braille_dots_5 - 0x2811: 0x1002811, // XK_braille_dots_15 - 0x2812: 0x1002812, // XK_braille_dots_25 - 0x2813: 0x1002813, // XK_braille_dots_125 - 0x2814: 0x1002814, // XK_braille_dots_35 - 0x2815: 0x1002815, // XK_braille_dots_135 - 0x2816: 0x1002816, // XK_braille_dots_235 - 0x2817: 0x1002817, // XK_braille_dots_1235 - 0x2818: 0x1002818, // XK_braille_dots_45 - 0x2819: 0x1002819, // XK_braille_dots_145 - 0x281a: 0x100281a, // XK_braille_dots_245 - 0x281b: 0x100281b, // XK_braille_dots_1245 - 0x281c: 0x100281c, // XK_braille_dots_345 - 0x281d: 0x100281d, // XK_braille_dots_1345 - 0x281e: 0x100281e, // XK_braille_dots_2345 - 0x281f: 0x100281f, // XK_braille_dots_12345 - 0x2820: 0x1002820, // XK_braille_dots_6 - 0x2821: 0x1002821, // XK_braille_dots_16 - 0x2822: 0x1002822, // XK_braille_dots_26 - 0x2823: 0x1002823, // XK_braille_dots_126 - 0x2824: 0x1002824, // XK_braille_dots_36 - 0x2825: 0x1002825, // XK_braille_dots_136 - 0x2826: 0x1002826, // XK_braille_dots_236 - 0x2827: 0x1002827, // XK_braille_dots_1236 - 0x2828: 0x1002828, // XK_braille_dots_46 - 0x2829: 0x1002829, // XK_braille_dots_146 - 0x282a: 0x100282a, // XK_braille_dots_246 - 0x282b: 0x100282b, // XK_braille_dots_1246 - 0x282c: 0x100282c, // XK_braille_dots_346 - 0x282d: 0x100282d, // XK_braille_dots_1346 - 0x282e: 0x100282e, // XK_braille_dots_2346 - 0x282f: 0x100282f, // XK_braille_dots_12346 - 0x2830: 0x1002830, // XK_braille_dots_56 - 0x2831: 0x1002831, // XK_braille_dots_156 - 0x2832: 0x1002832, // XK_braille_dots_256 - 0x2833: 0x1002833, // XK_braille_dots_1256 - 0x2834: 0x1002834, // XK_braille_dots_356 - 0x2835: 0x1002835, // XK_braille_dots_1356 - 0x2836: 0x1002836, // XK_braille_dots_2356 - 0x2837: 0x1002837, // XK_braille_dots_12356 - 0x2838: 0x1002838, // XK_braille_dots_456 - 0x2839: 0x1002839, // XK_braille_dots_1456 - 0x283a: 0x100283a, // XK_braille_dots_2456 - 0x283b: 0x100283b, // XK_braille_dots_12456 - 0x283c: 0x100283c, // XK_braille_dots_3456 - 0x283d: 0x100283d, // XK_braille_dots_13456 - 0x283e: 0x100283e, // XK_braille_dots_23456 - 0x283f: 0x100283f, // XK_braille_dots_123456 - 0x2840: 0x1002840, // XK_braille_dots_7 - 0x2841: 0x1002841, // XK_braille_dots_17 - 0x2842: 0x1002842, // XK_braille_dots_27 - 0x2843: 0x1002843, // XK_braille_dots_127 - 0x2844: 0x1002844, // XK_braille_dots_37 - 0x2845: 0x1002845, // XK_braille_dots_137 - 0x2846: 0x1002846, // XK_braille_dots_237 - 0x2847: 0x1002847, // XK_braille_dots_1237 - 0x2848: 0x1002848, // XK_braille_dots_47 - 0x2849: 0x1002849, // XK_braille_dots_147 - 0x284a: 0x100284a, // XK_braille_dots_247 - 0x284b: 0x100284b, // XK_braille_dots_1247 - 0x284c: 0x100284c, // XK_braille_dots_347 - 0x284d: 0x100284d, // XK_braille_dots_1347 - 0x284e: 0x100284e, // XK_braille_dots_2347 - 0x284f: 0x100284f, // XK_braille_dots_12347 - 0x2850: 0x1002850, // XK_braille_dots_57 - 0x2851: 0x1002851, // XK_braille_dots_157 - 0x2852: 0x1002852, // XK_braille_dots_257 - 0x2853: 0x1002853, // XK_braille_dots_1257 - 0x2854: 0x1002854, // XK_braille_dots_357 - 0x2855: 0x1002855, // XK_braille_dots_1357 - 0x2856: 0x1002856, // XK_braille_dots_2357 - 0x2857: 0x1002857, // XK_braille_dots_12357 - 0x2858: 0x1002858, // XK_braille_dots_457 - 0x2859: 0x1002859, // XK_braille_dots_1457 - 0x285a: 0x100285a, // XK_braille_dots_2457 - 0x285b: 0x100285b, // XK_braille_dots_12457 - 0x285c: 0x100285c, // XK_braille_dots_3457 - 0x285d: 0x100285d, // XK_braille_dots_13457 - 0x285e: 0x100285e, // XK_braille_dots_23457 - 0x285f: 0x100285f, // XK_braille_dots_123457 - 0x2860: 0x1002860, // XK_braille_dots_67 - 0x2861: 0x1002861, // XK_braille_dots_167 - 0x2862: 0x1002862, // XK_braille_dots_267 - 0x2863: 0x1002863, // XK_braille_dots_1267 - 0x2864: 0x1002864, // XK_braille_dots_367 - 0x2865: 0x1002865, // XK_braille_dots_1367 - 0x2866: 0x1002866, // XK_braille_dots_2367 - 0x2867: 0x1002867, // XK_braille_dots_12367 - 0x2868: 0x1002868, // XK_braille_dots_467 - 0x2869: 0x1002869, // XK_braille_dots_1467 - 0x286a: 0x100286a, // XK_braille_dots_2467 - 0x286b: 0x100286b, // XK_braille_dots_12467 - 0x286c: 0x100286c, // XK_braille_dots_3467 - 0x286d: 0x100286d, // XK_braille_dots_13467 - 0x286e: 0x100286e, // XK_braille_dots_23467 - 0x286f: 0x100286f, // XK_braille_dots_123467 - 0x2870: 0x1002870, // XK_braille_dots_567 - 0x2871: 0x1002871, // XK_braille_dots_1567 - 0x2872: 0x1002872, // XK_braille_dots_2567 - 0x2873: 0x1002873, // XK_braille_dots_12567 - 0x2874: 0x1002874, // XK_braille_dots_3567 - 0x2875: 0x1002875, // XK_braille_dots_13567 - 0x2876: 0x1002876, // XK_braille_dots_23567 - 0x2877: 0x1002877, // XK_braille_dots_123567 - 0x2878: 0x1002878, // XK_braille_dots_4567 - 0x2879: 0x1002879, // XK_braille_dots_14567 - 0x287a: 0x100287a, // XK_braille_dots_24567 - 0x287b: 0x100287b, // XK_braille_dots_124567 - 0x287c: 0x100287c, // XK_braille_dots_34567 - 0x287d: 0x100287d, // XK_braille_dots_134567 - 0x287e: 0x100287e, // XK_braille_dots_234567 - 0x287f: 0x100287f, // XK_braille_dots_1234567 - 0x2880: 0x1002880, // XK_braille_dots_8 - 0x2881: 0x1002881, // XK_braille_dots_18 - 0x2882: 0x1002882, // XK_braille_dots_28 - 0x2883: 0x1002883, // XK_braille_dots_128 - 0x2884: 0x1002884, // XK_braille_dots_38 - 0x2885: 0x1002885, // XK_braille_dots_138 - 0x2886: 0x1002886, // XK_braille_dots_238 - 0x2887: 0x1002887, // XK_braille_dots_1238 - 0x2888: 0x1002888, // XK_braille_dots_48 - 0x2889: 0x1002889, // XK_braille_dots_148 - 0x288a: 0x100288a, // XK_braille_dots_248 - 0x288b: 0x100288b, // XK_braille_dots_1248 - 0x288c: 0x100288c, // XK_braille_dots_348 - 0x288d: 0x100288d, // XK_braille_dots_1348 - 0x288e: 0x100288e, // XK_braille_dots_2348 - 0x288f: 0x100288f, // XK_braille_dots_12348 - 0x2890: 0x1002890, // XK_braille_dots_58 - 0x2891: 0x1002891, // XK_braille_dots_158 - 0x2892: 0x1002892, // XK_braille_dots_258 - 0x2893: 0x1002893, // XK_braille_dots_1258 - 0x2894: 0x1002894, // XK_braille_dots_358 - 0x2895: 0x1002895, // XK_braille_dots_1358 - 0x2896: 0x1002896, // XK_braille_dots_2358 - 0x2897: 0x1002897, // XK_braille_dots_12358 - 0x2898: 0x1002898, // XK_braille_dots_458 - 0x2899: 0x1002899, // XK_braille_dots_1458 - 0x289a: 0x100289a, // XK_braille_dots_2458 - 0x289b: 0x100289b, // XK_braille_dots_12458 - 0x289c: 0x100289c, // XK_braille_dots_3458 - 0x289d: 0x100289d, // XK_braille_dots_13458 - 0x289e: 0x100289e, // XK_braille_dots_23458 - 0x289f: 0x100289f, // XK_braille_dots_123458 - 0x28a0: 0x10028a0, // XK_braille_dots_68 - 0x28a1: 0x10028a1, // XK_braille_dots_168 - 0x28a2: 0x10028a2, // XK_braille_dots_268 - 0x28a3: 0x10028a3, // XK_braille_dots_1268 - 0x28a4: 0x10028a4, // XK_braille_dots_368 - 0x28a5: 0x10028a5, // XK_braille_dots_1368 - 0x28a6: 0x10028a6, // XK_braille_dots_2368 - 0x28a7: 0x10028a7, // XK_braille_dots_12368 - 0x28a8: 0x10028a8, // XK_braille_dots_468 - 0x28a9: 0x10028a9, // XK_braille_dots_1468 - 0x28aa: 0x10028aa, // XK_braille_dots_2468 - 0x28ab: 0x10028ab, // XK_braille_dots_12468 - 0x28ac: 0x10028ac, // XK_braille_dots_3468 - 0x28ad: 0x10028ad, // XK_braille_dots_13468 - 0x28ae: 0x10028ae, // XK_braille_dots_23468 - 0x28af: 0x10028af, // XK_braille_dots_123468 - 0x28b0: 0x10028b0, // XK_braille_dots_568 - 0x28b1: 0x10028b1, // XK_braille_dots_1568 - 0x28b2: 0x10028b2, // XK_braille_dots_2568 - 0x28b3: 0x10028b3, // XK_braille_dots_12568 - 0x28b4: 0x10028b4, // XK_braille_dots_3568 - 0x28b5: 0x10028b5, // XK_braille_dots_13568 - 0x28b6: 0x10028b6, // XK_braille_dots_23568 - 0x28b7: 0x10028b7, // XK_braille_dots_123568 - 0x28b8: 0x10028b8, // XK_braille_dots_4568 - 0x28b9: 0x10028b9, // XK_braille_dots_14568 - 0x28ba: 0x10028ba, // XK_braille_dots_24568 - 0x28bb: 0x10028bb, // XK_braille_dots_124568 - 0x28bc: 0x10028bc, // XK_braille_dots_34568 - 0x28bd: 0x10028bd, // XK_braille_dots_134568 - 0x28be: 0x10028be, // XK_braille_dots_234568 - 0x28bf: 0x10028bf, // XK_braille_dots_1234568 - 0x28c0: 0x10028c0, // XK_braille_dots_78 - 0x28c1: 0x10028c1, // XK_braille_dots_178 - 0x28c2: 0x10028c2, // XK_braille_dots_278 - 0x28c3: 0x10028c3, // XK_braille_dots_1278 - 0x28c4: 0x10028c4, // XK_braille_dots_378 - 0x28c5: 0x10028c5, // XK_braille_dots_1378 - 0x28c6: 0x10028c6, // XK_braille_dots_2378 - 0x28c7: 0x10028c7, // XK_braille_dots_12378 - 0x28c8: 0x10028c8, // XK_braille_dots_478 - 0x28c9: 0x10028c9, // XK_braille_dots_1478 - 0x28ca: 0x10028ca, // XK_braille_dots_2478 - 0x28cb: 0x10028cb, // XK_braille_dots_12478 - 0x28cc: 0x10028cc, // XK_braille_dots_3478 - 0x28cd: 0x10028cd, // XK_braille_dots_13478 - 0x28ce: 0x10028ce, // XK_braille_dots_23478 - 0x28cf: 0x10028cf, // XK_braille_dots_123478 - 0x28d0: 0x10028d0, // XK_braille_dots_578 - 0x28d1: 0x10028d1, // XK_braille_dots_1578 - 0x28d2: 0x10028d2, // XK_braille_dots_2578 - 0x28d3: 0x10028d3, // XK_braille_dots_12578 - 0x28d4: 0x10028d4, // XK_braille_dots_3578 - 0x28d5: 0x10028d5, // XK_braille_dots_13578 - 0x28d6: 0x10028d6, // XK_braille_dots_23578 - 0x28d7: 0x10028d7, // XK_braille_dots_123578 - 0x28d8: 0x10028d8, // XK_braille_dots_4578 - 0x28d9: 0x10028d9, // XK_braille_dots_14578 - 0x28da: 0x10028da, // XK_braille_dots_24578 - 0x28db: 0x10028db, // XK_braille_dots_124578 - 0x28dc: 0x10028dc, // XK_braille_dots_34578 - 0x28dd: 0x10028dd, // XK_braille_dots_134578 - 0x28de: 0x10028de, // XK_braille_dots_234578 - 0x28df: 0x10028df, // XK_braille_dots_1234578 - 0x28e0: 0x10028e0, // XK_braille_dots_678 - 0x28e1: 0x10028e1, // XK_braille_dots_1678 - 0x28e2: 0x10028e2, // XK_braille_dots_2678 - 0x28e3: 0x10028e3, // XK_braille_dots_12678 - 0x28e4: 0x10028e4, // XK_braille_dots_3678 - 0x28e5: 0x10028e5, // XK_braille_dots_13678 - 0x28e6: 0x10028e6, // XK_braille_dots_23678 - 0x28e7: 0x10028e7, // XK_braille_dots_123678 - 0x28e8: 0x10028e8, // XK_braille_dots_4678 - 0x28e9: 0x10028e9, // XK_braille_dots_14678 - 0x28ea: 0x10028ea, // XK_braille_dots_24678 - 0x28eb: 0x10028eb, // XK_braille_dots_124678 - 0x28ec: 0x10028ec, // XK_braille_dots_34678 - 0x28ed: 0x10028ed, // XK_braille_dots_134678 - 0x28ee: 0x10028ee, // XK_braille_dots_234678 - 0x28ef: 0x10028ef, // XK_braille_dots_1234678 - 0x28f0: 0x10028f0, // XK_braille_dots_5678 - 0x28f1: 0x10028f1, // XK_braille_dots_15678 - 0x28f2: 0x10028f2, // XK_braille_dots_25678 - 0x28f3: 0x10028f3, // XK_braille_dots_125678 - 0x28f4: 0x10028f4, // XK_braille_dots_35678 - 0x28f5: 0x10028f5, // XK_braille_dots_135678 - 0x28f6: 0x10028f6, // XK_braille_dots_235678 - 0x28f7: 0x10028f7, // XK_braille_dots_1235678 - 0x28f8: 0x10028f8, // XK_braille_dots_45678 - 0x28f9: 0x10028f9, // XK_braille_dots_145678 - 0x28fa: 0x10028fa, // XK_braille_dots_245678 - 0x28fb: 0x10028fb, // XK_braille_dots_1245678 - 0x28fc: 0x10028fc, // XK_braille_dots_345678 - 0x28fd: 0x10028fd, // XK_braille_dots_1345678 - 0x28fe: 0x10028fe, // XK_braille_dots_2345678 - 0x28ff: 0x10028ff, // XK_braille_dots_12345678 0x3001: 0x04a4, // XK_kana_comma 0x3002: 0x04a1, // XK_kana_fullstop 0x300c: 0x04a2, // XK_kana_openingbracket @@ -1565,10 +671,18 @@ var codepoints = { export default { lookup : function(u) { - var keysym = codepoints[u]; - if (keysym === undefined) { - keysym = 0x01000000 | u; + // Latin-1 is one-to-one mapping + if ((u >= 0x20) && (u <= 0xff)) { + return u; } - return keysym; + + // Lookup table (fairly random) + var keysym = codepoints[u]; + if (keysym !== undefined) { + return keysym; + } + + // General mapping as final fallback + return 0x01000000 | u; }, }; diff --git a/tests/test.helper.js b/tests/test.helper.js index c3c34626..0c1ba0dc 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -37,7 +37,7 @@ describe('Helpers', function() { expect(keysyms.lookup('Š'.charCodeAt())).to.be.equal(0x01a9); }); it('should map characters which aren\'t in Latin1 *or* Windows-1252 to keysyms', function() { - expect(keysyms.lookup('ŵ'.charCodeAt())).to.be.equal(0x1000175); + expect(keysyms.lookup('ũ'.charCodeAt())).to.be.equal(0x03fd); }); it('should map unknown codepoints to the Unicode range', function() { expect(keysyms.lookup('\n'.charCodeAt())).to.be.equal(0x100000a); diff --git a/utils/genkeysymdef.js b/utils/genkeysymdef.js index 42dddb7e..8486da39 100755 --- a/utils/genkeysymdef.js +++ b/utils/genkeysymdef.js @@ -87,7 +87,19 @@ function toHex(num) { }; for (var codepoint in codepoints) { - out += " " + toHex(parseInt(codepoint)) + ": " + + codepoint = parseInt(codepoint); + + // Latin-1? + if ((codepoint >= 0x20) && (codepoint <= 0xff)) { + continue; + } + + // Handled by the general Unicode mapping? + if ((codepoint | 0x01000000) === codepoints[codepoint].keysym) { + continue; + } + + out += " " + toHex(codepoint) + ": " + toHex(codepoints[codepoint].keysym) + ", // XK_" + codepoints[codepoint].name + "\n"; } @@ -97,11 +109,19 @@ out += "\n" + "export default {\n" + " lookup : function(u) {\n" + -" var keysym = codepoints[u];\n" + -" if (keysym === undefined) {\n" + -" keysym = 0x01000000 | u;\n" + +" // Latin-1 is one-to-one mapping\n" + +" if ((u >= 0x20) && (u <= 0xff)) {\n" + +" return u;\n" + " }\n" + -" return keysym;\n" + +"\n" + +" // Lookup table (fairly random)\n" + +" var keysym = codepoints[u];\n" + +" if (keysym !== undefined) {\n" + +" return keysym;\n" + +" }\n" + +"\n" + +" // General mapping as final fallback\n" + +" return 0x01000000 | u;\n" + " },\n" + "};"; From 0a865e15ff9f43c1d697ab1f996cb5e145d0ea16 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 24 Jan 2017 12:55:54 +0100 Subject: [PATCH 05/20] Remove character substitution We can handle any Unicode codepoint now, so stop replacing symbols. --- core/input/util.js | 18 +----------------- tests/test.helper.js | 15 --------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/core/input/util.js b/core/input/util.js index 39ac905e..36bcd720 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -1,22 +1,6 @@ import KeyTable from "./keysym.js"; import keysyms from "./keysymdef.js"; -export function substituteCodepoint(cp) { - // Any Unicode code points which do not have corresponding keysym entries - // can be swapped out for another code point by adding them to this table - var substitutions = { - // {S,s} with comma below -> {S,s} with cedilla - 0x218 : 0x15e, - 0x219 : 0x15f, - // {T,t} with comma below -> {T,t} with cedilla - 0x21a : 0x162, - 0x21b : 0x163 - }; - - var sub = substitutions[cp]; - return sub ? sub : cp; -} - function isMac() { return navigator && !!(/mac/i).exec(navigator.platform); } @@ -176,7 +160,7 @@ export function getKeysym(evt){ codepoint = evt.keyCode; } if (codepoint) { - return keysyms.lookup(substituteCodepoint(codepoint)); + return keysyms.lookup(codepoint); } // we could check evt.key here. // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list, diff --git a/tests/test.helper.js b/tests/test.helper.js index 0c1ba0dc..e161d34d 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -49,18 +49,6 @@ describe('Helpers', function() { }); }); - describe('substituteCodepoint', function() { - it('should replace characters which don\'t have a keysym', function() { - expect(KeyboardUtil.substituteCodepoint('Ș'.charCodeAt())).to.equal('Ş'.charCodeAt()); - expect(KeyboardUtil.substituteCodepoint('ș'.charCodeAt())).to.equal('ş'.charCodeAt()); - expect(KeyboardUtil.substituteCodepoint('Ț'.charCodeAt())).to.equal('Ţ'.charCodeAt()); - expect(KeyboardUtil.substituteCodepoint('ț'.charCodeAt())).to.equal('ţ'.charCodeAt()); - }); - it('should pass other characters through unchanged', function() { - expect(KeyboardUtil.substituteCodepoint('T'.charCodeAt())).to.equal('T'.charCodeAt()); - }); - }); - describe('nonCharacterKey', function() { it('should recognize the right keys', function() { expect(KeyboardUtil.nonCharacterKey({keyCode: 0xd}), 'enter').to.be.defined; @@ -96,9 +84,6 @@ describe('Helpers', function() { expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: false})).to.be.equal(0x63); expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: true})).to.be.equal(0x43); }); - it('should substitute where applicable', function() { - expect(KeyboardUtil.getKeysym({char : 'Ș'})).to.be.equal(0x1aa); - }); }); describe('Modifier Sync', function() { // return a list of fake events necessary to fix modifier state From a5c8a755e8b024c3584121132d68936a7d74f2c4 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 24 Jan 2017 13:36:31 +0100 Subject: [PATCH 06/20] Hide internal keyboard functions These may change and are not part of a stable API. This also reindents the object functions to make sure they can access private functions. --- core/input/util.js | 4 +-- tests/test.helper.js | 59 +++++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/core/input/util.js b/core/input/util.js index 36bcd720..9124c196 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -177,7 +177,7 @@ export function getKeysym(evt){ // Given a keycode, try to predict which keysym it might be. // If the keycode is unknown, null is returned. -export function keysymFromKeyCode(keycode, shiftPressed) { +function keysymFromKeyCode(keycode, shiftPressed) { if (typeof(keycode) !== 'number') { return null; } @@ -212,7 +212,7 @@ export function keysymFromKeyCode(keycode, shiftPressed) { // if the key is a known non-character key (any key which doesn't generate character data) // return its keysym value. Otherwise return null -export function nonCharacterKey(evt) { +function nonCharacterKey(evt) { // evt.key not implemented yet if (!evt.keyCode) { return null; } var keycode = evt.keyCode; diff --git a/tests/test.helper.js b/tests/test.helper.js index e161d34d..a10b8b2f 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -6,22 +6,6 @@ import * as KeyboardUtil from "../core/input/util.js"; describe('Helpers', function() { "use strict"; - describe('keysymFromKeyCode', function() { - it('should map known keycodes to keysyms', function() { - expect(KeyboardUtil.keysymFromKeyCode(0x41, false), 'a').to.be.equal(0x61); - expect(KeyboardUtil.keysymFromKeyCode(0x41, true), 'A').to.be.equal(0x41); - expect(KeyboardUtil.keysymFromKeyCode(0xd, false), 'enter').to.be.equal(0xFF0D); - expect(KeyboardUtil.keysymFromKeyCode(0x11, false), 'ctrl').to.be.equal(0xFFE3); - expect(KeyboardUtil.keysymFromKeyCode(0x12, false), 'alt').to.be.equal(0xFFE9); - expect(KeyboardUtil.keysymFromKeyCode(0xe1, false), 'altgr').to.be.equal(0xFE03); - expect(KeyboardUtil.keysymFromKeyCode(0x1b, false), 'esc').to.be.equal(0xFF1B); - expect(KeyboardUtil.keysymFromKeyCode(0x26, false), 'up').to.be.equal(0xFF52); - }); - it('should return null for unknown keycodes', function() { - expect(KeyboardUtil.keysymFromKeyCode(0xc0, false), 'DK æ').to.be.null; - expect(KeyboardUtil.keysymFromKeyCode(0xde, false), 'DK ø').to.be.null; - }); - }); describe('keysyms.lookup', function() { it('should map ASCII characters to keysyms', function() { @@ -49,24 +33,6 @@ describe('Helpers', function() { }); }); - describe('nonCharacterKey', function() { - it('should recognize the right keys', function() { - expect(KeyboardUtil.nonCharacterKey({keyCode: 0xd}), 'enter').to.be.defined; - expect(KeyboardUtil.nonCharacterKey({keyCode: 0x08}), 'backspace').to.be.defined; - expect(KeyboardUtil.nonCharacterKey({keyCode: 0x09}), 'tab').to.be.defined; - expect(KeyboardUtil.nonCharacterKey({keyCode: 0x10}), 'shift').to.be.defined; - expect(KeyboardUtil.nonCharacterKey({keyCode: 0x11}), 'ctrl').to.be.defined; - expect(KeyboardUtil.nonCharacterKey({keyCode: 0x12}), 'alt').to.be.defined; - expect(KeyboardUtil.nonCharacterKey({keyCode: 0xe0}), 'meta').to.be.defined; - }); - it('should not recognize character keys', function() { - expect(KeyboardUtil.nonCharacterKey({keyCode: 'A'}), 'A').to.be.null; - expect(KeyboardUtil.nonCharacterKey({keyCode: '1'}), '1').to.be.null; - expect(KeyboardUtil.nonCharacterKey({keyCode: '.'}), '.').to.be.null; - expect(KeyboardUtil.nonCharacterKey({keyCode: ' '}), 'space').to.be.null; - }); - }); - describe('getKeysym', function() { it('should prefer char', function() { expect(KeyboardUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61); @@ -80,10 +46,35 @@ describe('Helpers', function() { expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: false})).to.be.equal(0x62); expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: true})).to.be.equal(0x42); }); + it('should return null for unknown keycodes', function() { + expect(KeyboardUtil.getKeysym({keyCode: 0xc0, which: 0xc1, shiftKey:false})).to.be.null; + expect(KeyboardUtil.getKeysym({keyCode: 0xde, which: 0xdf, shiftKey:false})).to.be.null; + }); it('should use which if no keyCode', function() { expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: false})).to.be.equal(0x63); expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: true})).to.be.equal(0x43); }); + + describe('Non-character keys', function() { + it('should recognize the right keys', function() { + expect(KeyboardUtil.getKeysym({keyCode: 0x0d})).to.be.equal(0xFF0D); + expect(KeyboardUtil.getKeysym({keyCode: 0x08})).to.be.equal(0xFF08); + expect(KeyboardUtil.getKeysym({keyCode: 0x09})).to.be.equal(0xFF09); + expect(KeyboardUtil.getKeysym({keyCode: 0x10})).to.be.equal(0xFFE1); + expect(KeyboardUtil.getKeysym({keyCode: 0x11})).to.be.equal(0xFFE3); + expect(KeyboardUtil.getKeysym({keyCode: 0x12})).to.be.equal(0xFFE9); + expect(KeyboardUtil.getKeysym({keyCode: 0xe0})).to.be.equal(0xFFE7); + expect(KeyboardUtil.getKeysym({keyCode: 0xe1})).to.be.equal(0xFE03); + expect(KeyboardUtil.getKeysym({keyCode: 0x1b})).to.be.equal(0xFF1B); + expect(KeyboardUtil.getKeysym({keyCode: 0x26})).to.be.equal(0xFF52); + }); + it('should not recognize character keys', function() { + expect(KeyboardUtil.getKeysym({keyCode: 'A'})).to.be.null; + expect(KeyboardUtil.getKeysym({keyCode: '1'})).to.be.null; + expect(KeyboardUtil.getKeysym({keyCode: '.'})).to.be.null; + expect(KeyboardUtil.getKeysym({keyCode: ' '})).to.be.null; + }); + }); }); describe('Modifier Sync', function() { // return a list of fake events necessary to fix modifier state From 80cb8ffddd49d828fe3b9c94b556ad154381b225 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 24 Jan 2017 15:16:10 +0100 Subject: [PATCH 07/20] Use standard DOM identifiers for physical keys --- core/input/util.js | 101 +++++++++++---- core/input/vkeys.js | 116 +++++++++++++++++ core/input/xtscancodes.js | 2 - tests/input.html | 1 + tests/test.helper.js | 65 ++++++++++ tests/test.keyboard.js | 256 +++++++++++++++++++------------------- tests/vnc_perf.html | 3 +- 7 files changed, 389 insertions(+), 155 deletions(-) create mode 100644 core/input/vkeys.js diff --git a/core/input/util.js b/core/input/util.js index 9124c196..508193ee 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -1,5 +1,6 @@ import KeyTable from "./keysym.js"; import keysyms from "./keysymdef.js"; +import vkeys from "./vkeys.js"; function isMac() { return navigator && !!(/mac/i).exec(navigator.platform); @@ -131,18 +132,64 @@ export function ModifierSync(charModifier) { }; } -// Get a key ID from a keyboard event -// May be a string or an integer depending on the available properties -export function getKey(evt){ - if ('keyCode' in evt && 'key' in evt) { - return evt.key + ':' + evt.keyCode; +// Get 'KeyboardEvent.code', handling legacy browsers +export function getKeycode(evt){ + // Are we getting proper key identifiers? + // (unfortunately Firefox and Chrome are crappy here and gives + // us an empty string on some platforms, rather than leaving it + // undefined) + if (evt.code) { + // Mozilla isn't fully in sync with the spec yet + switch (evt.code) { + case 'OSLeft': return 'MetaLeft'; + case 'OSRight': return 'MetaRight'; + } + + return evt.code; } - else if ('keyCode' in evt) { - return evt.keyCode; - } - else { - return evt.key; + + // The de-facto standard is to use Windows Virtual-Key codes + // in the 'keyCode' field for non-printable characters. However + // Webkit sets it to the same as charCode in 'keypress' events. + if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) { + var code = vkeys[evt.keyCode]; + + // macOS has messed up this code for some reason + if (isMac() && (code === 'ContextMenu')) { + code = 'MetaRight'; + } + + // The keyCode doesn't distinguish between left and right + // for the standard modifiers + if (evt.location === 2) { + switch (code) { + case 'ShiftLeft': return 'ShiftRight'; + case 'ControlLeft': return 'ControlRight'; + case 'AltLeft': return 'AltRight'; + } + } + + // Nor a bunch of the numpad keys + if (evt.location === 3) { + switch (code) { + case 'Delete': return 'NumpadDecimal'; + case 'Insert': return 'Numpad0'; + case 'End': return 'Numpad1'; + case 'ArrowDown': return 'Numpad2'; + case 'PageDown': return 'Numpad3'; + case 'ArrowLeft': return 'Numpad4'; + case 'ArrowRight': return 'Numpad6'; + case 'Home': return 'Numpad7'; + case 'ArrowUp': return 'Numpad8'; + case 'PageUp': return 'Numpad9'; + case 'Enter': return 'NumpadEnter'; + } + } + + return code; } + + return 'Unidentified'; } // Get the most reliable keysym value we can get from a key event @@ -290,7 +337,7 @@ export function QEMUKeyEventDecoder (modifierState, next) { function process(evt, type) { var result = {type: type}; - result.code = evt.code; + result.code = getKeycode(evt); result.keysym = 0; if (isNumPadMultiKey(evt)) { @@ -298,7 +345,7 @@ export function QEMUKeyEventDecoder (modifierState, next) { } var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier(); - var isShift = evt.keyCode === 0x10 || evt.key === 'Shift'; + var isShift = result.code === 'ShiftLeft' || result.code === 'ShiftRight'; var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt)); @@ -387,7 +434,7 @@ export function TrackQEMUKeyState (next) { // Takes a DOM keyboard event and: // - determines which keysym it represents -// - determines a keyId identifying the key that was pressed (corresponding to the key/keyCode properties on the DOM event) +// - determines a code identifying the key that was pressed (corresponding to the code/keyCode properties on the DOM event) // - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down // - marks each event with an 'escape' property if a modifier was down which should be "escaped" // - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown @@ -401,10 +448,16 @@ export function KeyEventDecoder (modifierState, next) { } function process(evt, type) { var result = {type: type}; - var keyId = getKey(evt); - if (keyId) { - result.keyId = keyId; + var code = getKeycode(evt); + if (code === 'Unidentified') { + // Unstable, but we don't have anything else to go on + // (don't use it for 'keypress' events thought since + // WebKit sets it to the same as charCode) + if (evt.keyCode && (evt.type !== 'keypress')) { + code = 'Platform' + evt.keyCode; + } } + result.code = code; var keysym = getKeysym(evt); @@ -416,7 +469,7 @@ export function KeyEventDecoder (modifierState, next) { result.keysym = keysym; } - var isShift = evt.keyCode === 0x10 || evt.key === 'Shift'; + var isShift = code === 'ShiftLeft' || code === 'ShiftRight'; // Should we prevent the browser from handling the event? // Doing so on a keydown (in most browsers) prevents keypress from being generated @@ -546,8 +599,8 @@ export function TrackKeyState (next) { switch (evt.type) { case 'keydown': // insert a new entry if last seen key was different. - if (!last || !evt.keyId || last.keyId !== evt.keyId) { - last = {keyId: evt.keyId, keysyms: {}}; + if (!last || evt.code === 'Unidentified' || last.code !== evt.code) { + last = {code: evt.code, keysyms: {}}; state.push(last); } if (evt.keysym) { @@ -560,7 +613,7 @@ export function TrackKeyState (next) { break; case 'keypress': if (!last) { - last = {keyId: evt.keyId, keysyms: {}}; + last = {code: evt.code, keysyms: {}}; state.push(last); } if (!evt.keysym) { @@ -582,7 +635,7 @@ export function TrackKeyState (next) { var idx = null; // do we have a matching key tracked as being down? for (var i = 0; i !== state.length; ++i) { - if (state[i].keyId === evt.keyId) { + if (state[i].code === evt.code) { idx = i; break; } @@ -609,7 +662,7 @@ export function TrackKeyState (next) { for (var i = 0; i < state.length; ++i) { for (var key in state[i].keysyms) { var keysym = state[i].keysyms[key]; - next({keyId: 0, keysym: keysym, type: 'keyup'}); + next({code: 'Unidentified', keysym: keysym, type: 'keyup'}); } } /* jshint shadow: false */ @@ -629,14 +682,14 @@ export function EscapeModifiers (next) { } // undo modifiers for (var i = 0; i < evt.escape.length; ++i) { - next({type: 'keyup', keyId: 0, keysym: evt.escape[i]}); + next({type: 'keyup', code: 'Unidentified', keysym: evt.escape[i]}); } // send the character event next(evt); // redo modifiers /* jshint shadow: true */ for (var i = 0; i < evt.escape.length; ++i) { - next({type: 'keydown', keyId: 0, keysym: evt.escape[i]}); + next({type: 'keydown', code: 'Unidentified', keysym: evt.escape[i]}); } /* jshint shadow: false */ }; diff --git a/core/input/vkeys.js b/core/input/vkeys.js new file mode 100644 index 00000000..dc784ffd --- /dev/null +++ b/core/input/vkeys.js @@ -0,0 +1,116 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2017 Pierre Ossman for Cendio AB + * Licensed under MPL 2.0 or any later version (see LICENSE.txt) + */ + +/* + * Mapping between Microsoft® Windows® Virtual-Key codes and + * HTML key codes. + */ + +export default { + 0x08: 'Backspace', + 0x09: 'Tab', + 0x0a: 'NumpadClear', + 0x0d: 'Enter', + 0x10: 'ShiftLeft', + 0x11: 'ControlLeft', + 0x12: 'AltLeft', + 0x13: 'Pause', + 0x14: 'CapsLock', + 0x15: 'Lang1', + 0x19: 'Lang2', + 0x1b: 'Escape', + 0x1c: 'Convert', + 0x1d: 'NonConvert', + 0x20: 'Space', + 0x21: 'PageUp', + 0x22: 'PageDown', + 0x23: 'End', + 0x24: 'Home', + 0x25: 'ArrowLeft', + 0x26: 'ArrowUp', + 0x27: 'ArrowRight', + 0x28: 'ArrowDown', + 0x29: 'Select', + 0x2c: 'PrintScreen', + 0x2d: 'Insert', + 0x2e: 'Delete', + 0x2f: 'Help', + 0x30: 'Digit0', + 0x31: 'Digit1', + 0x32: 'Digit2', + 0x33: 'Digit3', + 0x34: 'Digit4', + 0x35: 'Digit5', + 0x36: 'Digit6', + 0x37: 'Digit7', + 0x38: 'Digit8', + 0x39: 'Digit9', + 0x5b: 'MetaLeft', + 0x5c: 'MetaRight', + 0x5d: 'ContextMenu', + 0x5f: 'Sleep', + 0x60: 'Numpad0', + 0x61: 'Numpad1', + 0x62: 'Numpad2', + 0x63: 'Numpad3', + 0x64: 'Numpad4', + 0x65: 'Numpad5', + 0x66: 'Numpad6', + 0x67: 'Numpad7', + 0x68: 'Numpad8', + 0x69: 'Numpad9', + 0x6a: 'NumpadMultiply', + 0x6b: 'NumpadAdd', + 0x6c: 'NumpadDecimal', + 0x6d: 'NumpadSubtract', + 0x6e: 'NumpadDecimal', // Duplicate, because buggy on Windows + 0x6f: 'NumpadDivide', + 0x70: 'F1', + 0x71: 'F2', + 0x72: 'F3', + 0x73: 'F4', + 0x74: 'F5', + 0x75: 'F6', + 0x76: 'F7', + 0x77: 'F8', + 0x78: 'F9', + 0x79: 'F10', + 0x7a: 'F11', + 0x7b: 'F12', + 0x7c: 'F13', + 0x7d: 'F14', + 0x7e: 'F15', + 0x7f: 'F16', + 0x80: 'F17', + 0x81: 'F18', + 0x82: 'F19', + 0x83: 'F20', + 0x84: 'F21', + 0x85: 'F22', + 0x86: 'F23', + 0x87: 'F24', + 0x90: 'NumLock', + 0x91: 'ScrollLock', + 0xa6: 'BrowserBack', + 0xa7: 'BrowserForward', + 0xa8: 'BrowserRefresh', + 0xa9: 'BrowserStop', + 0xaa: 'BrowserSearch', + 0xab: 'BrowserFavorites', + 0xac: 'BrowserHome', + 0xad: 'AudioVolumeMute', + 0xae: 'AudioVolumeDown', + 0xaf: 'AudioVolumeUp', + 0xb0: 'MediaTrackNext', + 0xb1: 'MediaTrackPrevious', + 0xb2: 'MediaStop', + 0xb3: 'MediaPlayPause', + 0xb4: 'LaunchMail', + 0xb5: 'MediaSelect', + 0xb6: 'LaunchApp1', + 0xb7: 'LaunchApp2', + 0xe1: 'AltRight', // Only when it is AltGraph +}; diff --git a/core/input/xtscancodes.js b/core/input/xtscancodes.js index 43ea51b7..e61b4253 100644 --- a/core/input/xtscancodes.js +++ b/core/input/xtscancodes.js @@ -112,8 +112,6 @@ export default { "Delete": 0xE053, "MetaLeft": 0xE05B, "MetaRight": 0xE05C, - "OSLeft": 0xE05B, // OSLeft and OSRight are kept for compatability since - "OSRight": 0xE05C, // Firefox haven't updated to MetaLeft and MetaRight yet "ContextMenu": 0xE05D, "BrowserSearch": 0xE065, "BrowserFavorites": 0xE066, diff --git a/tests/input.html b/tests/input.html index febee5fd..cd31f7ed 100644 --- a/tests/input.html +++ b/tests/input.html @@ -29,6 +29,7 @@ + diff --git a/tests/test.helper.js b/tests/test.helper.js index a10b8b2f..3d0afb55 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -33,6 +33,71 @@ describe('Helpers', function() { }); }); + describe('getKeycode', function() { + it('should pass through proper code', function() { + expect(KeyboardUtil.getKeycode({code: 'Semicolon'})).to.be.equal('Semicolon'); + }); + it('should map legacy values', function() { + expect(KeyboardUtil.getKeycode({code: ''})).to.be.equal('Unidentified'); + expect(KeyboardUtil.getKeycode({code: 'OSLeft'})).to.be.equal('MetaLeft'); + }); + it('should map keyCode to code when possible', function() { + expect(KeyboardUtil.getKeycode({keyCode: 0x14})).to.be.equal('CapsLock'); + expect(KeyboardUtil.getKeycode({keyCode: 0x5b})).to.be.equal('MetaLeft'); + expect(KeyboardUtil.getKeycode({keyCode: 0x35})).to.be.equal('Digit5'); + expect(KeyboardUtil.getKeycode({keyCode: 0x65})).to.be.equal('Numpad5'); + }); + it('should map keyCode left/right side', function() { + expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 1})).to.be.equal('ShiftLeft'); + expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 2})).to.be.equal('ShiftRight'); + expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 1})).to.be.equal('ControlLeft'); + expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 2})).to.be.equal('ControlRight'); + }); + it('should map keyCode on numpad', function() { + expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 0})).to.be.equal('Enter'); + expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 3})).to.be.equal('NumpadEnter'); + expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 0})).to.be.equal('End'); + expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 3})).to.be.equal('Numpad1'); + }); + it('should return Unidentified when it cannot map the keyCode', function() { + expect(KeyboardUtil.getKeycode({keycode: 0x42})).to.be.equal('Unidentified'); + }); + + describe('Fix Meta on macOS', function() { + var origNavigator; + beforeEach(function () { + // window.navigator is a protected read-only property in many + // environments, so we need to redefine it whilst running these + // tests. + origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); + if (origNavigator === undefined) { + // Object.getOwnPropertyDescriptor() doesn't work + // properly in any version of IE + this.skip(); + } + + Object.defineProperty(window, "navigator", {value: {}}); + if (window.navigator.platform !== undefined) { + // Object.defineProperty() doesn't work properly in old + // versions of Chrome + this.skip(); + } + + window.navigator.platform = "Mac x86_64"; + }); + afterEach(function () { + Object.defineProperty(window, "navigator", origNavigator); + }); + + it('should respect ContextMenu on modern browser', function() { + expect(KeyboardUtil.getKeycode({code: 'ContextMenu', keyCode: 0x5d})).to.be.equal('ContextMenu'); + }); + it('should translate legacy ContextMenu to MetaRight', function() { + expect(KeyboardUtil.getKeycode({keyCode: 0x5d})).to.be.equal('MetaRight'); + }); + }); + }); + describe('getKeysym', function() { it('should prefer char', function() { expect(KeyboardUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61); diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index dc8f0dbc..aa961420 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -12,26 +12,26 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.an.object; done(); - }).keydown({keyCode: 0x41}); + }).keydown({code: 'KeyA', keyCode: 0x41}); }); it('should pass the right keysym through', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt.keysym).to.be.deep.equal(0x61); done(); - }).keypress({keyCode: 0x41}); + }).keypress({code: 'KeyA', keyCode: 0x41}); }); it('should pass the right keyid through', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.have.property('keyId', 0x41); + expect(evt).to.have.property('code', 'KeyA'); done(); - }).keydown({keyCode: 0x41}); + }).keydown({code: 'KeyA', keyCode: 0x41}); }); it('should not sync modifiers on a keypress', function() { // Firefox provides unreliable modifier state on keypress events var count = 0; KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { ++count; - }).keypress({keyCode: 0x41, ctrlKey: true}); + }).keypress({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); expect(count).to.be.equal(1); }); it('should sync modifiers if necessary', function(done) { @@ -43,29 +43,29 @@ describe('Key Event Pipeline Stages', function() { ++count; break; case 1: - expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown', keysym: 0x61}); + expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown', keysym: 0x61}); done(); break; } - }).keydown({keyCode: 0x41, ctrlKey: true}); + }).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); }); it('should forward keydown events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'}); + expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); done(); - }).keydown({keyCode: 0x41}); + }).keydown({code: 'KeyA', keyCode: 0x41}); }); it('should forward keyup events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keyup'}); + expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'}); done(); - }).keyup({keyCode: 0x41}); + }).keyup({code: 'KeyA', keyCode: 0x41}); }); it('should forward keypress events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keypress'}); + expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'}); done(); - }).keypress({keyCode: 0x41}); + }).keypress({code: 'KeyA', keyCode: 0x41}); }); it('should generate stalls if a char modifier is down while a key is pressed', function(done) { var count = 0; @@ -82,14 +82,14 @@ describe('Key Event Pipeline Stages', function() { case 2: // 'a' expect(evt).to.be.deep.equal({ type: 'keydown', - keyId: 0x41, + code: 'KeyA', keysym: 0x61 }); done(); break; } - }).keydown({keyCode: 0x41, altGraphKey: true}); + }).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true}); }); describe('suppress the right events at the right time', function() { @@ -184,7 +184,7 @@ describe('Key Event Pipeline Stages', function() { }); obj.keydown({keyCode: 0xe1}); // press altgr - obj.keydown({keyCode: 'A'.charCodeAt()}); + obj.keydown({code: 'KeyA', keyCode: 0x41}); }); it('should indicate on events if a single-key char modifier is down', function(done) { @@ -196,8 +196,8 @@ describe('Key Event Pipeline Stages', function() { case 1: // 'a' expect(evt).to.be.deep.equal({ type: 'keypress', - keyId: 'A'.charCodeAt(), - keysym: 'a'.charCodeAt(), + code: 'KeyA', + keysym: 0x61, escape: [0xfe03] }); done(); @@ -206,7 +206,7 @@ describe('Key Event Pipeline Stages', function() { }); obj.keydown({keyCode: 0xe1}); // press altgr - obj.keypress({keyCode: 'A'.charCodeAt()}); + obj.keypress({code: 'KeyA', keyCode: 0x41}); }); it('should indicate on events if a multi-key char modifier is down', function(done) { var times_called = 0; @@ -219,8 +219,8 @@ describe('Key Event Pipeline Stages', function() { case 2: // 'a' expect(evt).to.be.deep.equal({ type: 'keypress', - keyId: 'A'.charCodeAt(), - keysym: 'a'.charCodeAt(), + code: 'KeyA', + keysym: 0x61, escape: [0xffe9, 0xffe3] }); done(); @@ -230,7 +230,7 @@ describe('Key Event Pipeline Stages', function() { obj.keydown({keyCode: 0x11}); // press ctrl obj.keydown({keyCode: 0x12}); // press alt - obj.keypress({keyCode: 'A'.charCodeAt()}); + obj.keypress({code: 'KeyA', keyCode: 0x41}); }); it('should not consider a char modifier to be down on the modifier key itself', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { @@ -244,18 +244,18 @@ describe('Key Event Pipeline Stages', function() { describe('add/remove keysym', function() { it('should remove keysym from keydown if a char key and no modifier', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'}); - }).keydown({keyCode: 0x41}); + expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); + }).keydown({code: 'KeyA', keyCode: 0x41}); }); it('should not remove keysym from keydown if a shortcut modifier is down', function() { var times_called = 0; KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { switch (times_called++) { case 1: - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keydown'}); + expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); break; } - }).keydown({keyCode: 0x41, ctrlKey: true}); + }).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); expect(times_called).to.be.equal(2); }); it('should not remove keysym from keydown if a char modifier is down', function() { @@ -263,30 +263,30 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { switch (times_called++) { case 2: - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keydown'}); + expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); break; } - }).keydown({keyCode: 0x41, altGraphKey: true}); + }).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true}); expect(times_called).to.be.equal(3); }); it('should not remove keysym from keydown if key is noncharacter', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt, 'bacobjpace').to.be.deep.equal({keyId: 0x09, keysym: 0xff09, type: 'keydown'}); + expect(evt, 'tab').to.be.deep.equal({code: 'Tab', keysym: 0xff09, type: 'keydown'}); }).keydown({keyCode: 0x09}); KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt, 'ctrl').to.be.deep.equal({keyId: 0x11, keysym: 0xffe3, type: 'keydown'}); + expect(evt, 'ctrl').to.be.deep.equal({code: 'ControlLeft', keysym: 0xffe3, type: 'keydown'}); }).keydown({keyCode: 0x11}); }); it('should never remove keysym from keypress', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keypress'}); - }).keypress({keyCode: 0x41}); + expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'}); + }).keypress({code: 'KeyA', keyCode: 0x41}); }); it('should never remove keysym from keyup', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keyup'}); - }).keyup({keyCode: 0x41}); + expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'}); + }).keyup({code: 'KeyA', keyCode: 0x41}); }); }); // on keypress, keyup(?), always set keysym @@ -296,31 +296,31 @@ describe('Key Event Pipeline Stages', function() { describe('Verify that char modifiers are active', function() { it('should pass keydown events through if there is no stall', function(done) { var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x41}); + expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x41}); done(); - })({type: 'keydown', keyId: 0x41, keysym: 0x41}); + })({type: 'keydown', code: 'KeyA', keysym: 0x41}); }); it('should pass keyup events through if there is no stall', function(done) { var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keyup', keyId: 0x41, keysym: 0x41}); + expect(evt).to.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x41}); done(); - })({type: 'keyup', keyId: 0x41, keysym: 0x41}); + })({type: 'keyup', code: 'KeyA', keysym: 0x41}); }); it('should pass keypress events through if there is no stall', function(done) { var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keypress', keyId: 0x41, keysym: 0x41}); + expect(evt).to.deep.equal({type: 'keypress', code: 'KeyA', keysym: 0x41}); done(); - })({type: 'keypress', keyId: 0x41, keysym: 0x41}); + })({type: 'keypress', code: 'KeyA', keysym: 0x41}); }); it('should not pass stall events through', function(done){ var obj = KeyboardUtil.VerifyCharModifier(function(evt){ // should only be called once, for the keydown - expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x41}); + expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x41}); done(); }); obj({type: 'stall'}); - obj({type: 'keydown', keyId: 0x41, keysym: 0x41}); + obj({type: 'keydown', code: 'KeyA', keysym: 0x41}); }); it('should merge keydown and keypress events if they come after a stall', function(done) { var next_called = false; @@ -328,13 +328,13 @@ describe('Key Event Pipeline Stages', function() { // should only be called once, for the keydown expect(next_called).to.be.false; next_called = true; - expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x44}); + expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44}); done(); }); obj({type: 'stall'}); - obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); - obj({type: 'keypress', keyId: 0x43, keysym: 0x44}); + obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); + obj({type: 'keypress', code: 'KeyC', keysym: 0x44}); expect(next_called).to.be.false; }); it('should preserve modifier attribute when merging if keysyms differ', function(done) { @@ -343,13 +343,13 @@ describe('Key Event Pipeline Stages', function() { // should only be called once, for the keydown expect(next_called).to.be.false; next_called = true; - expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x44, escape: [0xffe3]}); + expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44, escape: [0xffe3]}); done(); }); obj({type: 'stall'}); - obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); - obj({type: 'keypress', keyId: 0x43, keysym: 0x44, escape: [0xffe3]}); + obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); + obj({type: 'keypress', code: 'KeyC', keysym: 0x44, escape: [0xffe3]}); expect(next_called).to.be.false; }); it('should not preserve modifier attribute when merging if keysyms are the same', function() { @@ -358,18 +358,18 @@ describe('Key Event Pipeline Stages', function() { }); obj({type: 'stall'}); - obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); - obj({type: 'keypress', keyId: 0x43, keysym: 0x42, escape: [0xffe3]}); + obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); + obj({type: 'keypress', code: 'KeyC', keysym: 0x42, escape: [0xffe3]}); }); it('should not merge keydown and keypress events if there is no stall', function(done) { var times_called = 0; var obj = KeyboardUtil.VerifyCharModifier(function(evt){ switch(times_called) { case 0: - expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42}); + expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42}); break; case 1: - expect(evt).to.deep.equal({type: 'keypress', keyId: 0x43, keysym: 0x44}); + expect(evt).to.deep.equal({type: 'keypress', code: 'KeyC', keysym: 0x44}); done(); break; } @@ -377,21 +377,21 @@ describe('Key Event Pipeline Stages', function() { ++times_called; }); - obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); - obj({type: 'keypress', keyId: 0x43, keysym: 0x44}); + obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); + obj({type: 'keypress', code: 'KeyC', keysym: 0x44}); }); it('should not merge keydown and keypress events if separated by another event', function(done) { var times_called = 0; var obj = KeyboardUtil.VerifyCharModifier(function(evt){ switch(times_called) { case 0: - expect(evt,1).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42}); + expect(evt,1).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42}); break; case 1: - expect(evt,2).to.deep.equal({type: 'keyup', keyId: 0x43, keysym: 0x44}); + expect(evt,2).to.deep.equal({type: 'keyup', code: 'KeyC', keysym: 0x44}); break; case 2: - expect(evt,3).to.deep.equal({type: 'keypress', keyId: 0x45, keysym: 0x46}); + expect(evt,3).to.deep.equal({type: 'keypress', code: 'KeyE', keysym: 0x46}); done(); break; } @@ -400,9 +400,9 @@ describe('Key Event Pipeline Stages', function() { }); obj({type: 'stall'}); - obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); - obj({type: 'keyup', keyId: 0x43, keysym: 0x44}); - obj({type: 'keypress', keyId: 0x45, keysym: 0x46}); + obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); + obj({type: 'keyup', code: 'KeyC', keysym: 0x44}); + obj({type: 'keypress', code: 'KeyE', keysym: 0x46}); }); }); @@ -411,7 +411,7 @@ describe('Key Event Pipeline Stages', function() { var obj = KeyboardUtil.TrackKeyState(function(evt) { expect(true).to.be.false; }); - obj({type: 'keyup', keyId: 0x41}); + obj({type: 'keyup', code: 'KeyA'}); }); it('should insert into the queue on keydown if no keys are down', function() { var times_called = 0; @@ -432,11 +432,11 @@ describe('Key Event Pipeline Stages', function() { }); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0x41, keysym: 0x42}; + elem = {type: 'keydown', code: 'KeyA', keysym: 0x42}; keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keyup', keyId: 0x41}; + elem = {type: 'keyup', code: 'KeyA'}; obj(elem); expect(elem).to.be.null; expect(times_called).to.be.equal(2); @@ -460,16 +460,16 @@ describe('Key Event Pipeline Stages', function() { }); expect(elem).to.be.null; - elem = {type: 'keypress', keyId: 0x41, keysym: 0x42}; + elem = {type: 'keypress', code: 'KeyA', keysym: 0x42}; keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keyup', keyId: 0x41}; + elem = {type: 'keyup', code: 'KeyA'}; obj(elem); expect(elem).to.be.null; expect(times_called).to.be.equal(2); }); - it('should add keysym to last key entry if keyId matches', function() { + it('should add keysym to last key entry if code matches', function() { // this implies that a single keyup will release both keysyms var times_called = 0; var elem = null; @@ -489,19 +489,19 @@ describe('Key Event Pipeline Stages', function() { }); expect(elem).to.be.null; - elem = {type: 'keypress', keyId: 0x41, keysym: 0x42}; + elem = {type: 'keypress', code: 'KeyA', keysym: 0x42}; keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keypress', keyId: 0x41, keysym: 0x43}; + elem = {type: 'keypress', code: 'KeyA', keysym: 0x43}; keysymsdown[0x43] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keyup', keyId: 0x41}; + elem = {type: 'keyup', code: 'KeyA'}; obj(elem); expect(times_called).to.be.equal(4); }); - it('should create new key entry if keyId matches and keysym does not', function() { + it('should create new key entry if code matches and keysym does not', function() { // this implies that a single keyup will release both keysyms var times_called = 0; var elem = null; @@ -521,23 +521,23 @@ describe('Key Event Pipeline Stages', function() { }); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: 0x42}; + elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42}; keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: 0x43}; + elem = {type: 'keydown', code: 'Unidentified', keysym: 0x43}; keysymsdown[0x43] = true; obj(elem); expect(times_called).to.be.equal(2); expect(elem).to.be.null; - elem = {type: 'keyup', keyId: 0}; + elem = {type: 'keyup', code: 'Unidentified'}; obj(elem); expect(times_called).to.be.equal(3); - elem = {type: 'keyup', keyId: 0}; + elem = {type: 'keyup', code: 'Unidentified'}; obj(elem); expect(times_called).to.be.equal(4); }); - it('should merge key entry if keyIds are zero and keysyms match', function() { + it('should merge key entry if codes are zero and keysyms match', function() { // this implies that a single keyup will release both keysyms var times_called = 0; var elem = null; @@ -557,20 +557,20 @@ describe('Key Event Pipeline Stages', function() { }); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: 0x42}; + elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42}; keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: 0x42}; + elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42}; keysymsdown[0x42] = true; obj(elem); expect(times_called).to.be.equal(2); expect(elem).to.be.null; - elem = {type: 'keyup', keyId: 0}; + elem = {type: 'keyup', code: 'Unidentified'}; obj(elem); expect(times_called).to.be.equal(3); }); - it('should add keysym as separate entry if keyId does not match last event', function() { + it('should add keysym as separate entry if code does not match last event', function() { // this implies that separate keyups are required var times_called = 0; var elem = null; @@ -590,22 +590,22 @@ describe('Key Event Pipeline Stages', function() { }); expect(elem).to.be.null; - elem = {type: 'keypress', keyId: 0x41, keysym: 0x42}; + elem = {type: 'keypress', code: 'KeyA', keysym: 0x42}; keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keypress', keyId: 0x42, keysym: 0x43}; + elem = {type: 'keypress', code: 'KeyB', keysym: 0x43}; keysymsdown[0x43] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keyup', keyId: 0x41}; + elem = {type: 'keyup', code: 'KeyA'}; obj(elem); expect(times_called).to.be.equal(4); - elem = {type: 'keyup', keyId: 0x42}; + elem = {type: 'keyup', code: 'KeyB'}; obj(elem); expect(times_called).to.be.equal(4); }); - it('should add keysym as separate entry if keyId does not match last event and first is zero', function() { + it('should add keysym as separate entry if code does not match last event and first is zero', function() { // this implies that separate keyups are required var times_called = 0; var elem = null; @@ -625,23 +625,23 @@ describe('Key Event Pipeline Stages', function() { }); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: 0x42}; + elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42}; keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0x42, keysym: 0x43}; + elem = {type: 'keydown', code: 'KeyB', keysym: 0x43}; keysymsdown[0x43] = true; obj(elem); expect(elem).to.be.null; expect(times_called).to.be.equal(2); - elem = {type: 'keyup', keyId: 0}; + elem = {type: 'keyup', code: 'Unidentified'}; obj(elem); expect(times_called).to.be.equal(3); - elem = {type: 'keyup', keyId: 0x42}; + elem = {type: 'keyup', code: 'KeyB'}; obj(elem); expect(times_called).to.be.equal(4); }); - it('should add keysym as separate entry if keyId does not match last event and second is zero', function() { + it('should add keysym as separate entry if code does not match last event and second is zero', function() { // this implies that a separate keyups are required var times_called = 0; var elem = null; @@ -661,18 +661,18 @@ describe('Key Event Pipeline Stages', function() { }); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0x41, keysym: 0x42}; + elem = {type: 'keydown', code: 'KeyA', keysym: 0x42}; keysymsdown[0x42] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keydown', keyId: 0, keysym: 0x43}; + elem = {type: 'keydown', code: 'Unidentified', keysym: 0x43}; keysymsdown[0x43] = true; obj(elem); expect(elem).to.be.null; - elem = {type: 'keyup', keyId: 0x41}; + elem = {type: 'keyup', code: 'KeyA'}; obj(elem); expect(times_called).to.be.equal(3); - elem = {type: 'keyup', keyId: 0}; + elem = {type: 'keyup', code: 'Unidentified'}; obj(elem); expect(times_called).to.be.equal(4); }); @@ -686,18 +686,18 @@ describe('Key Event Pipeline Stages', function() { expect(evt.type).to.be.equal('keydown'); break; case 3: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x42, keysym: 0x62}); + expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyB', keysym: 0x62}); break; } }); - obj({type: 'keydown', keyId: 0x41, keysym: 0x61}); - obj({type: 'keydown', keyId: 0x42, keysym: 0x62}); - obj({type: 'keydown', keyId: 0x43, keysym: 0x63}); - obj({type: 'keyup', keyId: 0x42}); + obj({type: 'keydown', code: 'KeyA', keysym: 0x61}); + obj({type: 'keydown', code: 'KeyB', keysym: 0x62}); + obj({type: 'keydown', code: 'KeyC', keysym: 0x63}); + obj({type: 'keyup', code: 'KeyB'}); expect(times_called).to.equal(4); }); - it('should pop the first zero keyevent on keyup with zero keyId', function() { + it('should pop the first zero keyevent on keyup with zero code', function() { var times_called = 0; var obj = KeyboardUtil.TrackKeyState(function(evt) { switch (times_called++) { @@ -707,18 +707,18 @@ describe('Key Event Pipeline Stages', function() { expect(evt.type).to.be.equal('keydown'); break; case 3: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0x61}); + expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x61}); break; } }); - obj({type: 'keydown', keyId: 0, keysym: 0x61}); - obj({type: 'keydown', keyId: 0, keysym: 0x62}); - obj({type: 'keydown', keyId: 0x41, keysym: 0x63}); - obj({type: 'keyup', keyId: 0x0}); + obj({type: 'keydown', code: 'Unidentified', keysym: 0x61}); + obj({type: 'keydown', code: 'Unidentified', keysym: 0x62}); + obj({type: 'keydown', code: 'KeyA', keysym: 0x63}); + obj({type: 'keyup', code: 'Unidentified'}); expect(times_called).to.equal(4); }); - it('should pop the last keyevents keysym if no match is found for keyId', function() { + it('should pop the last keyevents keysym if no match is found for code', function() { var times_called = 0; var obj = KeyboardUtil.TrackKeyState(function(evt) { switch (times_called++) { @@ -728,15 +728,15 @@ describe('Key Event Pipeline Stages', function() { expect(evt.type).to.be.equal('keydown'); break; case 3: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x44, keysym: 0x63}); + expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyD', keysym: 0x63}); break; } }); - obj({type: 'keydown', keyId: 0x41, keysym: 0x61}); - obj({type: 'keydown', keyId: 0x42, keysym: 0x62}); - obj({type: 'keydown', keyId: 0x43, keysym: 0x63}); - obj({type: 'keyup', keyId: 0x44}); + obj({type: 'keydown', code: 'KeyA', keysym: 0x61}); + obj({type: 'keydown', code: 'KeyB', keysym: 0x62}); + obj({type: 'keydown', code: 'KeyC', keysym: 0x63}); + obj({type: 'keyup', code: 'KeyD'}); expect(times_called).to.equal(4); }); describe('Firefox sends keypress even when keydown is suppressed', function() { @@ -747,9 +747,9 @@ describe('Key Event Pipeline Stages', function() { ++times_called; }); - obj({type: 'keydown', keyId: 0x41, keysym: 0x42}); + obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); expect(times_called).to.be.equal(1); - obj({type: 'keypress', keyId: 0x41, keysym: 0x43}); + obj({type: 'keypress', code: 'KeyA', keysym: 0x43}); }); }); describe('releaseAll', function() { @@ -766,15 +766,15 @@ describe('Key Event Pipeline Stages', function() { var obj = KeyboardUtil.TrackKeyState(function(evt) { switch (times_called++) { case 2: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0x41}); + expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x41}); break; case 3: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0x42}); + expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x42}); break; } }); - obj({type: 'keydown', keyId: 0x41, keysym: 0x41}); - obj({type: 'keydown', keyId: 0x42, keysym: 0x42}); + obj({type: 'keydown', code: 'KeyA', keysym: 0x41}); + obj({type: 'keydown', code: 'KeyB', keysym: 0x42}); expect(times_called).to.be.equal(2); obj({type: 'releaseall'}); expect(times_called).to.be.equal(4); @@ -792,8 +792,8 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.EscapeModifiers(function(evt) { expect(times_called).to.be.equal(0); ++times_called; - expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42}); - })({type: 'keydown', keyId: 0x41, keysym: 0x42}); + expect(evt).to.be.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42}); + })({type: 'keydown', code: 'KeyA', keysym: 0x42}); expect(times_called).to.be.equal(1); }); it('should generate fake undo/redo events when a char modifier is down', function() { @@ -801,22 +801,22 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.EscapeModifiers(function(evt) { switch(times_called++) { case 0: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0xffe9}); + expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe9}); break; case 1: - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0xffe3}); + expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe3}); break; case 2: - expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42, escape: [0xffe9, 0xffe3]}); + expect(evt).to.be.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42, escape: [0xffe9, 0xffe3]}); break; case 3: - expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: 0xffe9}); + expect(evt).to.be.deep.equal({type: 'keydown', code: 'Unidentified', keysym: 0xffe9}); break; case 4: - expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: 0xffe3}); + expect(evt).to.be.deep.equal({type: 'keydown', code: 'Unidentified', keysym: 0xffe3}); break; } - })({type: 'keydown', keyId: 0x41, keysym: 0x42, escape: [0xffe9, 0xffe3]}); + })({type: 'keydown', code: 'KeyA', keysym: 0x42, escape: [0xffe9, 0xffe3]}); expect(times_called).to.be.equal(5); }); }); @@ -826,8 +826,8 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.EscapeModifiers(function(evt) { expect(times_called).to.be.equal(0); ++times_called; - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: 0x42, escape: [0xfe03]}); - })({type: 'keyup', keyId: 0x41, keysym: 0x42, escape: [0xfe03]}); + expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]}); + })({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]}); expect(times_called).to.be.equal(1); }); it('should pass through when a char modifier is not down', function() { @@ -835,8 +835,8 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.EscapeModifiers(function(evt) { expect(times_called).to.be.equal(0); ++times_called; - expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: 0x42}); - })({type: 'keyup', keyId: 0x41, keysym: 0x42}); + expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42}); + })({type: 'keyup', code: 'KeyA', keysym: 0x42}); expect(times_called).to.be.equal(1); }); }); diff --git a/tests/vnc_perf.html b/tests/vnc_perf.html index ce97ca4c..36331eb1 100644 --- a/tests/vnc_perf.html +++ b/tests/vnc_perf.html @@ -50,7 +50,8 @@ WebUtil.load_scripts({ 'core': ["base64.js", "websock.js", "des.js", "input/keysym.js", "input/keysymdef.js", "input/xtscancodes.js", "input/util.js", - "input/devices.js", "display.js", "rfb.js", "inflator.js"], + "input/devices.js", "display.js", "rfb.js", "inflator.js", + "input/vkeys.js"], 'tests': ["playback.js"], 'recordings': [fname]}); } else { From bfa1b237b9caec28380f3bb3e03f409eadc7f2b0 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 25 Jan 2017 11:29:08 +0100 Subject: [PATCH 08/20] Improve character keysym lookup Use the more modern 'key' field, and remove some legacy fallbacks that are no longer required. This also removes the "stall" mechanism as it is not needed with current browsers. --- core/input/devices.js | 8 +- core/input/util.js | 88 ++--------------- tests/test.helper.js | 24 ++--- tests/test.keyboard.js | 219 +++++++---------------------------------- 4 files changed, 53 insertions(+), 286 deletions(-) diff --git a/core/input/devices.js b/core/input/devices.js index 8aaabbd6..c68966b3 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -29,12 +29,10 @@ const Keyboard = function (defaults) { // create the keyboard handler this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), - KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */ - KeyboardUtil.TrackKeyState( - KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this)) - ) + KeyboardUtil.TrackKeyState( + KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this)) ) - ); /* jshint newcap: true */ + ); // keep these here so we can refer to them later this._eventHandlers = { diff --git a/core/input/util.js b/core/input/util.js index 508193ee..18867af8 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -196,23 +196,20 @@ export function getKeycode(evt){ // if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which export function getKeysym(evt){ var codepoint; - if (evt.char && evt.char.length === 1) { - codepoint = evt.char.charCodeAt(); - } - else if (evt.charCode) { + + if ('key' in evt) { + // Ignore special keys + if (evt.key.length === 1) { + codepoint = evt.key.charCodeAt(); + } + } else if ('charCode' in evt) { codepoint = evt.charCode; } - else if (evt.keyCode && evt.type === 'keypress') { - // IE10 stores the char code as keyCode, and has no other useful properties - codepoint = evt.keyCode; - } + if (codepoint) { return keysyms.lookup(codepoint); } - // we could check evt.key here. - // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list, - // so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key - // so we don't *need* it yet + if (evt.keyCode) { return keysymFromKeyCode(evt.keyCode, evt.shiftKey); } @@ -437,7 +434,6 @@ export function TrackQEMUKeyState (next) { // - determines a code identifying the key that was pressed (corresponding to the code/keyCode properties on the DOM event) // - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down // - marks each event with an 'escape' property if a modifier was down which should be "escaped" -// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown // This information is collected into an object which is passed to the next() function. (one call per event) export function KeyEventDecoder (modifierState, next) { "use strict"; @@ -476,10 +472,6 @@ export function KeyEventDecoder (modifierState, next) { // so only do that if we have to. var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt)); - // If a char modifier is down on a keydown, we need to insert a stall, - // so VerifyCharModifier knows to wait and see if a keypress is comnig - var stall = type === 'keydown' && modifierState.activeCharModifier() && !nonCharacterKey(evt); - // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt) var active = modifierState.activeCharModifier(); @@ -498,10 +490,6 @@ export function KeyEventDecoder (modifierState, next) { } } - if (stall) { - // insert a fake "stall" event - next({type: 'stall'}); - } next(result); return suppress; @@ -526,64 +514,6 @@ export function KeyEventDecoder (modifierState, next) { }; }; -// Combines keydown and keypress events where necessary to handle char modifiers. -// On some OS'es, a char modifier is sometimes used as a shortcut modifier. -// For example, on Windows, AltGr is synonymous with Ctrl-Alt. On a Danish keyboard layout, AltGr-2 yields a @, but Ctrl-Alt-D does nothing -// so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not. -// The only way we can distinguish these cases is to wait and see if a keypress event arrives -// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two -export function VerifyCharModifier (next) { - "use strict"; - var queue = []; - var timer = null; - function process() { - if (timer) { - return; - } - - var delayProcess = function () { - clearTimeout(timer); - timer = null; - process(); - }; - - while (queue.length !== 0) { - var cur = queue[0]; - queue = queue.splice(1); - switch (cur.type) { - case 'stall': - // insert a delay before processing available events. - /* jshint loopfunc: true */ - timer = setTimeout(delayProcess, 5); - /* jshint loopfunc: false */ - return; - case 'keydown': - // is the next element a keypress? Then we should merge the two - if (queue.length !== 0 && queue[0].type === 'keypress') { - // Firefox sends keypress even when no char is generated. - // so, if keypress keysym is the same as we'd have guessed from keydown, - // the modifier didn't have any effect, and should not be escaped - if (queue[0].escape && (!cur.keysym || cur.keysym !== queue[0].keysym)) { - cur.escape = queue[0].escape; - } - cur.keysym = queue[0].keysym; - queue = queue.splice(1); - } - break; - } - - // swallow stall events, and pass all others to the next stage - if (cur.type !== 'stall') { - next(cur); - } - } - } - return function(evt) { - queue.push(evt); - process(); - }; -}; - // Keeps track of which keys we (and the server) believe are down // When a keyup is received, match it against this list, to determine the corresponding keysym(s) // in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars diff --git a/tests/test.helper.js b/tests/test.helper.js index 3d0afb55..b66c5e57 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -99,25 +99,11 @@ describe('Helpers', function() { }); describe('getKeysym', function() { - it('should prefer char', function() { - expect(KeyboardUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61); + it('should prefer key', function() { + expect(KeyboardUtil.getKeysym({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61); }); - it('should use charCode if no char', function() { - expect(KeyboardUtil.getKeysym({char : '', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); + it('should use charCode if no key', function() { expect(KeyboardUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); - expect(KeyboardUtil.getKeysym({char : 'hello', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); - }); - it('should use keyCode if no charCode', function() { - expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: false})).to.be.equal(0x62); - expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: true})).to.be.equal(0x42); - }); - it('should return null for unknown keycodes', function() { - expect(KeyboardUtil.getKeysym({keyCode: 0xc0, which: 0xc1, shiftKey:false})).to.be.null; - expect(KeyboardUtil.getKeysym({keyCode: 0xde, which: 0xdf, shiftKey:false})).to.be.null; - }); - it('should use which if no keyCode', function() { - expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: false})).to.be.equal(0x63); - expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: true})).to.be.equal(0x43); }); describe('Non-character keys', function() { @@ -133,6 +119,10 @@ describe('Helpers', function() { expect(KeyboardUtil.getKeysym({keyCode: 0x1b})).to.be.equal(0xFF1B); expect(KeyboardUtil.getKeysym({keyCode: 0x26})).to.be.equal(0xFF52); }); + it('should return null for unknown keycodes', function() { + expect(KeyboardUtil.getKeysym({keyCode: 0xc0})).to.be.null; + expect(KeyboardUtil.getKeysym({keyCode: 0xde})).to.be.null; + }); it('should not recognize character keys', function() { expect(KeyboardUtil.getKeysym({keyCode: 'A'})).to.be.null; expect(KeyboardUtil.getKeysym({keyCode: '1'})).to.be.null; diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index aa961420..f29a3394 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -12,26 +12,26 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.an.object; done(); - }).keydown({code: 'KeyA', keyCode: 0x41}); + }).keydown({code: 'KeyA', key: 'a'}); }); it('should pass the right keysym through', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt.keysym).to.be.deep.equal(0x61); done(); - }).keypress({code: 'KeyA', keyCode: 0x41}); + }).keypress({code: 'KeyA', key: 'a'}); }); it('should pass the right keyid through', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.have.property('code', 'KeyA'); done(); - }).keydown({code: 'KeyA', keyCode: 0x41}); + }).keydown({code: 'KeyA', key: 'a'}); }); it('should not sync modifiers on a keypress', function() { // Firefox provides unreliable modifier state on keypress events var count = 0; KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { ++count; - }).keypress({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); + }).keypress({code: 'KeyA', key: 'a', ctrlKey: true}); expect(count).to.be.equal(1); }); it('should sync modifiers if necessary', function(done) { @@ -47,61 +47,36 @@ describe('Key Event Pipeline Stages', function() { done(); break; } - }).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); + }).keydown({code: 'KeyA', key: 'a', ctrlKey: true}); }); it('should forward keydown events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); done(); - }).keydown({code: 'KeyA', keyCode: 0x41}); + }).keydown({code: 'KeyA', key: 'a'}); }); it('should forward keyup events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'}); done(); - }).keyup({code: 'KeyA', keyCode: 0x41}); + }).keyup({code: 'KeyA', key: 'a'}); }); it('should forward keypress events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'}); done(); - }).keypress({code: 'KeyA', keyCode: 0x41}); - }); - it('should generate stalls if a char modifier is down while a key is pressed', function(done) { - var count = 0; - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { - switch (count) { - case 0: // fake altgr - expect(evt).to.be.deep.equal({keysym: 0xfe03, type: 'keydown'}); - ++count; - break; - case 1: // stall before processing the 'a' keydown - expect(evt).to.be.deep.equal({type: 'stall'}); - ++count; - break; - case 2: // 'a' - expect(evt).to.be.deep.equal({ - type: 'keydown', - code: 'KeyA', - keysym: 0x61 - }); - - done(); - break; - } - }).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true}); - + }).keypress({code: 'KeyA', key: 'a'}); }); describe('suppress the right events at the right time', function() { it('should suppress anything while a shortcut modifier is down', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); obj.keydown({keyCode: 0x11}); // press ctrl - expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.true; + expect(obj.keydown({key: 'A'})).to.be.true; expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true; - expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.true; - expect(obj.keydown({keyCode: 0x3c})).to.be.true; // < key on DK Windows - expect(obj.keydown({keyCode: 0xde})).to.be.true; // Ø key on DK + expect(obj.keydown({key: '1'})).to.be.true; + expect(obj.keydown({key: '<'})).to.be.true; + expect(obj.keydown({key: 'ø'})).to.be.true; }); it('should suppress non-character keys', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); @@ -127,48 +102,35 @@ describe('Key Event Pipeline Stages', function() { it('should not suppress character keys', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false; + expect(obj.keydown({key: 'A'})).to.be.false; expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; - expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false; - expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows - expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK + expect(obj.keydown({key: '1'})).to.be.false; + expect(obj.keydown({key: '<'})).to.be.false; // < key on DK Windows + expect(obj.keydown({key: 'ø'})).to.be.false; // Ø key on DK }); it('should not suppress if a char modifier is down', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {}); obj.keydown({keyCode: 0xe1}); // press altgr - expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false; + expect(obj.keydown({key: 'A'})).to.be.false; expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; - expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false; - expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows - expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK + expect(obj.keydown({key: '1'})).to.be.false; + expect(obj.keydown({key: '<'})).to.be.false; + expect(obj.keydown({key: 'ø'})).to.be.false; }); }); describe('Keypress and keyup events', function() { it('should always suppress event propagation', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - expect(obj.keypress({keyCode: 'A'.charCodeAt()})).to.be.true; - expect(obj.keypress({keyCode: 0x3c})).to.be.true; // < key on DK Windows + expect(obj.keypress({key: 'A'})).to.be.true; + expect(obj.keypress({key: '<'})).to.be.true; expect(obj.keypress({keyCode: 0x11})).to.be.true; - expect(obj.keyup({keyCode: 'A'.charCodeAt()})).to.be.true; - expect(obj.keyup({keyCode: 0x3c})).to.be.true; // < key on DK Windows + expect(obj.keyup({key: 'A'})).to.be.true; + expect(obj.keyup({key: '<'})).to.be.true; expect(obj.keyup({keyCode: 0x11})).to.be.true; }); - it('should never generate stalls', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt.type).to.not.be.equal('stall'); - }); - - obj.keypress({keyCode: 'A'.charCodeAt()}); - obj.keypress({keyCode: 0x3c}); - obj.keypress({keyCode: 0x11}); - - obj.keyup({keyCode: 'A'.charCodeAt()}); - obj.keyup({keyCode: 0x3c}); - obj.keyup({keyCode: 0x11}); - }); }); describe('mark events if a char modifier is down', function() { it('should not mark modifiers on a keydown event', function() { @@ -184,7 +146,7 @@ describe('Key Event Pipeline Stages', function() { }); obj.keydown({keyCode: 0xe1}); // press altgr - obj.keydown({code: 'KeyA', keyCode: 0x41}); + obj.keydown({code: 'KeyA', key: 'a'}); }); it('should indicate on events if a single-key char modifier is down', function(done) { @@ -206,7 +168,7 @@ describe('Key Event Pipeline Stages', function() { }); obj.keydown({keyCode: 0xe1}); // press altgr - obj.keypress({code: 'KeyA', keyCode: 0x41}); + obj.keypress({code: 'KeyA', key: 'a'}); }); it('should indicate on events if a multi-key char modifier is down', function(done) { var times_called = 0; @@ -230,7 +192,7 @@ describe('Key Event Pipeline Stages', function() { obj.keydown({keyCode: 0x11}); // press ctrl obj.keydown({keyCode: 0x12}); // press alt - obj.keypress({code: 'KeyA', keyCode: 0x41}); + obj.keypress({code: 'KeyA', key: 'a'}); }); it('should not consider a char modifier to be down on the modifier key itself', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { @@ -245,7 +207,7 @@ describe('Key Event Pipeline Stages', function() { it('should remove keysym from keydown if a char key and no modifier', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); - }).keydown({code: 'KeyA', keyCode: 0x41}); + }).keydown({code: 'KeyA', key: 'a'}); }); it('should not remove keysym from keydown if a shortcut modifier is down', function() { var times_called = 0; @@ -255,19 +217,19 @@ describe('Key Event Pipeline Stages', function() { expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); break; } - }).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); + }).keydown({code: 'KeyA', key: 'a', ctrlKey: true}); expect(times_called).to.be.equal(2); }); it('should not remove keysym from keydown if a char modifier is down', function() { var times_called = 0; KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { switch (times_called++) { - case 2: + case 1: expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); break; } - }).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true}); - expect(times_called).to.be.equal(3); + }).keydown({code: 'KeyA', key: 'a', altGraphKey: true}); + expect(times_called).to.be.equal(2); }); it('should not remove keysym from keydown if key is noncharacter', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { @@ -281,131 +243,18 @@ describe('Key Event Pipeline Stages', function() { it('should never remove keysym from keypress', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'}); - }).keypress({code: 'KeyA', keyCode: 0x41}); + }).keypress({code: 'KeyA', key: 'a'}); }); it('should never remove keysym from keyup', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'}); - }).keyup({code: 'KeyA', keyCode: 0x41}); + }).keyup({code: 'KeyA', key: 'a'}); }); }); // on keypress, keyup(?), always set keysym // on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down }); - describe('Verify that char modifiers are active', function() { - it('should pass keydown events through if there is no stall', function(done) { - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x41}); - done(); - })({type: 'keydown', code: 'KeyA', keysym: 0x41}); - }); - it('should pass keyup events through if there is no stall', function(done) { - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x41}); - done(); - })({type: 'keyup', code: 'KeyA', keysym: 0x41}); - }); - it('should pass keypress events through if there is no stall', function(done) { - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keypress', code: 'KeyA', keysym: 0x41}); - done(); - })({type: 'keypress', code: 'KeyA', keysym: 0x41}); - }); - it('should not pass stall events through', function(done){ - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - // should only be called once, for the keydown - expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x41}); - done(); - }); - - obj({type: 'stall'}); - obj({type: 'keydown', code: 'KeyA', keysym: 0x41}); - }); - it('should merge keydown and keypress events if they come after a stall', function(done) { - var next_called = false; - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - // should only be called once, for the keydown - expect(next_called).to.be.false; - next_called = true; - expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44}); - done(); - }); - - obj({type: 'stall'}); - obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); - obj({type: 'keypress', code: 'KeyC', keysym: 0x44}); - expect(next_called).to.be.false; - }); - it('should preserve modifier attribute when merging if keysyms differ', function(done) { - var next_called = false; - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - // should only be called once, for the keydown - expect(next_called).to.be.false; - next_called = true; - expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44, escape: [0xffe3]}); - done(); - }); - - obj({type: 'stall'}); - obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); - obj({type: 'keypress', code: 'KeyC', keysym: 0x44, escape: [0xffe3]}); - expect(next_called).to.be.false; - }); - it('should not preserve modifier attribute when merging if keysyms are the same', function() { - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.not.have.property('escape'); - }); - - obj({type: 'stall'}); - obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); - obj({type: 'keypress', code: 'KeyC', keysym: 0x42, escape: [0xffe3]}); - }); - it('should not merge keydown and keypress events if there is no stall', function(done) { - var times_called = 0; - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - switch(times_called) { - case 0: - expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42}); - break; - case 1: - expect(evt).to.deep.equal({type: 'keypress', code: 'KeyC', keysym: 0x44}); - done(); - break; - } - - ++times_called; - }); - - obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); - obj({type: 'keypress', code: 'KeyC', keysym: 0x44}); - }); - it('should not merge keydown and keypress events if separated by another event', function(done) { - var times_called = 0; - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - switch(times_called) { - case 0: - expect(evt,1).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42}); - break; - case 1: - expect(evt,2).to.deep.equal({type: 'keyup', code: 'KeyC', keysym: 0x44}); - break; - case 2: - expect(evt,3).to.deep.equal({type: 'keypress', code: 'KeyE', keysym: 0x46}); - done(); - break; - } - - ++times_called; - }); - - obj({type: 'stall'}); - obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); - obj({type: 'keyup', code: 'KeyC', keysym: 0x44}); - obj({type: 'keypress', code: 'KeyE', keysym: 0x46}); - }); - }); - describe('Track Key State', function() { it('should do nothing on keyup events if no keys are down', function() { var obj = KeyboardUtil.TrackKeyState(function(evt) { From f714f7deae1f883b9d1e70815c50267bedacc767 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 24 Jan 2017 17:18:43 +0100 Subject: [PATCH 09/20] Improve lookup of special keys Look up keys that are independent of layout and state first, followed by keys that are only mild variations in layouts. This is more robust as there might be multiple physical keys generating the same symbols, and Keysyms don't map directly to Unicode in all cases. At the same time switch over to using the modern, standardised 'code' field for lookup. --- core/input/fixedkeys.js | 112 ++++++++++++++++++++++ core/input/keysym.js | 184 +++++++++++++++++++++++++++++++++++++ core/input/util.js | 199 +++++++++++++++------------------------- tests/test.helper.js | 97 ++++++++++++-------- tests/test.keyboard.js | 126 +++++++------------------ tests/vnc_perf.html | 2 +- 6 files changed, 466 insertions(+), 254 deletions(-) create mode 100644 core/input/fixedkeys.js diff --git a/core/input/fixedkeys.js b/core/input/fixedkeys.js new file mode 100644 index 00000000..2a2594e8 --- /dev/null +++ b/core/input/fixedkeys.js @@ -0,0 +1,112 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2017 Pierre Ossman for Cendio AB + * Licensed under MPL 2.0 or any later version (see LICENSE.txt) + */ + +/* + * Mapping between HTML key codes and VNC/X11 keysyms for the + * subset of keys that have the same mapping on every keyboard + * layout. Keys that vary between layouts must never be included + * in this list. + */ + +import KeyTable from "./keysym.js"; + +export default { + 'Backspace': KeyTable.XK_BackSpace, + 'AltLeft': KeyTable.XK_Alt_L, + // AltRight is special + 'CapsLock': KeyTable.XK_Caps_Lock, + 'ContextMenu': KeyTable.XK_Menu, + 'ControlLeft': KeyTable.XK_Control_L, + 'ControlRight': KeyTable.XK_Control_R, + 'Enter': KeyTable.XK_Return, + 'MetaLeft': KeyTable.XK_Super_L, + 'MetaRight': KeyTable.XK_Super_R, + 'ShiftLeft': KeyTable.XK_Shift_L, + 'ShiftRight': KeyTable.XK_Shift_R, + 'Space': KeyTable.XK_space, + 'Tab': KeyTable.XK_Tab, + // FIXME: Japanese/Korean keys + 'Delete': KeyTable.XK_Delete, + 'End': KeyTable.XK_End, + 'Help': KeyTable.XK_Help, + 'Home': KeyTable.XK_Home, + 'Insert': KeyTable.XK_Insert, + 'PageDown': KeyTable.XK_Next, + 'PageUp': KeyTable.XK_Prior, + 'ArrowDown': KeyTable.XK_Down, + 'ArrowLeft': KeyTable.XK_Left, + 'ArrowRight': KeyTable.XK_Right, + 'ArrowUp': KeyTable.XK_Up, + 'NumLock': KeyTable.XK_Num_Lock, + 'NumpadAdd': KeyTable.XK_KP_Add, + 'NumpadBackspace': KeyTable.XK_KP_Delete, + 'NumpadClear': KeyTable.XK_Clear, + // NumpadDecimal is special + 'NumpadDivide': KeyTable.XK_KP_Divide, + 'NumpadEnter': KeyTable.XK_KP_Enter, + 'NumpadEqual': KeyTable.XK_KP_Equal, + 'NumpadMultiply': KeyTable.XK_KP_Multiply, + 'NumpadSubtract': KeyTable.XK_KP_Subtract, + 'Escape': KeyTable.XK_Escape, + 'F1': KeyTable.XK_F1, + 'F2': KeyTable.XK_F2, + 'F3': KeyTable.XK_F3, + 'F4': KeyTable.XK_F4, + 'F5': KeyTable.XK_F5, + 'F6': KeyTable.XK_F6, + 'F7': KeyTable.XK_F7, + 'F8': KeyTable.XK_F8, + 'F9': KeyTable.XK_F9, + 'F10': KeyTable.XK_F10, + 'F11': KeyTable.XK_F11, + 'F12': KeyTable.XK_F12, + 'F13': KeyTable.XK_F13, + 'F14': KeyTable.XK_F14, + 'F15': KeyTable.XK_F15, + 'F16': KeyTable.XK_F16, + 'F17': KeyTable.XK_F17, + 'F18': KeyTable.XK_F18, + 'F19': KeyTable.XK_F19, + 'F20': KeyTable.XK_F20, + 'F21': KeyTable.XK_F21, + 'F22': KeyTable.XK_F22, + 'F23': KeyTable.XK_F23, + 'F24': KeyTable.XK_F24, + 'F25': KeyTable.XK_F25, + 'F26': KeyTable.XK_F26, + 'F27': KeyTable.XK_F27, + 'F28': KeyTable.XK_F28, + 'F29': KeyTable.XK_F29, + 'F30': KeyTable.XK_F30, + 'F31': KeyTable.XK_F31, + 'F32': KeyTable.XK_F32, + 'F33': KeyTable.XK_F33, + 'F34': KeyTable.XK_F34, + 'F35': KeyTable.XK_F35, + 'PrintScreen': KeyTable.XK_Print, + 'ScrollLock': KeyTable.XK_Scroll_Lock, + 'Pause': KeyTable.XK_Pause, + 'BrowserBack': KeyTable.XF86XK_Back, + 'BrowserFavorites': KeyTable.XF86XK_Favorites, + 'BrowserForward': KeyTable.XF86XK_Forward, + 'BrowserHome': KeyTable.XF86XK_HomePage, + 'BrowserRefresh': KeyTable.XF86XK_Refresh, + 'BrowserSearch': KeyTable.XF86XK_Search, + 'BrowserStop': KeyTable.XF86XK_Stop, + 'LaunchApp1': KeyTable.XF86XK_Explorer, + 'LaunchApp2': KeyTable.XF86XK_Calculator, + 'LaunchMail': KeyTable.XF86XK_Mail, + 'MediaPlayPause': KeyTable.XF86XK_AudioPlay, + 'MediaStop': KeyTable.XF86XK_AudioStop, + 'MediaTrackNext': KeyTable.XF86XK_AudioNext, + 'MediaTrackPrevious': KeyTable.XF86XK_AudioPrev, + 'Power': KeyTable.XF86XK_PowerOff, + 'Sleep': KeyTable.XF86XK_Sleep, + 'AudioVolumeDown': KeyTable.XF86XK_AudioLowerVolume, + 'AudioVolumeMute': KeyTable.XF86XK_AudioMute, + 'AudioVolumeUp': KeyTable.XF86XK_AudioRaiseVolume, + 'WakeUp': KeyTable.XF86XK_WakeUp, +}; diff --git a/core/input/keysym.js b/core/input/keysym.js index f3a247fd..06ea70f8 100644 --- a/core/input/keysym.js +++ b/core/input/keysym.js @@ -377,4 +377,188 @@ export default { XK_yacute: 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */ XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */ XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */ + + /* + * XFree86 vendor specific keysyms. + * + * The XFree86 keysym range is 0x10080001 - 0x1008FFFF. + */ + + XF86XK_ModeLock: 0x1008FF01, + XF86XK_MonBrightnessUp: 0x1008FF02, + XF86XK_MonBrightnessDown: 0x1008FF03, + XF86XK_KbdLightOnOff: 0x1008FF04, + XF86XK_KbdBrightnessUp: 0x1008FF05, + XF86XK_KbdBrightnessDown: 0x1008FF06, + XF86XK_Standby: 0x1008FF10, + XF86XK_AudioLowerVolume: 0x1008FF11, + XF86XK_AudioMute: 0x1008FF12, + XF86XK_AudioRaiseVolume: 0x1008FF13, + XF86XK_AudioPlay: 0x1008FF14, + XF86XK_AudioStop: 0x1008FF15, + XF86XK_AudioPrev: 0x1008FF16, + XF86XK_AudioNext: 0x1008FF17, + XF86XK_HomePage: 0x1008FF18, + XF86XK_Mail: 0x1008FF19, + XF86XK_Start: 0x1008FF1A, + XF86XK_Search: 0x1008FF1B, + XF86XK_AudioRecord: 0x1008FF1C, + XF86XK_Calculator: 0x1008FF1D, + XF86XK_Memo: 0x1008FF1E, + XF86XK_ToDoList: 0x1008FF1F, + XF86XK_Calendar: 0x1008FF20, + XF86XK_PowerDown: 0x1008FF21, + XF86XK_ContrastAdjust: 0x1008FF22, + XF86XK_RockerUp: 0x1008FF23, + XF86XK_RockerDown: 0x1008FF24, + XF86XK_RockerEnter: 0x1008FF25, + XF86XK_Back: 0x1008FF26, + XF86XK_Forward: 0x1008FF27, + XF86XK_Stop: 0x1008FF28, + XF86XK_Refresh: 0x1008FF29, + XF86XK_PowerOff: 0x1008FF2A, + XF86XK_WakeUp: 0x1008FF2B, + XF86XK_Eject: 0x1008FF2C, + XF86XK_ScreenSaver: 0x1008FF2D, + XF86XK_WWW: 0x1008FF2E, + XF86XK_Sleep: 0x1008FF2F, + XF86XK_Favorites: 0x1008FF30, + XF86XK_AudioPause: 0x1008FF31, + XF86XK_AudioMedia: 0x1008FF32, + XF86XK_MyComputer: 0x1008FF33, + XF86XK_VendorHome: 0x1008FF34, + XF86XK_LightBulb: 0x1008FF35, + XF86XK_Shop: 0x1008FF36, + XF86XK_History: 0x1008FF37, + XF86XK_OpenURL: 0x1008FF38, + XF86XK_AddFavorite: 0x1008FF39, + XF86XK_HotLinks: 0x1008FF3A, + XF86XK_BrightnessAdjust: 0x1008FF3B, + XF86XK_Finance: 0x1008FF3C, + XF86XK_Community: 0x1008FF3D, + XF86XK_AudioRewind: 0x1008FF3E, + XF86XK_BackForward: 0x1008FF3F, + XF86XK_Launch0: 0x1008FF40, + XF86XK_Launch1: 0x1008FF41, + XF86XK_Launch2: 0x1008FF42, + XF86XK_Launch3: 0x1008FF43, + XF86XK_Launch4: 0x1008FF44, + XF86XK_Launch5: 0x1008FF45, + XF86XK_Launch6: 0x1008FF46, + XF86XK_Launch7: 0x1008FF47, + XF86XK_Launch8: 0x1008FF48, + XF86XK_Launch9: 0x1008FF49, + XF86XK_LaunchA: 0x1008FF4A, + XF86XK_LaunchB: 0x1008FF4B, + XF86XK_LaunchC: 0x1008FF4C, + XF86XK_LaunchD: 0x1008FF4D, + XF86XK_LaunchE: 0x1008FF4E, + XF86XK_LaunchF: 0x1008FF4F, + XF86XK_ApplicationLeft: 0x1008FF50, + XF86XK_ApplicationRight: 0x1008FF51, + XF86XK_Book: 0x1008FF52, + XF86XK_CD: 0x1008FF53, + XF86XK_Calculater: 0x1008FF54, + XF86XK_Clear: 0x1008FF55, + XF86XK_Close: 0x1008FF56, + XF86XK_Copy: 0x1008FF57, + XF86XK_Cut: 0x1008FF58, + XF86XK_Display: 0x1008FF59, + XF86XK_DOS: 0x1008FF5A, + XF86XK_Documents: 0x1008FF5B, + XF86XK_Excel: 0x1008FF5C, + XF86XK_Explorer: 0x1008FF5D, + XF86XK_Game: 0x1008FF5E, + XF86XK_Go: 0x1008FF5F, + XF86XK_iTouch: 0x1008FF60, + XF86XK_LogOff: 0x1008FF61, + XF86XK_Market: 0x1008FF62, + XF86XK_Meeting: 0x1008FF63, + XF86XK_MenuKB: 0x1008FF65, + XF86XK_MenuPB: 0x1008FF66, + XF86XK_MySites: 0x1008FF67, + XF86XK_New: 0x1008FF68, + XF86XK_News: 0x1008FF69, + XF86XK_OfficeHome: 0x1008FF6A, + XF86XK_Open: 0x1008FF6B, + XF86XK_Option: 0x1008FF6C, + XF86XK_Paste: 0x1008FF6D, + XF86XK_Phone: 0x1008FF6E, + XF86XK_Q: 0x1008FF70, + XF86XK_Reply: 0x1008FF72, + XF86XK_Reload: 0x1008FF73, + XF86XK_RotateWindows: 0x1008FF74, + XF86XK_RotationPB: 0x1008FF75, + XF86XK_RotationKB: 0x1008FF76, + XF86XK_Save: 0x1008FF77, + XF86XK_ScrollUp: 0x1008FF78, + XF86XK_ScrollDown: 0x1008FF79, + XF86XK_ScrollClick: 0x1008FF7A, + XF86XK_Send: 0x1008FF7B, + XF86XK_Spell: 0x1008FF7C, + XF86XK_SplitScreen: 0x1008FF7D, + XF86XK_Support: 0x1008FF7E, + XF86XK_TaskPane: 0x1008FF7F, + XF86XK_Terminal: 0x1008FF80, + XF86XK_Tools: 0x1008FF81, + XF86XK_Travel: 0x1008FF82, + XF86XK_UserPB: 0x1008FF84, + XF86XK_User1KB: 0x1008FF85, + XF86XK_User2KB: 0x1008FF86, + XF86XK_Video: 0x1008FF87, + XF86XK_WheelButton: 0x1008FF88, + XF86XK_Word: 0x1008FF89, + XF86XK_Xfer: 0x1008FF8A, + XF86XK_ZoomIn: 0x1008FF8B, + XF86XK_ZoomOut: 0x1008FF8C, + XF86XK_Away: 0x1008FF8D, + XF86XK_Messenger: 0x1008FF8E, + XF86XK_WebCam: 0x1008FF8F, + XF86XK_MailForward: 0x1008FF90, + XF86XK_Pictures: 0x1008FF91, + XF86XK_Music: 0x1008FF92, + XF86XK_Battery: 0x1008FF93, + XF86XK_Bluetooth: 0x1008FF94, + XF86XK_WLAN: 0x1008FF95, + XF86XK_UWB: 0x1008FF96, + XF86XK_AudioForward: 0x1008FF97, + XF86XK_AudioRepeat: 0x1008FF98, + XF86XK_AudioRandomPlay: 0x1008FF99, + XF86XK_Subtitle: 0x1008FF9A, + XF86XK_AudioCycleTrack: 0x1008FF9B, + XF86XK_CycleAngle: 0x1008FF9C, + XF86XK_FrameBack: 0x1008FF9D, + XF86XK_FrameForward: 0x1008FF9E, + XF86XK_Time: 0x1008FF9F, + XF86XK_Select: 0x1008FFA0, + XF86XK_View: 0x1008FFA1, + XF86XK_TopMenu: 0x1008FFA2, + XF86XK_Red: 0x1008FFA3, + XF86XK_Green: 0x1008FFA4, + XF86XK_Yellow: 0x1008FFA5, + XF86XK_Blue: 0x1008FFA6, + XF86XK_Suspend: 0x1008FFA7, + XF86XK_Hibernate: 0x1008FFA8, + XF86XK_TouchpadToggle: 0x1008FFA9, + XF86XK_TouchpadOn: 0x1008FFB0, + XF86XK_TouchpadOff: 0x1008FFB1, + XF86XK_AudioMicMute: 0x1008FFB2, + XF86XK_Switch_VT_1: 0x1008FE01, + XF86XK_Switch_VT_2: 0x1008FE02, + XF86XK_Switch_VT_3: 0x1008FE03, + XF86XK_Switch_VT_4: 0x1008FE04, + XF86XK_Switch_VT_5: 0x1008FE05, + XF86XK_Switch_VT_6: 0x1008FE06, + XF86XK_Switch_VT_7: 0x1008FE07, + XF86XK_Switch_VT_8: 0x1008FE08, + XF86XK_Switch_VT_9: 0x1008FE09, + XF86XK_Switch_VT_10: 0x1008FE0A, + XF86XK_Switch_VT_11: 0x1008FE0B, + XF86XK_Switch_VT_12: 0x1008FE0C, + XF86XK_Ungrab: 0x1008FE20, + XF86XK_ClearGrab: 0x1008FE21, + XF86XK_Next_VMode: 0x1008FE22, + XF86XK_Prev_VMode: 0x1008FE23, + XF86XK_LogWindowTree: 0x1008FE24, + XF86XK_LogGrabInfo: 0x1008FE25, }; diff --git a/core/input/util.js b/core/input/util.js index 18867af8..b8fd83ab 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -1,6 +1,7 @@ import KeyTable from "./keysym.js"; import keysyms from "./keysymdef.js"; import vkeys from "./vkeys.js"; +import fixedkeys from "./fixedkeys.js"; function isMac() { return navigator && !!(/mac/i).exec(navigator.platform); @@ -12,29 +13,6 @@ function isLinux() { return navigator && !!(/linux/i).exec(navigator.platform); } -// Return true if a modifier which is not the specified char modifier (and is not shift) is down -export function hasShortcutModifier(charModifier, currentModifiers) { - var mods = {}; - for (var key in currentModifiers) { - if (parseInt(key) !== KeyTable.XK_Shift_L) { - mods[key] = currentModifiers[key]; - } - } - - var sum = 0; - for (var k in currentModifiers) { - if (mods[k]) { - ++sum; - } - } - if (hasCharModifier(charModifier, mods)) { - return sum > charModifier.length; - } - else { - return sum > 0; - } -} - // Return true if the specified char modifier is currently down export function hasCharModifier(charModifier, currentModifiers) { if (charModifier.length === 0) { return false; } @@ -125,8 +103,6 @@ export function ModifierSync(charModifier) { // Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway syncAny: function(evt) { return sync(evt);}, - // is a shortcut modifier down? - hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); }, // if a char modifier is down, return the keys it consists of, otherwise return null activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; } }; @@ -193,15 +169,83 @@ export function getKeycode(evt){ } // Get the most reliable keysym value we can get from a key event -// if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which export function getKeysym(evt){ + + // We start with layout independent keys + var code = getKeycode(evt); + if (code in fixedkeys) { + return fixedkeys[code]; + } + + // Next with mildly layout or state sensitive stuff + + // Like AltGraph + if (code === 'AltRight') { + if (evt.key === 'AltGraph') { + return KeyTable.XK_ISO_Level3_Shift; + } else { + return KeyTable.XK_Alt_R; + } + } + + // Or the numpad + if (evt.location === 3) { + var key = evt.key; + + // IE and Edge use some ancient version of the spec + // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/ + switch (key) { + case 'Up': key = 'ArrowUp'; break; + case 'Left': key = 'ArrowLeft'; break; + case 'Right': key = 'ArrowRight'; break; + case 'Down': key = 'ArrowDown'; break; + case 'Del': key = 'Delete'; break; + } + + // Safari doesn't support KeyboardEvent.key yet + if ((key === undefined) && (evt.charCode)) { + key = String.fromCharCode(evt.charCode); + } + + switch (key) { + case '0': return KeyTable.XK_KP_0; + case '1': return KeyTable.XK_KP_1; + case '2': return KeyTable.XK_KP_2; + case '3': return KeyTable.XK_KP_3; + case '4': return KeyTable.XK_KP_4; + case '5': return KeyTable.XK_KP_5; + case '6': return KeyTable.XK_KP_6; + case '7': return KeyTable.XK_KP_7; + case '8': return KeyTable.XK_KP_8; + case '9': return KeyTable.XK_KP_9; + // There is utter mayhem in the world when it comes to which + // character to use as a decimal separator... + case '.': return KeyTable.XK_KP_Decimal; + case ',': return KeyTable.XK_KP_Separator; + case 'Home': return KeyTable.XK_KP_Home; + case 'End': return KeyTable.XK_KP_End; + case 'PageUp': return KeyTable.XK_KP_Prior; + case 'PageDown': return KeyTable.XK_KP_Next; + case 'Insert': return KeyTable.XK_KP_Insert; + case 'Delete': return KeyTable.XK_KP_Delete; + case 'ArrowUp': return KeyTable.XK_KP_Up; + case 'ArrowLeft': return KeyTable.XK_KP_Left; + case 'ArrowRight': return KeyTable.XK_KP_Right; + case 'ArrowDown': return KeyTable.XK_KP_Down; + } + } + + // Now we need to look at the Unicode symbol instead + var codepoint; if ('key' in evt) { - // Ignore special keys - if (evt.key.length === 1) { - codepoint = evt.key.charCodeAt(); + // Special key? (FIXME: Should have been caught earlier) + if (evt.key.length !== 1) { + return null; } + + codepoint = evt.key.charCodeAt(); } else if ('charCode' in evt) { codepoint = evt.charCode; } @@ -210,94 +254,9 @@ export function getKeysym(evt){ return keysyms.lookup(codepoint); } - if (evt.keyCode) { - return keysymFromKeyCode(evt.keyCode, evt.shiftKey); - } - if (evt.which) { - return keysymFromKeyCode(evt.which, evt.shiftKey); - } return null; } -// Given a keycode, try to predict which keysym it might be. -// If the keycode is unknown, null is returned. -function keysymFromKeyCode(keycode, shiftPressed) { - if (typeof(keycode) !== 'number') { - return null; - } - // won't be accurate for azerty - if (keycode >= 0x30 && keycode <= 0x39) { - return keycode; // digit - } - if (keycode >= 0x41 && keycode <= 0x5a) { - // remap to lowercase unless shift is down - return shiftPressed ? keycode : keycode + 32; // A-Z - } - if (keycode >= 0x60 && keycode <= 0x69) { - return KeyTable.XK_KP_0 + (keycode - 0x60); // numpad 0-9 - } - - switch(keycode) { - case 0x20: return KeyTable.XK_space; - case 0x6a: return KeyTable.XK_KP_Multiply; - case 0x6b: return KeyTable.XK_KP_Add; - case 0x6c: return KeyTable.XK_KP_Separator; - case 0x6d: return KeyTable.XK_KP_Subtract; - case 0x6e: return KeyTable.XK_KP_Decimal; - case 0x6f: return KeyTable.XK_KP_Divide; - case 0xbb: return KeyTable.XK_plus; - case 0xbc: return KeyTable.XK_comma; - case 0xbd: return KeyTable.XK_minus; - case 0xbe: return KeyTable.XK_period; - } - - return nonCharacterKey({keyCode: keycode}); -} - -// if the key is a known non-character key (any key which doesn't generate character data) -// return its keysym value. Otherwise return null -function nonCharacterKey(evt) { - // evt.key not implemented yet - if (!evt.keyCode) { return null; } - var keycode = evt.keyCode; - - if (keycode >= 0x70 && keycode <= 0x87) { - return KeyTable.XK_F1 + keycode - 0x70; // F1-F24 - } - switch (keycode) { - - case 8 : return KeyTable.XK_BackSpace; - case 13 : return KeyTable.XK_Return; - - case 9 : return KeyTable.XK_Tab; - - case 27 : return KeyTable.XK_Escape; - case 46 : return KeyTable.XK_Delete; - - case 36 : return KeyTable.XK_Home; - case 35 : return KeyTable.XK_End; - case 33 : return KeyTable.XK_Page_Up; - case 34 : return KeyTable.XK_Page_Down; - case 45 : return KeyTable.XK_Insert; - - case 37 : return KeyTable.XK_Left; - case 38 : return KeyTable.XK_Up; - case 39 : return KeyTable.XK_Right; - case 40 : return KeyTable.XK_Down; - - case 16 : return KeyTable.XK_Shift_L; - case 17 : return KeyTable.XK_Control_L; - case 18 : return KeyTable.XK_Alt_L; // also: Option-key on Mac - - case 224 : return KeyTable.XK_Meta_L; - case 225 : return KeyTable.XK_ISO_Level3_Shift; // AltGr - case 91 : return KeyTable.XK_Super_L; // also: Windows-key - case 92 : return KeyTable.XK_Super_R; // also: Windows-key - case 93 : return KeyTable.XK_Menu; // also: Windows-Menu, Command on Mac - default: return null; - } -} - export function QEMUKeyEventDecoder (modifierState, next) { "use strict"; @@ -341,10 +300,7 @@ export function QEMUKeyEventDecoder (modifierState, next) { result.keysym = getNumPadKeySym(evt); } - var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier(); - var isShift = result.code === 'ShiftLeft' || result.code === 'ShiftRight'; - - var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt)); + var suppress = type !== 'keydown' || !!getKeysym(evt); next(result); return suppress; @@ -457,20 +413,17 @@ export function KeyEventDecoder (modifierState, next) { var keysym = getKeysym(evt); - var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier(); // Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress? // "special" keys like enter, tab or backspace don't send keypress events, // and some browsers don't send keypresses at all if a modifier is down - if (keysym && (type !== 'keydown' || nonCharacterKey(evt) || hasModifier)) { + if (keysym) { result.keysym = keysym; } - var isShift = code === 'ShiftLeft' || code === 'ShiftRight'; - // Should we prevent the browser from handling the event? // Doing so on a keydown (in most browsers) prevents keypress from being generated // so only do that if we have to. - var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt)); + var suppress = type !== 'keydown' || !!keysym; // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt) var active = modifierState.activeCharModifier(); diff --git a/tests/test.helper.js b/tests/test.helper.js index b66c5e57..ce742f0a 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -108,26 +108,50 @@ describe('Helpers', function() { describe('Non-character keys', function() { it('should recognize the right keys', function() { - expect(KeyboardUtil.getKeysym({keyCode: 0x0d})).to.be.equal(0xFF0D); - expect(KeyboardUtil.getKeysym({keyCode: 0x08})).to.be.equal(0xFF08); - expect(KeyboardUtil.getKeysym({keyCode: 0x09})).to.be.equal(0xFF09); - expect(KeyboardUtil.getKeysym({keyCode: 0x10})).to.be.equal(0xFFE1); - expect(KeyboardUtil.getKeysym({keyCode: 0x11})).to.be.equal(0xFFE3); - expect(KeyboardUtil.getKeysym({keyCode: 0x12})).to.be.equal(0xFFE9); - expect(KeyboardUtil.getKeysym({keyCode: 0xe0})).to.be.equal(0xFFE7); - expect(KeyboardUtil.getKeysym({keyCode: 0xe1})).to.be.equal(0xFE03); - expect(KeyboardUtil.getKeysym({keyCode: 0x1b})).to.be.equal(0xFF1B); - expect(KeyboardUtil.getKeysym({keyCode: 0x26})).to.be.equal(0xFF52); + expect(KeyboardUtil.getKeysym({code: 'Enter'})).to.be.equal(0xFF0D); + expect(KeyboardUtil.getKeysym({code: 'Backspace'})).to.be.equal(0xFF08); + expect(KeyboardUtil.getKeysym({code: 'Tab'})).to.be.equal(0xFF09); + expect(KeyboardUtil.getKeysym({code: 'ShiftLeft'})).to.be.equal(0xFFE1); + expect(KeyboardUtil.getKeysym({code: 'ControlLeft'})).to.be.equal(0xFFE3); + expect(KeyboardUtil.getKeysym({code: 'AltLeft'})).to.be.equal(0xFFE9); + expect(KeyboardUtil.getKeysym({code: 'MetaLeft'})).to.be.equal(0xFFEB); + expect(KeyboardUtil.getKeysym({code: 'Escape'})).to.be.equal(0xFF1B); + expect(KeyboardUtil.getKeysym({code: 'ArrowUp'})).to.be.equal(0xFF52); }); - it('should return null for unknown keycodes', function() { - expect(KeyboardUtil.getKeysym({keyCode: 0xc0})).to.be.null; - expect(KeyboardUtil.getKeysym({keyCode: 0xde})).to.be.null; + it('should handle AltGraph', function() { + expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltRight'})).to.be.equal(0xFFEA); + expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph'})).to.be.equal(0xFE03); + }); + it('should return null for unknown codes', function() { + expect(KeyboardUtil.getKeysym({code: 'Semicolon'})).to.be.null; + expect(KeyboardUtil.getKeysym({code: 'BracketRight'})).to.be.null; }); it('should not recognize character keys', function() { - expect(KeyboardUtil.getKeysym({keyCode: 'A'})).to.be.null; - expect(KeyboardUtil.getKeysym({keyCode: '1'})).to.be.null; - expect(KeyboardUtil.getKeysym({keyCode: '.'})).to.be.null; - expect(KeyboardUtil.getKeysym({keyCode: ' '})).to.be.null; + expect(KeyboardUtil.getKeysym({code: 'KeyA'})).to.be.null; + expect(KeyboardUtil.getKeysym({code: 'Digit1'})).to.be.null; + expect(KeyboardUtil.getKeysym({code: 'Period'})).to.be.null; + expect(KeyboardUtil.getKeysym({code: 'Numpad1'})).to.be.null; + }); + }); + + describe('Numpad', function() { + it('should handle Numpad numbers', function() { + expect(KeyboardUtil.getKeysym({code: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035); + expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5); + }); + it('should handle Numpad non-character keys', function() { + expect(KeyboardUtil.getKeysym({code: 'Home', key: 'Home', location: 0})).to.be.equal(0xFF50); + expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: 'Home', location: 3})).to.be.equal(0xFF95); + expect(KeyboardUtil.getKeysym({code: 'Delete', key: 'Delete', location: 0})).to.be.equal(0xFFFF); + expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F); + }); + it('should handle IE/Edge key names', function() { + expect(KeyboardUtil.getKeysym({code: 'Numpad6', key: 'Right', location: 3})).to.be.equal(0xFF98); + expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Del', location: 3})).to.be.equal(0xFF9F); + }); + it('should handle Numpad Decimal key', function() { + expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE); + expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC); }); }); }); @@ -137,7 +161,7 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it ('should do nothing if all modifiers are up as expected', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: false, altKey: false, altGraphKey: false, @@ -147,7 +171,7 @@ describe('Helpers', function() { }); it ('should synthesize events if all keys are unexpectedly down', function() { var result = sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: true, altKey: true, altGraphKey: true, @@ -167,7 +191,7 @@ describe('Helpers', function() { }); it ('should do nothing if all modifiers are down as expected', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: true, altKey: true, altGraphKey: true, @@ -180,13 +204,13 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it('should sync if modifier is suddenly down', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: true, })).to.be.deep.equal([{keysym: 0xffe3, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: false })).to.be.deep.equal([{keysym: 0xffe3, type: 'keyup'}]); }); @@ -195,13 +219,13 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it('should sync if modifier is suddenly down', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', altKey: true, })).to.be.deep.equal([{keysym: 0xffe9, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', altKey: false })).to.be.deep.equal([{keysym: 0xffe9, type: 'keyup'}]); }); @@ -210,13 +234,13 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it('should sync if modifier is suddenly down', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', altGraphKey: true, })).to.be.deep.equal([{keysym: 0xfe03, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', altGraphKey: false })).to.be.deep.equal([{keysym: 0xfe03, type: 'keyup'}]); }); @@ -225,13 +249,13 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it('should sync if modifier is suddenly down', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', shiftKey: true, })).to.be.deep.equal([{keysym: 0xffe1, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', shiftKey: false })).to.be.deep.equal([{keysym: 0xffe1, type: 'keyup'}]); }); @@ -240,13 +264,13 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it('should sync if modifier is suddenly down', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', metaKey: true, })).to.be.deep.equal([{keysym: 0xffe7, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', metaKey: false })).to.be.deep.equal([{keysym: 0xffe7, type: 'keyup'}]); }); @@ -254,27 +278,27 @@ describe('Helpers', function() { describe('Modifier keyevents', function() { it('should not sync a modifier on its own events', function() { expect(KeyboardUtil.ModifierSync().keydown({ - keyCode: 0x11, + code: 'ControlLeft', ctrlKey: false })).to.be.deep.equal([]); expect(KeyboardUtil.ModifierSync().keydown({ - keyCode: 0x11, + code: 'ControlLeft', ctrlKey: true }), 'B').to.be.deep.equal([]); }) it('should update state on modifier keyevents', function() { var sync = KeyboardUtil.ModifierSync(); sync.keydown({ - keyCode: 0x11, + code: 'ControlLeft', }); expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: true, })).to.be.deep.equal([]); }); it('should sync other modifiers on ctrl events', function() { expect(KeyboardUtil.ModifierSync().keydown({ - keyCode: 0x11, + code: 'ControlLeft', altKey: true })).to.be.deep.equal([{keysym: 0xffe9, type: 'keydown'}]); }) @@ -287,9 +311,6 @@ describe('Helpers', function() { }); }); describe('do not treat shift as a modifier key', function() { - it('should not treat shift as a shortcut modifier', function() { - expect(KeyboardUtil.hasShortcutModifier([], {0xffe1 : true})).to.be.false; - }); it('should not treat shift as a char modifier', function() { expect(KeyboardUtil.hasCharModifier([], {0xffe1 : true})).to.be.false; }); diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index f29a3394..19a8a66a 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -51,7 +51,7 @@ describe('Key Event Pipeline Stages', function() { }); it('should forward keydown events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); + expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); done(); }).keydown({code: 'KeyA', key: 'a'}); }); @@ -71,65 +71,57 @@ describe('Key Event Pipeline Stages', function() { it('should suppress anything while a shortcut modifier is down', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - obj.keydown({keyCode: 0x11}); // press ctrl - expect(obj.keydown({key: 'A'})).to.be.true; - expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true; - expect(obj.keydown({key: '1'})).to.be.true; - expect(obj.keydown({key: '<'})).to.be.true; - expect(obj.keydown({key: 'ø'})).to.be.true; + obj.keydown({code: 'ControlLeft'}); + expect(obj.keydown({code: 'KeyA', key: 'a'})).to.be.true; + expect(obj.keydown({code: 'Space', key: ' '})).to.be.true; + expect(obj.keydown({code: 'Digit1', key: '1'})).to.be.true; + expect(obj.keydown({code: 'IntlBackslash', key: '<'})).to.be.true; + expect(obj.keydown({code: 'Semicolon', key: 'ø'})).to.be.true; }); it('should suppress non-character keys', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - expect(obj.keydown({keyCode: 0x08}), 'a').to.be.true; - expect(obj.keydown({keyCode: 0x09}), 'b').to.be.true; - expect(obj.keydown({keyCode: 0x11}), 'd').to.be.true; - expect(obj.keydown({keyCode: 0x12}), 'e').to.be.true; - }); - it('should not suppress shift', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - - expect(obj.keydown({keyCode: 0x10}), 'd').to.be.false; + expect(obj.keydown({code: 'Backspace'}), 'a').to.be.true; + expect(obj.keydown({code: 'Tab'}), 'b').to.be.true; + expect(obj.keydown({code: 'ControlLeft'}), 'd').to.be.true; + expect(obj.keydown({code: 'AltLeft'}), 'e').to.be.true; }); it('should generate event for shift keydown', function() { var called = false; var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.have.property('keysym'); called = true; - }).keydown({keyCode: 0x10}); + }).keydown({code: 'ShiftLeft'}); expect(called).to.be.true; }); - it('should not suppress character keys', function() { + it('should suppress character keys with key', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - expect(obj.keydown({key: 'A'})).to.be.false; - expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; - expect(obj.keydown({key: '1'})).to.be.false; - expect(obj.keydown({key: '<'})).to.be.false; // < key on DK Windows - expect(obj.keydown({key: 'ø'})).to.be.false; // Ø key on DK + expect(obj.keydown({code: 'KeyA', key: 'a'})).to.be.true; + expect(obj.keydown({code: 'Digit1', key: '1'})).to.be.true; + expect(obj.keydown({code: 'IntlBackslash', key: '<'})).to.be.true; + expect(obj.keydown({code: 'Semicolon', key: 'ø'})).to.be.true; }); - it('should not suppress if a char modifier is down', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {}); + it('should not suppress character keys without key', function() { + var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - obj.keydown({keyCode: 0xe1}); // press altgr - expect(obj.keydown({key: 'A'})).to.be.false; - expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; - expect(obj.keydown({key: '1'})).to.be.false; - expect(obj.keydown({key: '<'})).to.be.false; - expect(obj.keydown({key: 'ø'})).to.be.false; + expect(obj.keydown({code: 'KeyA'})).to.be.false; + expect(obj.keydown({code: 'Digit1'})).to.be.false; + expect(obj.keydown({code: 'IntlBackslash'})).to.be.false; + expect(obj.keydown({code: 'Semicolon'})).to.be.false; }); }); describe('Keypress and keyup events', function() { it('should always suppress event propagation', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - expect(obj.keypress({key: 'A'})).to.be.true; - expect(obj.keypress({key: '<'})).to.be.true; - expect(obj.keypress({keyCode: 0x11})).to.be.true; + expect(obj.keypress({code: 'KeyA', key: 'a'})).to.be.true; + expect(obj.keypress({code: 'IntlBackslash', key: '<'})).to.be.true; + expect(obj.keypress({code: 'ControlLeft', key: 'Control'})).to.be.true; - expect(obj.keyup({key: 'A'})).to.be.true; - expect(obj.keyup({key: '<'})).to.be.true; - expect(obj.keyup({keyCode: 0x11})).to.be.true; + expect(obj.keyup({code: 'KeyA', key: 'a'})).to.be.true; + expect(obj.keyup({code: 'IntlBackslash', key: '<'})).to.be.true; + expect(obj.keyup({code: 'ControlLeft', key: 'Control'})).to.be.true; }); }); describe('mark events if a char modifier is down', function() { @@ -145,7 +137,7 @@ describe('Key Event Pipeline Stages', function() { } }); - obj.keydown({keyCode: 0xe1}); // press altgr + obj.keydown({code: 'AltRight', key: 'AltGraph'}) obj.keydown({code: 'KeyA', key: 'a'}); }); @@ -167,7 +159,7 @@ describe('Key Event Pipeline Stages', function() { } }); - obj.keydown({keyCode: 0xe1}); // press altgr + obj.keydown({code: 'AltRight', key: 'AltGraph'}) obj.keypress({code: 'KeyA', key: 'a'}); }); it('should indicate on events if a multi-key char modifier is down', function(done) { @@ -190,8 +182,8 @@ describe('Key Event Pipeline Stages', function() { } }); - obj.keydown({keyCode: 0x11}); // press ctrl - obj.keydown({keyCode: 0x12}); // press alt + obj.keydown({code: 'ControlLeft'}); + obj.keydown({code: 'AltLeft'}); obj.keypress({code: 'KeyA', key: 'a'}); }); it('should not consider a char modifier to be down on the modifier key itself', function() { @@ -199,60 +191,10 @@ describe('Key Event Pipeline Stages', function() { expect(evt).to.not.have.property('escape'); }); - obj.keydown({keyCode: 0xe1}); // press altgr + obj.keydown({code: 'AltRight', key: 'AltGraph'}) }); }); - describe('add/remove keysym', function() { - it('should remove keysym from keydown if a char key and no modifier', function() { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); - }).keydown({code: 'KeyA', key: 'a'}); - }); - it('should not remove keysym from keydown if a shortcut modifier is down', function() { - var times_called = 0; - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - switch (times_called++) { - case 1: - expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); - break; - } - }).keydown({code: 'KeyA', key: 'a', ctrlKey: true}); - expect(times_called).to.be.equal(2); - }); - it('should not remove keysym from keydown if a char modifier is down', function() { - var times_called = 0; - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { - switch (times_called++) { - case 1: - expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); - break; - } - }).keydown({code: 'KeyA', key: 'a', altGraphKey: true}); - expect(times_called).to.be.equal(2); - }); - it('should not remove keysym from keydown if key is noncharacter', function() { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt, 'tab').to.be.deep.equal({code: 'Tab', keysym: 0xff09, type: 'keydown'}); - }).keydown({keyCode: 0x09}); - - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt, 'ctrl').to.be.deep.equal({code: 'ControlLeft', keysym: 0xffe3, type: 'keydown'}); - }).keydown({keyCode: 0x11}); - }); - it('should never remove keysym from keypress', function() { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'}); - }).keypress({code: 'KeyA', key: 'a'}); - }); - it('should never remove keysym from keyup', function() { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'}); - }).keyup({code: 'KeyA', key: 'a'}); - }); - }); - // on keypress, keyup(?), always set keysym - // on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down }); describe('Track Key State', function() { diff --git a/tests/vnc_perf.html b/tests/vnc_perf.html index 36331eb1..acae0d15 100644 --- a/tests/vnc_perf.html +++ b/tests/vnc_perf.html @@ -51,7 +51,7 @@ 'core': ["base64.js", "websock.js", "des.js", "input/keysym.js", "input/keysymdef.js", "input/xtscancodes.js", "input/util.js", "input/devices.js", "display.js", "rfb.js", "inflator.js", - "input/vkeys.js"], + "input/vkeys.js", "input/fixedkeys.js"], 'tests': ["playback.js"], 'recordings': [fname]}); } else { From 94f5cf05f37191ec028781bd486fbf22a460e515 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 26 Jan 2017 17:59:25 +0100 Subject: [PATCH 10/20] Send keyboard events from single place This makes it easier to handle any needed variations, like different types of messages. --- app/ui.js | 12 +++++----- core/rfb.js | 57 ++++++++++++++++++++++++++--------------------- tests/test.rfb.js | 18 +++++++++++---- 3 files changed, 51 insertions(+), 36 deletions(-) diff --git a/app/ui.js b/app/ui.js index cd0901b3..b53aaeaa 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1516,7 +1516,7 @@ const UI = { // Send the key events for (i = 0; i < backspaces; i++) { - UI.rfb.sendKey(KeyTable.XK_BackSpace); + UI.rfb.sendKey(KeyTable.XK_BackSpace, "Backspace"); } for (i = newLen - inputs; i < newLen; i++) { UI.rfb.sendKey(keysyms.lookup(newValue.charCodeAt(i))); @@ -1573,7 +1573,7 @@ const UI = { }, sendEsc: function() { - UI.rfb.sendKey(KeyTable.XK_Escape); + UI.rfb.sendKey(KeyTable.XK_Escape, "Escape"); }, sendTab: function() { @@ -1583,10 +1583,10 @@ const UI = { toggleCtrl: function() { var btn = document.getElementById('noVNC_toggle_ctrl_button'); if (btn.classList.contains("noVNC_selected")) { - UI.rfb.sendKey(KeyTable.XK_Control_L, false); + UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false); btn.classList.remove("noVNC_selected"); } else { - UI.rfb.sendKey(KeyTable.XK_Control_L, true); + UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true); btn.classList.add("noVNC_selected"); } }, @@ -1594,10 +1594,10 @@ const UI = { toggleAlt: function() { var btn = document.getElementById('noVNC_toggle_alt_button'); if (btn.classList.contains("noVNC_selected")) { - UI.rfb.sendKey(KeyTable.XK_Alt_L, false); + UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false); btn.classList.remove("noVNC_selected"); } else { - UI.rfb.sendKey(KeyTable.XK_Alt_L, true); + UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true); btn.classList.add("noVNC_selected"); } }, diff --git a/core/rfb.js b/core/rfb.js index 9afe9e59..7f55123c 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -298,12 +298,13 @@ RFB.prototype = { if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; } Log.Info("Sending Ctrl-Alt-Del"); - RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 1); - RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 1); - RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 1); - RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 0); - RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 0); - RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 0); + this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true); + this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true); + this.sendKey(KeyTable.XK_Delete, "Delete", true); + this.sendKey(KeyTable.XK_Delete, "Delete", false); + this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false); + this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false); + return true; }, @@ -328,16 +329,33 @@ RFB.prototype = { // Send a key press. If 'down' is not specified then send a down key // followed by an up key. - sendKey: function (keysym, down) { + sendKey: function (keysym, code, down) { if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; } - if (typeof down !== 'undefined') { + + if (down === undefined) { + this.sendKey(keysym, code, true); + this.sendKey(keysym, code, false); + return true; + } + + if (this._qemuExtKeyEventSupported) { + var scancode = XtScancode[code]; + + if (scancode === undefined) { + Log.Error('Unable to find a xt scancode for code: ' + code); + // FIXME: not in the spec, but this is what + // gtk-vnc does + scancode = 0; + } + + Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode); + + RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode); + } else { Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym); RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0); - } else { - Log.Info("Sending keysym (down + up): " + keysym); - RFB.messages.keyEvent(this._sock, keysym, 1); - RFB.messages.keyEvent(this._sock, keysym, 0); } + return true; }, @@ -647,21 +665,8 @@ RFB.prototype = { }, _handleKeyPress: function (keyevent) { - if (this._view_only) { return; } // View only, skip keyboard, events - var down = (keyevent.type == 'keydown'); - if (this._qemuExtKeyEventSupported) { - var scancode = XtScancode[keyevent.code]; - if (scancode) { - var keysym = keyevent.keysym; - RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode); - } else { - Log.Error('Unable to find a xt scancode for code = ' + keyevent.code); - } - } else { - keysym = keyevent.keysym; - RFB.messages.keyEvent(this._sock, keysym, down); - } + this.sendKey(keyevent.keysym, keyevent.code, down); }, _handleMouseButton: function (x, y, down, bmask) { diff --git a/tests/test.rfb.js b/tests/test.rfb.js index c8035f40..70504a5f 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -193,7 +193,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send a single key with the given code and state (down = true)', function () { var expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(expected, 123, 1); - client.sendKey(123, true); + client.sendKey(123, 'Key123', true); expect(client._sock).to.have.sent(expected._sQ); }); @@ -201,21 +201,29 @@ describe('Remote Frame Buffer Protocol Client', function() { var expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(expected, 123, 1); RFB.messages.keyEvent(expected, 123, 0); - client.sendKey(123); + client.sendKey(123, 'Key123'); expect(client._sock).to.have.sent(expected._sQ); }); it('should not send the key if we are not in a normal state', function () { client._rfb_connection_state = "broken"; - client.sendKey(123); + client.sendKey(123, 'Key123'); expect(client._sock.flush).to.not.have.been.called; }); it('should not send the key if we are set as view_only', function () { client._view_only = true; - client.sendKey(123); + client.sendKey(123, 'Key123'); expect(client._sock.flush).to.not.have.been.called; }); + + it('should send QEMU extended events if supported', function () { + client._qemuExtKeyEventSupported = true; + var expected = {_sQ: new Uint8Array(12), _sQlen: 0, flush: function () {}}; + RFB.messages.QEMUExtendedKeyEvent(expected, 0x20, true, 0x0039); + client.sendKey(0x20, 'Space', true); + expect(client._sock).to.have.sent(expected._sQ); + }); }); describe('#clipboardPasteFrom', function () { @@ -2012,6 +2020,8 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock.open('ws://', 'binary'); client._sock._websocket._open(); sinon.spy(client._sock, 'flush'); + client._rfb_connection_state = 'connected'; + client._view_only = false; }); it('should send a key message on a key press', function () { From d0703d1bdebe610261bc492e2c23877d359e5eb3 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 26 Jan 2017 18:09:40 +0100 Subject: [PATCH 11/20] Simplify keyboard event API No need for an object for three static fields. --- core/input/devices.js | 8 ++++---- core/rfb.js | 7 +++---- tests/input.html | 6 +++--- tests/test.rfb.js | 8 +++----- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/core/input/devices.js b/core/input/devices.js index c68966b3..c92c6a34 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -47,10 +47,10 @@ Keyboard.prototype = { // private methods _handleRfbEvent: function (e) { - if (this._onKeyPress) { - Log.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") + + if (this._onKeyEvent) { + Log.Debug("onKeyEvent " + (e.type == 'keydown' ? "down" : "up") + ", keysym: " + e.keysym); - this._onKeyPress(e); + this._onKeyEvent(e.keysym, e.code, e.type == 'keydown'); } }, @@ -138,7 +138,7 @@ make_properties(Keyboard, [ ['target', 'wo', 'dom'], // DOM element that captures keyboard input ['focused', 'rw', 'bool'], // Capture and send key events - ['onKeyPress', 'rw', 'func'] // Handler for key press/release + ['onKeyEvent', 'rw', 'func'] // Handler for key press/release ]); const Mouse = function (defaults) { diff --git a/core/rfb.js b/core/rfb.js index 7f55123c..46b3158e 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -200,7 +200,7 @@ export default function RFB(defaults) { } this._keyboard = new Keyboard({target: this._focusContainer, - onKeyPress: this._handleKeyPress.bind(this)}); + onKeyEvent: this._handleKeyEvent.bind(this)}); this._mouse = new Mouse({target: this._target, onMouseButton: this._handleMouseButton.bind(this), @@ -664,9 +664,8 @@ RFB.prototype = { } }, - _handleKeyPress: function (keyevent) { - var down = (keyevent.type == 'keydown'); - this.sendKey(keyevent.keysym, keyevent.code, down); + _handleKeyEvent: function (keysym, code, down) { + this.sendKey(keysym, code, down); }, _handleMouseButton: function (x, y, down, bmask) { diff --git a/tests/input.html b/tests/input.html index cd31f7ed..aee9e769 100644 --- a/tests/input.html +++ b/tests/input.html @@ -62,9 +62,9 @@ //console.log(msg); } - function rfbKeyPress(keysym, down) { + function rfbKeyEvent(keysym, code, down) { var d = down ? "down" : " up "; - var msg = "RFB keypress " + d + " keysym: " + keysym; + var msg = "RFB key event " + d + " keysym: " + keysym + " code: " + code; message(msg); } function rawKey(e) { @@ -103,7 +103,7 @@ window.onload = function() { canvas = new Display({'target' : document.getElementById('canvas')}); keyboard = new Keyboard({'target': document, - 'onKeyPress': rfbKeyPress}); + 'onKeyEvent': rfbKeyEvent}); document.addEventListener('keypress', rawKey); document.addEventListener('keydown', rawKey); document.addEventListener('keyup', rawKey); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 70504a5f..4f5267f9 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -2026,17 +2026,15 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send a key message on a key press', function () { var keyevent = {}; - keyevent.type = 'keydown'; - keyevent.keysym = 1234; - client._keyboard._onKeyPress(keyevent); + client._keyboard._onKeyEvent(0x41, 'KeyA', true); var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}}; - RFB.messages.keyEvent(key_msg, 1234, 1); + RFB.messages.keyEvent(key_msg, 0x41, 1); expect(client._sock).to.have.sent(key_msg._sQ); }); it('should not send messages in view-only mode', function () { client._view_only = true; - client._keyboard._onKeyPress(1234, 1); + client._keyboard._onKeyEvent('a', 'KeyA', true); expect(client._sock.flush).to.not.have.been.called; }); }); From a784a9cabc4aef667e9ea2ea1f942a3a5f0d0ddd Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 26 Jan 2017 18:20:19 +0100 Subject: [PATCH 12/20] Remove QEMU key event handler The normal event handler provides all the necessary information now, so it is no longer needed. --- core/input/devices.js | 8 --- core/input/util.js | 128 ------------------------------------------ core/rfb.js | 1 - 3 files changed, 137 deletions(-) diff --git a/core/input/devices.js b/core/input/devices.js index c92c6a34..979756dd 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -54,14 +54,6 @@ Keyboard.prototype = { } }, - setQEMUVNCKeyboardHandler: function () { - this._handler = new KeyboardUtil.QEMUKeyEventDecoder(KeyboardUtil.ModifierSync(), - KeyboardUtil.TrackQEMUKeyState( - this._handleRfbEvent.bind(this) - ) - ); - }, - _handleKeyDown: function (e) { if (!this._focused) { return; } diff --git a/core/input/util.js b/core/input/util.js index b8fd83ab..7dea0c09 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -257,134 +257,6 @@ export function getKeysym(evt){ return null; } -export function QEMUKeyEventDecoder (modifierState, next) { - "use strict"; - - function sendAll(evts) { - for (var i = 0; i < evts.length; ++i) { - next(evts[i]); - } - } - - var numPadCodes = ["Numpad0", "Numpad1", "Numpad2", - "Numpad3", "Numpad4", "Numpad5", "Numpad6", - "Numpad7", "Numpad8", "Numpad9", "NumpadDecimal"]; - - var numLockOnKeySyms = { - "Numpad0": 0xffb0, "Numpad1": 0xffb1, "Numpad2": 0xffb2, - "Numpad3": 0xffb3, "Numpad4": 0xffb4, "Numpad5": 0xffb5, - "Numpad6": 0xffb6, "Numpad7": 0xffb7, "Numpad8": 0xffb8, - "Numpad9": 0xffb9, "NumpadDecimal": 0xffac - }; - - var numLockOnKeyCodes = [96, 97, 98, 99, 100, 101, 102, - 103, 104, 105, 108, 110]; - - function isNumPadMultiKey(evt) { - return (numPadCodes.indexOf(evt.code) !== -1); - } - - function getNumPadKeySym(evt) { - if (numLockOnKeyCodes.indexOf(evt.keyCode) !== -1) { - return numLockOnKeySyms[evt.code]; - } - return 0; - } - - function process(evt, type) { - var result = {type: type}; - result.code = getKeycode(evt); - result.keysym = 0; - - if (isNumPadMultiKey(evt)) { - result.keysym = getNumPadKeySym(evt); - } - - var suppress = type !== 'keydown' || !!getKeysym(evt); - - next(result); - return suppress; - } - return { - keydown: function(evt) { - sendAll(modifierState.keydown(evt)); - return process(evt, 'keydown'); - }, - keypress: function(evt) { - return true; - }, - keyup: function(evt) { - sendAll(modifierState.keyup(evt)); - return process(evt, 'keyup'); - }, - syncModifiers: function(evt) { - sendAll(modifierState.syncAny(evt)); - }, - releaseAll: function() { next({type: 'releaseall'}); } - }; -}; - -export function TrackQEMUKeyState (next) { - "use strict"; - var state = []; - - return function (evt) { - var last = state.length !== 0 ? state[state.length-1] : null; - - switch (evt.type) { - case 'keydown': - - if (!last || last.code !== evt.code) { - last = {code: evt.code}; - - if (state.length > 0 && state[state.length-1].code == 'ControlLeft') { - if (evt.code !== 'AltRight') { - next({code: 'ControlLeft', type: 'keydown', keysym: 0}); - } else { - state.pop(); - } - } - state.push(last); - } - if (evt.code !== 'ControlLeft') { - next(evt); - } - break; - - case 'keyup': - if (state.length === 0) { - return; - } - var idx = null; - // do we have a matching key tracked as being down? - for (var i = 0; i !== state.length; ++i) { - if (state[i].code === evt.code) { - idx = i; - break; - } - } - // if we couldn't find a match (it happens), assume it was the last key pressed - if (idx === null) { - if (evt.code === 'ControlLeft') { - return; - } - idx = state.length - 1; - } - - state.splice(idx, 1); - next(evt); - break; - case 'releaseall': - /* jshint shadow: true */ - for (var i = 0; i < state.length; ++i) { - next({code: state[i].code, keysym: 0, type: 'keyup'}); - } - /* jshint shadow: false */ - state = []; - } - }; -}; - // Takes a DOM keyboard event and: // - determines which keysym it represents // - determines a code identifying the key that was pressed (corresponding to the code/keyCode properties on the DOM event) diff --git a/core/rfb.js b/core/rfb.js index 46b3158e..c82dd23e 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -2464,7 +2464,6 @@ RFB.encodingHandlers = { var keyboardEvent = document.createEvent("keyboardEvent"); if (keyboardEvent.code !== undefined) { this._qemuExtKeyEventSupported = true; - this._keyboard.setQEMUVNCKeyboardHandler(); } }, From 9e6f71cb753405507f8977ed8cb64f11189ac4c2 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 27 Jan 2017 10:49:04 +0100 Subject: [PATCH 13/20] Remove modifier synchronisation The fields provided cannot tell us if it is the left or right version of the key that's pressed, so they are inherently unreliable. It is also not a huge problem in practice as we'll get in sync on the next press or release of the modifier. --- core/input/devices.js | 17 ----- core/input/util.js | 15 +--- core/rfb.js | 3 +- tests/test.helper.js | 161 ----------------------------------------- tests/test.keyboard.js | 23 ------ 5 files changed, 3 insertions(+), 216 deletions(-) diff --git a/core/input/devices.js b/core/input/devices.js index 979756dd..2308be9c 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -120,10 +120,6 @@ Keyboard.prototype = { //Log.Debug(">> Keyboard.ungrab"); }, - - sync: function (e) { - this._handler.syncModifiers(e); - } }; make_properties(Keyboard, [ @@ -178,10 +174,6 @@ Mouse.prototype = { _handleMouseButton: function (e, down) { if (!this._focused) { return; } - if (this._notify) { - this._notify(e); - } - var pos = this._getMousePosition(e); var bmask; @@ -248,10 +240,6 @@ Mouse.prototype = { _handleMouseWheel: function (e) { if (!this._focused) { return; } - if (this._notify) { - this._notify(e); - } - var pos = this._getMousePosition(e); if (this._onMouseButton) { @@ -278,10 +266,6 @@ Mouse.prototype = { _handleMouseMove: function (e) { if (! this._focused) { return; } - if (this._notify) { - this._notify(e); - } - var pos = this._getMousePosition(e); if (this._onMouseMove) { this._onMouseMove(pos.x, pos.y); @@ -373,7 +357,6 @@ Mouse.prototype = { make_properties(Mouse, [ ['target', 'ro', 'dom'], // DOM element that captures mouse input - ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release diff --git a/core/input/util.js b/core/input/util.js index 7dea0c09..e5363a52 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -100,8 +100,6 @@ export function ModifierSync(charModifier) { // sync on the appropriate keyboard event keydown: function(evt) { return syncKeyEvent(evt, true);}, keyup: function(evt) { return syncKeyEvent(evt, false);}, - // Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway - syncAny: function(evt) { return sync(evt);}, // if a char modifier is down, return the keys it consists of, otherwise return null activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; } @@ -260,16 +258,10 @@ export function getKeysym(evt){ // Takes a DOM keyboard event and: // - determines which keysym it represents // - determines a code identifying the key that was pressed (corresponding to the code/keyCode properties on the DOM event) -// - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down // - marks each event with an 'escape' property if a modifier was down which should be "escaped" // This information is collected into an object which is passed to the next() function. (one call per event) export function KeyEventDecoder (modifierState, next) { "use strict"; - function sendAll(evts) { - for (var i = 0; i < evts.length; ++i) { - next(evts[i]); - } - } function process(evt, type) { var result = {type: type}; var code = getKeycode(evt); @@ -322,19 +314,16 @@ export function KeyEventDecoder (modifierState, next) { return { keydown: function(evt) { - sendAll(modifierState.keydown(evt)); + modifierState.keydown(evt); return process(evt, 'keydown'); }, keypress: function(evt) { return process(evt, 'keypress'); }, keyup: function(evt) { - sendAll(modifierState.keyup(evt)); + modifierState.keyup(evt); return process(evt, 'keyup'); }, - syncModifiers: function(evt) { - sendAll(modifierState.syncAny(evt)); - }, releaseAll: function() { next({type: 'releaseall'}); } }; }; diff --git a/core/rfb.js b/core/rfb.js index c82dd23e..430fa798 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -204,8 +204,7 @@ export default function RFB(defaults) { this._mouse = new Mouse({target: this._target, onMouseButton: this._handleMouseButton.bind(this), - onMouseMove: this._handleMouseMove.bind(this), - notify: this._keyboard.sync.bind(this._keyboard)}); + onMouseMove: this._handleMouseMove.bind(this)}); this._sock = new Websock(); this._sock.on('message', this._handle_message.bind(this)); diff --git a/tests/test.helper.js b/tests/test.helper.js index ce742f0a..f705cff0 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -155,165 +155,4 @@ describe('Helpers', function() { }); }); }); - - describe('Modifier Sync', function() { // return a list of fake events necessary to fix modifier state - describe('Toggle all modifiers', function() { - var sync = KeyboardUtil.ModifierSync(); - it ('should do nothing if all modifiers are up as expected', function() { - expect(sync.keydown({ - code: 'KeyA', - ctrlKey: false, - altKey: false, - altGraphKey: false, - shiftKey: false, - metaKey: false}) - ).to.have.lengthOf(0); - }); - it ('should synthesize events if all keys are unexpectedly down', function() { - var result = sync.keydown({ - code: 'KeyA', - ctrlKey: true, - altKey: true, - altGraphKey: true, - shiftKey: true, - metaKey: true - }); - expect(result).to.have.lengthOf(5); - var keysyms = {}; - for (var i = 0; i < result.length; ++i) { - keysyms[result[i].keysym] = (result[i].type == 'keydown'); - } - expect(keysyms[0xffe3]); - expect(keysyms[0xffe9]); - expect(keysyms[0xfe03]); - expect(keysyms[0xffe1]); - expect(keysyms[0xffe7]); - }); - it ('should do nothing if all modifiers are down as expected', function() { - expect(sync.keydown({ - code: 'KeyA', - ctrlKey: true, - altKey: true, - altGraphKey: true, - shiftKey: true, - metaKey: true - })).to.have.lengthOf(0); - }); - }); - describe('Toggle Ctrl', function() { - var sync = KeyboardUtil.ModifierSync(); - it('should sync if modifier is suddenly down', function() { - expect(sync.keydown({ - code: 'KeyA', - ctrlKey: true, - })).to.be.deep.equal([{keysym: 0xffe3, type: 'keydown'}]); - }); - it('should sync if modifier is suddenly up', function() { - expect(sync.keydown({ - code: 'KeyA', - ctrlKey: false - })).to.be.deep.equal([{keysym: 0xffe3, type: 'keyup'}]); - }); - }); - describe('Toggle Alt', function() { - var sync = KeyboardUtil.ModifierSync(); - it('should sync if modifier is suddenly down', function() { - expect(sync.keydown({ - code: 'KeyA', - altKey: true, - })).to.be.deep.equal([{keysym: 0xffe9, type: 'keydown'}]); - }); - it('should sync if modifier is suddenly up', function() { - expect(sync.keydown({ - code: 'KeyA', - altKey: false - })).to.be.deep.equal([{keysym: 0xffe9, type: 'keyup'}]); - }); - }); - describe('Toggle AltGr', function() { - var sync = KeyboardUtil.ModifierSync(); - it('should sync if modifier is suddenly down', function() { - expect(sync.keydown({ - code: 'KeyA', - altGraphKey: true, - })).to.be.deep.equal([{keysym: 0xfe03, type: 'keydown'}]); - }); - it('should sync if modifier is suddenly up', function() { - expect(sync.keydown({ - code: 'KeyA', - altGraphKey: false - })).to.be.deep.equal([{keysym: 0xfe03, type: 'keyup'}]); - }); - }); - describe('Toggle Shift', function() { - var sync = KeyboardUtil.ModifierSync(); - it('should sync if modifier is suddenly down', function() { - expect(sync.keydown({ - code: 'KeyA', - shiftKey: true, - })).to.be.deep.equal([{keysym: 0xffe1, type: 'keydown'}]); - }); - it('should sync if modifier is suddenly up', function() { - expect(sync.keydown({ - code: 'KeyA', - shiftKey: false - })).to.be.deep.equal([{keysym: 0xffe1, type: 'keyup'}]); - }); - }); - describe('Toggle Meta', function() { - var sync = KeyboardUtil.ModifierSync(); - it('should sync if modifier is suddenly down', function() { - expect(sync.keydown({ - code: 'KeyA', - metaKey: true, - })).to.be.deep.equal([{keysym: 0xffe7, type: 'keydown'}]); - }); - it('should sync if modifier is suddenly up', function() { - expect(sync.keydown({ - code: 'KeyA', - metaKey: false - })).to.be.deep.equal([{keysym: 0xffe7, type: 'keyup'}]); - }); - }); - describe('Modifier keyevents', function() { - it('should not sync a modifier on its own events', function() { - expect(KeyboardUtil.ModifierSync().keydown({ - code: 'ControlLeft', - ctrlKey: false - })).to.be.deep.equal([]); - expect(KeyboardUtil.ModifierSync().keydown({ - code: 'ControlLeft', - ctrlKey: true - }), 'B').to.be.deep.equal([]); - }) - it('should update state on modifier keyevents', function() { - var sync = KeyboardUtil.ModifierSync(); - sync.keydown({ - code: 'ControlLeft', - }); - expect(sync.keydown({ - code: 'KeyA', - ctrlKey: true, - })).to.be.deep.equal([]); - }); - it('should sync other modifiers on ctrl events', function() { - expect(KeyboardUtil.ModifierSync().keydown({ - code: 'ControlLeft', - altKey: true - })).to.be.deep.equal([{keysym: 0xffe9, type: 'keydown'}]); - }) - }); - describe('sync modifiers on non-key events', function() { - it('should generate sync events when receiving non-keyboard events', function() { - expect(KeyboardUtil.ModifierSync().syncAny({ - altKey: true - })).to.be.deep.equal([{keysym: 0xffe9, type: 'keydown'}]); - }); - }); - describe('do not treat shift as a modifier key', function() { - it('should not treat shift as a char modifier', function() { - expect(KeyboardUtil.hasCharModifier([], {0xffe1 : true})).to.be.false; - }); - }); - }); }); diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 19a8a66a..7ecfcaf1 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -26,29 +26,6 @@ describe('Key Event Pipeline Stages', function() { done(); }).keydown({code: 'KeyA', key: 'a'}); }); - it('should not sync modifiers on a keypress', function() { - // Firefox provides unreliable modifier state on keypress events - var count = 0; - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - ++count; - }).keypress({code: 'KeyA', key: 'a', ctrlKey: true}); - expect(count).to.be.equal(1); - }); - it('should sync modifiers if necessary', function(done) { - var count = 0; - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - switch (count) { - case 0: // fake a ctrl keydown - expect(evt).to.be.deep.equal({keysym: 0xffe3, type: 'keydown'}); - ++count; - break; - case 1: - expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown', keysym: 0x61}); - done(); - break; - } - }).keydown({code: 'KeyA', key: 'a', ctrlKey: true}); - }); it('should forward keydown events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); From f7363fd26dd7d9fced3bce4c629f5119d8b476ae Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 27 Jan 2017 10:36:10 +0100 Subject: [PATCH 14/20] Move keyboard handling in to Keyboard class Replace the multi stage pipeline system with something simpler. That level of abstraction is not needed. --- core/input/devices.js | 214 +++++++++-- core/input/util.js | 184 ---------- tests/test.keyboard.js | 801 +++++++++++------------------------------ 3 files changed, 411 insertions(+), 788 deletions(-) diff --git a/core/input/devices.js b/core/input/devices.js index 2308be9c..f981c6f9 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -22,18 +22,13 @@ const Keyboard = function (defaults) { this._keyDownList = []; // List of depressed keys // (even if they are happy) + this._modifierState = KeyboardUtil.ModifierSync(); + set_defaults(this, defaults, { 'target': document, 'focused': true }); - // create the keyboard handler - this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), - KeyboardUtil.TrackKeyState( - KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this)) - ) - ); - // keep these here so we can refer to them later this._eventHandlers = { 'keyup': this._handleKeyUp.bind(this), @@ -46,47 +41,220 @@ const Keyboard = function (defaults) { Keyboard.prototype = { // private methods - _handleRfbEvent: function (e) { - if (this._onKeyEvent) { - Log.Debug("onKeyEvent " + (e.type == 'keydown' ? "down" : "up") + - ", keysym: " + e.keysym); - this._onKeyEvent(e.keysym, e.code, e.type == 'keydown'); + _sendKeyEvent: function (keysym, code, down) { + if (!this._onKeyEvent) { + return; } + + Log.Debug("onKeyEvent " + (down ? "down" : "up") + + ", keysym: " + keysym, ", code: " + code); + + this._onKeyEvent(keysym, code, down); + }, + + _getKeyCode: function (e) { + var code = KeyboardUtil.getKeycode(e); + if (code === 'Unidentified') { + // Unstable, but we don't have anything else to go on + // (don't use it for 'keypress' events thought since + // WebKit sets it to the same as charCode) + if (e.keyCode && (e.type !== 'keypress')) { + code = 'Platform' + e.keyCode; + } + } + + return code; }, _handleKeyDown: function (e) { if (!this._focused) { return; } - if (this._handler.keydown(e)) { - // Suppress bubbling/default actions + this._modifierState.keydown(e); + + var code = this._getKeyCode(e); + var keysym = KeyboardUtil.getKeysym(e); + + // If this is a legacy browser then we'll need to wait for + // a keypress event as well. Otherwise we supress the + // browser's handling at this point + if (keysym) { stopEvent(e); + } + + // if a char modifier is pressed, get the keys it consists + // of (on Windows, AltGr is equivalent to Ctrl+Alt) + var active = this._modifierState.activeCharModifier(); + + // If we have a char modifier down, and we're able to + // determine a keysym reliably then (a) we know to treat + // the modifier as a char modifier, and (b) we'll have to + // "escape" the modifier to undo the modifier when sending + // the char. + if (active && keysym) { + var isCharModifier = false; + for (var i = 0; i < active.length; ++i) { + if (active[i] === keysym) { + isCharModifier = true; + } + } + if (!isCharModifier) { + var escape = this._modifierState.activeCharModifier(); + } + } + + var last; + if (this._keyDownList.length === 0) { + last = null; } else { - // Allow the event to bubble and become a keyPress event which - // will have the character code translated + last = this._keyDownList[this._keyDownList.length-1]; + } + + // insert a new entry if last seen key was different. + if (!last || code === 'Unidentified' || last.code !== code) { + last = {code: code, keysyms: {}}; + this._keyDownList.push(last); + } + + // Wait for keypress? + if (!keysym) { + return; + } + + // make sure last event contains this keysym (a single "logical" keyevent + // can cause multiple key events to be sent to the VNC server) + last.keysyms[keysym] = keysym; + last.ignoreKeyPress = true; + + // undo modifiers + if (escape) { + for (var i = 0; i < escape.length; ++i) { + this._sendKeyEvent(escape[i], 'Unidentified', false); + } + } + + // send the character event + this._sendKeyEvent(keysym, code, true); + + // redo modifiers + if (escape) { + for (i = 0; i < escape.length; ++i) { + this._sendKeyEvent(escape[i], 'Unidentified', true); + } } }, + // Legacy event for browsers without code/key _handleKeyPress: function (e) { if (!this._focused) { return; } - if (this._handler.keypress(e)) { - // Suppress bubbling/default actions - stopEvent(e); + stopEvent(e); + + var code = this._getKeyCode(e); + var keysym = KeyboardUtil.getKeysym(e); + + // if a char modifier is pressed, get the keys it consists + // of (on Windows, AltGr is equivalent to Ctrl+Alt) + var active = this._modifierState.activeCharModifier(); + + // If we have a char modifier down, and we're able to + // determine a keysym reliably then (a) we know to treat + // the modifier as a char modifier, and (b) we'll have to + // "escape" the modifier to undo the modifier when sending + // the char. + if (active && keysym) { + var isCharModifier = false; + for (var i = 0; i < active.length; ++i) { + if (active[i] === keysym) { + isCharModifier = true; + } + } + if (!isCharModifier) { + var escape = this._modifierState.activeCharModifier(); + } + } + + var last; + if (this._keyDownList.length === 0) { + last = null; + } else { + last = this._keyDownList[this._keyDownList.length-1]; + } + + if (!last) { + last = {code: code, keysyms: {}}; + this._keyDownList.push(last); + } + if (!keysym) { + console.log('keypress with no keysym:', e); + return; + } + + // If we didn't expect a keypress, and already sent a keydown to the VNC server + // based on the keydown, make sure to skip this event. + if (last.ignoreKeyPress) { + return; + } + + last.keysyms[keysym] = keysym; + + // undo modifiers + if (escape) { + for (var i = 0; i < escape.length; ++i) { + this._sendKeyEvent(escape[i], 'Unidentified', false); + } + } + + // send the character event + this._sendKeyEvent(keysym, code, true); + + // redo modifiers + if (escape) { + for (i = 0; i < escape.length; ++i) { + this._sendKeyEvent(escape[i], 'Unidentified', true); + } } }, _handleKeyUp: function (e) { if (!this._focused) { return; } - if (this._handler.keyup(e)) { - // Suppress bubbling/default actions - stopEvent(e); + stopEvent(e); + + this._modifierState.keyup(e); + + var code = this._getKeyCode(e); + + if (this._keyDownList.length === 0) { + return; + } + var idx = null; + // do we have a matching key tracked as being down? + for (var i = 0; i !== this._keyDownList.length; ++i) { + if (this._keyDownList[i].code === code) { + idx = i; + break; + } + } + // if we couldn't find a match (it happens), assume it was the last key pressed + if (idx === null) { + idx = this._keyDownList.length - 1; + } + + var item = this._keyDownList.splice(idx, 1)[0]; + for (var key in item.keysyms) { + this._sendKeyEvent(item.keysyms[key], code, false); } }, _allKeysUp: function () { Log.Debug(">> Keyboard.allKeysUp"); - this._handler.releaseAll(); + for (var i = 0; i < this._keyDownList.length; i++) { + var item = this._keyDownList[i]; + for (var key in item.keysyms) { + this._sendKeyEvent(item.keysyms[key], 'Unidentified', false); + } + }; + this._keyDownList = []; Log.Debug("<< Keyboard.allKeysUp"); }, diff --git a/core/input/util.js b/core/input/util.js index e5363a52..110526a3 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -254,187 +254,3 @@ export function getKeysym(evt){ return null; } - -// Takes a DOM keyboard event and: -// - determines which keysym it represents -// - determines a code identifying the key that was pressed (corresponding to the code/keyCode properties on the DOM event) -// - marks each event with an 'escape' property if a modifier was down which should be "escaped" -// This information is collected into an object which is passed to the next() function. (one call per event) -export function KeyEventDecoder (modifierState, next) { - "use strict"; - function process(evt, type) { - var result = {type: type}; - var code = getKeycode(evt); - if (code === 'Unidentified') { - // Unstable, but we don't have anything else to go on - // (don't use it for 'keypress' events thought since - // WebKit sets it to the same as charCode) - if (evt.keyCode && (evt.type !== 'keypress')) { - code = 'Platform' + evt.keyCode; - } - } - result.code = code; - - var keysym = getKeysym(evt); - - // Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress? - // "special" keys like enter, tab or backspace don't send keypress events, - // and some browsers don't send keypresses at all if a modifier is down - if (keysym) { - result.keysym = keysym; - } - - // Should we prevent the browser from handling the event? - // Doing so on a keydown (in most browsers) prevents keypress from being generated - // so only do that if we have to. - var suppress = type !== 'keydown' || !!keysym; - - // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt) - var active = modifierState.activeCharModifier(); - - // If we have a char modifier down, and we're able to determine a keysym reliably - // then (a) we know to treat the modifier as a char modifier, - // and (b) we'll have to "escape" the modifier to undo the modifier when sending the char. - if (active && keysym) { - var isCharModifier = false; - for (var i = 0; i < active.length; ++i) { - if (active[i] === keysym) { - isCharModifier = true; - } - } - if (type === 'keypress' && !isCharModifier) { - result.escape = modifierState.activeCharModifier(); - } - } - - next(result); - - return suppress; - } - - return { - keydown: function(evt) { - modifierState.keydown(evt); - return process(evt, 'keydown'); - }, - keypress: function(evt) { - return process(evt, 'keypress'); - }, - keyup: function(evt) { - modifierState.keyup(evt); - return process(evt, 'keyup'); - }, - releaseAll: function() { next({type: 'releaseall'}); } - }; -}; - -// Keeps track of which keys we (and the server) believe are down -// When a keyup is received, match it against this list, to determine the corresponding keysym(s) -// in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars -// key repeat events should be merged into a single entry. -// Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess -export function TrackKeyState (next) { - "use strict"; - var state = []; - - return function (evt) { - var last = state.length !== 0 ? state[state.length-1] : null; - - switch (evt.type) { - case 'keydown': - // insert a new entry if last seen key was different. - if (!last || evt.code === 'Unidentified' || last.code !== evt.code) { - last = {code: evt.code, keysyms: {}}; - state.push(last); - } - if (evt.keysym) { - // make sure last event contains this keysym (a single "logical" keyevent - // can cause multiple key events to be sent to the VNC server) - last.keysyms[evt.keysym] = evt.keysym; - last.ignoreKeyPress = true; - next(evt); - } - break; - case 'keypress': - if (!last) { - last = {code: evt.code, keysyms: {}}; - state.push(last); - } - if (!evt.keysym) { - console.log('keypress with no keysym:', evt); - } - - // If we didn't expect a keypress, and already sent a keydown to the VNC server - // based on the keydown, make sure to skip this event. - if (evt.keysym && !last.ignoreKeyPress) { - last.keysyms[evt.keysym] = evt.keysym; - evt.type = 'keydown'; - next(evt); - } - break; - case 'keyup': - if (state.length === 0) { - return; - } - var idx = null; - // do we have a matching key tracked as being down? - for (var i = 0; i !== state.length; ++i) { - if (state[i].code === evt.code) { - idx = i; - break; - } - } - // if we couldn't find a match (it happens), assume it was the last key pressed - if (idx === null) { - idx = state.length - 1; - } - - var item = state.splice(idx, 1)[0]; - // for each keysym tracked by this key entry, clone the current event and override the keysym - var clone = (function(){ - function Clone(){} - return function (obj) { Clone.prototype=obj; return new Clone(); }; - }()); - for (var key in item.keysyms) { - var out = clone(evt); - out.keysym = item.keysyms[key]; - next(out); - } - break; - case 'releaseall': - /* jshint shadow: true */ - for (var i = 0; i < state.length; ++i) { - for (var key in state[i].keysyms) { - var keysym = state[i].keysyms[key]; - next({code: 'Unidentified', keysym: keysym, type: 'keyup'}); - } - } - /* jshint shadow: false */ - state = []; - } - }; -}; - -// Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @), -// then the modifier must be "undone" before sending the @, and "redone" afterwards. -export function EscapeModifiers (next) { - "use strict"; - return function(evt) { - if (evt.type !== 'keydown' || evt.escape === undefined) { - next(evt); - return; - } - // undo modifiers - for (var i = 0; i < evt.escape.length; ++i) { - next({type: 'keyup', code: 'Unidentified', keysym: evt.escape[i]}); - } - // send the character event - next(evt); - // redo modifiers - /* jshint shadow: true */ - for (var i = 0; i < evt.escape.length; ++i) { - next({type: 'keydown', code: 'Unidentified', keysym: evt.escape[i]}); - } - /* jshint shadow: false */ - }; -}; diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 7ecfcaf1..e4ee503f 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -1,612 +1,251 @@ var assert = chai.assert; var expect = chai.expect; +import { Keyboard } from '../core/input/devices.js'; import keysyms from '../core/input/keysymdef.js'; import * as KeyboardUtil from '../core/input/util.js'; /* jshint newcap: false, expr: true */ -describe('Key Event Pipeline Stages', function() { +describe('Key Event Handling', function() { "use strict"; + + // The real KeyboardEvent constructor might not work everywhere we + // want to run these tests + function keyevent(typeArg, KeyboardEventInit) { + var e = { type: typeArg }; + for (var key in KeyboardEventInit) { + e[key] = KeyboardEventInit[key]; + } + e.stopPropagation = sinon.spy(); + e.preventDefault = sinon.spy(); + return e; + }; + describe('Decode Keyboard Events', function() { - it('should pass events to the next stage', function(done) { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.an.object; + it('should decode keydown events', function(done) { + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('KeyA'); + expect(down).to.be.equal(true); done(); - }).keydown({code: 'KeyA', key: 'a'}); + }}); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); }); - it('should pass the right keysym through', function(done) { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt.keysym).to.be.deep.equal(0x61); - done(); - }).keypress({code: 'KeyA', key: 'a'}); + it('should decode keyup events', function(done) { + var calls = 0; + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('KeyA'); + if (calls++ === 1) { + expect(down).to.be.equal(false); + done(); + } + }}); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); + kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); }); - it('should pass the right keyid through', function(done) { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.have.property('code', 'KeyA'); - done(); - }).keydown({code: 'KeyA', key: 'a'}); - }); - it('should forward keydown events with the right type', function(done) { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); - done(); - }).keydown({code: 'KeyA', key: 'a'}); - }); - it('should forward keyup events with the right type', function(done) { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'}); - done(); - }).keyup({code: 'KeyA', key: 'a'}); - }); - it('should forward keypress events with the right type', function(done) { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'}); - done(); - }).keypress({code: 'KeyA', key: 'a'}); + + describe('Legacy keypress Events', function() { + it('should wait for keypress when needed', function() { + var callback = sinon.spy(); + var kbd = new Keyboard({onKeyEvent: callback}); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); + expect(callback).to.not.have.been.called; + }); + it('should decode keypress events', function(done) { + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('KeyA'); + expect(down).to.be.equal(true); + done(); + }}); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); + kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61})); + }); }); + describe('suppress the right events at the right time', function() { - it('should suppress anything while a shortcut modifier is down', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - - obj.keydown({code: 'ControlLeft'}); - expect(obj.keydown({code: 'KeyA', key: 'a'})).to.be.true; - expect(obj.keydown({code: 'Space', key: ' '})).to.be.true; - expect(obj.keydown({code: 'Digit1', key: '1'})).to.be.true; - expect(obj.keydown({code: 'IntlBackslash', key: '<'})).to.be.true; - expect(obj.keydown({code: 'Semicolon', key: 'ø'})).to.be.true; + it('should suppress anything with a valid key', function() { + var kbd = new Keyboard({}); + var evt = keyevent('keydown', {code: 'KeyA', key: 'a'}); + kbd._handleKeyDown(evt); + expect(evt.preventDefault).to.have.been.called; + evt = keyevent('keyup', {code: 'KeyA', key: 'a'}); + kbd._handleKeyUp(evt); + expect(evt.preventDefault).to.have.been.called; }); - it('should suppress non-character keys', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - - expect(obj.keydown({code: 'Backspace'}), 'a').to.be.true; - expect(obj.keydown({code: 'Tab'}), 'b').to.be.true; - expect(obj.keydown({code: 'ControlLeft'}), 'd').to.be.true; - expect(obj.keydown({code: 'AltLeft'}), 'e').to.be.true; + it('should not suppress keys without key', function() { + var kbd = new Keyboard({}); + var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41}); + kbd._handleKeyDown(evt); + expect(evt.preventDefault).to.not.have.been.called; }); - it('should generate event for shift keydown', function() { - var called = false; - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.have.property('keysym'); - called = true; - }).keydown({code: 'ShiftLeft'}); - expect(called).to.be.true; - }); - it('should suppress character keys with key', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - - expect(obj.keydown({code: 'KeyA', key: 'a'})).to.be.true; - expect(obj.keydown({code: 'Digit1', key: '1'})).to.be.true; - expect(obj.keydown({code: 'IntlBackslash', key: '<'})).to.be.true; - expect(obj.keydown({code: 'Semicolon', key: 'ø'})).to.be.true; - }); - it('should not suppress character keys without key', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - - expect(obj.keydown({code: 'KeyA'})).to.be.false; - expect(obj.keydown({code: 'Digit1'})).to.be.false; - expect(obj.keydown({code: 'IntlBackslash'})).to.be.false; - expect(obj.keydown({code: 'Semicolon'})).to.be.false; - }); - }); - describe('Keypress and keyup events', function() { - it('should always suppress event propagation', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - - expect(obj.keypress({code: 'KeyA', key: 'a'})).to.be.true; - expect(obj.keypress({code: 'IntlBackslash', key: '<'})).to.be.true; - expect(obj.keypress({code: 'ControlLeft', key: 'Control'})).to.be.true; - - expect(obj.keyup({code: 'KeyA', key: 'a'})).to.be.true; - expect(obj.keyup({code: 'IntlBackslash', key: '<'})).to.be.true; - expect(obj.keyup({code: 'ControlLeft', key: 'Control'})).to.be.true; - }); - }); - describe('mark events if a char modifier is down', function() { - it('should not mark modifiers on a keydown event', function() { - var times_called = 0; - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { - switch (times_called++) { - case 0: //altgr - break; - case 1: // 'a' - expect(evt).to.not.have.property('escape'); - break; - } - }); - - obj.keydown({code: 'AltRight', key: 'AltGraph'}) - obj.keydown({code: 'KeyA', key: 'a'}); - }); - - it('should indicate on events if a single-key char modifier is down', function(done) { - var times_called = 0; - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { - switch (times_called++) { - case 0: //altgr - break; - case 1: // 'a' - expect(evt).to.be.deep.equal({ - type: 'keypress', - code: 'KeyA', - keysym: 0x61, - escape: [0xfe03] - }); - done(); - return; - } - }); - - obj.keydown({code: 'AltRight', key: 'AltGraph'}) - obj.keypress({code: 'KeyA', key: 'a'}); - }); - it('should indicate on events if a multi-key char modifier is down', function(done) { - var times_called = 0; - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xffe9, 0xffe3]), function(evt) { - switch (times_called++) { - case 0: //ctrl - break; - case 1: //alt - break; - case 2: // 'a' - expect(evt).to.be.deep.equal({ - type: 'keypress', - code: 'KeyA', - keysym: 0x61, - escape: [0xffe9, 0xffe3] - }); - done(); - return; - } - }); - - obj.keydown({code: 'ControlLeft'}); - obj.keydown({code: 'AltLeft'}); - obj.keypress({code: 'KeyA', key: 'a'}); - }); - it('should not consider a char modifier to be down on the modifier key itself', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { - expect(evt).to.not.have.property('escape'); - }); - - obj.keydown({code: 'AltRight', key: 'AltGraph'}) - + it('should suppress the following keypress event', function() { + var kbd = new Keyboard({}); + var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41}); + kbd._handleKeyDown(evt); + var evt = keyevent('keypress', {code: 'KeyA', charCode: 0x41}); + kbd._handleKeyPress(evt); + expect(evt.preventDefault).to.have.been.called; }); }); }); describe('Track Key State', function() { + it('should send release using the same keysym as the press', function(done) { + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('KeyA'); + if (!down) { + done(); + } + }}); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); + kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'})); + }); it('should do nothing on keyup events if no keys are down', function() { - var obj = KeyboardUtil.TrackKeyState(function(evt) { - expect(true).to.be.false; - }); - obj({type: 'keyup', code: 'KeyA'}); + var callback = sinon.spy(); + var kbd = new Keyboard({onKeyEvent: callback}); + kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); + expect(callback).to.not.have.been.called; }); - it('should insert into the queue on keydown if no keys are down', function() { - var times_called = 0; - var elem = null; - var keysymsdown = {}; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - ++times_called; - if (elem.type == 'keyup') { - expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym]; - } - else { - expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - } - elem = null; - }); - - expect(elem).to.be.null; - elem = {type: 'keydown', code: 'KeyA', keysym: 0x42}; - keysymsdown[0x42] = true; - obj(elem); - expect(elem).to.be.null; - elem = {type: 'keyup', code: 'KeyA'}; - obj(elem); - expect(elem).to.be.null; - expect(times_called).to.be.equal(2); + it('should send a key release for each key press with the same code', function() { + var callback = sinon.spy(); + var kbd = new Keyboard({onKeyEvent: callback}); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'})); + kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA'})); + expect(callback.callCount).to.be.equal(4); }); - it('should insert into the queue on keypress if no keys are down', function() { - var times_called = 0; - var elem = null; - var keysymsdown = {}; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - ++times_called; - if (elem.type == 'keyup') { - expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym]; - } - else { - expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - } - elem = null; - }); - - expect(elem).to.be.null; - elem = {type: 'keypress', code: 'KeyA', keysym: 0x42}; - keysymsdown[0x42] = true; - obj(elem); - expect(elem).to.be.null; - elem = {type: 'keyup', code: 'KeyA'}; - obj(elem); - expect(elem).to.be.null; - expect(times_called).to.be.equal(2); - }); - it('should add keysym to last key entry if code matches', function() { - // this implies that a single keyup will release both keysyms - var times_called = 0; - var elem = null; - var keysymsdown = {}; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - ++times_called; - if (elem.type == 'keyup') { - expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym]; - } - else { - expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - elem = null; - } - }); - - expect(elem).to.be.null; - elem = {type: 'keypress', code: 'KeyA', keysym: 0x42}; - keysymsdown[0x42] = true; - obj(elem); - expect(elem).to.be.null; - elem = {type: 'keypress', code: 'KeyA', keysym: 0x43}; - keysymsdown[0x43] = true; - obj(elem); - expect(elem).to.be.null; - elem = {type: 'keyup', code: 'KeyA'}; - obj(elem); - expect(times_called).to.be.equal(4); - }); - it('should create new key entry if code matches and keysym does not', function() { - // this implies that a single keyup will release both keysyms - var times_called = 0; - var elem = null; - var keysymsdown = {}; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - ++times_called; - if (elem.type == 'keyup') { - expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym]; - } - else { - expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - elem = null; - } - }); - - expect(elem).to.be.null; - elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42}; - keysymsdown[0x42] = true; - obj(elem); - expect(elem).to.be.null; - elem = {type: 'keydown', code: 'Unidentified', keysym: 0x43}; - keysymsdown[0x43] = true; - obj(elem); - expect(times_called).to.be.equal(2); - expect(elem).to.be.null; - elem = {type: 'keyup', code: 'Unidentified'}; - obj(elem); - expect(times_called).to.be.equal(3); - elem = {type: 'keyup', code: 'Unidentified'}; - obj(elem); - expect(times_called).to.be.equal(4); - }); - it('should merge key entry if codes are zero and keysyms match', function() { - // this implies that a single keyup will release both keysyms - var times_called = 0; - var elem = null; - var keysymsdown = {}; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - ++times_called; - if (elem.type == 'keyup') { - expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym]; - } - else { - expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - elem = null; - } - }); - - expect(elem).to.be.null; - elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42}; - keysymsdown[0x42] = true; - obj(elem); - expect(elem).to.be.null; - elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42}; - keysymsdown[0x42] = true; - obj(elem); - expect(times_called).to.be.equal(2); - expect(elem).to.be.null; - elem = {type: 'keyup', code: 'Unidentified'}; - obj(elem); - expect(times_called).to.be.equal(3); - }); - it('should add keysym as separate entry if code does not match last event', function() { - // this implies that separate keyups are required - var times_called = 0; - var elem = null; - var keysymsdown = {}; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - ++times_called; - if (elem.type == 'keyup') { - expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym]; - } - else { - expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - elem = null; - } - }); - - expect(elem).to.be.null; - elem = {type: 'keypress', code: 'KeyA', keysym: 0x42}; - keysymsdown[0x42] = true; - obj(elem); - expect(elem).to.be.null; - elem = {type: 'keypress', code: 'KeyB', keysym: 0x43}; - keysymsdown[0x43] = true; - obj(elem); - expect(elem).to.be.null; - elem = {type: 'keyup', code: 'KeyA'}; - obj(elem); - expect(times_called).to.be.equal(4); - elem = {type: 'keyup', code: 'KeyB'}; - obj(elem); - expect(times_called).to.be.equal(4); - }); - it('should add keysym as separate entry if code does not match last event and first is zero', function() { - // this implies that separate keyups are required - var times_called = 0; - var elem = null; - var keysymsdown = {}; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - ++times_called; - if (elem.type == 'keyup') { - expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym]; - } - else { - expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - elem = null; - } - }); - - expect(elem).to.be.null; - elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42}; - keysymsdown[0x42] = true; - obj(elem); - expect(elem).to.be.null; - elem = {type: 'keydown', code: 'KeyB', keysym: 0x43}; - keysymsdown[0x43] = true; - obj(elem); - expect(elem).to.be.null; - expect(times_called).to.be.equal(2); - elem = {type: 'keyup', code: 'Unidentified'}; - obj(elem); - expect(times_called).to.be.equal(3); - elem = {type: 'keyup', code: 'KeyB'}; - obj(elem); - expect(times_called).to.be.equal(4); - }); - it('should add keysym as separate entry if code does not match last event and second is zero', function() { - // this implies that a separate keyups are required - var times_called = 0; - var elem = null; - var keysymsdown = {}; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - ++times_called; - if (elem.type == 'keyup') { - expect(evt).to.have.property('keysym'); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - delete keysymsdown[evt.keysym]; - } - else { - expect(evt).to.be.deep.equal(elem); - expect (keysymsdown[evt.keysym]).to.not.be.undefined; - elem = null; - } - }); - - expect(elem).to.be.null; - elem = {type: 'keydown', code: 'KeyA', keysym: 0x42}; - keysymsdown[0x42] = true; - obj(elem); - expect(elem).to.be.null; - elem = {type: 'keydown', code: 'Unidentified', keysym: 0x43}; - keysymsdown[0x43] = true; - obj(elem); - expect(elem).to.be.null; - elem = {type: 'keyup', code: 'KeyA'}; - obj(elem); - expect(times_called).to.be.equal(3); - elem = {type: 'keyup', code: 'Unidentified'}; - obj(elem); - expect(times_called).to.be.equal(4); - }); - it('should pop matching key event on keyup', function() { - var times_called = 0; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - switch (times_called++) { - case 0: - case 1: - case 2: - expect(evt.type).to.be.equal('keydown'); - break; - case 3: - expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyB', keysym: 0x62}); - break; - } - }); - - obj({type: 'keydown', code: 'KeyA', keysym: 0x61}); - obj({type: 'keydown', code: 'KeyB', keysym: 0x62}); - obj({type: 'keydown', code: 'KeyC', keysym: 0x63}); - obj({type: 'keyup', code: 'KeyB'}); - expect(times_called).to.equal(4); - }); - it('should pop the first zero keyevent on keyup with zero code', function() { - var times_called = 0; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - switch (times_called++) { - case 0: - case 1: - case 2: - expect(evt.type).to.be.equal('keydown'); - break; - case 3: - expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x61}); - break; - } - }); - - obj({type: 'keydown', code: 'Unidentified', keysym: 0x61}); - obj({type: 'keydown', code: 'Unidentified', keysym: 0x62}); - obj({type: 'keydown', code: 'KeyA', keysym: 0x63}); - obj({type: 'keyup', code: 'Unidentified'}); - expect(times_called).to.equal(4); - }); - it('should pop the last keyevents keysym if no match is found for code', function() { - var times_called = 0; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - switch (times_called++) { - case 0: - case 1: - case 2: - expect(evt.type).to.be.equal('keydown'); - break; - case 3: - expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyD', keysym: 0x63}); - break; - } - }); - - obj({type: 'keydown', code: 'KeyA', keysym: 0x61}); - obj({type: 'keydown', code: 'KeyB', keysym: 0x62}); - obj({type: 'keydown', code: 'KeyC', keysym: 0x63}); - obj({type: 'keyup', code: 'KeyD'}); - expect(times_called).to.equal(4); - }); - describe('Firefox sends keypress even when keydown is suppressed', function() { - it('should discard the keypress', function() { - var times_called = 0; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - expect(times_called).to.be.equal(0); - ++times_called; - }); - - obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); - expect(times_called).to.be.equal(1); - obj({type: 'keypress', code: 'KeyA', keysym: 0x43}); - }); - }); - describe('releaseAll', function() { - it('should do nothing if no keys have been pressed', function() { - var times_called = 0; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - ++times_called; - }); - obj({type: 'releaseall'}); - expect(times_called).to.be.equal(0); - }); - it('should release the keys that have been pressed', function() { - var times_called = 0; - var obj = KeyboardUtil.TrackKeyState(function(evt) { - switch (times_called++) { - case 2: - expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x41}); - break; - case 3: - expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x42}); - break; - } - }); - obj({type: 'keydown', code: 'KeyA', keysym: 0x41}); - obj({type: 'keydown', code: 'KeyB', keysym: 0x42}); - expect(times_called).to.be.equal(2); - obj({type: 'releaseall'}); - expect(times_called).to.be.equal(4); - obj({type: 'releaseall'}); - expect(times_called).to.be.equal(4); - }); - }); - }); describe('Escape Modifiers', function() { - describe('Keydown', function() { - it('should pass through when a char modifier is not down', function() { - var times_called = 0; - KeyboardUtil.EscapeModifiers(function(evt) { - expect(times_called).to.be.equal(0); - ++times_called; - expect(evt).to.be.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42}); - })({type: 'keydown', code: 'KeyA', keysym: 0x42}); - expect(times_called).to.be.equal(1); - }); - it('should generate fake undo/redo events when a char modifier is down', function() { - var times_called = 0; - KeyboardUtil.EscapeModifiers(function(evt) { - switch(times_called++) { - case 0: - expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe9}); - break; - case 1: - expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe3}); - break; - case 2: - expect(evt).to.be.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42, escape: [0xffe9, 0xffe3]}); - break; - case 3: - expect(evt).to.be.deep.equal({type: 'keydown', code: 'Unidentified', keysym: 0xffe9}); - break; - case 4: - expect(evt).to.be.deep.equal({type: 'keydown', code: 'Unidentified', keysym: 0xffe3}); - break; - } - })({type: 'keydown', code: 'KeyA', keysym: 0x42, escape: [0xffe9, 0xffe3]}); - expect(times_called).to.be.equal(5); - }); + var origNavigator; + beforeEach(function () { + // window.navigator is a protected read-only property in many + // environments, so we need to redefine it whilst running these + // tests. + origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); + if (origNavigator === undefined) { + // Object.getOwnPropertyDescriptor() doesn't work + // properly in any version of IE + this.skip(); + } + + Object.defineProperty(window, "navigator", {value: {}}); + if (window.navigator.platform !== undefined) { + // Object.defineProperty() doesn't work properly in old + // versions of Chrome + this.skip(); + } + + window.navigator.platform = "Windows x86_64"; }); - describe('Keyup', function() { - it('should pass through when a char modifier is down', function() { - var times_called = 0; - KeyboardUtil.EscapeModifiers(function(evt) { - expect(times_called).to.be.equal(0); - ++times_called; - expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]}); - })({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]}); - expect(times_called).to.be.equal(1); - }); - it('should pass through when a char modifier is not down', function() { - var times_called = 0; - KeyboardUtil.EscapeModifiers(function(evt) { - expect(times_called).to.be.equal(0); - ++times_called; - expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42}); - })({type: 'keyup', code: 'KeyA', keysym: 0x42}); - expect(times_called).to.be.equal(1); - }); + afterEach(function () { + Object.defineProperty(window, "navigator", origNavigator); + }); + + it('should generate fake undo/redo events on press when a char modifier is down', function() { + var times_called = 0; + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + switch(times_called++) { + case 0: + expect(keysym).to.be.equal(0xFFE3); + expect(code).to.be.equal('ControlLeft'); + expect(down).to.be.equal(true); + break; + case 1: + expect(keysym).to.be.equal(0xFFE9); + expect(code).to.be.equal('AltLeft'); + expect(down).to.be.equal(true); + break; + case 2: + expect(keysym).to.be.equal(0xFFE9); + expect(code).to.be.equal('Unidentified'); + expect(down).to.be.equal(false); + break; + case 3: + expect(keysym).to.be.equal(0xFFE3); + expect(code).to.be.equal('Unidentified'); + expect(down).to.be.equal(false); + break; + case 4: + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('KeyA'); + expect(down).to.be.equal(true); + break; + case 5: + expect(keysym).to.be.equal(0xFFE9); + expect(code).to.be.equal('Unidentified'); + expect(down).to.be.equal(true); + break; + case 6: + expect(keysym).to.be.equal(0xFFE3); + expect(code).to.be.equal('Unidentified'); + expect(down).to.be.equal(true); + break; + } + }}); + // First the modifier combo + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'})); + // Next a normal character + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); + expect(times_called).to.be.equal(7); + }); + it('should no do anything on key release', function() { + var times_called = 0; + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + switch(times_called++) { + case 7: + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('KeyA'); + expect(down).to.be.equal(false); + break; + } + }}); + // First the modifier combo + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'})); + // Next a normal character + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); + kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); + expect(times_called).to.be.equal(8); + }); + it('should not consider a char modifier to be down on the modifier key itself', function() { + var times_called = 0; + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + switch(times_called++) { + case 0: + expect(keysym).to.be.equal(0xFFE3); + expect(code).to.be.equal('ControlLeft'); + expect(down).to.be.equal(true); + break; + case 1: + expect(keysym).to.be.equal(0xFFE9); + expect(code).to.be.equal('AltLeft'); + expect(down).to.be.equal(true); + break; + case 2: + expect(keysym).to.be.equal(0xFFE3); + expect(code).to.be.equal('ControlLeft'); + expect(down).to.be.equal(true); + break; + } + }}); + // First the modifier combo + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'})); + // Then one of the keys again + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); + expect(times_called).to.be.equal(3); }); }); }); From 9fce233d51c803b08b6779bc8e906334de6916cf Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 27 Jan 2017 12:24:20 +0100 Subject: [PATCH 15/20] Simplify handling of keypress Use a dedicated variable to track a two stage key rather than piggy-backing on the key state array. --- core/input/devices.js | 39 ++++++++++++++++++++++----------------- tests/test.keyboard.js | 18 ++++++++++++++++++ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/core/input/devices.js b/core/input/devices.js index f981c6f9..d1c0649f 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -21,6 +21,7 @@ import * as KeyboardUtil from "./util.js"; const Keyboard = function (defaults) { this._keyDownList = []; // List of depressed keys // (even if they are happy) + this._pendingKey = null; // Key waiting for keypress this._modifierState = KeyboardUtil.ModifierSync(); @@ -75,12 +76,15 @@ Keyboard.prototype = { var keysym = KeyboardUtil.getKeysym(e); // If this is a legacy browser then we'll need to wait for - // a keypress event as well. Otherwise we supress the - // browser's handling at this point - if (keysym) { - stopEvent(e); + // a keypress event as well + if (!keysym) { + this._pendingKey = code; + return; } + this._pendingKey = null; + stopEvent(e); + // if a char modifier is pressed, get the keys it consists // of (on Windows, AltGr is equivalent to Ctrl+Alt) var active = this._modifierState.activeCharModifier(); @@ -90,7 +94,7 @@ Keyboard.prototype = { // the modifier as a char modifier, and (b) we'll have to // "escape" the modifier to undo the modifier when sending // the char. - if (active && keysym) { + if (active) { var isCharModifier = false; for (var i = 0; i < active.length; ++i) { if (active[i] === keysym) { @@ -115,15 +119,9 @@ Keyboard.prototype = { this._keyDownList.push(last); } - // Wait for keypress? - if (!keysym) { - return; - } - // make sure last event contains this keysym (a single "logical" keyevent // can cause multiple key events to be sent to the VNC server) last.keysyms[keysym] = keysym; - last.ignoreKeyPress = true; // undo modifiers if (escape) { @@ -149,9 +147,22 @@ Keyboard.prototype = { stopEvent(e); + // Are we expecting a keypress? + if (this._pendingKey === null) { + return; + } + var code = this._getKeyCode(e); var keysym = KeyboardUtil.getKeysym(e); + // The key we were waiting for? + if ((code !== 'Unidentified') && (code != this._pendingKey)) { + return; + } + + code = this._pendingKey; + this._pendingKey = null; + // if a char modifier is pressed, get the keys it consists // of (on Windows, AltGr is equivalent to Ctrl+Alt) var active = this._modifierState.activeCharModifier(); @@ -189,12 +200,6 @@ Keyboard.prototype = { return; } - // If we didn't expect a keypress, and already sent a keydown to the VNC server - // based on the keydown, make sure to skip this event. - if (last.ignoreKeyPress) { - return; - } - last.keysyms[keysym] = keysym; // undo modifiers diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index e4ee503f..51c6b8f7 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -65,6 +65,24 @@ describe('Key Event Handling', function() { kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61})); }); + it('should ignore keypress with different code', function() { + var callback = sinon.spy(); + var kbd = new Keyboard({onKeyEvent: callback}); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); + kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61})); + expect(callback).to.not.have.been.called; + }); + it('should handle keypress with missing code', function(done) { + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('KeyA'); + expect(down).to.be.equal(true); + done(); + }}); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); + kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61})); + }); }); describe('suppress the right events at the right time', function() { From ae82053366bdf91c6cbcb459f92101ec4e693f0a Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 27 Jan 2017 12:26:55 +0100 Subject: [PATCH 16/20] Simplify pressed key handling Prefer avoid having the server simulate multiple key presses by refusing to use multiple keysyms for the same physical key. --- core/input/devices.js | 84 ++++++++++++++++-------------------------- tests/test.keyboard.js | 21 +++++++---- 2 files changed, 45 insertions(+), 60 deletions(-) diff --git a/core/input/devices.js b/core/input/devices.js index d1c0649f..f7d9052c 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -19,7 +19,7 @@ import * as KeyboardUtil from "./util.js"; // const Keyboard = function (defaults) { - this._keyDownList = []; // List of depressed keys + this._keyDownList = {}; // List of depressed keys // (even if they are happy) this._pendingKey = null; // Key waiting for keypress @@ -75,6 +75,27 @@ Keyboard.prototype = { var code = this._getKeyCode(e); var keysym = KeyboardUtil.getKeysym(e); + // We cannot handle keys we cannot track, but we also need + // to deal with virtual keyboards which omit key info + if (code === 'Unidentified') { + if (keysym) { + // If it's a virtual keyboard then it should be + // sufficient to just send press and release right + // after each other + this._sendKeyEvent(keysym, 'Unidentified', true); + this._sendKeyEvent(keysym, 'Unidentified', false); + } + + stopEvent(e); + return; + } + + // Is this key already pressed? If so, then we must use the + // same keysym or we'll confuse the server + if (code in this._keyDownList) { + keysym = this._keyDownList[code]; + } + // If this is a legacy browser then we'll need to wait for // a keypress event as well if (!keysym) { @@ -106,22 +127,7 @@ Keyboard.prototype = { } } - var last; - if (this._keyDownList.length === 0) { - last = null; - } else { - last = this._keyDownList[this._keyDownList.length-1]; - } - - // insert a new entry if last seen key was different. - if (!last || code === 'Unidentified' || last.code !== code) { - last = {code: code, keysyms: {}}; - this._keyDownList.push(last); - } - - // make sure last event contains this keysym (a single "logical" keyevent - // can cause multiple key events to be sent to the VNC server) - last.keysyms[keysym] = keysym; + this._keyDownList[code] = keysym; // undo modifiers if (escape) { @@ -184,23 +190,12 @@ Keyboard.prototype = { } } - var last; - if (this._keyDownList.length === 0) { - last = null; - } else { - last = this._keyDownList[this._keyDownList.length-1]; - } - - if (!last) { - last = {code: code, keysyms: {}}; - this._keyDownList.push(last); - } if (!keysym) { console.log('keypress with no keysym:', e); return; } - last.keysyms[keysym] = keysym; + this._keyDownList[code] = keysym; // undo modifiers if (escape) { @@ -229,37 +224,22 @@ Keyboard.prototype = { var code = this._getKeyCode(e); - if (this._keyDownList.length === 0) { + // Do we really think this key is down? + if (!(code in this._keyDownList)) { return; } - var idx = null; - // do we have a matching key tracked as being down? - for (var i = 0; i !== this._keyDownList.length; ++i) { - if (this._keyDownList[i].code === code) { - idx = i; - break; - } - } - // if we couldn't find a match (it happens), assume it was the last key pressed - if (idx === null) { - idx = this._keyDownList.length - 1; - } - var item = this._keyDownList.splice(idx, 1)[0]; - for (var key in item.keysyms) { - this._sendKeyEvent(item.keysyms[key], code, false); - } + this._sendKeyEvent(this._keyDownList[code], code, false); + + delete this._keyDownList[code]; }, _allKeysUp: function () { Log.Debug(">> Keyboard.allKeysUp"); - for (var i = 0; i < this._keyDownList.length; i++) { - var item = this._keyDownList[i]; - for (var key in item.keysyms) { - this._sendKeyEvent(item.keysyms[key], 'Unidentified', false); - } + for (var code in this._keyDownList) { + this._sendKeyEvent(this._keyDownList[code], code, false); }; - this._keyDownList = []; + this._keyDownList = {}; Log.Debug("<< Keyboard.allKeysUp"); }, diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 51c6b8f7..f9d5ff11 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -125,20 +125,25 @@ describe('Key Event Handling', function() { kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'})); }); + it('should send the same keysym for multiple presses', function() { + var count = 0; + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('KeyA'); + expect(down).to.be.equal(true); + count++; + }}); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'})); + expect(count).to.be.equal(2); + }); it('should do nothing on keyup events if no keys are down', function() { var callback = sinon.spy(); var kbd = new Keyboard({onKeyEvent: callback}); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); expect(callback).to.not.have.been.called; }); - it('should send a key release for each key press with the same code', function() { - var callback = sinon.spy(); - var kbd = new Keyboard({onKeyEvent: callback}); - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'})); - kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA'})); - expect(callback.callCount).to.be.equal(4); - }); }); describe('Escape Modifiers', function() { From bf43c26319a7bfea989a2306c9423feac0509c32 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 27 Jan 2017 12:52:24 +0100 Subject: [PATCH 17/20] Clean up AltGraph handling It doesn't need to be this general as the issue is mostly about Windows. Also use the same modifier shuffle that RealVNC and TigerVNC uses to get macOS working well. --- core/input/devices.js | 132 +++++++++++++++++------------------------ core/input/util.js | 99 ------------------------------- tests/test.keyboard.js | 91 ++++++++++++++++++++++++---- 3 files changed, 132 insertions(+), 190 deletions(-) diff --git a/core/input/devices.js b/core/input/devices.js index f7d9052c..2c9af31c 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -13,6 +13,7 @@ import { isTouchDevice } from '../util/browsers.js' import { setCapture, releaseCapture, stopEvent, getPointerEvent } from '../util/events.js'; import { set_defaults, make_properties } from '../util/properties.js'; import * as KeyboardUtil from "./util.js"; +import KeyTable from "./keysym.js"; // // Keyboard event handler @@ -23,8 +24,6 @@ const Keyboard = function (defaults) { // (even if they are happy) this._pendingKey = null; // Key waiting for keypress - this._modifierState = KeyboardUtil.ModifierSync(); - set_defaults(this, defaults, { 'target': document, 'focused': true @@ -39,6 +38,13 @@ const Keyboard = function (defaults) { }; }; +function isMac() { + return navigator && !!(/mac/i).exec(navigator.platform); +} +function isWindows() { + return navigator && !!(/win/i).exec(navigator.platform); +} + Keyboard.prototype = { // private methods @@ -50,7 +56,32 @@ Keyboard.prototype = { Log.Debug("onKeyEvent " + (down ? "down" : "up") + ", keysym: " + keysym, ", code: " + code); + // Windows sends CtrlLeft+AltRight when you press + // AltGraph, which tends to confuse the hell out of + // remote systems. Fake a release of these keys until + // there is a way to detect AltGraph properly. + var fakeAltGraph = false; + if (down && isWindows()) { + if ((code !== 'ControlLeft') && + (code !== 'AltRight') && + ('ControlLeft' in this._keyDownList) && + ('AltRight' in this._keyDownList)) { + fakeAltGraph = true; + this._onKeyEvent(this._keyDownList['AltRight'], + 'AltRight', false); + this._onKeyEvent(this._keyDownList['ControlLeft'], + 'ControlLeft', false); + } + } + this._onKeyEvent(keysym, code, down); + + if (fakeAltGraph) { + this._onKeyEvent(this._keyDownList['ControlLeft'], + 'ControlLeft', true); + this._onKeyEvent(this._keyDownList['AltRight'], + 'AltRight', true); + } }, _getKeyCode: function (e) { @@ -70,8 +101,6 @@ Keyboard.prototype = { _handleKeyDown: function (e) { if (!this._focused) { return; } - this._modifierState.keydown(e); - var code = this._getKeyCode(e); var keysym = KeyboardUtil.getKeysym(e); @@ -90,6 +119,27 @@ Keyboard.prototype = { return; } + // Alt behaves more like AltGraph on macOS, so shuffle the + // keys around a bit to make things more sane for the remote + // server. This method is used by RealVNC and TigerVNC (and + // possibly others). + if (isMac()) { + switch (keysym) { + case KeyTable.XK_Super_L: + keysym = KeyTable.XK_Alt_L; + break; + case KeyTable.XK_Super_R: + keysym = KeyTable.XK_Super_L; + break; + case KeyTable.XK_Alt_L: + keysym = KeyTable.XK_Mode_switch; + break; + case KeyTable.XK_Alt_R: + keysym = KeyTable.XK_ISO_Level3_Shift; + break; + } + } + // Is this key already pressed? If so, then we must use the // same keysym or we'll confuse the server if (code in this._keyDownList) { @@ -106,45 +156,9 @@ Keyboard.prototype = { this._pendingKey = null; stopEvent(e); - // if a char modifier is pressed, get the keys it consists - // of (on Windows, AltGr is equivalent to Ctrl+Alt) - var active = this._modifierState.activeCharModifier(); - - // If we have a char modifier down, and we're able to - // determine a keysym reliably then (a) we know to treat - // the modifier as a char modifier, and (b) we'll have to - // "escape" the modifier to undo the modifier when sending - // the char. - if (active) { - var isCharModifier = false; - for (var i = 0; i < active.length; ++i) { - if (active[i] === keysym) { - isCharModifier = true; - } - } - if (!isCharModifier) { - var escape = this._modifierState.activeCharModifier(); - } - } - this._keyDownList[code] = keysym; - // undo modifiers - if (escape) { - for (var i = 0; i < escape.length; ++i) { - this._sendKeyEvent(escape[i], 'Unidentified', false); - } - } - - // send the character event this._sendKeyEvent(keysym, code, true); - - // redo modifiers - if (escape) { - for (i = 0; i < escape.length; ++i) { - this._sendKeyEvent(escape[i], 'Unidentified', true); - } - } }, // Legacy event for browsers without code/key @@ -169,27 +183,6 @@ Keyboard.prototype = { code = this._pendingKey; this._pendingKey = null; - // if a char modifier is pressed, get the keys it consists - // of (on Windows, AltGr is equivalent to Ctrl+Alt) - var active = this._modifierState.activeCharModifier(); - - // If we have a char modifier down, and we're able to - // determine a keysym reliably then (a) we know to treat - // the modifier as a char modifier, and (b) we'll have to - // "escape" the modifier to undo the modifier when sending - // the char. - if (active && keysym) { - var isCharModifier = false; - for (var i = 0; i < active.length; ++i) { - if (active[i] === keysym) { - isCharModifier = true; - } - } - if (!isCharModifier) { - var escape = this._modifierState.activeCharModifier(); - } - } - if (!keysym) { console.log('keypress with no keysym:', e); return; @@ -197,22 +190,7 @@ Keyboard.prototype = { this._keyDownList[code] = keysym; - // undo modifiers - if (escape) { - for (var i = 0; i < escape.length; ++i) { - this._sendKeyEvent(escape[i], 'Unidentified', false); - } - } - - // send the character event this._sendKeyEvent(keysym, code, true); - - // redo modifiers - if (escape) { - for (i = 0; i < escape.length; ++i) { - this._sendKeyEvent(escape[i], 'Unidentified', true); - } - } }, _handleKeyUp: function (e) { @@ -220,8 +198,6 @@ Keyboard.prototype = { stopEvent(e); - this._modifierState.keyup(e); - var code = this._getKeyCode(e); // Do we really think this key is down? diff --git a/core/input/util.js b/core/input/util.js index 110526a3..d755c20f 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -6,105 +6,6 @@ import fixedkeys from "./fixedkeys.js"; function isMac() { return navigator && !!(/mac/i).exec(navigator.platform); } -function isWindows() { - return navigator && !!(/win/i).exec(navigator.platform); -} -function isLinux() { - return navigator && !!(/linux/i).exec(navigator.platform); -} - -// Return true if the specified char modifier is currently down -export function hasCharModifier(charModifier, currentModifiers) { - if (charModifier.length === 0) { return false; } - - for (var i = 0; i < charModifier.length; ++i) { - if (!currentModifiers[charModifier[i]]) { - return false; - } - } - return true; -} - -// Helper object tracking modifier key state -// and generates fake key events to compensate if it gets out of sync -export function ModifierSync(charModifier) { - if (!charModifier) { - if (isMac()) { - // on Mac, Option (AKA Alt) is used as a char modifier - charModifier = [KeyTable.XK_Alt_L]; - } - else if (isWindows()) { - // on Windows, Ctrl+Alt is used as a char modifier - charModifier = [KeyTable.XK_Alt_L, KeyTable.XK_Control_L]; - } - else if (isLinux()) { - // on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier - charModifier = [KeyTable.XK_ISO_Level3_Shift]; - } - else { - charModifier = []; - } - } - - var state = {}; - state[KeyTable.XK_Control_L] = false; - state[KeyTable.XK_Alt_L] = false; - state[KeyTable.XK_ISO_Level3_Shift] = false; - state[KeyTable.XK_Shift_L] = false; - state[KeyTable.XK_Meta_L] = false; - - function sync(evt, keysym) { - var result = []; - function syncKey(keysym) { - return {keysym: keysym, type: state[keysym] ? 'keydown' : 'keyup'}; - } - - if (evt.ctrlKey !== undefined && - evt.ctrlKey !== state[KeyTable.XK_Control_L] && keysym !== KeyTable.XK_Control_L) { - state[KeyTable.XK_Control_L] = evt.ctrlKey; - result.push(syncKey(KeyTable.XK_Control_L)); - } - if (evt.altKey !== undefined && - evt.altKey !== state[KeyTable.XK_Alt_L] && keysym !== KeyTable.XK_Alt_L) { - state[KeyTable.XK_Alt_L] = evt.altKey; - result.push(syncKey(KeyTable.XK_Alt_L)); - } - if (evt.altGraphKey !== undefined && - evt.altGraphKey !== state[KeyTable.XK_ISO_Level3_Shift] && keysym !== KeyTable.XK_ISO_Level3_Shift) { - state[KeyTable.XK_ISO_Level3_Shift] = evt.altGraphKey; - result.push(syncKey(KeyTable.XK_ISO_Level3_Shift)); - } - if (evt.shiftKey !== undefined && - evt.shiftKey !== state[KeyTable.XK_Shift_L] && keysym !== KeyTable.XK_Shift_L) { - state[KeyTable.XK_Shift_L] = evt.shiftKey; - result.push(syncKey(KeyTable.XK_Shift_L)); - } - if (evt.metaKey !== undefined && - evt.metaKey !== state[KeyTable.XK_Meta_L] && keysym !== KeyTable.XK_Meta_L) { - state[KeyTable.XK_Meta_L] = evt.metaKey; - result.push(syncKey(KeyTable.XK_Meta_L)); - } - return result; - } - function syncKeyEvent(evt, down) { - var keysym = getKeysym(evt); - - // first, apply the event itself, if relevant - if (keysym !== null && state[keysym] !== undefined) { - state[keysym] = down; - } - return sync(evt, keysym); - } - - return { - // sync on the appropriate keyboard event - keydown: function(evt) { return syncKeyEvent(evt, true);}, - keyup: function(evt) { return syncKeyEvent(evt, false);}, - - // if a char modifier is down, return the keys it consists of, otherwise return null - activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; } - }; -} // Get 'KeyboardEvent.code', handling legacy browsers export function getKeycode(evt){ diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index f9d5ff11..1ebbbd4a 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -146,7 +146,72 @@ describe('Key Event Handling', function() { }); }); - describe('Escape Modifiers', function() { + describe('Shuffle modifiers on macOS', function() { + var origNavigator; + beforeEach(function () { + // window.navigator is a protected read-only property in many + // environments, so we need to redefine it whilst running these + // tests. + origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); + if (origNavigator === undefined) { + // Object.getOwnPropertyDescriptor() doesn't work + // properly in any version of IE + this.skip(); + } + + Object.defineProperty(window, "navigator", {value: {}}); + if (window.navigator.platform !== undefined) { + // Object.defineProperty() doesn't work properly in old + // versions of Chrome + this.skip(); + } + + window.navigator.platform = "Mac x86_64"; + }); + afterEach(function () { + Object.defineProperty(window, "navigator", origNavigator); + }); + + it('should change Alt to AltGraph', function() { + var count = 0; + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + switch (count++) { + case 0: + expect(keysym).to.be.equal(0xFF7E); + expect(code).to.be.equal('AltLeft'); + break; + case 1: + expect(keysym).to.be.equal(0xFE03); + expect(code).to.be.equal('AltRight'); + break; + } + }}); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt'})); + expect(count).to.be.equal(2); + }); + it('should change left Super to Alt', function(done) { + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + expect(keysym).to.be.equal(0xFFE9); + expect(code).to.be.equal('MetaLeft'); + done(); + }}); + kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta'})); + }); + it('should change right Super to left Super', function(done) { + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + expect(keysym).to.be.equal(0xFFEB); + expect(code).to.be.equal('MetaRight'); + done(); + }}); + kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta'})); + }); + }); + + describe('Escape AltGraph on Windows', function() { var origNavigator; beforeEach(function () { // window.navigator is a protected read-only property in many @@ -172,7 +237,7 @@ describe('Key Event Handling', function() { Object.defineProperty(window, "navigator", origNavigator); }); - it('should generate fake undo/redo events on press when a char modifier is down', function() { + it('should generate fake undo/redo events on press when AltGraph is down', function() { var times_called = 0; var kbd = new Keyboard({ onKeyEvent: function(keysym, code, down) { @@ -183,18 +248,18 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(true); break; case 1: - expect(keysym).to.be.equal(0xFFE9); - expect(code).to.be.equal('AltLeft'); + expect(keysym).to.be.equal(0xFFEA); + expect(code).to.be.equal('AltRight'); expect(down).to.be.equal(true); break; case 2: - expect(keysym).to.be.equal(0xFFE9); - expect(code).to.be.equal('Unidentified'); + expect(keysym).to.be.equal(0xFFEA); + expect(code).to.be.equal('AltRight'); expect(down).to.be.equal(false); break; case 3: expect(keysym).to.be.equal(0xFFE3); - expect(code).to.be.equal('Unidentified'); + expect(code).to.be.equal('ControlLeft'); expect(down).to.be.equal(false); break; case 4: @@ -203,20 +268,20 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(true); break; case 5: - expect(keysym).to.be.equal(0xFFE9); - expect(code).to.be.equal('Unidentified'); + expect(keysym).to.be.equal(0xFFE3); + expect(code).to.be.equal('ControlLeft'); expect(down).to.be.equal(true); break; case 6: - expect(keysym).to.be.equal(0xFFE3); - expect(code).to.be.equal('Unidentified'); + expect(keysym).to.be.equal(0xFFEA); + expect(code).to.be.equal('AltRight'); expect(down).to.be.equal(true); break; } }}); // First the modifier combo kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt'})); // Next a normal character kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); expect(times_called).to.be.equal(7); @@ -235,7 +300,7 @@ describe('Key Event Handling', function() { }}); // First the modifier combo kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt'})); // Next a normal character kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); From 634cc1ba46222125c7680472a234126e875cfc3d Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 4 May 2017 11:52:40 +0200 Subject: [PATCH 18/20] Handle CapsLock on macOS Modifiers behave a bit oddly on macOS, causing weird CapsLock events to be sent by the browsers. --- core/input/devices.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/input/devices.js b/core/input/devices.js index 2c9af31c..1efcfb5c 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -146,6 +146,17 @@ Keyboard.prototype = { keysym = this._keyDownList[code]; } + // macOS doesn't send proper key events for modifiers, only + // state change events. That gets extra confusing for CapsLock + // which toggles on each press, but not on release. So pretend + // it was a quick press and release of the button. + if (isMac() && (code === 'CapsLock')) { + this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true); + this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false); + stopEvent(e); + return; + } + // If this is a legacy browser then we'll need to wait for // a keypress event as well if (!keysym) { @@ -200,6 +211,13 @@ Keyboard.prototype = { var code = this._getKeyCode(e); + // See comment in _handleKeyDown() + if (isMac() && (code === 'CapsLock')) { + this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true); + this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false); + return; + } + // Do we really think this key is down? if (!(code in this._keyDownList)) { return; From 9782d4a324d24179f42f381080699a20eb164d06 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 13 Apr 2017 14:50:55 +0200 Subject: [PATCH 19/20] Use KeyboardEvent.key too look up keysyms And emulate it on browsers where it is missing or incorrect. This makes the code more future oriented as it primarily uses the standardised fields. --- core/input/domkeytable.js | 310 ++++++++++++++++++++++++++++++++++++++ core/input/fixedkeys.js | 215 ++++++++++++++------------ core/input/keysym.js | 50 ++++++ core/input/util.js | 132 ++++++++-------- tests/test.helper.js | 113 ++++++++++---- tests/test.keyboard.js | 22 +-- 6 files changed, 644 insertions(+), 198 deletions(-) create mode 100644 core/input/domkeytable.js diff --git a/core/input/domkeytable.js b/core/input/domkeytable.js new file mode 100644 index 00000000..6758c089 --- /dev/null +++ b/core/input/domkeytable.js @@ -0,0 +1,310 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2017 Pierre Ossman for Cendio AB + * Licensed under MPL 2.0 or any later version (see LICENSE.txt) + */ + +import KeyTable from "./keysym.js" + +/* + * Mapping between HTML key values and VNC/X11 keysyms for "special" + * keys that cannot be handled via their Unicode codepoint. + * + * See https://www.w3.org/TR/uievents-key/ for possible values. + */ + +var DOMKeyTable = {}; + +function addStandard(key, standard) +{ + if (standard === undefined) throw "Undefined keysym for key \"" + key + "\""; + if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\""; + DOMKeyTable[key] = [standard, standard, standard, standard]; +} + +function addLeftRight(key, left, right) +{ + if (left === undefined) throw "Undefined keysym for key \"" + key + "\""; + if (right === undefined) throw "Undefined keysym for key \"" + key + "\""; + if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\""; + DOMKeyTable[key] = [left, left, right, left]; +} + +function addNumpad(key, standard, numpad) +{ + if (standard === undefined) throw "Undefined keysym for key \"" + key + "\""; + if (numpad === undefined) throw "Undefined keysym for key \"" + key + "\""; + if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\""; + DOMKeyTable[key] = [standard, standard, standard, numpad]; +} + +// 2.2. Modifier Keys + +addLeftRight("Alt", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R); +addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift); +addStandard("CapsLock", KeyTable.XK_Caps_Lock); +addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R); +// - Fn +// - FnLock +addLeftRight("Hyper", KeyTable.XK_Super_L, KeyTable.XK_Super_R); +addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R); +addStandard("NumLock", KeyTable.XK_Num_Lock); +addStandard("ScrollLock", KeyTable.XK_Scroll_Lock); +addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R); +addLeftRight("Super", KeyTable.XK_Super_L, KeyTable.XK_Super_R); +// - Symbol +// - SymbolLock + +// 2.3. Whitespace Keys + +addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter); +addStandard("Tab", KeyTable.XK_Tab); +addNumpad(" ", KeyTable.XK_space, KeyTable.XK_KP_Space); + +// 2.4. Navigation Keys + +addNumpad("ArrowDown", KeyTable.XK_Down, KeyTable.XK_KP_Down); +addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up); +addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left); +addNumpad("ArrowRight", KeyTable.XK_Right, KeyTable.XK_KP_Right); +addNumpad("End", KeyTable.XK_End, KeyTable.XK_KP_End); +addNumpad("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home); +addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next); +addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior); + +// 2.5. Editing Keys + +addStandard("Backspace", KeyTable.XK_BackSpace); +addStandard("Clear", KeyTable.XK_Clear); +addStandard("Copy", KeyTable.XF86XK_Copy); +// - CrSel +addStandard("Cut", KeyTable.XF86XK_Cut); +addNumpad("Delete", KeyTable.XK_Delete, KeyTable.XK_KP_Delete); +// - EraseEof +// - ExSel +addNumpad("Insert", KeyTable.XK_Insert, KeyTable.XK_KP_Insert); +addStandard("Paste", KeyTable.XF86XK_Paste); +addStandard("Redo", KeyTable.XK_Redo); +addStandard("Undo", KeyTable.XK_Undo); + +// 2.6. UI Keys + +// - Accept +// - Again (could just be XK_Redo) +// - Attn +addStandard("Cancel", KeyTable.XK_Cancel); +addStandard("ContextMenu", KeyTable.XK_Menu); +addStandard("Escape", KeyTable.XK_Escape); +addStandard("Execute", KeyTable.XK_Execute); +addStandard("Find", KeyTable.XK_Find); +addStandard("Help", KeyTable.XK_Help); +addStandard("Pause", KeyTable.XK_Pause); +// - Play +// - Props +addStandard("Select", KeyTable.XK_Select); +addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn); +addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut); + +// 2.7. Device Keys + +addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown); +addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp); +addStandard("Eject", KeyTable.XF86XK_Eject); +addStandard("LogOff", KeyTable.XF86XK_LogOff); +addStandard("Power", KeyTable.XF86XK_PowerOff); +addStandard("PowerOff", KeyTable.XF86XK_PowerDown); +addStandard("PrintScreen", KeyTable.XK_Print); +addStandard("Hibernate", KeyTable.XF86XK_Hibernate); +addStandard("Standby", KeyTable.XF86XK_Standby); +addStandard("WakeUp", KeyTable.XF86XK_WakeUp); + +// 2.8. IME and Composition Keys + +addStandard("AllCandidates", KeyTable.XK_MultipleCandidate); +addStandard("Alphanumeric", KeyTable.XK_Eisu_Shift); // could also be _Eisu_Toggle +addStandard("CodeInput", KeyTable.XK_Codeinput); +addStandard("Compose", KeyTable.XK_Multi_key); +addStandard("Convert", KeyTable.XK_Henkan); +// - Dead +// - FinalMode +addStandard("GroupFirst", KeyTable.XK_ISO_First_Group); +addStandard("GroupLast", KeyTable.XK_ISO_Last_Group); +addStandard("GroupNext", KeyTable.XK_ISO_Next_Group); +addStandard("GroupPrevious", KeyTable.XK_ISO_Prev_Group); +// - ModeChange (XK_Mode_switch is often used for AltGr) +// - NextCandidate +addStandard("NonConvert", KeyTable.XK_Muhenkan); +addStandard("PreviousCandidate", KeyTable.XK_PreviousCandidate); +// - Process +addStandard("SingleCandidate", KeyTable.XK_SingleCandidate); +addStandard("HangulMode", KeyTable.XK_Hangul); +addStandard("HanjaMode", KeyTable.XK_Hangul_Hanja); +addStandard("JunjuaMode", KeyTable.XK_Hangul_Jeonja); +addStandard("Eisu", KeyTable.XK_Eisu_toggle); +addStandard("Hankaku", KeyTable.XK_Hankaku); +addStandard("Hiragana", KeyTable.XK_Hiragana); +addStandard("HiraganaKatakana", KeyTable.XK_Hiragana_Katakana); +addStandard("KanaMode", KeyTable.XK_Kana_Shift); // could also be _Kana_Lock +addStandard("KanjiMode", KeyTable.XK_Kanji); +addStandard("Katakana", KeyTable.XK_Katakana); +addStandard("Romaji", KeyTable.XK_Romaji); +addStandard("Zenkaku", KeyTable.XK_Zenkaku); +addStandard("ZenkakuHanaku", KeyTable.XK_Zenkaku_Hankaku); + +// 2.9. General-Purpose Function Keys + +addStandard("F1", KeyTable.XK_F1); +addStandard("F2", KeyTable.XK_F2); +addStandard("F3", KeyTable.XK_F3); +addStandard("F4", KeyTable.XK_F4); +addStandard("F5", KeyTable.XK_F5); +addStandard("F6", KeyTable.XK_F6); +addStandard("F7", KeyTable.XK_F7); +addStandard("F8", KeyTable.XK_F8); +addStandard("F9", KeyTable.XK_F9); +addStandard("F10", KeyTable.XK_F10); +addStandard("F11", KeyTable.XK_F11); +addStandard("F12", KeyTable.XK_F12); +addStandard("F13", KeyTable.XK_F13); +addStandard("F14", KeyTable.XK_F14); +addStandard("F15", KeyTable.XK_F15); +addStandard("F16", KeyTable.XK_F16); +addStandard("F17", KeyTable.XK_F17); +addStandard("F18", KeyTable.XK_F18); +addStandard("F19", KeyTable.XK_F19); +addStandard("F20", KeyTable.XK_F20); +addStandard("F21", KeyTable.XK_F21); +addStandard("F22", KeyTable.XK_F22); +addStandard("F23", KeyTable.XK_F23); +addStandard("F24", KeyTable.XK_F24); +addStandard("F25", KeyTable.XK_F25); +addStandard("F26", KeyTable.XK_F26); +addStandard("F27", KeyTable.XK_F27); +addStandard("F28", KeyTable.XK_F28); +addStandard("F29", KeyTable.XK_F29); +addStandard("F30", KeyTable.XK_F30); +addStandard("F31", KeyTable.XK_F31); +addStandard("F32", KeyTable.XK_F32); +addStandard("F33", KeyTable.XK_F33); +addStandard("F34", KeyTable.XK_F34); +addStandard("F35", KeyTable.XK_F35); +// - Soft1... + +// 2.10. Multimedia Keys + +// - ChannelDown +// - ChannelUp +addStandard("Close", KeyTable.XF86XK_Close); +addStandard("MailForward", KeyTable.XF86XK_MailForward); +addStandard("MailReply", KeyTable.XF86XK_Reply); +addStandard("MainSend", KeyTable.XF86XK_Send); +addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward); +addStandard("MediaPause", KeyTable.XF86XK_AudioPause); +addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay); +addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord); +addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind); +addStandard("MediaStop", KeyTable.XF86XK_AudioStop); +addStandard("MediaTrackNext", KeyTable.XF86XK_AudioNext); +addStandard("MediaTrackPrevious", KeyTable.XF86XK_AudioPrev); +addStandard("New", KeyTable.XF86XK_New); +addStandard("Open", KeyTable.XF86XK_Open); +addStandard("Print", KeyTable.XK_Print); +addStandard("Save", KeyTable.XF86XK_Save); +addStandard("SpellCheck", KeyTable.XF86XK_Spell); + +// 2.11. Multimedia Numpad Keys + +// - Key11 +// - Key12 + +// 2.12. Audio Keys + +// - AudioBalanceLeft +// - AudioBalanceRight +// - AudioBassDown +// - AudioBassBoostDown +// - AudioBassBoostToggle +// - AudioBassBoostUp +// - AudioBassUp +// - AudioFaderFront +// - AudioFaderRear +// - AudioSurroundModeNext +// - AudioTrebleDown +// - AudioTrebleUp +addStandard("AudioVolumeDown", KeyTable.XF86XK_AudioLowerVolume); +addStandard("AudioVolumeUp", KeyTable.XF86XK_AudioRaiseVolume); +addStandard("AudioVolumeMute", KeyTable.XF86XK_AudioMute); +// - MicrophoneToggle +// - MicrophoneVolumeDown +// - MicrophoneVolumeUp +addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute); + +// 2.13. Speech Keys + +// - SpeechCorrectionList +// - SpeechInputToggle + +// 2.14. Application Keys + +addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator); +addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar); +addStandard("LaunchMail", KeyTable.XF86XK_Mail); +addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia); +addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music); +addStandard("LaunchMyComputer", KeyTable.XF86XK_MyComputer); +addStandard("LaunchPhone", KeyTable.XF86XK_Phone); +addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver); +addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel); +addStandard("LaunchWebBrowser", KeyTable.XF86XK_WWW); +addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam); +addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word); + +// 2.15. Browser Keys + +addStandard("BrowserBack", KeyTable.XF86XK_Back); +addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites); +addStandard("BrowserForward", KeyTable.XF86XK_Forward); +addStandard("BrowserHome", KeyTable.XF86XK_HomePage); +addStandard("BrowserRefresh", KeyTable.XF86XK_Refresh); +addStandard("BrowserSearch", KeyTable.XF86XK_Search); +addStandard("BrowserStop", KeyTable.XF86XK_Stop); + +// 2.16. Mobile Phone Keys + +// - A whole bunch... + +// 2.17. TV Keys + +// - A whole bunch... + +// 2.18. Media Controller Keys + +// - A whole bunch... +addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust); +addStandard("MediaAudioTrack", KeyTable.XF86XK_AudioCycleTrack); +addStandard("RandomToggle", KeyTable.XF86XK_AudioRandomPlay); +addStandard("SplitScreenToggle", KeyTable.XF86XK_SplitScreen); +addStandard("Subtitle", KeyTable.XF86XK_Subtitle); +addStandard("VideoModeNext", KeyTable.XF86XK_Next_VMode); + +// Extra: Numpad + +addNumpad("=", KeyTable.XK_equal, KeyTable.XK_KP_Equal); +addNumpad("+", KeyTable.XK_plus, KeyTable.XK_KP_Add); +addNumpad("-", KeyTable.XK_minus, KeyTable.XK_KP_Subtract); +addNumpad("*", KeyTable.XK_asterisk, KeyTable.XK_KP_Multiply); +addNumpad("/", KeyTable.XK_slash, KeyTable.XK_KP_Divide); +addNumpad(".", KeyTable.XK_period, KeyTable.XK_KP_Decimal); +addNumpad(",", KeyTable.XK_comma, KeyTable.XK_KP_Separator); +addNumpad("0", KeyTable.XK_0, KeyTable.XK_KP_0); +addNumpad("1", KeyTable.XK_1, KeyTable.XK_KP_1); +addNumpad("2", KeyTable.XK_2, KeyTable.XK_KP_2); +addNumpad("3", KeyTable.XK_3, KeyTable.XK_KP_3); +addNumpad("4", KeyTable.XK_4, KeyTable.XK_KP_4); +addNumpad("5", KeyTable.XK_5, KeyTable.XK_KP_5); +addNumpad("6", KeyTable.XK_6, KeyTable.XK_KP_6); +addNumpad("7", KeyTable.XK_7, KeyTable.XK_KP_7); +addNumpad("8", KeyTable.XK_8, KeyTable.XK_KP_8); +addNumpad("9", KeyTable.XK_9, KeyTable.XK_KP_9); + +export default DOMKeyTable; diff --git a/core/input/fixedkeys.js b/core/input/fixedkeys.js index 2a2594e8..6dd42223 100644 --- a/core/input/fixedkeys.js +++ b/core/input/fixedkeys.js @@ -5,108 +5,123 @@ */ /* - * Mapping between HTML key codes and VNC/X11 keysyms for the - * subset of keys that have the same mapping on every keyboard - * layout. Keys that vary between layouts must never be included - * in this list. + * Fallback mapping between HTML key codes (physical keys) and + * HTML key values. This only works for keys that don't vary + * between layouts. We also omit those who manage fine by mapping the + * Unicode representation. + * + * See https://www.w3.org/TR/uievents-code/ for possible codes. + * See https://www.w3.org/TR/uievents-key/ for possible values. */ -import KeyTable from "./keysym.js"; - export default { - 'Backspace': KeyTable.XK_BackSpace, - 'AltLeft': KeyTable.XK_Alt_L, - // AltRight is special - 'CapsLock': KeyTable.XK_Caps_Lock, - 'ContextMenu': KeyTable.XK_Menu, - 'ControlLeft': KeyTable.XK_Control_L, - 'ControlRight': KeyTable.XK_Control_R, - 'Enter': KeyTable.XK_Return, - 'MetaLeft': KeyTable.XK_Super_L, - 'MetaRight': KeyTable.XK_Super_R, - 'ShiftLeft': KeyTable.XK_Shift_L, - 'ShiftRight': KeyTable.XK_Shift_R, - 'Space': KeyTable.XK_space, - 'Tab': KeyTable.XK_Tab, + +// 3.1.1.1. Writing System Keys + + 'Backspace': 'Backspace', + +// 3.1.1.2. Functional Keys + + 'AltLeft': 'Alt', + 'AltRight': 'Alt', // This could also be 'AltGraph' + 'CapsLock': 'CapsLock', + 'ContextMenu': 'ContextMenu', + 'ControlLeft': 'Control', + 'ControlRight': 'Control', + 'Enter': 'Enter', + 'MetaLeft': 'Meta', + 'MetaRight': 'Meta', + 'ShiftLeft': 'Shift', + 'ShiftRight': 'Shift', + 'Tab': 'Tab', // FIXME: Japanese/Korean keys - 'Delete': KeyTable.XK_Delete, - 'End': KeyTable.XK_End, - 'Help': KeyTable.XK_Help, - 'Home': KeyTable.XK_Home, - 'Insert': KeyTable.XK_Insert, - 'PageDown': KeyTable.XK_Next, - 'PageUp': KeyTable.XK_Prior, - 'ArrowDown': KeyTable.XK_Down, - 'ArrowLeft': KeyTable.XK_Left, - 'ArrowRight': KeyTable.XK_Right, - 'ArrowUp': KeyTable.XK_Up, - 'NumLock': KeyTable.XK_Num_Lock, - 'NumpadAdd': KeyTable.XK_KP_Add, - 'NumpadBackspace': KeyTable.XK_KP_Delete, - 'NumpadClear': KeyTable.XK_Clear, - // NumpadDecimal is special - 'NumpadDivide': KeyTable.XK_KP_Divide, - 'NumpadEnter': KeyTable.XK_KP_Enter, - 'NumpadEqual': KeyTable.XK_KP_Equal, - 'NumpadMultiply': KeyTable.XK_KP_Multiply, - 'NumpadSubtract': KeyTable.XK_KP_Subtract, - 'Escape': KeyTable.XK_Escape, - 'F1': KeyTable.XK_F1, - 'F2': KeyTable.XK_F2, - 'F3': KeyTable.XK_F3, - 'F4': KeyTable.XK_F4, - 'F5': KeyTable.XK_F5, - 'F6': KeyTable.XK_F6, - 'F7': KeyTable.XK_F7, - 'F8': KeyTable.XK_F8, - 'F9': KeyTable.XK_F9, - 'F10': KeyTable.XK_F10, - 'F11': KeyTable.XK_F11, - 'F12': KeyTable.XK_F12, - 'F13': KeyTable.XK_F13, - 'F14': KeyTable.XK_F14, - 'F15': KeyTable.XK_F15, - 'F16': KeyTable.XK_F16, - 'F17': KeyTable.XK_F17, - 'F18': KeyTable.XK_F18, - 'F19': KeyTable.XK_F19, - 'F20': KeyTable.XK_F20, - 'F21': KeyTable.XK_F21, - 'F22': KeyTable.XK_F22, - 'F23': KeyTable.XK_F23, - 'F24': KeyTable.XK_F24, - 'F25': KeyTable.XK_F25, - 'F26': KeyTable.XK_F26, - 'F27': KeyTable.XK_F27, - 'F28': KeyTable.XK_F28, - 'F29': KeyTable.XK_F29, - 'F30': KeyTable.XK_F30, - 'F31': KeyTable.XK_F31, - 'F32': KeyTable.XK_F32, - 'F33': KeyTable.XK_F33, - 'F34': KeyTable.XK_F34, - 'F35': KeyTable.XK_F35, - 'PrintScreen': KeyTable.XK_Print, - 'ScrollLock': KeyTable.XK_Scroll_Lock, - 'Pause': KeyTable.XK_Pause, - 'BrowserBack': KeyTable.XF86XK_Back, - 'BrowserFavorites': KeyTable.XF86XK_Favorites, - 'BrowserForward': KeyTable.XF86XK_Forward, - 'BrowserHome': KeyTable.XF86XK_HomePage, - 'BrowserRefresh': KeyTable.XF86XK_Refresh, - 'BrowserSearch': KeyTable.XF86XK_Search, - 'BrowserStop': KeyTable.XF86XK_Stop, - 'LaunchApp1': KeyTable.XF86XK_Explorer, - 'LaunchApp2': KeyTable.XF86XK_Calculator, - 'LaunchMail': KeyTable.XF86XK_Mail, - 'MediaPlayPause': KeyTable.XF86XK_AudioPlay, - 'MediaStop': KeyTable.XF86XK_AudioStop, - 'MediaTrackNext': KeyTable.XF86XK_AudioNext, - 'MediaTrackPrevious': KeyTable.XF86XK_AudioPrev, - 'Power': KeyTable.XF86XK_PowerOff, - 'Sleep': KeyTable.XF86XK_Sleep, - 'AudioVolumeDown': KeyTable.XF86XK_AudioLowerVolume, - 'AudioVolumeMute': KeyTable.XF86XK_AudioMute, - 'AudioVolumeUp': KeyTable.XF86XK_AudioRaiseVolume, - 'WakeUp': KeyTable.XF86XK_WakeUp, + +// 3.1.2. Control Pad Section + + 'Delete': 'Delete', + 'End': 'End', + 'Help': 'Help', + 'Home': 'Home', + 'Insert': 'Insert', + 'PageDown': 'PageDown', + 'PageUp': 'PageUp', + +// 3.1.3. Arrow Pad Section + + 'ArrowDown': 'ArrowDown', + 'ArrowLeft': 'ArrowLeft', + 'ArrowRight': 'ArrowRight', + 'ArrowUp': 'ArrowUp', + +// 3.1.4. Numpad Section + + 'NumLock': 'NumLock', + 'NumpadBackspace': 'Backspace', + 'NumpadClear': 'Clear', + +// 3.1.5. Function Section + + 'Escape': 'Escape', + 'F1': 'F1', + 'F2': 'F2', + 'F3': 'F3', + 'F4': 'F4', + 'F5': 'F5', + 'F6': 'F6', + 'F7': 'F7', + 'F8': 'F8', + 'F9': 'F9', + 'F10': 'F10', + 'F11': 'F11', + 'F12': 'F12', + 'F13': 'F13', + 'F14': 'F14', + 'F15': 'F15', + 'F16': 'F16', + 'F17': 'F17', + 'F18': 'F18', + 'F19': 'F19', + 'F20': 'F20', + 'F21': 'F21', + 'F22': 'F22', + 'F23': 'F23', + 'F24': 'F24', + 'F25': 'F25', + 'F26': 'F26', + 'F27': 'F27', + 'F28': 'F28', + 'F29': 'F29', + 'F30': 'F30', + 'F31': 'F31', + 'F32': 'F32', + 'F33': 'F33', + 'F34': 'F34', + 'F35': 'F35', + 'PrintScreen': 'PrintScreen', + 'ScrollLock': 'ScrollLock', + 'Pause': 'Pause', + +// 3.1.6. Media Keys + + 'BrowserBack': 'BrowserBack', + 'BrowserFavorites': 'BrowserFavorites', + 'BrowserForward': 'BrowserForward', + 'BrowserHome': 'BrowserHome', + 'BrowserRefresh': 'BrowserRefresh', + 'BrowserSearch': 'BrowserSearch', + 'BrowserStop': 'BrowserStop', + 'Eject': 'Eject', + 'LaunchApp1': 'LaunchMyComputer', + 'LaunchApp2': 'LaunchCalendar', + 'LaunchMail': 'LaunchMail', + 'MediaPlayPause': 'MediaPlay', + 'MediaStop': 'MediaStop', + 'MediaTrackNext': 'MediaTrackNext', + 'MediaTrackPrevious': 'MediaTrackPrevious', + 'Power': 'Power', + 'Sleep': 'Sleep', + 'AudioVolumeDown': 'AudioVolumeDown', + 'AudioVolumeMute': 'AudioVolumeMute', + 'AudioVolumeUp': 'AudioVolumeUp', + 'WakeUp': 'WakeUp', }; diff --git a/core/input/keysym.js b/core/input/keysym.js index 06ea70f8..ba58be68 100644 --- a/core/input/keysym.js +++ b/core/input/keysym.js @@ -12,6 +12,37 @@ export default { XK_Escape: 0xff1b, XK_Delete: 0xffff, /* Delete, rubout */ + /* International & multi-key character composition */ + + XK_Multi_key: 0xff20, /* Multi-key character compose */ + XK_Codeinput: 0xff37, + XK_SingleCandidate: 0xff3c, + XK_MultipleCandidate: 0xff3d, + XK_PreviousCandidate: 0xff3e, + + /* Japanese keyboard support */ + + XK_Kanji: 0xff21, /* Kanji, Kanji convert */ + XK_Muhenkan: 0xff22, /* Cancel Conversion */ + XK_Henkan_Mode: 0xff23, /* Start/Stop Conversion */ + XK_Henkan: 0xff23, /* Alias for Henkan_Mode */ + XK_Romaji: 0xff24, /* to Romaji */ + XK_Hiragana: 0xff25, /* to Hiragana */ + XK_Katakana: 0xff26, /* to Katakana */ + XK_Hiragana_Katakana: 0xff27, /* Hiragana/Katakana toggle */ + XK_Zenkaku: 0xff28, /* to Zenkaku */ + XK_Hankaku: 0xff29, /* to Hankaku */ + XK_Zenkaku_Hankaku: 0xff2a, /* Zenkaku/Hankaku toggle */ + XK_Touroku: 0xff2b, /* Add to Dictionary */ + XK_Massyo: 0xff2c, /* Delete from Dictionary */ + XK_Kana_Lock: 0xff2d, /* Kana Lock */ + XK_Kana_Shift: 0xff2e, /* Kana Shift */ + XK_Eisu_Shift: 0xff2f, /* Alphanumeric Shift */ + XK_Eisu_toggle: 0xff30, /* Alphanumeric toggle */ + XK_Kanji_Bangou: 0xff37, /* Codeinput */ + XK_Zen_Koho: 0xff3d, /* Multiple/All Candidate(s) */ + XK_Mae_Koho: 0xff3e, /* Previous Candidate */ + /* Cursor control & motion */ XK_Home: 0xff50, @@ -171,7 +202,17 @@ export default { XK_Hyper_L: 0xffed, /* Left hyper */ XK_Hyper_R: 0xffee, /* Right hyper */ + /* + * Keyboard (XKB) Extension function and modifier keys + * (from Appendix C of "The X Keyboard Extension: Protocol Specification") + * Byte 3 = 0xfe + */ + XK_ISO_Level3_Shift: 0xfe03, /* AltGr */ + XK_ISO_Next_Group: 0xfe08, + XK_ISO_Prev_Group: 0xfe0a, + XK_ISO_First_Group: 0xfe0c, + XK_ISO_Last_Group: 0xfe0e, /* * Latin 1 @@ -378,6 +419,15 @@ export default { XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */ XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */ + /* + * Korean + * Byte 3 = 0x0e + */ + + XK_Hangul: 0xff31, /* Hangul start/stop(toggle) */ + XK_Hangul_Hanja: 0xff34, /* Start Hangul->Hanja Conversion */ + XK_Hangul_Jeonja: 0xff38, /* Jeonja mode */ + /* * XFree86 vendor specific keysyms. * diff --git a/core/input/util.js b/core/input/util.js index d755c20f..4335b0bf 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -2,10 +2,17 @@ import KeyTable from "./keysym.js"; import keysyms from "./keysymdef.js"; import vkeys from "./vkeys.js"; import fixedkeys from "./fixedkeys.js"; +import DOMKeyTable from "./domkeytable.js"; function isMac() { return navigator && !!(/mac/i).exec(navigator.platform); } +function isIE() { + return navigator && !!(/trident/i).exec(navigator.userAgent); +} +function isEdge() { + return navigator && !!(/edge/i).exec(navigator.userAgent); +} // Get 'KeyboardEvent.code', handling legacy browsers export function getKeycode(evt){ @@ -67,88 +74,91 @@ export function getKeycode(evt){ return 'Unidentified'; } -// Get the most reliable keysym value we can get from a key event -export function getKeysym(evt){ +// Get 'KeyboardEvent.key', handling legacy browsers +export function getKey(evt) { + // Are we getting a proper key value? + if (evt.key !== undefined) { + // IE and Edge use some ancient version of the spec + // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/ + switch (evt.key) { + case 'Spacebar': return ' '; + case 'Esc': return 'Escape'; + case 'Scroll': return 'ScrollLock'; + case 'Win': return 'Meta'; + case 'Apps': return 'ContextMenu'; + case 'Up': return 'ArrowUp'; + case 'Left': return 'ArrowLeft'; + case 'Right': return 'ArrowRight'; + case 'Down': return 'ArrowDown'; + case 'Del': return 'Delete'; + case 'Divide': return '/'; + case 'Multiply': return '*'; + case 'Subtract': return '-'; + case 'Add': return '+'; + case 'Decimal': return evt.char; + } - // We start with layout independent keys + // Mozilla isn't fully in sync with the spec yet + switch (evt.key) { + case 'OS': return 'Meta'; + } + + // IE and Edge have broken handling of AltGraph so we cannot + // trust them for printable characters + if ((evt.key.length !== 1) || (!isIE() && !isEdge())) { + return evt.key; + } + } + + // Try to deduce it based on the physical key var code = getKeycode(evt); if (code in fixedkeys) { return fixedkeys[code]; } - // Next with mildly layout or state sensitive stuff - - // Like AltGraph - if (code === 'AltRight') { - if (evt.key === 'AltGraph') { - return KeyTable.XK_ISO_Level3_Shift; - } else { - return KeyTable.XK_Alt_R; - } + // If that failed, then see if we have a printable character + if (evt.charCode) { + return String.fromCharCode(evt.charCode); } - // Or the numpad - if (evt.location === 3) { - var key = evt.key; + // At this point we have nothing left to go on + return 'Unidentified'; +} - // IE and Edge use some ancient version of the spec - // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/ - switch (key) { - case 'Up': key = 'ArrowUp'; break; - case 'Left': key = 'ArrowLeft'; break; - case 'Right': key = 'ArrowRight'; break; - case 'Down': key = 'ArrowDown'; break; - case 'Del': key = 'Delete'; break; +// Get the most reliable keysym value we can get from a key event +export function getKeysym(evt){ + var key = getKey(evt); + + if (key === 'Unidentified') { + return null; + } + + // First look up special keys + if (key in DOMKeyTable) { + var location = evt.location; + + // Safari screws up location for the right cmd key + if ((key === 'Meta') && (location === 0)) { + location = 2; } - // Safari doesn't support KeyboardEvent.key yet - if ((key === undefined) && (evt.charCode)) { - key = String.fromCharCode(evt.charCode); + if ((location === undefined) || (location > 3)) { + location = 0; } - switch (key) { - case '0': return KeyTable.XK_KP_0; - case '1': return KeyTable.XK_KP_1; - case '2': return KeyTable.XK_KP_2; - case '3': return KeyTable.XK_KP_3; - case '4': return KeyTable.XK_KP_4; - case '5': return KeyTable.XK_KP_5; - case '6': return KeyTable.XK_KP_6; - case '7': return KeyTable.XK_KP_7; - case '8': return KeyTable.XK_KP_8; - case '9': return KeyTable.XK_KP_9; - // There is utter mayhem in the world when it comes to which - // character to use as a decimal separator... - case '.': return KeyTable.XK_KP_Decimal; - case ',': return KeyTable.XK_KP_Separator; - case 'Home': return KeyTable.XK_KP_Home; - case 'End': return KeyTable.XK_KP_End; - case 'PageUp': return KeyTable.XK_KP_Prior; - case 'PageDown': return KeyTable.XK_KP_Next; - case 'Insert': return KeyTable.XK_KP_Insert; - case 'Delete': return KeyTable.XK_KP_Delete; - case 'ArrowUp': return KeyTable.XK_KP_Up; - case 'ArrowLeft': return KeyTable.XK_KP_Left; - case 'ArrowRight': return KeyTable.XK_KP_Right; - case 'ArrowDown': return KeyTable.XK_KP_Down; - } + return DOMKeyTable[key][location]; } // Now we need to look at the Unicode symbol instead var codepoint; - if ('key' in evt) { - // Special key? (FIXME: Should have been caught earlier) - if (evt.key.length !== 1) { - return null; - } - - codepoint = evt.key.charCodeAt(); - } else if ('charCode' in evt) { - codepoint = evt.charCode; + // Special key? (FIXME: Should have been caught earlier) + if (key.length !== 1) { + return null; } + codepoint = key.charCodeAt(); if (codepoint) { return keysyms.lookup(codepoint); } diff --git a/tests/test.helper.js b/tests/test.helper.js index f705cff0..e0ae80ac 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -98,39 +98,104 @@ describe('Helpers', function() { }); }); - describe('getKeysym', function() { + describe('getKey', function() { it('should prefer key', function() { - expect(KeyboardUtil.getKeysym({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61); + expect(KeyboardUtil.getKey({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a'); + }); + it('should map legacy values', function() { + expect(KeyboardUtil.getKey({key: 'Spacebar'})).to.be.equal(' '); + expect(KeyboardUtil.getKey({key: 'Left'})).to.be.equal('ArrowLeft'); + expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta'); + expect(KeyboardUtil.getKey({key: 'Win'})).to.be.equal('Meta'); + }); + it('should use code if no key', function() { + expect(KeyboardUtil.getKey({code: 'NumpadBackspace'})).to.be.equal('Backspace'); + }); + it('should not use code fallback for character keys', function() { + expect(KeyboardUtil.getKey({code: 'KeyA'})).to.be.equal('Unidentified'); + expect(KeyboardUtil.getKey({code: 'Digit1'})).to.be.equal('Unidentified'); + expect(KeyboardUtil.getKey({code: 'Period'})).to.be.equal('Unidentified'); + expect(KeyboardUtil.getKey({code: 'Numpad1'})).to.be.equal('Unidentified'); }); it('should use charCode if no key', function() { - expect(KeyboardUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); + expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('Š'); + }); + it('should return Unidentified when it cannot map the key', function() { + expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified'); }); + describe('Broken key AltGraph on IE/Edge', function() { + var origNavigator; + beforeEach(function () { + // window.navigator is a protected read-only property in many + // environments, so we need to redefine it whilst running these + // tests. + origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); + if (origNavigator === undefined) { + // Object.getOwnPropertyDescriptor() doesn't work + // properly in any version of IE + this.skip(); + } + + Object.defineProperty(window, "navigator", {value: {}}); + if (window.navigator.platform !== undefined) { + // Object.defineProperty() doesn't work properly in old + // versions of Chrome + this.skip(); + } + }); + afterEach(function () { + Object.defineProperty(window, "navigator", origNavigator); + }); + + it('should ignore printable character key on IE', function() { + window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"; + expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified'); + }); + it('should ignore printable character key on Edge', function() { + window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393"; + expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified'); + }); + it('should allow non-printable character key on IE', function() { + window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"; + expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift'); + }); + it('should allow non-printable character key on Edge', function() { + window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393"; + expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift'); + }); + }); + }); + + describe('getKeysym', function() { describe('Non-character keys', function() { it('should recognize the right keys', function() { - expect(KeyboardUtil.getKeysym({code: 'Enter'})).to.be.equal(0xFF0D); - expect(KeyboardUtil.getKeysym({code: 'Backspace'})).to.be.equal(0xFF08); - expect(KeyboardUtil.getKeysym({code: 'Tab'})).to.be.equal(0xFF09); - expect(KeyboardUtil.getKeysym({code: 'ShiftLeft'})).to.be.equal(0xFFE1); - expect(KeyboardUtil.getKeysym({code: 'ControlLeft'})).to.be.equal(0xFFE3); - expect(KeyboardUtil.getKeysym({code: 'AltLeft'})).to.be.equal(0xFFE9); - expect(KeyboardUtil.getKeysym({code: 'MetaLeft'})).to.be.equal(0xFFEB); - expect(KeyboardUtil.getKeysym({code: 'Escape'})).to.be.equal(0xFF1B); - expect(KeyboardUtil.getKeysym({code: 'ArrowUp'})).to.be.equal(0xFF52); + expect(KeyboardUtil.getKeysym({key: 'Enter'})).to.be.equal(0xFF0D); + expect(KeyboardUtil.getKeysym({key: 'Backspace'})).to.be.equal(0xFF08); + expect(KeyboardUtil.getKeysym({key: 'Tab'})).to.be.equal(0xFF09); + expect(KeyboardUtil.getKeysym({key: 'Shift'})).to.be.equal(0xFFE1); + expect(KeyboardUtil.getKeysym({key: 'Control'})).to.be.equal(0xFFE3); + expect(KeyboardUtil.getKeysym({key: 'Alt'})).to.be.equal(0xFFE9); + expect(KeyboardUtil.getKeysym({key: 'Meta'})).to.be.equal(0xFFEB); + expect(KeyboardUtil.getKeysym({key: 'Escape'})).to.be.equal(0xFF1B); + expect(KeyboardUtil.getKeysym({key: 'ArrowUp'})).to.be.equal(0xFF52); + }); + it('should map left/right side', function() { + expect(KeyboardUtil.getKeysym({key: 'Shift', location: 1})).to.be.equal(0xFFE1); + expect(KeyboardUtil.getKeysym({key: 'Shift', location: 2})).to.be.equal(0xFFE2); + expect(KeyboardUtil.getKeysym({key: 'Control', location: 1})).to.be.equal(0xFFE3); + expect(KeyboardUtil.getKeysym({key: 'Control', location: 2})).to.be.equal(0xFFE4); }); it('should handle AltGraph', function() { - expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltRight'})).to.be.equal(0xFFEA); - expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph'})).to.be.equal(0xFE03); + expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'Alt', location: 2})).to.be.equal(0xFFEA); + expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph', location: 2})).to.be.equal(0xFE03); }); - it('should return null for unknown codes', function() { - expect(KeyboardUtil.getKeysym({code: 'Semicolon'})).to.be.null; - expect(KeyboardUtil.getKeysym({code: 'BracketRight'})).to.be.null; + it('should return null for unknown keys', function() { + expect(KeyboardUtil.getKeysym({key: 'Semicolon'})).to.be.null; + expect(KeyboardUtil.getKeysym({key: 'BracketRight'})).to.be.null; }); - it('should not recognize character keys', function() { - expect(KeyboardUtil.getKeysym({code: 'KeyA'})).to.be.null; - expect(KeyboardUtil.getKeysym({code: 'Digit1'})).to.be.null; - expect(KeyboardUtil.getKeysym({code: 'Period'})).to.be.null; - expect(KeyboardUtil.getKeysym({code: 'Numpad1'})).to.be.null; + it('should handle remappings', function() { + expect(KeyboardUtil.getKeysym({code: 'ControlLeft', key: 'Tab'})).to.be.equal(0xFF09); }); }); @@ -145,10 +210,6 @@ describe('Helpers', function() { expect(KeyboardUtil.getKeysym({code: 'Delete', key: 'Delete', location: 0})).to.be.equal(0xFFFF); expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F); }); - it('should handle IE/Edge key names', function() { - expect(KeyboardUtil.getKeysym({code: 'Numpad6', key: 'Right', location: 3})).to.be.equal(0xFF98); - expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Del', location: 3})).to.be.equal(0xFF9F); - }); it('should handle Numpad Decimal key', function() { expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE); expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC); diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 1ebbbd4a..a4ac6304 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -187,8 +187,8 @@ describe('Key Event Handling', function() { break; } }}); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); expect(count).to.be.equal(2); }); it('should change left Super to Alt', function(done) { @@ -198,7 +198,7 @@ describe('Key Event Handling', function() { expect(code).to.be.equal('MetaLeft'); done(); }}); - kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1})); }); it('should change right Super to left Super', function(done) { var kbd = new Keyboard({ @@ -207,7 +207,7 @@ describe('Key Event Handling', function() { expect(code).to.be.equal('MetaRight'); done(); }}); - kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2})); }); }); @@ -280,8 +280,8 @@ describe('Key Event Handling', function() { } }}); // First the modifier combo - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); // Next a normal character kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); expect(times_called).to.be.equal(7); @@ -299,8 +299,8 @@ describe('Key Event Handling', function() { } }}); // First the modifier combo - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); // Next a normal character kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); @@ -329,10 +329,10 @@ describe('Key Event Handling', function() { } }}); // First the modifier combo - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1})); // Then one of the keys again - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); expect(times_called).to.be.equal(3); }); }); From 5a3e9d3da89d854131396aceca1881487abded26 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 13 Apr 2017 14:49:42 +0200 Subject: [PATCH 20/20] Error.error can be null in some cases --- app/error-handler.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/error-handler.js b/app/error-handler.js index 30f77068..8ac7879e 100644 --- a/app/error-handler.js +++ b/app/error-handler.js @@ -39,8 +39,7 @@ msg.appendChild(div); } - if ((err !== undefined) && - (err.stack !== undefined)) { + if (err && (err.stack !== undefined)) { div = document.createElement("div"); div.className = 'noVNC_stack'; div.appendChild(document.createTextNode(err.stack));