├── .gitignore ├── AUTHORS.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── main.py ├── setup.cfg ├── setup.py └── system_hotkey ├── __init__.py ├── keysymdef.py ├── system_hotkey.py ├── tests └── test_system_hotkey.py ├── util.py └── xpybutil_keybind.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea/ 3 | *.*~ 4 | build/ 5 | dist/ 6 | *egg-info/ 7 | *.bat 8 | .cache/ 9 | tags 10 | 11 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Authors and Contributors 2 | ======================== 3 | 4 | Timothy Eichler 5 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | 2 | To be done 3 | ========== 4 | 5 | Mac support 6 | 7 | eta > 8 months 8 | 9 | 10 | Version Release Notes 11 | ===================== 12 | 1.0.4 13 | ----- 14 | * Exceptions are now run in main 15 | 16 | 1.0.3 17 | ----- 18 | * Documented the fact that xlib shouldn't really be used 19 | 20 | 1.0.2 21 | ----- 22 | * Fixed a linux bug where spurious events got passed through 23 | 24 | 1.0.0 25 | ----- 26 | * calls to the unregister and register are now thread safe 27 | * kp keys work properly 28 | * kp_unite option 29 | 30 | 0.1.4 31 | ----- 32 | * Bug fix on linux where pressing numlock would crash us.. 33 | 34 | 0.1.3 35 | ----- 36 | 37 | * Custom args can now be passed to a custom consumer 38 | 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, tim 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of system_wide_hotkeys nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst 2 | include LICENSE 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | System Hotkey 2 | ============= 3 | 4 | Multi platform system wide hotkeys for python 3, 5 | 6 | Currently no mac or python2 support :( 7 | 8 | 9 | Installation 10 | ------------ 11 | 12 | the old 13 | 14 | .. code-block:: bash 15 | 16 | pip3 install system_hotkey 17 | 18 | should do the trick 19 | 20 | Windows 21 | ^^^^^^^ 22 | install pywin32 23 | 24 | Linux 25 | ^^^^^ 26 | For x11 you should use `xcffib `_ (bsd license), 27 | 28 | If for some reason you have to use the python xlib bindings (gpl license), a few fixes need be added first. See `here `_ 29 | 30 | 31 | Usage 32 | ------ 33 | 34 | **Input Keysyms** 35 | 36 | System hotkeys uses the keysym names from xlib for everything besides modifiers.(although case insensitive) 37 | grep for vk_codes for a list of available chars. 38 | If you are unable to bind to a certain key please let us know. 39 | 40 | You can bind directly to symbols such as ["',. etc 41 | Numpad keys can be binded by prefixing with kp\_. 42 | 43 | Supported modifiers include: 44 | 45 | - control 46 | - shift 47 | - super (windows key) 48 | - alt 49 | 50 | InvalidKeyError will be raised if a key was not understood 51 | 52 | .. code-block:: python 53 | 54 | from system_hotkey import SystemHotkey 55 | hk = SystemHotkey() 56 | hk.register(('control', 'shift', 'h'), callback=lambda x: print("Easy!")) 57 | 58 | A SystemRegisterError will be raised if a hotkey is already in use. 59 | 60 | To unregister a hotkey 61 | 62 | .. code-block:: python 63 | 64 | hk.unregister(('control', 'shift', 'h')) 65 | 66 | A KeyError will be raised if the combination is not already grabbed. 67 | 68 | A UnregisterError will be raised if unregistering failed for any other reason. 69 | 70 | If you want you can pass in a custom consumer: 71 | 72 | .. code-block:: python 73 | 74 | def some_func(self, event, hotkey, args): 75 | pass 76 | 77 | hk = SystemHotkey(consumer=some_func) 78 | hk.register(hotkey, arg1, arg2, arg3) 79 | 80 | So you have a master function that receives all hotkey presses and can delegate as desired. 81 | 82 | **Note** 83 | Modifier keys are independent of order i.e control + alt + del is the same as alt + control + del 84 | 85 | Features 86 | -------- 87 | - Support for up to 3 modifiers and a key 88 | 89 | Limitations 90 | ----------- 91 | - I have only mapped most common keys, i have not experimented with Unicode/Japanese characters etc. It's only a matter of mapping a name to the keysym on Linux and virtual key code on windows. 92 | 93 | - binding to kp_left (key pad left) will also bind to kp_4, there is a flag (unite_kp) to toggle this behaviour but it is experimental 94 | 95 | - Requires an xserver (x11)... 96 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mainly for testing 3 | ''' 4 | 5 | from system_hotkey.system_hotkey import SystemHotkey 6 | 7 | hk = SystemHotkey(use_xlib=False) 8 | hk.register(('control', 'a',), callback=lambda e:print('hi')) 9 | 10 | while 1: 11 | pass 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 0 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from codecs import open 3 | import os 4 | here = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | def read(*paths): 7 | """Build a file path from *paths* and return the contents.""" 8 | with open(os.path.join(*paths), 'r') as f: 9 | return f.read() 10 | 11 | setup( 12 | name = 'system_hotkey', 13 | version='1.0.3', 14 | description = 'System wide hotkeys', 15 | long_description = (read('README.rst') + '\n\n' + 16 | read('HISTORY.rst') + '\n\n' + 17 | read('AUTHORS.rst')), 18 | url = 'https://github.com/timeyyy/system_hotkey', 19 | author='timothy eichler', 20 | author_email='tim_eichler@hotmail.com', 21 | license='BSD3', 22 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 23 | classifiers=[ 24 | # How mature is this project? Common values are 25 | #~ 3 - Alpha 26 | # 4 - Beta 27 | # 5 - Production/Stable 28 | 'Development Status :: 4 - Beta', 29 | 'Intended Audience :: Developers', 30 | 'Operating System :: OS Independent', 31 | 'License :: OSI Approved :: BSD License', 32 | #~ 'Programming Language :: Python :: 2', 33 | 'Programming Language :: Python :: 3', 34 | ], 35 | 36 | # What does your project relate to? 37 | keywords = 'hotkeys python3 shortcutkeys shortuct x11 windows', 38 | packages=find_packages(exclude=['contrib', 'docs', 'tests*']), 39 | ) 40 | -------------------------------------------------------------------------------- /system_hotkey/__init__.py: -------------------------------------------------------------------------------- 1 | from system_hotkey.system_hotkey import * 2 | -------------------------------------------------------------------------------- /system_hotkey/keysymdef.py: -------------------------------------------------------------------------------- 1 | """ 2 | A module that creates a dictionary mapping from an english string of a key, 3 | to a keysym (an X data type) in ``keysyms``. 4 | 5 | The module also creates a reverse mapping called ``keysym_strings`` that maps 6 | a keysym to a **list** of english string versions of that keysym. (There are 7 | more string representations than there are keysyms.) 8 | 9 | This list is taken from Xlib. 10 | """ 11 | from collections import defaultdict 12 | 13 | keysyms = { 14 | 'VoidSymbol': 0xffffff, 15 | 'BackSpace': 0xff08, 16 | 'Tab': 0xff09, 17 | 'Linefeed': 0xff0a, 18 | 'Clear': 0xff0b, 19 | 'Return': 0xff0d, 20 | 'Pause': 0xff13, 21 | 'Scroll_Lock': 0xff14, 22 | 'Sys_Req': 0xff15, 23 | 'Escape': 0xff1b, 24 | 'Delete': 0xffff, 25 | 'Multi_key': 0xff20, 26 | 'Codeinput': 0xff37, 27 | 'SingleCandidate': 0xff3c, 28 | 'MultipleCandidate': 0xff3d, 29 | 'PreviousCandidate': 0xff3e, 30 | 'Kanji': 0xff21, 31 | 'Muhenkan': 0xff22, 32 | 'Henkan_Mode': 0xff23, 33 | 'Henkan': 0xff23, 34 | 'Romaji': 0xff24, 35 | 'Hiragana': 0xff25, 36 | 'Katakana': 0xff26, 37 | 'Hiragana_Katakana': 0xff27, 38 | 'Zenkaku': 0xff28, 39 | 'Hankaku': 0xff29, 40 | 'Zenkaku_Hankaku': 0xff2a, 41 | 'Touroku': 0xff2b, 42 | 'Massyo': 0xff2c, 43 | 'Kana_Lock': 0xff2d, 44 | 'Kana_Shift': 0xff2e, 45 | 'Eisu_Shift': 0xff2f, 46 | 'Eisu_toggle': 0xff30, 47 | 'Kanji_Bangou': 0xff37, 48 | 'Zen_Koho': 0xff3d, 49 | 'Mae_Koho': 0xff3e, 50 | 'Home': 0xff50, 51 | 'Left': 0xff51, 52 | 'Up': 0xff52, 53 | 'Right': 0xff53, 54 | 'Down': 0xff54, 55 | 'Prior': 0xff55, 56 | 'Page_Up': 0xff55, 57 | 'Next': 0xff56, 58 | 'Page_Down': 0xff56, 59 | 'End': 0xff57, 60 | 'Begin': 0xff58, 61 | 'Select': 0xff60, 62 | 'Print': 0xff61, 63 | 'Execute': 0xff62, 64 | 'Insert': 0xff63, 65 | 'Undo': 0xff65, 66 | 'Redo': 0xff66, 67 | 'Menu': 0xff67, 68 | 'Find': 0xff68, 69 | 'Cancel': 0xff69, 70 | 'Help': 0xff6a, 71 | 'Break': 0xff6b, 72 | 'Mode_switch': 0xff7e, 73 | 'script_switch': 0xff7e, 74 | 'Num_Lock': 0xff7f, 75 | 'KP_Space': 0xff80, 76 | 'KP_Tab': 0xff89, 77 | 'KP_Enter': 0xff8d, 78 | 'KP_F1': 0xff91, 79 | 'KP_F2': 0xff92, 80 | 'KP_F3': 0xff93, 81 | 'KP_F4': 0xff94, 82 | 'KP_Home': 0xff95, 83 | 'KP_Left': 0xff96, 84 | 'KP_Up': 0xff97, 85 | 'KP_Right': 0xff98, 86 | 'KP_Down': 0xff99, 87 | 'KP_Prior': 0xff9a, 88 | 'KP_Page_Up': 0xff9a, 89 | 'KP_Next': 0xff9b, 90 | 'KP_Page_Down': 0xff9b, 91 | 'KP_End': 0xff9c, 92 | 'KP_Begin': 0xff9d, 93 | 'KP_Insert': 0xff9e, 94 | 'KP_Delete': 0xff9f, 95 | 'KP_Equal': 0xffbd, 96 | 'KP_Multiply': 0xffaa, 97 | 'KP_Add': 0xffab, 98 | 'KP_Separator': 0xffac, 99 | 'KP_Subtract': 0xffad, 100 | 'KP_Decimal': 0xffae, 101 | 'KP_Divide': 0xffaf, 102 | 'KP_0': 0xffb0, 103 | 'KP_1': 0xffb1, 104 | 'KP_2': 0xffb2, 105 | 'KP_3': 0xffb3, 106 | 'KP_4': 0xffb4, 107 | 'KP_5': 0xffb5, 108 | 'KP_6': 0xffb6, 109 | 'KP_7': 0xffb7, 110 | 'KP_8': 0xffb8, 111 | 'KP_9': 0xffb9, 112 | 'F1': 0xffbe, 113 | 'F2': 0xffbf, 114 | 'F3': 0xffc0, 115 | 'F4': 0xffc1, 116 | 'F5': 0xffc2, 117 | 'F6': 0xffc3, 118 | 'F7': 0xffc4, 119 | 'F8': 0xffc5, 120 | 'F9': 0xffc6, 121 | 'F10': 0xffc7, 122 | 'F11': 0xffc8, 123 | 'L1': 0xffc8, 124 | 'F12': 0xffc9, 125 | 'L2': 0xffc9, 126 | 'F13': 0xffca, 127 | 'L3': 0xffca, 128 | 'F14': 0xffcb, 129 | 'L4': 0xffcb, 130 | 'F15': 0xffcc, 131 | 'L5': 0xffcc, 132 | 'F16': 0xffcd, 133 | 'L6': 0xffcd, 134 | 'F17': 0xffce, 135 | 'L7': 0xffce, 136 | 'F18': 0xffcf, 137 | 'L8': 0xffcf, 138 | 'F19': 0xffd0, 139 | 'L9': 0xffd0, 140 | 'F20': 0xffd1, 141 | 'L10': 0xffd1, 142 | 'F21': 0xffd2, 143 | 'R1': 0xffd2, 144 | 'F22': 0xffd3, 145 | 'R2': 0xffd3, 146 | 'F23': 0xffd4, 147 | 'R3': 0xffd4, 148 | 'F24': 0xffd5, 149 | 'R4': 0xffd5, 150 | 'F25': 0xffd6, 151 | 'R5': 0xffd6, 152 | 'F26': 0xffd7, 153 | 'R6': 0xffd7, 154 | 'F27': 0xffd8, 155 | 'R7': 0xffd8, 156 | 'F28': 0xffd9, 157 | 'R8': 0xffd9, 158 | 'F29': 0xffda, 159 | 'R9': 0xffda, 160 | 'F30': 0xffdb, 161 | 'R10': 0xffdb, 162 | 'F31': 0xffdc, 163 | 'R11': 0xffdc, 164 | 'F32': 0xffdd, 165 | 'R12': 0xffdd, 166 | 'F33': 0xffde, 167 | 'R13': 0xffde, 168 | 'F34': 0xffdf, 169 | 'R14': 0xffdf, 170 | 'F35': 0xffe0, 171 | 'R15': 0xffe0, 172 | 'Shift_L': 0xffe1, 173 | 'Shift_R': 0xffe2, 174 | 'Control_L': 0xffe3, 175 | 'Control_R': 0xffe4, 176 | 'Caps_Lock': 0xffe5, 177 | 'Shift_Lock': 0xffe6, 178 | 'Meta_L': 0xffe7, 179 | 'Meta_R': 0xffe8, 180 | 'Alt_L': 0xffe9, 181 | 'Alt_R': 0xffea, 182 | 'Super_L': 0xffeb, 183 | 'Super_R': 0xffec, 184 | 'Hyper_L': 0xffed, 185 | 'Hyper_R': 0xffee, 186 | 'ISO_Lock': 0xfe01, 187 | 'ISO_Level2_Latch': 0xfe02, 188 | 'ISO_Level3_Shift': 0xfe03, 189 | 'ISO_Level3_Latch': 0xfe04, 190 | 'ISO_Level3_Lock': 0xfe05, 191 | 'ISO_Level5_Shift': 0xfe11, 192 | 'ISO_Level5_Latch': 0xfe12, 193 | 'ISO_Level5_Lock': 0xfe13, 194 | 'ISO_Group_Shift': 0xff7e, 195 | 'ISO_Group_Latch': 0xfe06, 196 | 'ISO_Group_Lock': 0xfe07, 197 | 'ISO_Next_Group': 0xfe08, 198 | 'ISO_Next_Group_Lock': 0xfe09, 199 | 'ISO_Prev_Group': 0xfe0a, 200 | 'ISO_Prev_Group_Lock': 0xfe0b, 201 | 'ISO_First_Group': 0xfe0c, 202 | 'ISO_First_Group_Lock': 0xfe0d, 203 | 'ISO_Last_Group': 0xfe0e, 204 | 'ISO_Last_Group_Lock': 0xfe0f, 205 | 'ISO_Left_Tab': 0xfe20, 206 | 'ISO_Move_Line_Up': 0xfe21, 207 | 'ISO_Move_Line_Down': 0xfe22, 208 | 'ISO_Partial_Line_Up': 0xfe23, 209 | 'ISO_Partial_Line_Down': 0xfe24, 210 | 'ISO_Partial_Space_Left': 0xfe25, 211 | 'ISO_Partial_Space_Right': 0xfe26, 212 | 'ISO_Set_Margin_Left': 0xfe27, 213 | 'ISO_Set_Margin_Right': 0xfe28, 214 | 'ISO_Release_Margin_Left': 0xfe29, 215 | 'ISO_Release_Margin_Right': 0xfe2a, 216 | 'ISO_Release_Both_Margins': 0xfe2b, 217 | 'ISO_Fast_Cursor_Left': 0xfe2c, 218 | 'ISO_Fast_Cursor_Right': 0xfe2d, 219 | 'ISO_Fast_Cursor_Up': 0xfe2e, 220 | 'ISO_Fast_Cursor_Down': 0xfe2f, 221 | 'ISO_Continuous_Underline': 0xfe30, 222 | 'ISO_Discontinuous_Underline': 0xfe31, 223 | 'ISO_Emphasize': 0xfe32, 224 | 'ISO_Center_Object': 0xfe33, 225 | 'ISO_Enter': 0xfe34, 226 | 'dead_grave': 0xfe50, 227 | 'dead_acute': 0xfe51, 228 | 'dead_circumflex': 0xfe52, 229 | 'dead_tilde': 0xfe53, 230 | 'dead_perispomeni': 0xfe53, 231 | 'dead_macron': 0xfe54, 232 | 'dead_breve': 0xfe55, 233 | 'dead_abovedot': 0xfe56, 234 | 'dead_diaeresis': 0xfe57, 235 | 'dead_abovering': 0xfe58, 236 | 'dead_doubleacute': 0xfe59, 237 | 'dead_caron': 0xfe5a, 238 | 'dead_cedilla': 0xfe5b, 239 | 'dead_ogonek': 0xfe5c, 240 | 'dead_iota': 0xfe5d, 241 | 'dead_voiced_sound': 0xfe5e, 242 | 'dead_semivoiced_sound': 0xfe5f, 243 | 'dead_belowdot': 0xfe60, 244 | 'dead_hook': 0xfe61, 245 | 'dead_horn': 0xfe62, 246 | 'dead_stroke': 0xfe63, 247 | 'dead_abovecomma': 0xfe64, 248 | 'dead_psili': 0xfe64, 249 | 'dead_abovereversedcomma': 0xfe65, 250 | 'dead_dasia': 0xfe65, 251 | 'dead_doublegrave': 0xfe66, 252 | 'dead_belowring': 0xfe67, 253 | 'dead_belowmacron': 0xfe68, 254 | 'dead_belowcircumflex': 0xfe69, 255 | 'dead_belowtilde': 0xfe6a, 256 | 'dead_belowbreve': 0xfe6b, 257 | 'dead_belowdiaeresis': 0xfe6c, 258 | 'dead_invertedbreve': 0xfe6d, 259 | 'dead_belowcomma': 0xfe6e, 260 | 'dead_currency': 0xfe6f, 261 | 'dead_a': 0xfe80, 262 | 'dead_A': 0xfe81, 263 | 'dead_e': 0xfe82, 264 | 'dead_E': 0xfe83, 265 | 'dead_i': 0xfe84, 266 | 'dead_I': 0xfe85, 267 | 'dead_o': 0xfe86, 268 | 'dead_O': 0xfe87, 269 | 'dead_u': 0xfe88, 270 | 'dead_U': 0xfe89, 271 | 'dead_small_schwa': 0xfe8a, 272 | 'dead_capital_schwa': 0xfe8b, 273 | 'First_Virtual_Screen': 0xfed0, 274 | 'Prev_Virtual_Screen': 0xfed1, 275 | 'Next_Virtual_Screen': 0xfed2, 276 | 'Last_Virtual_Screen': 0xfed4, 277 | 'Terminate_Server': 0xfed5, 278 | 'AccessX_Enable': 0xfe70, 279 | 'AccessX_Feedback_Enable': 0xfe71, 280 | 'RepeatKeys_Enable': 0xfe72, 281 | 'SlowKeys_Enable': 0xfe73, 282 | 'BounceKeys_Enable': 0xfe74, 283 | 'StickyKeys_Enable': 0xfe75, 284 | 'MouseKeys_Enable': 0xfe76, 285 | 'MouseKeys_Accel_Enable': 0xfe77, 286 | 'Overlay1_Enable': 0xfe78, 287 | 'Overlay2_Enable': 0xfe79, 288 | 'AudibleBell_Enable': 0xfe7a, 289 | 'Pointer_Left': 0xfee0, 290 | 'Pointer_Right': 0xfee1, 291 | 'Pointer_Up': 0xfee2, 292 | 'Pointer_Down': 0xfee3, 293 | 'Pointer_UpLeft': 0xfee4, 294 | 'Pointer_UpRight': 0xfee5, 295 | 'Pointer_DownLeft': 0xfee6, 296 | 'Pointer_DownRight': 0xfee7, 297 | 'Pointer_Button_Dflt': 0xfee8, 298 | 'Pointer_Button1': 0xfee9, 299 | 'Pointer_Button2': 0xfeea, 300 | 'Pointer_Button3': 0xfeeb, 301 | 'Pointer_Button4': 0xfeec, 302 | 'Pointer_Button5': 0xfeed, 303 | 'Pointer_DblClick_Dflt': 0xfeee, 304 | 'Pointer_DblClick1': 0xfeef, 305 | 'Pointer_DblClick2': 0xfef0, 306 | 'Pointer_DblClick3': 0xfef1, 307 | 'Pointer_DblClick4': 0xfef2, 308 | 'Pointer_DblClick5': 0xfef3, 309 | 'Pointer_Drag_Dflt': 0xfef4, 310 | 'Pointer_Drag1': 0xfef5, 311 | 'Pointer_Drag2': 0xfef6, 312 | 'Pointer_Drag3': 0xfef7, 313 | 'Pointer_Drag4': 0xfef8, 314 | 'Pointer_Drag5': 0xfefd, 315 | 'Pointer_EnableKeys': 0xfef9, 316 | 'Pointer_Accelerate': 0xfefa, 317 | 'Pointer_DfltBtnNext': 0xfefb, 318 | 'Pointer_DfltBtnPrev': 0xfefc, 319 | '3270_Duplicate': 0xfd01, 320 | '3270_FieldMark': 0xfd02, 321 | '3270_Right2': 0xfd03, 322 | '3270_Left2': 0xfd04, 323 | '3270_BackTab': 0xfd05, 324 | '3270_EraseEOF': 0xfd06, 325 | '3270_EraseInput': 0xfd07, 326 | '3270_Reset': 0xfd08, 327 | '3270_Quit': 0xfd09, 328 | '3270_PA1': 0xfd0a, 329 | '3270_PA2': 0xfd0b, 330 | '3270_PA3': 0xfd0c, 331 | '3270_Test': 0xfd0d, 332 | '3270_Attn': 0xfd0e, 333 | '3270_CursorBlink': 0xfd0f, 334 | '3270_AltCursor': 0xfd10, 335 | '3270_KeyClick': 0xfd11, 336 | '3270_Jump': 0xfd12, 337 | '3270_Ident': 0xfd13, 338 | '3270_Rule': 0xfd14, 339 | '3270_Copy': 0xfd15, 340 | '3270_Play': 0xfd16, 341 | '3270_Setup': 0xfd17, 342 | '3270_Record': 0xfd18, 343 | '3270_ChangeScreen': 0xfd19, 344 | '3270_DeleteWord': 0xfd1a, 345 | '3270_ExSelect': 0xfd1b, 346 | '3270_CursorSelect': 0xfd1c, 347 | '3270_PrintScreen': 0xfd1d, 348 | '3270_Enter': 0xfd1e, 349 | 'space': 0x0020, 350 | 'exclam': 0x0021, 351 | 'quotedbl': 0x0022, 352 | 'numbersign': 0x0023, 353 | 'dollar': 0x0024, 354 | 'percent': 0x0025, 355 | 'ampersand': 0x0026, 356 | 'apostrophe': 0x0027, 357 | 'quoteright': 0x0027, 358 | 'parenleft': 0x0028, 359 | 'parenright': 0x0029, 360 | 'asterisk': 0x002a, 361 | 'plus': 0x002b, 362 | 'comma': 0x002c, 363 | 'minus': 0x002d, 364 | 'period': 0x002e, 365 | 'slash': 0x002f, 366 | '0': 0x0030, 367 | '1': 0x0031, 368 | '2': 0x0032, 369 | '3': 0x0033, 370 | '4': 0x0034, 371 | '5': 0x0035, 372 | '6': 0x0036, 373 | '7': 0x0037, 374 | '8': 0x0038, 375 | '9': 0x0039, 376 | 'colon': 0x003a, 377 | 'semicolon': 0x003b, 378 | 'less': 0x003c, 379 | 'equal': 0x003d, 380 | 'greater': 0x003e, 381 | 'question': 0x003f, 382 | 'at': 0x0040, 383 | 'A': 0x0041, 384 | 'B': 0x0042, 385 | 'C': 0x0043, 386 | 'D': 0x0044, 387 | 'E': 0x0045, 388 | 'F': 0x0046, 389 | 'G': 0x0047, 390 | 'H': 0x0048, 391 | 'I': 0x0049, 392 | 'J': 0x004a, 393 | 'K': 0x004b, 394 | 'L': 0x004c, 395 | 'M': 0x004d, 396 | 'N': 0x004e, 397 | 'O': 0x004f, 398 | 'P': 0x0050, 399 | 'Q': 0x0051, 400 | 'R': 0x0052, 401 | 'S': 0x0053, 402 | 'T': 0x0054, 403 | 'U': 0x0055, 404 | 'V': 0x0056, 405 | 'W': 0x0057, 406 | 'X': 0x0058, 407 | 'Y': 0x0059, 408 | 'Z': 0x005a, 409 | 'bracketleft': 0x005b, 410 | 'backslash': 0x005c, 411 | 'bracketright': 0x005d, 412 | 'asciicircum': 0x005e, 413 | 'underscore': 0x005f, 414 | 'grave': 0x0060, 415 | 'quoteleft': 0x0060, 416 | 'a': 0x0061, 417 | 'b': 0x0062, 418 | 'c': 0x0063, 419 | 'd': 0x0064, 420 | 'e': 0x0065, 421 | 'f': 0x0066, 422 | 'g': 0x0067, 423 | 'h': 0x0068, 424 | 'i': 0x0069, 425 | 'j': 0x006a, 426 | 'k': 0x006b, 427 | 'l': 0x006c, 428 | 'm': 0x006d, 429 | 'n': 0x006e, 430 | 'o': 0x006f, 431 | 'p': 0x0070, 432 | 'q': 0x0071, 433 | 'r': 0x0072, 434 | 's': 0x0073, 435 | 't': 0x0074, 436 | 'u': 0x0075, 437 | 'v': 0x0076, 438 | 'w': 0x0077, 439 | 'x': 0x0078, 440 | 'y': 0x0079, 441 | 'z': 0x007a, 442 | 'braceleft': 0x007b, 443 | 'bar': 0x007c, 444 | 'braceright': 0x007d, 445 | 'asciitilde': 0x007e, 446 | 'nobreakspace': 0x00a0, 447 | 'exclamdown': 0x00a1, 448 | 'cent': 0x00a2, 449 | 'sterling': 0x00a3, 450 | 'currency': 0x00a4, 451 | 'yen': 0x00a5, 452 | 'brokenbar': 0x00a6, 453 | 'section': 0x00a7, 454 | 'diaeresis': 0x00a8, 455 | 'copyright': 0x00a9, 456 | 'ordfeminine': 0x00aa, 457 | 'guillemotleft': 0x00ab, 458 | 'notsign': 0x00ac, 459 | 'hyphen': 0x00ad, 460 | 'registered': 0x00ae, 461 | 'macron': 0x00af, 462 | 'degree': 0x00b0, 463 | 'plusminus': 0x00b1, 464 | 'twosuperior': 0x00b2, 465 | 'threesuperior': 0x00b3, 466 | 'acute': 0x00b4, 467 | 'mu': 0x00b5, 468 | 'paragraph': 0x00b6, 469 | 'periodcentered': 0x00b7, 470 | 'cedilla': 0x00b8, 471 | 'onesuperior': 0x00b9, 472 | 'masculine': 0x00ba, 473 | 'guillemotright': 0x00bb, 474 | 'onequarter': 0x00bc, 475 | 'onehalf': 0x00bd, 476 | 'threequarters': 0x00be, 477 | 'questiondown': 0x00bf, 478 | 'Agrave': 0x00c0, 479 | 'Aacute': 0x00c1, 480 | 'Acircumflex': 0x00c2, 481 | 'Atilde': 0x00c3, 482 | 'Adiaeresis': 0x00c4, 483 | 'Aring': 0x00c5, 484 | 'AE': 0x00c6, 485 | 'Ccedilla': 0x00c7, 486 | 'Egrave': 0x00c8, 487 | 'Eacute': 0x00c9, 488 | 'Ecircumflex': 0x00ca, 489 | 'Ediaeresis': 0x00cb, 490 | 'Igrave': 0x00cc, 491 | 'Iacute': 0x00cd, 492 | 'Icircumflex': 0x00ce, 493 | 'Idiaeresis': 0x00cf, 494 | 'ETH': 0x00d0, 495 | 'Eth': 0x00d0, 496 | 'Ntilde': 0x00d1, 497 | 'Ograve': 0x00d2, 498 | 'Oacute': 0x00d3, 499 | 'Ocircumflex': 0x00d4, 500 | 'Otilde': 0x00d5, 501 | 'Odiaeresis': 0x00d6, 502 | 'multiply': 0x00d7, 503 | 'Oslash': 0x00d8, 504 | 'Ooblique': 0x00d8, 505 | 'Ugrave': 0x00d9, 506 | 'Uacute': 0x00da, 507 | 'Ucircumflex': 0x00db, 508 | 'Udiaeresis': 0x00dc, 509 | 'Yacute': 0x00dd, 510 | 'THORN': 0x00de, 511 | 'Thorn': 0x00de, 512 | 'ssharp': 0x00df, 513 | 'agrave': 0x00e0, 514 | 'aacute': 0x00e1, 515 | 'acircumflex': 0x00e2, 516 | 'atilde': 0x00e3, 517 | 'adiaeresis': 0x00e4, 518 | 'aring': 0x00e5, 519 | 'ae': 0x00e6, 520 | 'ccedilla': 0x00e7, 521 | 'egrave': 0x00e8, 522 | 'eacute': 0x00e9, 523 | 'ecircumflex': 0x00ea, 524 | 'ediaeresis': 0x00eb, 525 | 'igrave': 0x00ec, 526 | 'iacute': 0x00ed, 527 | 'icircumflex': 0x00ee, 528 | 'idiaeresis': 0x00ef, 529 | 'eth': 0x00f0, 530 | 'ntilde': 0x00f1, 531 | 'ograve': 0x00f2, 532 | 'oacute': 0x00f3, 533 | 'ocircumflex': 0x00f4, 534 | 'otilde': 0x00f5, 535 | 'odiaeresis': 0x00f6, 536 | 'division': 0x00f7, 537 | 'oslash': 0x00f8, 538 | 'ooblique': 0x00f8, 539 | 'ugrave': 0x00f9, 540 | 'uacute': 0x00fa, 541 | 'ucircumflex': 0x00fb, 542 | 'udiaeresis': 0x00fc, 543 | 'yacute': 0x00fd, 544 | 'thorn': 0x00fe, 545 | 'ydiaeresis': 0x00ff, 546 | 'Aogonek': 0x01a1, 547 | 'breve': 0x01a2, 548 | 'Lstroke': 0x01a3, 549 | 'Lcaron': 0x01a5, 550 | 'Sacute': 0x01a6, 551 | 'Scaron': 0x01a9, 552 | 'Scedilla': 0x01aa, 553 | 'Tcaron': 0x01ab, 554 | 'Zacute': 0x01ac, 555 | 'Zcaron': 0x01ae, 556 | 'Zabovedot': 0x01af, 557 | 'aogonek': 0x01b1, 558 | 'ogonek': 0x01b2, 559 | 'lstroke': 0x01b3, 560 | 'lcaron': 0x01b5, 561 | 'sacute': 0x01b6, 562 | 'caron': 0x01b7, 563 | 'scaron': 0x01b9, 564 | 'scedilla': 0x01ba, 565 | 'tcaron': 0x01bb, 566 | 'zacute': 0x01bc, 567 | 'doubleacute': 0x01bd, 568 | 'zcaron': 0x01be, 569 | 'zabovedot': 0x01bf, 570 | 'Racute': 0x01c0, 571 | 'Abreve': 0x01c3, 572 | 'Lacute': 0x01c5, 573 | 'Cacute': 0x01c6, 574 | 'Ccaron': 0x01c8, 575 | 'Eogonek': 0x01ca, 576 | 'Ecaron': 0x01cc, 577 | 'Dcaron': 0x01cf, 578 | 'Dstroke': 0x01d0, 579 | 'Nacute': 0x01d1, 580 | 'Ncaron': 0x01d2, 581 | 'Odoubleacute': 0x01d5, 582 | 'Rcaron': 0x01d8, 583 | 'Uring': 0x01d9, 584 | 'Udoubleacute': 0x01db, 585 | 'Tcedilla': 0x01de, 586 | 'racute': 0x01e0, 587 | 'abreve': 0x01e3, 588 | 'lacute': 0x01e5, 589 | 'cacute': 0x01e6, 590 | 'ccaron': 0x01e8, 591 | 'eogonek': 0x01ea, 592 | 'ecaron': 0x01ec, 593 | 'dcaron': 0x01ef, 594 | 'dstroke': 0x01f0, 595 | 'nacute': 0x01f1, 596 | 'ncaron': 0x01f2, 597 | 'odoubleacute': 0x01f5, 598 | 'udoubleacute': 0x01fb, 599 | 'rcaron': 0x01f8, 600 | 'uring': 0x01f9, 601 | 'tcedilla': 0x01fe, 602 | 'abovedot': 0x01ff, 603 | 'Hstroke': 0x02a1, 604 | 'Hcircumflex': 0x02a6, 605 | 'Iabovedot': 0x02a9, 606 | 'Gbreve': 0x02ab, 607 | 'Jcircumflex': 0x02ac, 608 | 'hstroke': 0x02b1, 609 | 'hcircumflex': 0x02b6, 610 | 'idotless': 0x02b9, 611 | 'gbreve': 0x02bb, 612 | 'jcircumflex': 0x02bc, 613 | 'Cabovedot': 0x02c5, 614 | 'Ccircumflex': 0x02c6, 615 | 'Gabovedot': 0x02d5, 616 | 'Gcircumflex': 0x02d8, 617 | 'Ubreve': 0x02dd, 618 | 'Scircumflex': 0x02de, 619 | 'cabovedot': 0x02e5, 620 | 'ccircumflex': 0x02e6, 621 | 'gabovedot': 0x02f5, 622 | 'gcircumflex': 0x02f8, 623 | 'ubreve': 0x02fd, 624 | 'scircumflex': 0x02fe, 625 | 'kra': 0x03a2, 626 | 'kappa': 0x03a2, 627 | 'Rcedilla': 0x03a3, 628 | 'Itilde': 0x03a5, 629 | 'Lcedilla': 0x03a6, 630 | 'Emacron': 0x03aa, 631 | 'Gcedilla': 0x03ab, 632 | 'Tslash': 0x03ac, 633 | 'rcedilla': 0x03b3, 634 | 'itilde': 0x03b5, 635 | 'lcedilla': 0x03b6, 636 | 'emacron': 0x03ba, 637 | 'gcedilla': 0x03bb, 638 | 'tslash': 0x03bc, 639 | 'ENG': 0x03bd, 640 | 'eng': 0x03bf, 641 | 'Amacron': 0x03c0, 642 | 'Iogonek': 0x03c7, 643 | 'Eabovedot': 0x03cc, 644 | 'Imacron': 0x03cf, 645 | 'Ncedilla': 0x03d1, 646 | 'Omacron': 0x03d2, 647 | 'Kcedilla': 0x03d3, 648 | 'Uogonek': 0x03d9, 649 | 'Utilde': 0x03dd, 650 | 'Umacron': 0x03de, 651 | 'amacron': 0x03e0, 652 | 'iogonek': 0x03e7, 653 | 'eabovedot': 0x03ec, 654 | 'imacron': 0x03ef, 655 | 'ncedilla': 0x03f1, 656 | 'omacron': 0x03f2, 657 | 'kcedilla': 0x03f3, 658 | 'uogonek': 0x03f9, 659 | 'utilde': 0x03fd, 660 | 'umacron': 0x03fe, 661 | 'Babovedot': 0x1001e02, 662 | 'babovedot': 0x1001e03, 663 | 'Dabovedot': 0x1001e0a, 664 | 'Wgrave': 0x1001e80, 665 | 'Wacute': 0x1001e82, 666 | 'dabovedot': 0x1001e0b, 667 | 'Ygrave': 0x1001ef2, 668 | 'Fabovedot': 0x1001e1e, 669 | 'fabovedot': 0x1001e1f, 670 | 'Mabovedot': 0x1001e40, 671 | 'mabovedot': 0x1001e41, 672 | 'Pabovedot': 0x1001e56, 673 | 'wgrave': 0x1001e81, 674 | 'pabovedot': 0x1001e57, 675 | 'wacute': 0x1001e83, 676 | 'Sabovedot': 0x1001e60, 677 | 'ygrave': 0x1001ef3, 678 | 'Wdiaeresis': 0x1001e84, 679 | 'wdiaeresis': 0x1001e85, 680 | 'sabovedot': 0x1001e61, 681 | 'Wcircumflex': 0x1000174, 682 | 'Tabovedot': 0x1001e6a, 683 | 'Ycircumflex': 0x1000176, 684 | 'wcircumflex': 0x1000175, 685 | 'tabovedot': 0x1001e6b, 686 | 'ycircumflex': 0x1000177, 687 | 'OE': 0x13bc, 688 | 'oe': 0x13bd, 689 | 'Ydiaeresis': 0x13be, 690 | 'overline': 0x047e, 691 | 'kana_fullstop': 0x04a1, 692 | 'kana_openingbracket': 0x04a2, 693 | 'kana_closingbracket': 0x04a3, 694 | 'kana_comma': 0x04a4, 695 | 'kana_conjunctive': 0x04a5, 696 | 'kana_middledot': 0x04a5, 697 | 'kana_WO': 0x04a6, 698 | 'kana_a': 0x04a7, 699 | 'kana_i': 0x04a8, 700 | 'kana_u': 0x04a9, 701 | 'kana_e': 0x04aa, 702 | 'kana_o': 0x04ab, 703 | 'kana_ya': 0x04ac, 704 | 'kana_yu': 0x04ad, 705 | 'kana_yo': 0x04ae, 706 | 'kana_tsu': 0x04af, 707 | 'kana_tu': 0x04af, 708 | 'prolongedsound': 0x04b0, 709 | 'kana_A': 0x04b1, 710 | 'kana_I': 0x04b2, 711 | 'kana_U': 0x04b3, 712 | 'kana_E': 0x04b4, 713 | 'kana_O': 0x04b5, 714 | 'kana_KA': 0x04b6, 715 | 'kana_KI': 0x04b7, 716 | 'kana_KU': 0x04b8, 717 | 'kana_KE': 0x04b9, 718 | 'kana_KO': 0x04ba, 719 | 'kana_SA': 0x04bb, 720 | 'kana_SHI': 0x04bc, 721 | 'kana_SU': 0x04bd, 722 | 'kana_SE': 0x04be, 723 | 'kana_SO': 0x04bf, 724 | 'kana_TA': 0x04c0, 725 | 'kana_CHI': 0x04c1, 726 | 'kana_TI': 0x04c1, 727 | 'kana_TSU': 0x04c2, 728 | 'kana_TU': 0x04c2, 729 | 'kana_TE': 0x04c3, 730 | 'kana_TO': 0x04c4, 731 | 'kana_NA': 0x04c5, 732 | 'kana_NI': 0x04c6, 733 | 'kana_NU': 0x04c7, 734 | 'kana_NE': 0x04c8, 735 | 'kana_NO': 0x04c9, 736 | 'kana_HA': 0x04ca, 737 | 'kana_HI': 0x04cb, 738 | 'kana_FU': 0x04cc, 739 | 'kana_HU': 0x04cc, 740 | 'kana_HE': 0x04cd, 741 | 'kana_HO': 0x04ce, 742 | 'kana_MA': 0x04cf, 743 | 'kana_MI': 0x04d0, 744 | 'kana_MU': 0x04d1, 745 | 'kana_ME': 0x04d2, 746 | 'kana_MO': 0x04d3, 747 | 'kana_YA': 0x04d4, 748 | 'kana_YU': 0x04d5, 749 | 'kana_YO': 0x04d6, 750 | 'kana_RA': 0x04d7, 751 | 'kana_RI': 0x04d8, 752 | 'kana_RU': 0x04d9, 753 | 'kana_RE': 0x04da, 754 | 'kana_RO': 0x04db, 755 | 'kana_WA': 0x04dc, 756 | 'kana_N': 0x04dd, 757 | 'voicedsound': 0x04de, 758 | 'semivoicedsound': 0x04df, 759 | 'kana_switch': 0xff7e, 760 | 'Farsi_0': 0x10006f0, 761 | 'Farsi_1': 0x10006f1, 762 | 'Farsi_2': 0x10006f2, 763 | 'Farsi_3': 0x10006f3, 764 | 'Farsi_4': 0x10006f4, 765 | 'Farsi_5': 0x10006f5, 766 | 'Farsi_6': 0x10006f6, 767 | 'Farsi_7': 0x10006f7, 768 | 'Farsi_8': 0x10006f8, 769 | 'Farsi_9': 0x10006f9, 770 | 'Arabic_percent': 0x100066a, 771 | 'Arabic_superscript_alef': 0x1000670, 772 | 'Arabic_tteh': 0x1000679, 773 | 'Arabic_peh': 0x100067e, 774 | 'Arabic_tcheh': 0x1000686, 775 | 'Arabic_ddal': 0x1000688, 776 | 'Arabic_rreh': 0x1000691, 777 | 'Arabic_comma': 0x05ac, 778 | 'Arabic_fullstop': 0x10006d4, 779 | 'Arabic_0': 0x1000660, 780 | 'Arabic_1': 0x1000661, 781 | 'Arabic_2': 0x1000662, 782 | 'Arabic_3': 0x1000663, 783 | 'Arabic_4': 0x1000664, 784 | 'Arabic_5': 0x1000665, 785 | 'Arabic_6': 0x1000666, 786 | 'Arabic_7': 0x1000667, 787 | 'Arabic_8': 0x1000668, 788 | 'Arabic_9': 0x1000669, 789 | 'Arabic_semicolon': 0x05bb, 790 | 'Arabic_question_mark': 0x05bf, 791 | 'Arabic_hamza': 0x05c1, 792 | 'Arabic_maddaonalef': 0x05c2, 793 | 'Arabic_hamzaonalef': 0x05c3, 794 | 'Arabic_hamzaonwaw': 0x05c4, 795 | 'Arabic_hamzaunderalef': 0x05c5, 796 | 'Arabic_hamzaonyeh': 0x05c6, 797 | 'Arabic_alef': 0x05c7, 798 | 'Arabic_beh': 0x05c8, 799 | 'Arabic_tehmarbuta': 0x05c9, 800 | 'Arabic_teh': 0x05ca, 801 | 'Arabic_theh': 0x05cb, 802 | 'Arabic_jeem': 0x05cc, 803 | 'Arabic_hah': 0x05cd, 804 | 'Arabic_khah': 0x05ce, 805 | 'Arabic_dal': 0x05cf, 806 | 'Arabic_thal': 0x05d0, 807 | 'Arabic_ra': 0x05d1, 808 | 'Arabic_zain': 0x05d2, 809 | 'Arabic_seen': 0x05d3, 810 | 'Arabic_sheen': 0x05d4, 811 | 'Arabic_sad': 0x05d5, 812 | 'Arabic_dad': 0x05d6, 813 | 'Arabic_tah': 0x05d7, 814 | 'Arabic_zah': 0x05d8, 815 | 'Arabic_ain': 0x05d9, 816 | 'Arabic_ghain': 0x05da, 817 | 'Arabic_tatweel': 0x05e0, 818 | 'Arabic_feh': 0x05e1, 819 | 'Arabic_qaf': 0x05e2, 820 | 'Arabic_kaf': 0x05e3, 821 | 'Arabic_lam': 0x05e4, 822 | 'Arabic_meem': 0x05e5, 823 | 'Arabic_noon': 0x05e6, 824 | 'Arabic_ha': 0x05e7, 825 | 'Arabic_heh': 0x05e7, 826 | 'Arabic_waw': 0x05e8, 827 | 'Arabic_alefmaksura': 0x05e9, 828 | 'Arabic_yeh': 0x05ea, 829 | 'Arabic_fathatan': 0x05eb, 830 | 'Arabic_dammatan': 0x05ec, 831 | 'Arabic_kasratan': 0x05ed, 832 | 'Arabic_fatha': 0x05ee, 833 | 'Arabic_damma': 0x05ef, 834 | 'Arabic_kasra': 0x05f0, 835 | 'Arabic_shadda': 0x05f1, 836 | 'Arabic_sukun': 0x05f2, 837 | 'Arabic_madda_above': 0x1000653, 838 | 'Arabic_hamza_above': 0x1000654, 839 | 'Arabic_hamza_below': 0x1000655, 840 | 'Arabic_jeh': 0x1000698, 841 | 'Arabic_veh': 0x10006a4, 842 | 'Arabic_keheh': 0x10006a9, 843 | 'Arabic_gaf': 0x10006af, 844 | 'Arabic_noon_ghunna': 0x10006ba, 845 | 'Arabic_heh_doachashmee': 0x10006be, 846 | 'Farsi_yeh': 0x10006cc, 847 | 'Arabic_farsi_yeh': 0x10006cc, 848 | 'Arabic_yeh_baree': 0x10006d2, 849 | 'Arabic_heh_goal': 0x10006c1, 850 | 'Arabic_switch': 0xff7e, 851 | 'Cyrillic_GHE_bar': 0x1000492, 852 | 'Cyrillic_ghe_bar': 0x1000493, 853 | 'Cyrillic_ZHE_descender': 0x1000496, 854 | 'Cyrillic_zhe_descender': 0x1000497, 855 | 'Cyrillic_KA_descender': 0x100049a, 856 | 'Cyrillic_ka_descender': 0x100049b, 857 | 'Cyrillic_KA_vertstroke': 0x100049c, 858 | 'Cyrillic_ka_vertstroke': 0x100049d, 859 | 'Cyrillic_EN_descender': 0x10004a2, 860 | 'Cyrillic_en_descender': 0x10004a3, 861 | 'Cyrillic_U_straight': 0x10004ae, 862 | 'Cyrillic_u_straight': 0x10004af, 863 | 'Cyrillic_U_straight_bar': 0x10004b0, 864 | 'Cyrillic_u_straight_bar': 0x10004b1, 865 | 'Cyrillic_HA_descender': 0x10004b2, 866 | 'Cyrillic_ha_descender': 0x10004b3, 867 | 'Cyrillic_CHE_descender': 0x10004b6, 868 | 'Cyrillic_che_descender': 0x10004b7, 869 | 'Cyrillic_CHE_vertstroke': 0x10004b8, 870 | 'Cyrillic_che_vertstroke': 0x10004b9, 871 | 'Cyrillic_SHHA': 0x10004ba, 872 | 'Cyrillic_shha': 0x10004bb, 873 | 'Cyrillic_SCHWA': 0x10004d8, 874 | 'Cyrillic_schwa': 0x10004d9, 875 | 'Cyrillic_I_macron': 0x10004e2, 876 | 'Cyrillic_i_macron': 0x10004e3, 877 | 'Cyrillic_O_bar': 0x10004e8, 878 | 'Cyrillic_o_bar': 0x10004e9, 879 | 'Cyrillic_U_macron': 0x10004ee, 880 | 'Cyrillic_u_macron': 0x10004ef, 881 | 'Serbian_dje': 0x06a1, 882 | 'Macedonia_gje': 0x06a2, 883 | 'Cyrillic_io': 0x06a3, 884 | 'Ukrainian_ie': 0x06a4, 885 | 'Ukranian_je': 0x06a4, 886 | 'Macedonia_dse': 0x06a5, 887 | 'Ukrainian_i': 0x06a6, 888 | 'Ukranian_i': 0x06a6, 889 | 'Ukrainian_yi': 0x06a7, 890 | 'Ukranian_yi': 0x06a7, 891 | 'Cyrillic_je': 0x06a8, 892 | 'Serbian_je': 0x06a8, 893 | 'Cyrillic_lje': 0x06a9, 894 | 'Serbian_lje': 0x06a9, 895 | 'Cyrillic_nje': 0x06aa, 896 | 'Serbian_nje': 0x06aa, 897 | 'Serbian_tshe': 0x06ab, 898 | 'Macedonia_kje': 0x06ac, 899 | 'Ukrainian_ghe_with_upturn': 0x06ad, 900 | 'Byelorussian_shortu': 0x06ae, 901 | 'Cyrillic_dzhe': 0x06af, 902 | 'Serbian_dze': 0x06af, 903 | 'numerosign': 0x06b0, 904 | 'Serbian_DJE': 0x06b1, 905 | 'Macedonia_GJE': 0x06b2, 906 | 'Cyrillic_IO': 0x06b3, 907 | 'Ukrainian_IE': 0x06b4, 908 | 'Ukranian_JE': 0x06b4, 909 | 'Macedonia_DSE': 0x06b5, 910 | 'Ukrainian_I': 0x06b6, 911 | 'Ukranian_I': 0x06b6, 912 | 'Ukrainian_YI': 0x06b7, 913 | 'Ukranian_YI': 0x06b7, 914 | 'Cyrillic_JE': 0x06b8, 915 | 'Serbian_JE': 0x06b8, 916 | 'Cyrillic_LJE': 0x06b9, 917 | 'Serbian_LJE': 0x06b9, 918 | 'Cyrillic_NJE': 0x06ba, 919 | 'Serbian_NJE': 0x06ba, 920 | 'Serbian_TSHE': 0x06bb, 921 | 'Macedonia_KJE': 0x06bc, 922 | 'Ukrainian_GHE_WITH_UPTURN': 0x06bd, 923 | 'Byelorussian_SHORTU': 0x06be, 924 | 'Cyrillic_DZHE': 0x06bf, 925 | 'Serbian_DZE': 0x06bf, 926 | 'Cyrillic_yu': 0x06c0, 927 | 'Cyrillic_a': 0x06c1, 928 | 'Cyrillic_be': 0x06c2, 929 | 'Cyrillic_tse': 0x06c3, 930 | 'Cyrillic_de': 0x06c4, 931 | 'Cyrillic_ie': 0x06c5, 932 | 'Cyrillic_ef': 0x06c6, 933 | 'Cyrillic_ghe': 0x06c7, 934 | 'Cyrillic_ha': 0x06c8, 935 | 'Cyrillic_i': 0x06c9, 936 | 'Cyrillic_shorti': 0x06ca, 937 | 'Cyrillic_ka': 0x06cb, 938 | 'Cyrillic_el': 0x06cc, 939 | 'Cyrillic_em': 0x06cd, 940 | 'Cyrillic_en': 0x06ce, 941 | 'Cyrillic_o': 0x06cf, 942 | 'Cyrillic_pe': 0x06d0, 943 | 'Cyrillic_ya': 0x06d1, 944 | 'Cyrillic_er': 0x06d2, 945 | 'Cyrillic_es': 0x06d3, 946 | 'Cyrillic_te': 0x06d4, 947 | 'Cyrillic_u': 0x06d5, 948 | 'Cyrillic_zhe': 0x06d6, 949 | 'Cyrillic_ve': 0x06d7, 950 | 'Cyrillic_softsign': 0x06d8, 951 | 'Cyrillic_yeru': 0x06d9, 952 | 'Cyrillic_ze': 0x06da, 953 | 'Cyrillic_sha': 0x06db, 954 | 'Cyrillic_e': 0x06dc, 955 | 'Cyrillic_shcha': 0x06dd, 956 | 'Cyrillic_che': 0x06de, 957 | 'Cyrillic_hardsign': 0x06df, 958 | 'Cyrillic_YU': 0x06e0, 959 | 'Cyrillic_A': 0x06e1, 960 | 'Cyrillic_BE': 0x06e2, 961 | 'Cyrillic_TSE': 0x06e3, 962 | 'Cyrillic_DE': 0x06e4, 963 | 'Cyrillic_IE': 0x06e5, 964 | 'Cyrillic_EF': 0x06e6, 965 | 'Cyrillic_GHE': 0x06e7, 966 | 'Cyrillic_HA': 0x06e8, 967 | 'Cyrillic_I': 0x06e9, 968 | 'Cyrillic_SHORTI': 0x06ea, 969 | 'Cyrillic_KA': 0x06eb, 970 | 'Cyrillic_EL': 0x06ec, 971 | 'Cyrillic_EM': 0x06ed, 972 | 'Cyrillic_EN': 0x06ee, 973 | 'Cyrillic_O': 0x06ef, 974 | 'Cyrillic_PE': 0x06f0, 975 | 'Cyrillic_YA': 0x06f1, 976 | 'Cyrillic_ER': 0x06f2, 977 | 'Cyrillic_ES': 0x06f3, 978 | 'Cyrillic_TE': 0x06f4, 979 | 'Cyrillic_U': 0x06f5, 980 | 'Cyrillic_ZHE': 0x06f6, 981 | 'Cyrillic_VE': 0x06f7, 982 | 'Cyrillic_SOFTSIGN': 0x06f8, 983 | 'Cyrillic_YERU': 0x06f9, 984 | 'Cyrillic_ZE': 0x06fa, 985 | 'Cyrillic_SHA': 0x06fb, 986 | 'Cyrillic_E': 0x06fc, 987 | 'Cyrillic_SHCHA': 0x06fd, 988 | 'Cyrillic_CHE': 0x06fe, 989 | 'Cyrillic_HARDSIGN': 0x06ff, 990 | 'Greek_ALPHAaccent': 0x07a1, 991 | 'Greek_EPSILONaccent': 0x07a2, 992 | 'Greek_ETAaccent': 0x07a3, 993 | 'Greek_IOTAaccent': 0x07a4, 994 | 'Greek_IOTAdieresis': 0x07a5, 995 | 'Greek_IOTAdiaeresis': 0x07a5, 996 | 'Greek_OMICRONaccent': 0x07a7, 997 | 'Greek_UPSILONaccent': 0x07a8, 998 | 'Greek_UPSILONdieresis': 0x07a9, 999 | 'Greek_OMEGAaccent': 0x07ab, 1000 | 'Greek_accentdieresis': 0x07ae, 1001 | 'Greek_horizbar': 0x07af, 1002 | 'Greek_alphaaccent': 0x07b1, 1003 | 'Greek_epsilonaccent': 0x07b2, 1004 | 'Greek_etaaccent': 0x07b3, 1005 | 'Greek_iotaaccent': 0x07b4, 1006 | 'Greek_iotadieresis': 0x07b5, 1007 | 'Greek_iotaaccentdieresis': 0x07b6, 1008 | 'Greek_omicronaccent': 0x07b7, 1009 | 'Greek_upsilonaccent': 0x07b8, 1010 | 'Greek_upsilondieresis': 0x07b9, 1011 | 'Greek_upsilonaccentdieresis': 0x07ba, 1012 | 'Greek_omegaaccent': 0x07bb, 1013 | 'Greek_ALPHA': 0x07c1, 1014 | 'Greek_BETA': 0x07c2, 1015 | 'Greek_GAMMA': 0x07c3, 1016 | 'Greek_DELTA': 0x07c4, 1017 | 'Greek_EPSILON': 0x07c5, 1018 | 'Greek_ZETA': 0x07c6, 1019 | 'Greek_ETA': 0x07c7, 1020 | 'Greek_THETA': 0x07c8, 1021 | 'Greek_IOTA': 0x07c9, 1022 | 'Greek_KAPPA': 0x07ca, 1023 | 'Greek_LAMDA': 0x07cb, 1024 | 'Greek_LAMBDA': 0x07cb, 1025 | 'Greek_MU': 0x07cc, 1026 | 'Greek_NU': 0x07cd, 1027 | 'Greek_XI': 0x07ce, 1028 | 'Greek_OMICRON': 0x07cf, 1029 | 'Greek_PI': 0x07d0, 1030 | 'Greek_RHO': 0x07d1, 1031 | 'Greek_SIGMA': 0x07d2, 1032 | 'Greek_TAU': 0x07d4, 1033 | 'Greek_UPSILON': 0x07d5, 1034 | 'Greek_PHI': 0x07d6, 1035 | 'Greek_CHI': 0x07d7, 1036 | 'Greek_PSI': 0x07d8, 1037 | 'Greek_OMEGA': 0x07d9, 1038 | 'Greek_alpha': 0x07e1, 1039 | 'Greek_beta': 0x07e2, 1040 | 'Greek_gamma': 0x07e3, 1041 | 'Greek_delta': 0x07e4, 1042 | 'Greek_epsilon': 0x07e5, 1043 | 'Greek_zeta': 0x07e6, 1044 | 'Greek_eta': 0x07e7, 1045 | 'Greek_theta': 0x07e8, 1046 | 'Greek_iota': 0x07e9, 1047 | 'Greek_kappa': 0x07ea, 1048 | 'Greek_lamda': 0x07eb, 1049 | 'Greek_lambda': 0x07eb, 1050 | 'Greek_mu': 0x07ec, 1051 | 'Greek_nu': 0x07ed, 1052 | 'Greek_xi': 0x07ee, 1053 | 'Greek_omicron': 0x07ef, 1054 | 'Greek_pi': 0x07f0, 1055 | 'Greek_rho': 0x07f1, 1056 | 'Greek_sigma': 0x07f2, 1057 | 'Greek_finalsmallsigma': 0x07f3, 1058 | 'Greek_tau': 0x07f4, 1059 | 'Greek_upsilon': 0x07f5, 1060 | 'Greek_phi': 0x07f6, 1061 | 'Greek_chi': 0x07f7, 1062 | 'Greek_psi': 0x07f8, 1063 | 'Greek_omega': 0x07f9, 1064 | 'Greek_switch': 0xff7e, 1065 | 'leftradical': 0x08a1, 1066 | 'topleftradical': 0x08a2, 1067 | 'horizconnector': 0x08a3, 1068 | 'topintegral': 0x08a4, 1069 | 'botintegral': 0x08a5, 1070 | 'vertconnector': 0x08a6, 1071 | 'topleftsqbracket': 0x08a7, 1072 | 'botleftsqbracket': 0x08a8, 1073 | 'toprightsqbracket': 0x08a9, 1074 | 'botrightsqbracket': 0x08aa, 1075 | 'topleftparens': 0x08ab, 1076 | 'botleftparens': 0x08ac, 1077 | 'toprightparens': 0x08ad, 1078 | 'botrightparens': 0x08ae, 1079 | 'leftmiddlecurlybrace': 0x08af, 1080 | 'rightmiddlecurlybrace': 0x08b0, 1081 | 'topleftsummation': 0x08b1, 1082 | 'botleftsummation': 0x08b2, 1083 | 'topvertsummationconnector': 0x08b3, 1084 | 'botvertsummationconnector': 0x08b4, 1085 | 'toprightsummation': 0x08b5, 1086 | 'botrightsummation': 0x08b6, 1087 | 'rightmiddlesummation': 0x08b7, 1088 | 'lessthanequal': 0x08bc, 1089 | 'notequal': 0x08bd, 1090 | 'greaterthanequal': 0x08be, 1091 | 'integral': 0x08bf, 1092 | 'therefore': 0x08c0, 1093 | 'variation': 0x08c1, 1094 | 'infinity': 0x08c2, 1095 | 'nabla': 0x08c5, 1096 | 'approximate': 0x08c8, 1097 | 'similarequal': 0x08c9, 1098 | 'ifonlyif': 0x08cd, 1099 | 'implies': 0x08ce, 1100 | 'identical': 0x08cf, 1101 | 'radical': 0x08d6, 1102 | 'includedin': 0x08da, 1103 | 'includes': 0x08db, 1104 | 'intersection': 0x08dc, 1105 | 'union': 0x08dd, 1106 | 'logicaland': 0x08de, 1107 | 'logicalor': 0x08df, 1108 | 'partialderivative': 0x08ef, 1109 | 'function': 0x08f6, 1110 | 'leftarrow': 0x08fb, 1111 | 'uparrow': 0x08fc, 1112 | 'rightarrow': 0x08fd, 1113 | 'downarrow': 0x08fe, 1114 | 'blank': 0x09df, 1115 | 'soliddiamond': 0x09e0, 1116 | 'checkerboard': 0x09e1, 1117 | 'ht': 0x09e2, 1118 | 'ff': 0x09e3, 1119 | 'cr': 0x09e4, 1120 | 'lf': 0x09e5, 1121 | 'nl': 0x09e8, 1122 | 'vt': 0x09e9, 1123 | 'lowrightcorner': 0x09ea, 1124 | 'uprightcorner': 0x09eb, 1125 | 'upleftcorner': 0x09ec, 1126 | 'lowleftcorner': 0x09ed, 1127 | 'crossinglines': 0x09ee, 1128 | 'horizlinescan1': 0x09ef, 1129 | 'horizlinescan3': 0x09f0, 1130 | 'horizlinescan5': 0x09f1, 1131 | 'horizlinescan7': 0x09f2, 1132 | 'horizlinescan9': 0x09f3, 1133 | 'leftt': 0x09f4, 1134 | 'rightt': 0x09f5, 1135 | 'bott': 0x09f6, 1136 | 'topt': 0x09f7, 1137 | 'vertbar': 0x09f8, 1138 | 'emspace': 0x0aa1, 1139 | 'enspace': 0x0aa2, 1140 | 'em3space': 0x0aa3, 1141 | 'em4space': 0x0aa4, 1142 | 'digitspace': 0x0aa5, 1143 | 'punctspace': 0x0aa6, 1144 | 'thinspace': 0x0aa7, 1145 | 'hairspace': 0x0aa8, 1146 | 'emdash': 0x0aa9, 1147 | 'endash': 0x0aaa, 1148 | 'signifblank': 0x0aac, 1149 | 'ellipsis': 0x0aae, 1150 | 'doubbaselinedot': 0x0aaf, 1151 | 'onethird': 0x0ab0, 1152 | 'twothirds': 0x0ab1, 1153 | 'onefifth': 0x0ab2, 1154 | 'twofifths': 0x0ab3, 1155 | 'threefifths': 0x0ab4, 1156 | 'fourfifths': 0x0ab5, 1157 | 'onesixth': 0x0ab6, 1158 | 'fivesixths': 0x0ab7, 1159 | 'careof': 0x0ab8, 1160 | 'figdash': 0x0abb, 1161 | 'leftanglebracket': 0x0abc, 1162 | 'decimalpoint': 0x0abd, 1163 | 'rightanglebracket': 0x0abe, 1164 | 'marker': 0x0abf, 1165 | 'oneeighth': 0x0ac3, 1166 | 'threeeighths': 0x0ac4, 1167 | 'fiveeighths': 0x0ac5, 1168 | 'seveneighths': 0x0ac6, 1169 | 'trademark': 0x0ac9, 1170 | 'signaturemark': 0x0aca, 1171 | 'trademarkincircle': 0x0acb, 1172 | 'leftopentriangle': 0x0acc, 1173 | 'rightopentriangle': 0x0acd, 1174 | 'emopencircle': 0x0ace, 1175 | 'emopenrectangle': 0x0acf, 1176 | 'leftsinglequotemark': 0x0ad0, 1177 | 'rightsinglequotemark': 0x0ad1, 1178 | 'leftdoublequotemark': 0x0ad2, 1179 | 'rightdoublequotemark': 0x0ad3, 1180 | 'prescription': 0x0ad4, 1181 | 'minutes': 0x0ad6, 1182 | 'seconds': 0x0ad7, 1183 | 'latincross': 0x0ad9, 1184 | 'hexagram': 0x0ada, 1185 | 'filledrectbullet': 0x0adb, 1186 | 'filledlefttribullet': 0x0adc, 1187 | 'filledrighttribullet': 0x0add, 1188 | 'emfilledcircle': 0x0ade, 1189 | 'emfilledrect': 0x0adf, 1190 | 'enopencircbullet': 0x0ae0, 1191 | 'enopensquarebullet': 0x0ae1, 1192 | 'openrectbullet': 0x0ae2, 1193 | 'opentribulletup': 0x0ae3, 1194 | 'opentribulletdown': 0x0ae4, 1195 | 'openstar': 0x0ae5, 1196 | 'enfilledcircbullet': 0x0ae6, 1197 | 'enfilledsqbullet': 0x0ae7, 1198 | 'filledtribulletup': 0x0ae8, 1199 | 'filledtribulletdown': 0x0ae9, 1200 | 'leftpointer': 0x0aea, 1201 | 'rightpointer': 0x0aeb, 1202 | 'club': 0x0aec, 1203 | 'diamond': 0x0aed, 1204 | 'heart': 0x0aee, 1205 | 'maltesecross': 0x0af0, 1206 | 'dagger': 0x0af1, 1207 | 'doubledagger': 0x0af2, 1208 | 'checkmark': 0x0af3, 1209 | 'ballotcross': 0x0af4, 1210 | 'musicalsharp': 0x0af5, 1211 | 'musicalflat': 0x0af6, 1212 | 'malesymbol': 0x0af7, 1213 | 'femalesymbol': 0x0af8, 1214 | 'telephone': 0x0af9, 1215 | 'telephonerecorder': 0x0afa, 1216 | 'phonographcopyright': 0x0afb, 1217 | 'caret': 0x0afc, 1218 | 'singlelowquotemark': 0x0afd, 1219 | 'doublelowquotemark': 0x0afe, 1220 | 'cursor': 0x0aff, 1221 | 'leftcaret': 0x0ba3, 1222 | 'rightcaret': 0x0ba6, 1223 | 'downcaret': 0x0ba8, 1224 | 'upcaret': 0x0ba9, 1225 | 'overbar': 0x0bc0, 1226 | 'downtack': 0x0bc2, 1227 | 'upshoe': 0x0bc3, 1228 | 'downstile': 0x0bc4, 1229 | 'underbar': 0x0bc6, 1230 | 'jot': 0x0bca, 1231 | 'quad': 0x0bcc, 1232 | 'uptack': 0x0bce, 1233 | 'circle': 0x0bcf, 1234 | 'upstile': 0x0bd3, 1235 | 'downshoe': 0x0bd6, 1236 | 'rightshoe': 0x0bd8, 1237 | 'leftshoe': 0x0bda, 1238 | 'lefttack': 0x0bdc, 1239 | 'righttack': 0x0bfc, 1240 | 'hebrew_doublelowline': 0x0cdf, 1241 | 'hebrew_aleph': 0x0ce0, 1242 | 'hebrew_bet': 0x0ce1, 1243 | 'hebrew_beth': 0x0ce1, 1244 | 'hebrew_gimel': 0x0ce2, 1245 | 'hebrew_gimmel': 0x0ce2, 1246 | 'hebrew_dalet': 0x0ce3, 1247 | 'hebrew_daleth': 0x0ce3, 1248 | 'hebrew_he': 0x0ce4, 1249 | 'hebrew_waw': 0x0ce5, 1250 | 'hebrew_zain': 0x0ce6, 1251 | 'hebrew_zayin': 0x0ce6, 1252 | 'hebrew_chet': 0x0ce7, 1253 | 'hebrew_het': 0x0ce7, 1254 | 'hebrew_tet': 0x0ce8, 1255 | 'hebrew_teth': 0x0ce8, 1256 | 'hebrew_yod': 0x0ce9, 1257 | 'hebrew_finalkaph': 0x0cea, 1258 | 'hebrew_kaph': 0x0ceb, 1259 | 'hebrew_lamed': 0x0cec, 1260 | 'hebrew_finalmem': 0x0ced, 1261 | 'hebrew_mem': 0x0cee, 1262 | 'hebrew_finalnun': 0x0cef, 1263 | 'hebrew_nun': 0x0cf0, 1264 | 'hebrew_samech': 0x0cf1, 1265 | 'hebrew_samekh': 0x0cf1, 1266 | 'hebrew_ayin': 0x0cf2, 1267 | 'hebrew_finalpe': 0x0cf3, 1268 | 'hebrew_pe': 0x0cf4, 1269 | 'hebrew_finalzade': 0x0cf5, 1270 | 'hebrew_finalzadi': 0x0cf5, 1271 | 'hebrew_zade': 0x0cf6, 1272 | 'hebrew_zadi': 0x0cf6, 1273 | 'hebrew_qoph': 0x0cf7, 1274 | 'hebrew_kuf': 0x0cf7, 1275 | 'hebrew_resh': 0x0cf8, 1276 | 'hebrew_shin': 0x0cf9, 1277 | 'hebrew_taw': 0x0cfa, 1278 | 'hebrew_taf': 0x0cfa, 1279 | 'Hebrew_switch': 0xff7e, 1280 | 'Thai_kokai': 0x0da1, 1281 | 'Thai_khokhai': 0x0da2, 1282 | 'Thai_khokhuat': 0x0da3, 1283 | 'Thai_khokhwai': 0x0da4, 1284 | 'Thai_khokhon': 0x0da5, 1285 | 'Thai_khorakhang': 0x0da6, 1286 | 'Thai_ngongu': 0x0da7, 1287 | 'Thai_chochan': 0x0da8, 1288 | 'Thai_choching': 0x0da9, 1289 | 'Thai_chochang': 0x0daa, 1290 | 'Thai_soso': 0x0dab, 1291 | 'Thai_chochoe': 0x0dac, 1292 | 'Thai_yoying': 0x0dad, 1293 | 'Thai_dochada': 0x0dae, 1294 | 'Thai_topatak': 0x0daf, 1295 | 'Thai_thothan': 0x0db0, 1296 | 'Thai_thonangmontho': 0x0db1, 1297 | 'Thai_thophuthao': 0x0db2, 1298 | 'Thai_nonen': 0x0db3, 1299 | 'Thai_dodek': 0x0db4, 1300 | 'Thai_totao': 0x0db5, 1301 | 'Thai_thothung': 0x0db6, 1302 | 'Thai_thothahan': 0x0db7, 1303 | 'Thai_thothong': 0x0db8, 1304 | 'Thai_nonu': 0x0db9, 1305 | 'Thai_bobaimai': 0x0dba, 1306 | 'Thai_popla': 0x0dbb, 1307 | 'Thai_phophung': 0x0dbc, 1308 | 'Thai_fofa': 0x0dbd, 1309 | 'Thai_phophan': 0x0dbe, 1310 | 'Thai_fofan': 0x0dbf, 1311 | 'Thai_phosamphao': 0x0dc0, 1312 | 'Thai_moma': 0x0dc1, 1313 | 'Thai_yoyak': 0x0dc2, 1314 | 'Thai_rorua': 0x0dc3, 1315 | 'Thai_ru': 0x0dc4, 1316 | 'Thai_loling': 0x0dc5, 1317 | 'Thai_lu': 0x0dc6, 1318 | 'Thai_wowaen': 0x0dc7, 1319 | 'Thai_sosala': 0x0dc8, 1320 | 'Thai_sorusi': 0x0dc9, 1321 | 'Thai_sosua': 0x0dca, 1322 | 'Thai_hohip': 0x0dcb, 1323 | 'Thai_lochula': 0x0dcc, 1324 | 'Thai_oang': 0x0dcd, 1325 | 'Thai_honokhuk': 0x0dce, 1326 | 'Thai_paiyannoi': 0x0dcf, 1327 | 'Thai_saraa': 0x0dd0, 1328 | 'Thai_maihanakat': 0x0dd1, 1329 | 'Thai_saraaa': 0x0dd2, 1330 | 'Thai_saraam': 0x0dd3, 1331 | 'Thai_sarai': 0x0dd4, 1332 | 'Thai_saraii': 0x0dd5, 1333 | 'Thai_saraue': 0x0dd6, 1334 | 'Thai_sarauee': 0x0dd7, 1335 | 'Thai_sarau': 0x0dd8, 1336 | 'Thai_sarauu': 0x0dd9, 1337 | 'Thai_phinthu': 0x0dda, 1338 | 'Thai_maihanakat_maitho': 0x0dde, 1339 | 'Thai_baht': 0x0ddf, 1340 | 'Thai_sarae': 0x0de0, 1341 | 'Thai_saraae': 0x0de1, 1342 | 'Thai_sarao': 0x0de2, 1343 | 'Thai_saraaimaimuan': 0x0de3, 1344 | 'Thai_saraaimaimalai': 0x0de4, 1345 | 'Thai_lakkhangyao': 0x0de5, 1346 | 'Thai_maiyamok': 0x0de6, 1347 | 'Thai_maitaikhu': 0x0de7, 1348 | 'Thai_maiek': 0x0de8, 1349 | 'Thai_maitho': 0x0de9, 1350 | 'Thai_maitri': 0x0dea, 1351 | 'Thai_maichattawa': 0x0deb, 1352 | 'Thai_thanthakhat': 0x0dec, 1353 | 'Thai_nikhahit': 0x0ded, 1354 | 'Thai_leksun': 0x0df0, 1355 | 'Thai_leknung': 0x0df1, 1356 | 'Thai_leksong': 0x0df2, 1357 | 'Thai_leksam': 0x0df3, 1358 | 'Thai_leksi': 0x0df4, 1359 | 'Thai_lekha': 0x0df5, 1360 | 'Thai_lekhok': 0x0df6, 1361 | 'Thai_lekchet': 0x0df7, 1362 | 'Thai_lekpaet': 0x0df8, 1363 | 'Thai_lekkao': 0x0df9, 1364 | 'Hangul': 0xff31, 1365 | 'Hangul_Start': 0xff32, 1366 | 'Hangul_End': 0xff33, 1367 | 'Hangul_Hanja': 0xff34, 1368 | 'Hangul_Jamo': 0xff35, 1369 | 'Hangul_Romaja': 0xff36, 1370 | 'Hangul_Codeinput': 0xff37, 1371 | 'Hangul_Jeonja': 0xff38, 1372 | 'Hangul_Banja': 0xff39, 1373 | 'Hangul_PreHanja': 0xff3a, 1374 | 'Hangul_PostHanja': 0xff3b, 1375 | 'Hangul_SingleCandidate': 0xff3c, 1376 | 'Hangul_MultipleCandidate': 0xff3d, 1377 | 'Hangul_PreviousCandidate': 0xff3e, 1378 | 'Hangul_Special': 0xff3f, 1379 | 'Hangul_switch': 0xff7e, 1380 | 'Hangul_Kiyeog': 0x0ea1, 1381 | 'Hangul_SsangKiyeog': 0x0ea2, 1382 | 'Hangul_KiyeogSios': 0x0ea3, 1383 | 'Hangul_Nieun': 0x0ea4, 1384 | 'Hangul_NieunJieuj': 0x0ea5, 1385 | 'Hangul_NieunHieuh': 0x0ea6, 1386 | 'Hangul_Dikeud': 0x0ea7, 1387 | 'Hangul_SsangDikeud': 0x0ea8, 1388 | 'Hangul_Rieul': 0x0ea9, 1389 | 'Hangul_RieulKiyeog': 0x0eaa, 1390 | 'Hangul_RieulMieum': 0x0eab, 1391 | 'Hangul_RieulPieub': 0x0eac, 1392 | 'Hangul_RieulSios': 0x0ead, 1393 | 'Hangul_RieulTieut': 0x0eae, 1394 | 'Hangul_RieulPhieuf': 0x0eaf, 1395 | 'Hangul_RieulHieuh': 0x0eb0, 1396 | 'Hangul_Mieum': 0x0eb1, 1397 | 'Hangul_Pieub': 0x0eb2, 1398 | 'Hangul_SsangPieub': 0x0eb3, 1399 | 'Hangul_PieubSios': 0x0eb4, 1400 | 'Hangul_Sios': 0x0eb5, 1401 | 'Hangul_SsangSios': 0x0eb6, 1402 | 'Hangul_Ieung': 0x0eb7, 1403 | 'Hangul_Jieuj': 0x0eb8, 1404 | 'Hangul_SsangJieuj': 0x0eb9, 1405 | 'Hangul_Cieuc': 0x0eba, 1406 | 'Hangul_Khieuq': 0x0ebb, 1407 | 'Hangul_Tieut': 0x0ebc, 1408 | 'Hangul_Phieuf': 0x0ebd, 1409 | 'Hangul_Hieuh': 0x0ebe, 1410 | 'Hangul_A': 0x0ebf, 1411 | 'Hangul_AE': 0x0ec0, 1412 | 'Hangul_YA': 0x0ec1, 1413 | 'Hangul_YAE': 0x0ec2, 1414 | 'Hangul_EO': 0x0ec3, 1415 | 'Hangul_E': 0x0ec4, 1416 | 'Hangul_YEO': 0x0ec5, 1417 | 'Hangul_YE': 0x0ec6, 1418 | 'Hangul_O': 0x0ec7, 1419 | 'Hangul_WA': 0x0ec8, 1420 | 'Hangul_WAE': 0x0ec9, 1421 | 'Hangul_OE': 0x0eca, 1422 | 'Hangul_YO': 0x0ecb, 1423 | 'Hangul_U': 0x0ecc, 1424 | 'Hangul_WEO': 0x0ecd, 1425 | 'Hangul_WE': 0x0ece, 1426 | 'Hangul_WI': 0x0ecf, 1427 | 'Hangul_YU': 0x0ed0, 1428 | 'Hangul_EU': 0x0ed1, 1429 | 'Hangul_YI': 0x0ed2, 1430 | 'Hangul_I': 0x0ed3, 1431 | 'Hangul_J_Kiyeog': 0x0ed4, 1432 | 'Hangul_J_SsangKiyeog': 0x0ed5, 1433 | 'Hangul_J_KiyeogSios': 0x0ed6, 1434 | 'Hangul_J_Nieun': 0x0ed7, 1435 | 'Hangul_J_NieunJieuj': 0x0ed8, 1436 | 'Hangul_J_NieunHieuh': 0x0ed9, 1437 | 'Hangul_J_Dikeud': 0x0eda, 1438 | 'Hangul_J_Rieul': 0x0edb, 1439 | 'Hangul_J_RieulKiyeog': 0x0edc, 1440 | 'Hangul_J_RieulMieum': 0x0edd, 1441 | 'Hangul_J_RieulPieub': 0x0ede, 1442 | 'Hangul_J_RieulSios': 0x0edf, 1443 | 'Hangul_J_RieulTieut': 0x0ee0, 1444 | 'Hangul_J_RieulPhieuf': 0x0ee1, 1445 | 'Hangul_J_RieulHieuh': 0x0ee2, 1446 | 'Hangul_J_Mieum': 0x0ee3, 1447 | 'Hangul_J_Pieub': 0x0ee4, 1448 | 'Hangul_J_PieubSios': 0x0ee5, 1449 | 'Hangul_J_Sios': 0x0ee6, 1450 | 'Hangul_J_SsangSios': 0x0ee7, 1451 | 'Hangul_J_Ieung': 0x0ee8, 1452 | 'Hangul_J_Jieuj': 0x0ee9, 1453 | 'Hangul_J_Cieuc': 0x0eea, 1454 | 'Hangul_J_Khieuq': 0x0eeb, 1455 | 'Hangul_J_Tieut': 0x0eec, 1456 | 'Hangul_J_Phieuf': 0x0eed, 1457 | 'Hangul_J_Hieuh': 0x0eee, 1458 | 'Hangul_RieulYeorinHieuh': 0x0eef, 1459 | 'Hangul_SunkyeongeumMieum': 0x0ef0, 1460 | 'Hangul_SunkyeongeumPieub': 0x0ef1, 1461 | 'Hangul_PanSios': 0x0ef2, 1462 | 'Hangul_KkogjiDalrinIeung': 0x0ef3, 1463 | 'Hangul_SunkyeongeumPhieuf': 0x0ef4, 1464 | 'Hangul_YeorinHieuh': 0x0ef5, 1465 | 'Hangul_AraeA': 0x0ef6, 1466 | 'Hangul_AraeAE': 0x0ef7, 1467 | 'Hangul_J_PanSios': 0x0ef8, 1468 | 'Hangul_J_KkogjiDalrinIeung': 0x0ef9, 1469 | 'Hangul_J_YeorinHieuh': 0x0efa, 1470 | 'Korean_Won': 0x0eff, 1471 | 'Armenian_ligature_ew': 0x1000587, 1472 | 'Armenian_full_stop': 0x1000589, 1473 | 'Armenian_verjaket': 0x1000589, 1474 | 'Armenian_separation_mark': 0x100055d, 1475 | 'Armenian_but': 0x100055d, 1476 | 'Armenian_hyphen': 0x100058a, 1477 | 'Armenian_yentamna': 0x100058a, 1478 | 'Armenian_exclam': 0x100055c, 1479 | 'Armenian_amanak': 0x100055c, 1480 | 'Armenian_accent': 0x100055b, 1481 | 'Armenian_shesht': 0x100055b, 1482 | 'Armenian_question': 0x100055e, 1483 | 'Armenian_paruyk': 0x100055e, 1484 | 'Armenian_AYB': 0x1000531, 1485 | 'Armenian_ayb': 0x1000561, 1486 | 'Armenian_BEN': 0x1000532, 1487 | 'Armenian_ben': 0x1000562, 1488 | 'Armenian_GIM': 0x1000533, 1489 | 'Armenian_gim': 0x1000563, 1490 | 'Armenian_DA': 0x1000534, 1491 | 'Armenian_da': 0x1000564, 1492 | 'Armenian_YECH': 0x1000535, 1493 | 'Armenian_yech': 0x1000565, 1494 | 'Armenian_ZA': 0x1000536, 1495 | 'Armenian_za': 0x1000566, 1496 | 'Armenian_E': 0x1000537, 1497 | 'Armenian_e': 0x1000567, 1498 | 'Armenian_AT': 0x1000538, 1499 | 'Armenian_at': 0x1000568, 1500 | 'Armenian_TO': 0x1000539, 1501 | 'Armenian_to': 0x1000569, 1502 | 'Armenian_ZHE': 0x100053a, 1503 | 'Armenian_zhe': 0x100056a, 1504 | 'Armenian_INI': 0x100053b, 1505 | 'Armenian_ini': 0x100056b, 1506 | 'Armenian_LYUN': 0x100053c, 1507 | 'Armenian_lyun': 0x100056c, 1508 | 'Armenian_KHE': 0x100053d, 1509 | 'Armenian_khe': 0x100056d, 1510 | 'Armenian_TSA': 0x100053e, 1511 | 'Armenian_tsa': 0x100056e, 1512 | 'Armenian_KEN': 0x100053f, 1513 | 'Armenian_ken': 0x100056f, 1514 | 'Armenian_HO': 0x1000540, 1515 | 'Armenian_ho': 0x1000570, 1516 | 'Armenian_DZA': 0x1000541, 1517 | 'Armenian_dza': 0x1000571, 1518 | 'Armenian_GHAT': 0x1000542, 1519 | 'Armenian_ghat': 0x1000572, 1520 | 'Armenian_TCHE': 0x1000543, 1521 | 'Armenian_tche': 0x1000573, 1522 | 'Armenian_MEN': 0x1000544, 1523 | 'Armenian_men': 0x1000574, 1524 | 'Armenian_HI': 0x1000545, 1525 | 'Armenian_hi': 0x1000575, 1526 | 'Armenian_NU': 0x1000546, 1527 | 'Armenian_nu': 0x1000576, 1528 | 'Armenian_SHA': 0x1000547, 1529 | 'Armenian_sha': 0x1000577, 1530 | 'Armenian_VO': 0x1000548, 1531 | 'Armenian_vo': 0x1000578, 1532 | 'Armenian_CHA': 0x1000549, 1533 | 'Armenian_cha': 0x1000579, 1534 | 'Armenian_PE': 0x100054a, 1535 | 'Armenian_pe': 0x100057a, 1536 | 'Armenian_JE': 0x100054b, 1537 | 'Armenian_je': 0x100057b, 1538 | 'Armenian_RA': 0x100054c, 1539 | 'Armenian_ra': 0x100057c, 1540 | 'Armenian_SE': 0x100054d, 1541 | 'Armenian_se': 0x100057d, 1542 | 'Armenian_VEV': 0x100054e, 1543 | 'Armenian_vev': 0x100057e, 1544 | 'Armenian_TYUN': 0x100054f, 1545 | 'Armenian_tyun': 0x100057f, 1546 | 'Armenian_RE': 0x1000550, 1547 | 'Armenian_re': 0x1000580, 1548 | 'Armenian_TSO': 0x1000551, 1549 | 'Armenian_tso': 0x1000581, 1550 | 'Armenian_VYUN': 0x1000552, 1551 | 'Armenian_vyun': 0x1000582, 1552 | 'Armenian_PYUR': 0x1000553, 1553 | 'Armenian_pyur': 0x1000583, 1554 | 'Armenian_KE': 0x1000554, 1555 | 'Armenian_ke': 0x1000584, 1556 | 'Armenian_O': 0x1000555, 1557 | 'Armenian_o': 0x1000585, 1558 | 'Armenian_FE': 0x1000556, 1559 | 'Armenian_fe': 0x1000586, 1560 | 'Armenian_apostrophe': 0x100055a, 1561 | 'Georgian_an': 0x10010d0, 1562 | 'Georgian_ban': 0x10010d1, 1563 | 'Georgian_gan': 0x10010d2, 1564 | 'Georgian_don': 0x10010d3, 1565 | 'Georgian_en': 0x10010d4, 1566 | 'Georgian_vin': 0x10010d5, 1567 | 'Georgian_zen': 0x10010d6, 1568 | 'Georgian_tan': 0x10010d7, 1569 | 'Georgian_in': 0x10010d8, 1570 | 'Georgian_kan': 0x10010d9, 1571 | 'Georgian_las': 0x10010da, 1572 | 'Georgian_man': 0x10010db, 1573 | 'Georgian_nar': 0x10010dc, 1574 | 'Georgian_on': 0x10010dd, 1575 | 'Georgian_par': 0x10010de, 1576 | 'Georgian_zhar': 0x10010df, 1577 | 'Georgian_rae': 0x10010e0, 1578 | 'Georgian_san': 0x10010e1, 1579 | 'Georgian_tar': 0x10010e2, 1580 | 'Georgian_un': 0x10010e3, 1581 | 'Georgian_phar': 0x10010e4, 1582 | 'Georgian_khar': 0x10010e5, 1583 | 'Georgian_ghan': 0x10010e6, 1584 | 'Georgian_qar': 0x10010e7, 1585 | 'Georgian_shin': 0x10010e8, 1586 | 'Georgian_chin': 0x10010e9, 1587 | 'Georgian_can': 0x10010ea, 1588 | 'Georgian_jil': 0x10010eb, 1589 | 'Georgian_cil': 0x10010ec, 1590 | 'Georgian_char': 0x10010ed, 1591 | 'Georgian_xan': 0x10010ee, 1592 | 'Georgian_jhan': 0x10010ef, 1593 | 'Georgian_hae': 0x10010f0, 1594 | 'Georgian_he': 0x10010f1, 1595 | 'Georgian_hie': 0x10010f2, 1596 | 'Georgian_we': 0x10010f3, 1597 | 'Georgian_har': 0x10010f4, 1598 | 'Georgian_hoe': 0x10010f5, 1599 | 'Georgian_fi': 0x10010f6, 1600 | 'Xabovedot': 0x1001e8a, 1601 | 'Ibreve': 0x100012c, 1602 | 'Zstroke': 0x10001b5, 1603 | 'Gcaron': 0x10001e6, 1604 | 'Ocaron': 0x10001d1, 1605 | 'Obarred': 0x100019f, 1606 | 'xabovedot': 0x1001e8b, 1607 | 'ibreve': 0x100012d, 1608 | 'zstroke': 0x10001b6, 1609 | 'gcaron': 0x10001e7, 1610 | 'ocaron': 0x10001d2, 1611 | 'obarred': 0x1000275, 1612 | 'SCHWA': 0x100018f, 1613 | 'schwa': 0x1000259, 1614 | 'Lbelowdot': 0x1001e36, 1615 | 'lbelowdot': 0x1001e37, 1616 | 'Abelowdot': 0x1001ea0, 1617 | 'abelowdot': 0x1001ea1, 1618 | 'Ahook': 0x1001ea2, 1619 | 'ahook': 0x1001ea3, 1620 | 'Acircumflexacute': 0x1001ea4, 1621 | 'acircumflexacute': 0x1001ea5, 1622 | 'Acircumflexgrave': 0x1001ea6, 1623 | 'acircumflexgrave': 0x1001ea7, 1624 | 'Acircumflexhook': 0x1001ea8, 1625 | 'acircumflexhook': 0x1001ea9, 1626 | 'Acircumflextilde': 0x1001eaa, 1627 | 'acircumflextilde': 0x1001eab, 1628 | 'Acircumflexbelowdot': 0x1001eac, 1629 | 'acircumflexbelowdot': 0x1001ead, 1630 | 'Abreveacute': 0x1001eae, 1631 | 'abreveacute': 0x1001eaf, 1632 | 'Abrevegrave': 0x1001eb0, 1633 | 'abrevegrave': 0x1001eb1, 1634 | 'Abrevehook': 0x1001eb2, 1635 | 'abrevehook': 0x1001eb3, 1636 | 'Abrevetilde': 0x1001eb4, 1637 | 'abrevetilde': 0x1001eb5, 1638 | 'Abrevebelowdot': 0x1001eb6, 1639 | 'abrevebelowdot': 0x1001eb7, 1640 | 'Ebelowdot': 0x1001eb8, 1641 | 'ebelowdot': 0x1001eb9, 1642 | 'Ehook': 0x1001eba, 1643 | 'ehook': 0x1001ebb, 1644 | 'Etilde': 0x1001ebc, 1645 | 'etilde': 0x1001ebd, 1646 | 'Ecircumflexacute': 0x1001ebe, 1647 | 'ecircumflexacute': 0x1001ebf, 1648 | 'Ecircumflexgrave': 0x1001ec0, 1649 | 'ecircumflexgrave': 0x1001ec1, 1650 | 'Ecircumflexhook': 0x1001ec2, 1651 | 'ecircumflexhook': 0x1001ec3, 1652 | 'Ecircumflextilde': 0x1001ec4, 1653 | 'ecircumflextilde': 0x1001ec5, 1654 | 'Ecircumflexbelowdot': 0x1001ec6, 1655 | 'ecircumflexbelowdot': 0x1001ec7, 1656 | 'Ihook': 0x1001ec8, 1657 | 'ihook': 0x1001ec9, 1658 | 'Ibelowdot': 0x1001eca, 1659 | 'ibelowdot': 0x1001ecb, 1660 | 'Obelowdot': 0x1001ecc, 1661 | 'obelowdot': 0x1001ecd, 1662 | 'Ohook': 0x1001ece, 1663 | 'ohook': 0x1001ecf, 1664 | 'Ocircumflexacute': 0x1001ed0, 1665 | 'ocircumflexacute': 0x1001ed1, 1666 | 'Ocircumflexgrave': 0x1001ed2, 1667 | 'ocircumflexgrave': 0x1001ed3, 1668 | 'Ocircumflexhook': 0x1001ed4, 1669 | 'ocircumflexhook': 0x1001ed5, 1670 | 'Ocircumflextilde': 0x1001ed6, 1671 | 'ocircumflextilde': 0x1001ed7, 1672 | 'Ocircumflexbelowdot': 0x1001ed8, 1673 | 'ocircumflexbelowdot': 0x1001ed9, 1674 | 'Ohornacute': 0x1001eda, 1675 | 'ohornacute': 0x1001edb, 1676 | 'Ohorngrave': 0x1001edc, 1677 | 'ohorngrave': 0x1001edd, 1678 | 'Ohornhook': 0x1001ede, 1679 | 'ohornhook': 0x1001edf, 1680 | 'Ohorntilde': 0x1001ee0, 1681 | 'ohorntilde': 0x1001ee1, 1682 | 'Ohornbelowdot': 0x1001ee2, 1683 | 'ohornbelowdot': 0x1001ee3, 1684 | 'Ubelowdot': 0x1001ee4, 1685 | 'ubelowdot': 0x1001ee5, 1686 | 'Uhook': 0x1001ee6, 1687 | 'uhook': 0x1001ee7, 1688 | 'Uhornacute': 0x1001ee8, 1689 | 'uhornacute': 0x1001ee9, 1690 | 'Uhorngrave': 0x1001eea, 1691 | 'uhorngrave': 0x1001eeb, 1692 | 'Uhornhook': 0x1001eec, 1693 | 'uhornhook': 0x1001eed, 1694 | 'Uhorntilde': 0x1001eee, 1695 | 'uhorntilde': 0x1001eef, 1696 | 'Uhornbelowdot': 0x1001ef0, 1697 | 'uhornbelowdot': 0x1001ef1, 1698 | 'Ybelowdot': 0x1001ef4, 1699 | 'ybelowdot': 0x1001ef5, 1700 | 'Yhook': 0x1001ef6, 1701 | 'yhook': 0x1001ef7, 1702 | 'Ytilde': 0x1001ef8, 1703 | 'ytilde': 0x1001ef9, 1704 | 'Ohorn': 0x10001a0, 1705 | 'ohorn': 0x10001a1, 1706 | 'Uhorn': 0x10001af, 1707 | 'uhorn': 0x10001b0, 1708 | 'EcuSign': 0x10020a0, 1709 | 'ColonSign': 0x10020a1, 1710 | 'CruzeiroSign': 0x10020a2, 1711 | 'FFrancSign': 0x10020a3, 1712 | 'LiraSign': 0x10020a4, 1713 | 'MillSign': 0x10020a5, 1714 | 'NairaSign': 0x10020a6, 1715 | 'PesetaSign': 0x10020a7, 1716 | 'RupeeSign': 0x10020a8, 1717 | 'WonSign': 0x10020a9, 1718 | 'NewSheqelSign': 0x10020aa, 1719 | 'DongSign': 0x10020ab, 1720 | 'EuroSign': 0x20ac, 1721 | 'zerosuperior': 0x1002070, 1722 | 'foursuperior': 0x1002074, 1723 | 'fivesuperior': 0x1002075, 1724 | 'sixsuperior': 0x1002076, 1725 | 'sevensuperior': 0x1002077, 1726 | 'eightsuperior': 0x1002078, 1727 | 'ninesuperior': 0x1002079, 1728 | 'zerosubscript': 0x1002080, 1729 | 'onesubscript': 0x1002081, 1730 | 'twosubscript': 0x1002082, 1731 | 'threesubscript': 0x1002083, 1732 | 'foursubscript': 0x1002084, 1733 | 'fivesubscript': 0x1002085, 1734 | 'sixsubscript': 0x1002086, 1735 | 'sevensubscript': 0x1002087, 1736 | 'eightsubscript': 0x1002088, 1737 | 'ninesubscript': 0x1002089, 1738 | 'partdifferential': 0x1002202, 1739 | 'emptyset': 0x1002205, 1740 | 'elementof': 0x1002208, 1741 | 'notelementof': 0x1002209, 1742 | 'containsas': 0x100220B, 1743 | 'squareroot': 0x100221A, 1744 | 'cuberoot': 0x100221B, 1745 | 'fourthroot': 0x100221C, 1746 | 'dintegral': 0x100222C, 1747 | 'tintegral': 0x100222D, 1748 | 'because': 0x1002235, 1749 | 'approxeq': 0x1002248, 1750 | 'notapproxeq': 0x1002247, 1751 | 'notidentical': 0x1002262, 1752 | 'stricteq': 0x1002263, 1753 | 'braille_dot_1': 0xfff1, 1754 | 'braille_dot_2': 0xfff2, 1755 | 'braille_dot_3': 0xfff3, 1756 | 'braille_dot_4': 0xfff4, 1757 | 'braille_dot_5': 0xfff5, 1758 | 'braille_dot_6': 0xfff6, 1759 | 'braille_dot_7': 0xfff7, 1760 | 'braille_dot_8': 0xfff8, 1761 | 'braille_dot_9': 0xfff9, 1762 | 'braille_dot_10': 0xfffa, 1763 | 'braille_blank': 0x1002800, 1764 | 'braille_dots_1': 0x1002801, 1765 | 'braille_dots_2': 0x1002802, 1766 | 'braille_dots_12': 0x1002803, 1767 | 'braille_dots_3': 0x1002804, 1768 | 'braille_dots_13': 0x1002805, 1769 | 'braille_dots_23': 0x1002806, 1770 | 'braille_dots_123': 0x1002807, 1771 | 'braille_dots_4': 0x1002808, 1772 | 'braille_dots_14': 0x1002809, 1773 | 'braille_dots_24': 0x100280a, 1774 | 'braille_dots_124': 0x100280b, 1775 | 'braille_dots_34': 0x100280c, 1776 | 'braille_dots_134': 0x100280d, 1777 | 'braille_dots_234': 0x100280e, 1778 | 'braille_dots_1234': 0x100280f, 1779 | 'braille_dots_5': 0x1002810, 1780 | 'braille_dots_15': 0x1002811, 1781 | 'braille_dots_25': 0x1002812, 1782 | 'braille_dots_125': 0x1002813, 1783 | 'braille_dots_35': 0x1002814, 1784 | 'braille_dots_135': 0x1002815, 1785 | 'braille_dots_235': 0x1002816, 1786 | 'braille_dots_1235': 0x1002817, 1787 | 'braille_dots_45': 0x1002818, 1788 | 'braille_dots_145': 0x1002819, 1789 | 'braille_dots_245': 0x100281a, 1790 | 'braille_dots_1245': 0x100281b, 1791 | 'braille_dots_345': 0x100281c, 1792 | 'braille_dots_1345': 0x100281d, 1793 | 'braille_dots_2345': 0x100281e, 1794 | 'braille_dots_12345': 0x100281f, 1795 | 'braille_dots_6': 0x1002820, 1796 | 'braille_dots_16': 0x1002821, 1797 | 'braille_dots_26': 0x1002822, 1798 | 'braille_dots_126': 0x1002823, 1799 | 'braille_dots_36': 0x1002824, 1800 | 'braille_dots_136': 0x1002825, 1801 | 'braille_dots_236': 0x1002826, 1802 | 'braille_dots_1236': 0x1002827, 1803 | 'braille_dots_46': 0x1002828, 1804 | 'braille_dots_146': 0x1002829, 1805 | 'braille_dots_246': 0x100282a, 1806 | 'braille_dots_1246': 0x100282b, 1807 | 'braille_dots_346': 0x100282c, 1808 | 'braille_dots_1346': 0x100282d, 1809 | 'braille_dots_2346': 0x100282e, 1810 | 'braille_dots_12346': 0x100282f, 1811 | 'braille_dots_56': 0x1002830, 1812 | 'braille_dots_156': 0x1002831, 1813 | 'braille_dots_256': 0x1002832, 1814 | 'braille_dots_1256': 0x1002833, 1815 | 'braille_dots_356': 0x1002834, 1816 | 'braille_dots_1356': 0x1002835, 1817 | 'braille_dots_2356': 0x1002836, 1818 | 'braille_dots_12356': 0x1002837, 1819 | 'braille_dots_456': 0x1002838, 1820 | 'braille_dots_1456': 0x1002839, 1821 | 'braille_dots_2456': 0x100283a, 1822 | 'braille_dots_12456': 0x100283b, 1823 | 'braille_dots_3456': 0x100283c, 1824 | 'braille_dots_13456': 0x100283d, 1825 | 'braille_dots_23456': 0x100283e, 1826 | 'braille_dots_123456': 0x100283f, 1827 | 'braille_dots_7': 0x1002840, 1828 | 'braille_dots_17': 0x1002841, 1829 | 'braille_dots_27': 0x1002842, 1830 | 'braille_dots_127': 0x1002843, 1831 | 'braille_dots_37': 0x1002844, 1832 | 'braille_dots_137': 0x1002845, 1833 | 'braille_dots_237': 0x1002846, 1834 | 'braille_dots_1237': 0x1002847, 1835 | 'braille_dots_47': 0x1002848, 1836 | 'braille_dots_147': 0x1002849, 1837 | 'braille_dots_247': 0x100284a, 1838 | 'braille_dots_1247': 0x100284b, 1839 | 'braille_dots_347': 0x100284c, 1840 | 'braille_dots_1347': 0x100284d, 1841 | 'braille_dots_2347': 0x100284e, 1842 | 'braille_dots_12347': 0x100284f, 1843 | 'braille_dots_57': 0x1002850, 1844 | 'braille_dots_157': 0x1002851, 1845 | 'braille_dots_257': 0x1002852, 1846 | 'braille_dots_1257': 0x1002853, 1847 | 'braille_dots_357': 0x1002854, 1848 | 'braille_dots_1357': 0x1002855, 1849 | 'braille_dots_2357': 0x1002856, 1850 | 'braille_dots_12357': 0x1002857, 1851 | 'braille_dots_457': 0x1002858, 1852 | 'braille_dots_1457': 0x1002859, 1853 | 'braille_dots_2457': 0x100285a, 1854 | 'braille_dots_12457': 0x100285b, 1855 | 'braille_dots_3457': 0x100285c, 1856 | 'braille_dots_13457': 0x100285d, 1857 | 'braille_dots_23457': 0x100285e, 1858 | 'braille_dots_123457': 0x100285f, 1859 | 'braille_dots_67': 0x1002860, 1860 | 'braille_dots_167': 0x1002861, 1861 | 'braille_dots_267': 0x1002862, 1862 | 'braille_dots_1267': 0x1002863, 1863 | 'braille_dots_367': 0x1002864, 1864 | 'braille_dots_1367': 0x1002865, 1865 | 'braille_dots_2367': 0x1002866, 1866 | 'braille_dots_12367': 0x1002867, 1867 | 'braille_dots_467': 0x1002868, 1868 | 'braille_dots_1467': 0x1002869, 1869 | 'braille_dots_2467': 0x100286a, 1870 | 'braille_dots_12467': 0x100286b, 1871 | 'braille_dots_3467': 0x100286c, 1872 | 'braille_dots_13467': 0x100286d, 1873 | 'braille_dots_23467': 0x100286e, 1874 | 'braille_dots_123467': 0x100286f, 1875 | 'braille_dots_567': 0x1002870, 1876 | 'braille_dots_1567': 0x1002871, 1877 | 'braille_dots_2567': 0x1002872, 1878 | 'braille_dots_12567': 0x1002873, 1879 | 'braille_dots_3567': 0x1002874, 1880 | 'braille_dots_13567': 0x1002875, 1881 | 'braille_dots_23567': 0x1002876, 1882 | 'braille_dots_123567': 0x1002877, 1883 | 'braille_dots_4567': 0x1002878, 1884 | 'braille_dots_14567': 0x1002879, 1885 | 'braille_dots_24567': 0x100287a, 1886 | 'braille_dots_124567': 0x100287b, 1887 | 'braille_dots_34567': 0x100287c, 1888 | 'braille_dots_134567': 0x100287d, 1889 | 'braille_dots_234567': 0x100287e, 1890 | 'braille_dots_1234567': 0x100287f, 1891 | 'braille_dots_8': 0x1002880, 1892 | 'braille_dots_18': 0x1002881, 1893 | 'braille_dots_28': 0x1002882, 1894 | 'braille_dots_128': 0x1002883, 1895 | 'braille_dots_38': 0x1002884, 1896 | 'braille_dots_138': 0x1002885, 1897 | 'braille_dots_238': 0x1002886, 1898 | 'braille_dots_1238': 0x1002887, 1899 | 'braille_dots_48': 0x1002888, 1900 | 'braille_dots_148': 0x1002889, 1901 | 'braille_dots_248': 0x100288a, 1902 | 'braille_dots_1248': 0x100288b, 1903 | 'braille_dots_348': 0x100288c, 1904 | 'braille_dots_1348': 0x100288d, 1905 | 'braille_dots_2348': 0x100288e, 1906 | 'braille_dots_12348': 0x100288f, 1907 | 'braille_dots_58': 0x1002890, 1908 | 'braille_dots_158': 0x1002891, 1909 | 'braille_dots_258': 0x1002892, 1910 | 'braille_dots_1258': 0x1002893, 1911 | 'braille_dots_358': 0x1002894, 1912 | 'braille_dots_1358': 0x1002895, 1913 | 'braille_dots_2358': 0x1002896, 1914 | 'braille_dots_12358': 0x1002897, 1915 | 'braille_dots_458': 0x1002898, 1916 | 'braille_dots_1458': 0x1002899, 1917 | 'braille_dots_2458': 0x100289a, 1918 | 'braille_dots_12458': 0x100289b, 1919 | 'braille_dots_3458': 0x100289c, 1920 | 'braille_dots_13458': 0x100289d, 1921 | 'braille_dots_23458': 0x100289e, 1922 | 'braille_dots_123458': 0x100289f, 1923 | 'braille_dots_68': 0x10028a0, 1924 | 'braille_dots_168': 0x10028a1, 1925 | 'braille_dots_268': 0x10028a2, 1926 | 'braille_dots_1268': 0x10028a3, 1927 | 'braille_dots_368': 0x10028a4, 1928 | 'braille_dots_1368': 0x10028a5, 1929 | 'braille_dots_2368': 0x10028a6, 1930 | 'braille_dots_12368': 0x10028a7, 1931 | 'braille_dots_468': 0x10028a8, 1932 | 'braille_dots_1468': 0x10028a9, 1933 | 'braille_dots_2468': 0x10028aa, 1934 | 'braille_dots_12468': 0x10028ab, 1935 | 'braille_dots_3468': 0x10028ac, 1936 | 'braille_dots_13468': 0x10028ad, 1937 | 'braille_dots_23468': 0x10028ae, 1938 | 'braille_dots_123468': 0x10028af, 1939 | 'braille_dots_568': 0x10028b0, 1940 | 'braille_dots_1568': 0x10028b1, 1941 | 'braille_dots_2568': 0x10028b2, 1942 | 'braille_dots_12568': 0x10028b3, 1943 | 'braille_dots_3568': 0x10028b4, 1944 | 'braille_dots_13568': 0x10028b5, 1945 | 'braille_dots_23568': 0x10028b6, 1946 | 'braille_dots_123568': 0x10028b7, 1947 | 'braille_dots_4568': 0x10028b8, 1948 | 'braille_dots_14568': 0x10028b9, 1949 | 'braille_dots_24568': 0x10028ba, 1950 | 'braille_dots_124568': 0x10028bb, 1951 | 'braille_dots_34568': 0x10028bc, 1952 | 'braille_dots_134568': 0x10028bd, 1953 | 'braille_dots_234568': 0x10028be, 1954 | 'braille_dots_1234568': 0x10028bf, 1955 | 'braille_dots_78': 0x10028c0, 1956 | 'braille_dots_178': 0x10028c1, 1957 | 'braille_dots_278': 0x10028c2, 1958 | 'braille_dots_1278': 0x10028c3, 1959 | 'braille_dots_378': 0x10028c4, 1960 | 'braille_dots_1378': 0x10028c5, 1961 | 'braille_dots_2378': 0x10028c6, 1962 | 'braille_dots_12378': 0x10028c7, 1963 | 'braille_dots_478': 0x10028c8, 1964 | 'braille_dots_1478': 0x10028c9, 1965 | 'braille_dots_2478': 0x10028ca, 1966 | 'braille_dots_12478': 0x10028cb, 1967 | 'braille_dots_3478': 0x10028cc, 1968 | 'braille_dots_13478': 0x10028cd, 1969 | 'braille_dots_23478': 0x10028ce, 1970 | 'braille_dots_123478': 0x10028cf, 1971 | 'braille_dots_578': 0x10028d0, 1972 | 'braille_dots_1578': 0x10028d1, 1973 | 'braille_dots_2578': 0x10028d2, 1974 | 'braille_dots_12578': 0x10028d3, 1975 | 'braille_dots_3578': 0x10028d4, 1976 | 'braille_dots_13578': 0x10028d5, 1977 | 'braille_dots_23578': 0x10028d6, 1978 | 'braille_dots_123578': 0x10028d7, 1979 | 'braille_dots_4578': 0x10028d8, 1980 | 'braille_dots_14578': 0x10028d9, 1981 | 'braille_dots_24578': 0x10028da, 1982 | 'braille_dots_124578': 0x10028db, 1983 | 'braille_dots_34578': 0x10028dc, 1984 | 'braille_dots_134578': 0x10028dd, 1985 | 'braille_dots_234578': 0x10028de, 1986 | 'braille_dots_1234578': 0x10028df, 1987 | 'braille_dots_678': 0x10028e0, 1988 | 'braille_dots_1678': 0x10028e1, 1989 | 'braille_dots_2678': 0x10028e2, 1990 | 'braille_dots_12678': 0x10028e3, 1991 | 'braille_dots_3678': 0x10028e4, 1992 | 'braille_dots_13678': 0x10028e5, 1993 | 'braille_dots_23678': 0x10028e6, 1994 | 'braille_dots_123678': 0x10028e7, 1995 | 'braille_dots_4678': 0x10028e8, 1996 | 'braille_dots_14678': 0x10028e9, 1997 | 'braille_dots_24678': 0x10028ea, 1998 | 'braille_dots_124678': 0x10028eb, 1999 | 'braille_dots_34678': 0x10028ec, 2000 | 'braille_dots_134678': 0x10028ed, 2001 | 'braille_dots_234678': 0x10028ee, 2002 | 'braille_dots_1234678': 0x10028ef, 2003 | 'braille_dots_5678': 0x10028f0, 2004 | 'braille_dots_15678': 0x10028f1, 2005 | 'braille_dots_25678': 0x10028f2, 2006 | 'braille_dots_125678': 0x10028f3, 2007 | 'braille_dots_35678': 0x10028f4, 2008 | 'braille_dots_135678': 0x10028f5, 2009 | 'braille_dots_235678': 0x10028f6, 2010 | 'braille_dots_1235678': 0x10028f7, 2011 | 'braille_dots_45678': 0x10028f8, 2012 | 'braille_dots_145678': 0x10028f9, 2013 | 'braille_dots_245678': 0x10028fa, 2014 | 'braille_dots_1245678': 0x10028fb, 2015 | 'braille_dots_345678': 0x10028fc, 2016 | 'braille_dots_1345678': 0x10028fd, 2017 | 'braille_dots_2345678': 0x10028fe, 2018 | 'braille_dots_12345678': 0x10028ff, 2019 | } 2020 | 2021 | keysym_strings = defaultdict(list) 2022 | for keysym_string, keysym in list(keysyms.items()): 2023 | keysym_strings[keysym].append(keysym_string) 2024 | -------------------------------------------------------------------------------- /system_hotkey/system_hotkey.py: -------------------------------------------------------------------------------- 1 | import os 2 | import _thread as thread 3 | import queue 4 | import time 5 | import collections 6 | from pprint import pprint 7 | import struct 8 | 9 | try: 10 | from . import util 11 | except SystemError: 12 | import util 13 | 14 | class SystemHotkeyError(Exception):pass 15 | class SystemRegisterError(SystemHotkeyError):pass 16 | class UnregisterError(SystemHotkeyError):pass 17 | class InvalidKeyError(SystemHotkeyError):pass 18 | 19 | if os.name == 'nt': 20 | import ctypes 21 | from ctypes import wintypes 22 | import win32con 23 | byref = ctypes.byref 24 | user32 = ctypes.windll.user32 25 | PM_REMOVE = 0x0001 26 | 27 | vk_codes= { 28 | 'a':0x41, 29 | 'b':0x42, 30 | 'c':0x43, 31 | 'd':0x44, 32 | 'e':0x45, 33 | 'f':0x46, 34 | 'g':0x47, 35 | 'h':0x48, 36 | 'i':0x49, 37 | 'j':0x4A, 38 | 'k':0x4B, 39 | 'l':0x4C, 40 | 'm':0x4D, 41 | 'n':0x4E, 42 | 'o':0x5F, 43 | 'p':0x50, 44 | 'q':0x51, 45 | 'r':0x52, 46 | 's':0x53, 47 | 't':0x54, 48 | 'u':0x55, 49 | 'v':0x56, 50 | 'w':0x57, 51 | 'x':0x58, 52 | 'y':0x59, 53 | 'z':0x5A, 54 | '0':0x30, 55 | '1':0x31, 56 | '2':0x32, 57 | '3':0x33, 58 | '4':0x34, 59 | '5':0x35, 60 | '6':0x36, 61 | '7':0x37, 62 | '8':0x38, 63 | '9':0x39, 64 | "up": win32con.VK_UP 65 | , "kp_up": win32con.VK_UP 66 | , "down": win32con.VK_DOWN 67 | , "kp_down": win32con.VK_DOWN 68 | , "left": win32con.VK_LEFT 69 | , "kp_left": win32con.VK_LEFT 70 | , "right": win32con.VK_RIGHT 71 | , "kp_right": win32con.VK_RIGHT 72 | , "prior": win32con.VK_PRIOR 73 | , "kp_prior": win32con.VK_PRIOR 74 | , "next": win32con.VK_NEXT 75 | , "kp_next": win32con.VK_NEXT 76 | , "home": win32con.VK_HOME 77 | , "kp_home": win32con.VK_HOME 78 | , "end": win32con.VK_END 79 | , "kp_end": win32con.VK_END 80 | , "insert": win32con.VK_INSERT 81 | , "return": win32con.VK_RETURN 82 | , "tab": win32con.VK_TAB 83 | , "space": win32con.VK_SPACE 84 | , "backspace": win32con.VK_BACK 85 | , "delete": win32con.VK_DELETE 86 | , "escape": win32con.VK_ESCAPE , "pause": win32con.VK_PAUSE 87 | , "kp_multiply": win32con.VK_MULTIPLY 88 | , "kp_add": win32con.VK_ADD 89 | , "kp_separator": win32con.VK_SEPARATOR 90 | , "kp_subtract": win32con.VK_SUBTRACT 91 | , "kp_decimal": win32con.VK_DECIMAL 92 | , "kp_divide": win32con.VK_DIVIDE 93 | , "kp_0": win32con.VK_NUMPAD0 94 | , "kp_1": win32con.VK_NUMPAD1 95 | , "kp_2": win32con.VK_NUMPAD2 96 | , "kp_3": win32con.VK_NUMPAD3 97 | , "kp_4": win32con.VK_NUMPAD4 98 | , "kp_5": win32con.VK_NUMPAD5 99 | , "kp_6": win32con.VK_NUMPAD6 100 | , "kp_7": win32con.VK_NUMPAD7 101 | , "kp_8": win32con.VK_NUMPAD8 102 | , "kp_9": win32con.VK_NUMPAD9 103 | , "f1": win32con.VK_F1 104 | , "f2": win32con.VK_F2 105 | , "f3": win32con.VK_F3 106 | , "f4": win32con.VK_F4 107 | , "f5": win32con.VK_F5 108 | , "f6": win32con.VK_F6 109 | , "f7": win32con.VK_F7 110 | , "f8": win32con.VK_F8 111 | , "f9": win32con.VK_F9 112 | , "f10": win32con.VK_F10 113 | , "f11": win32con.VK_F11 114 | , "f12": win32con.VK_F12 115 | , "f13": win32con.VK_F13 116 | , "f14": win32con.VK_F14 117 | , "f15": win32con.VK_F15 118 | , "f16": win32con.VK_F16 119 | , "f17": win32con.VK_F17 120 | , "f18": win32con.VK_F18 121 | , "f19": win32con.VK_F19 122 | , "f20": win32con.VK_F20 123 | , "f21": win32con.VK_F21 124 | , "f22": win32con.VK_F22 125 | , "f23": win32con.VK_F23 126 | , "f24": win32con.VK_F24 127 | , "media_play_pause": win32con.VK_MEDIA_PLAY_PAUSE 128 | , "media_stop": win32con.VK_MEDIA_STOP 129 | , "media_next": win32con.VK_MEDIA_NEXT_TRACK 130 | , "media_previous": win32con.VK_MEDIA_PREV_TRACK 131 | } 132 | win_modders = { 133 | "shift": win32con.MOD_SHIFT 134 | ,"control": win32con.MOD_CONTROL 135 | ,"alt": win32con.MOD_ALT 136 | ,"super": win32con.MOD_WIN 137 | } 138 | win_trivial_mods = ( 139 | 0, 140 | # win32con.CAPSLOCK_ON, 141 | # win32con.NUMLOCK_ON, 142 | # win32con.NUMLOCK_ON | win32con.CAPSLOCK_ON 143 | ) 144 | # Linux 145 | else: 146 | try: 147 | from . import xpybutil_keybind as keybind 148 | except SystemError: 149 | import xpybutil_keybind as keybind 150 | 151 | # Xcb 152 | try: 153 | # Xproto con will not work unlesss you inport this first 154 | import xcffib 155 | from xcffib import xproto 156 | 157 | xcb_modifiers = { 158 | 'control' : xproto.ModMask.Control, 159 | 'shift' : xproto.ModMask.Shift, 160 | 'alt' : xproto.ModMask._1, 161 | 'super' : xproto.ModMask._4 162 | } 163 | xcb_trivial_mods = ( 164 | 0, 165 | xproto.ModMask.Lock, 166 | xproto.ModMask._2, 167 | xproto.ModMask.Lock | xproto.ModMask._2) 168 | except ImportError: 169 | pass 170 | # Xlib 171 | else: 172 | try: 173 | from Xlib import X 174 | from Xlib import XK 175 | from Xlib.display import Display 176 | # These couldn't be gotten from the xlib keycode function 177 | special_X_keysyms = { 178 | ' ' : "space", 179 | '\t' : "tab", 180 | '\n' : "return", 181 | '\r' : "return", 182 | '\e' : "escape", 183 | '!' : "exclam", 184 | '#' : "numbersign", 185 | '%' : "percent", 186 | '$' : "dollar", 187 | '&' : "ampersand", 188 | '"' : "quotedbl", 189 | '\'' : "apostrophe", 190 | '(' : "parenleft", 191 | ')' : "parenright", 192 | '*' : "asterisk", 193 | '=' : "equal", 194 | '+' : "plus", 195 | ',' : "comma", 196 | '-' : "minus", 197 | '.' : "period", 198 | '/' : "slash", 199 | ':' : "colon", 200 | ';' : "semicolon", 201 | '<' : "less", 202 | '>' : "greater", 203 | '?' : "question", 204 | '@' : "at", 205 | '[' : "bracketleft", 206 | ']' : "bracketright", 207 | '\\' : "backslash", 208 | '^' : "asciicircum", 209 | '_' : "underscore", 210 | '`' : "grave", 211 | '{' : "braceleft", 212 | '|' : "bar", 213 | '}' : "braceright", 214 | '~' : "asciitilde" 215 | } 216 | 217 | xlib_modifiers = { 218 | 'control' : X.ControlMask, 219 | 'shift' : X.ShiftMask, 220 | 'alt' : X.Mod1Mask, 221 | 'super' : X.Mod4Mask 222 | } 223 | 224 | xlib_trivial_mods = ( # only working for scrollock 225 | 0, 226 | X.LockMask, 227 | X.Mod2Mask, 228 | X.LockMask | X.Mod2Mask) 229 | except ImportError: 230 | pass 231 | 232 | 233 | 234 | class Aliases(): 235 | ''' 236 | Easily check if something is an alias of other things 237 | ''' 238 | def __init__(self, *aliases): 239 | self.aliases = {} 240 | for values in aliases: 241 | assert isinstance(values, tuple) 242 | for val in values: 243 | self.aliases[val] = values 244 | 245 | def get(self, thing, nonecase=None): 246 | return self.aliases.get(thing, nonecase) 247 | 248 | 249 | NUMPAD_ALIASES = Aliases( 250 | ('kp_1', 'kp_end',), 251 | ('kp_2', 'kp_down',), 252 | ('kp_3', 'kp_next', 'kp_page_down'), 253 | ('kp_4', 'kp_left',), 254 | ('kp_5', 'kp_begin',), 255 | ('kp_6', 'kp_right',), 256 | ('kp_7', 'kp_home',), 257 | ('kp_8', 'kp_up',), 258 | ('kp_9', 'kp_prior', 'kp_page_up'), 259 | ) 260 | 261 | thread_safe = util.CallSerializer() 262 | 263 | class MixIn(): 264 | @thread_safe.serialize_call(0.5) 265 | def register(self, hotkey, *args, callback=None, overwrite=False): 266 | ''' 267 | Add a system wide hotkey, 268 | 269 | hotkey needs to be a tuple/list 270 | 271 | If the Systemhotkey class consumer attribute value is set to callback, 272 | callback will need to be a callable object that will be run 273 | 274 | Otherwise pass in option arguments that will be passed to the 275 | to consumer function 276 | 277 | set overwrite to True to overwrite existing binds, otherwise a 278 | SystemRegisterError will be raised. 279 | 280 | Modifiers include 281 | control 282 | shift 283 | super 284 | alt 285 | 286 | thread safe 287 | ''' 288 | assert isinstance(hotkey, collections.Iterable) and type(hotkey) not in (str, bytes) 289 | if self.consumer == 'callback' and not callback: 290 | raise TypeError('Function register requires callback argument in non sonsumer mode') 291 | 292 | hotkey = self.order_hotkey(hotkey) 293 | keycode, masks = self.parse_hotkeylist(hotkey) 294 | 295 | if tuple(hotkey) in self.keybinds: 296 | if overwrite: 297 | self.unregister(hotkey) 298 | else: 299 | msg = 'existing bind detected... unregister or set overwrite to True' 300 | raise SystemRegisterError(msg, *hotkey) 301 | 302 | if os.name == 'nt': 303 | def nt_register(): 304 | uniq = util.unique_int(self.hk_ref.keys()) 305 | self.hk_ref[uniq] = ((keycode, masks)) 306 | self._the_grab(keycode, masks, uniq) 307 | self.hk_action_queue.put(lambda:nt_register()) 308 | time.sleep(self.check_queue_interval*3) 309 | else: 310 | self._the_grab(keycode, masks) 311 | 312 | if callback: 313 | self.keybinds[tuple(hotkey)] = callback 314 | else: 315 | self.keybinds[tuple(hotkey)] = args 316 | 317 | if self.verbose: 318 | print('Printing all keybinds') 319 | pprint(self.keybinds) 320 | print() 321 | if os.name == 'posix' and self.use_xlib: 322 | self.disp.flush() 323 | 324 | #~ This code works but im not sure abot pywin support for differentiation between keypress/keyrelease so.. 325 | #~ on my laptoop on linux anyway keyrelease fires even if key is still down... 326 | 327 | #~ assert event_type in ('keypress', 'keyrelease', 'both') 328 | #~ 329 | #~ copy = list(hotkey) 330 | #~ if event_type != 'both': 331 | #~ copy.append(event_type) 332 | #~ self.keybinds[tuple(copy)].append(callback) 333 | #~ else: # Binding to both keypress and keyrelease 334 | #~ copy.append('keypress') 335 | #~ self.keybinds[tuple(copy)].append(callback) 336 | #~ copy[-1] = 'keyrelease' 337 | #~ self.keybinds[tuple(copy)].append(callback) 338 | 339 | @thread_safe.serialize_call(0.5) 340 | def unregister(self, hotkey): 341 | ''' 342 | Remove the System wide hotkey, 343 | the order of the modifier keys is irrelevant 344 | 345 | InvalidKeyError raised if key not understood 346 | UnregisterError raiesd if key doesn't exist 347 | ''' 348 | keycode, masks = self.parse_hotkeylist(hotkey) 349 | if os.name == 'nt': 350 | def nt_unregister(hk_to_remove): 351 | for key, value in self.hk_ref.items(): 352 | if value == hk_to_remove: 353 | del self.hk_ref[key] 354 | user32.UnregisterHotKey(None, key) 355 | #~ logging.debug('Checking Error from unregister hotkey %s' % (win32api.GetLastError())) 356 | return hk_to_remove 357 | self.hk_action_queue.put(lambda: nt_unregister((keycode,masks))) 358 | time.sleep(self.check_queue_interval*3) 359 | elif os.name == 'posix': 360 | if self.use_xlib: 361 | for mod in self.trivial_mods: 362 | self.xRoot.ungrab_key(keycode, masks | mod) 363 | else: 364 | try: 365 | for mod in self.trivial_mods: 366 | self.conn.core.UngrabKeyChecked(keycode, self.root, masks | mod).check() 367 | except xproto.BadAccess as e: 368 | raise UnregisterError("Failed to unregister") from e 369 | try: 370 | del self.keybinds[tuple(self.order_hotkey(hotkey))] 371 | except KeyError as err: 372 | raise UnregisterError from err 373 | 374 | def order_hotkey(self, hotkey): 375 | # Order doesn't matter for modifiers, so we force an order here 376 | # control - shift - alt - win, and when we read back the modifers we spit them 377 | # out in the same value so our dictionary keys always match 378 | if len(hotkey) > 2: 379 | new_hotkey = [] 380 | for mod in hotkey[:-1]: 381 | if 'control' == mod: 382 | new_hotkey.append(mod) 383 | for mod in hotkey[:-1]: 384 | if 'shift' == mod: 385 | new_hotkey.append(mod) 386 | for mod in hotkey[:-1]: 387 | if 'alt' == mod: 388 | new_hotkey.append(mod) 389 | for mod in hotkey[:-1]: 390 | if 'super' == mod: 391 | new_hotkey.append(mod) 392 | new_hotkey.append(hotkey[-1]) 393 | hotkey = new_hotkey 394 | return hotkey 395 | 396 | def parse_hotkeylist(self, full_hotkey): 397 | # Returns keycodes and masks from a list of hotkey masks 398 | masks = [] 399 | keycode = self._get_keycode(full_hotkey[-1]) 400 | if keycode is None: 401 | key = full_hotkey[-1] 402 | # Make sure kp keys are in the correct format 403 | if key[:3].lower() == 'kp_': 404 | keycode = self._get_keycode('KP_' + full_hotkey[-1][3:].capitalize()) 405 | if keycode is None: 406 | msg = 'Unable to Register, Key not understood by systemhotkey' 407 | raise InvalidKeyError(msg) 408 | 409 | 410 | if len(full_hotkey) > 1: 411 | for item in full_hotkey[:-1]: 412 | try: 413 | masks.append(self.modders[item]) 414 | except KeyError: 415 | raise SystemRegisterError('Modifier: %s not supported' % item) from None 416 | masks = self.or_modifiers_together(masks) 417 | else: 418 | masks = 0 419 | return keycode, masks 420 | 421 | def or_modifiers_together(self, modifiers): 422 | # Binary or the modifiers together 423 | result = 0 424 | for part in modifiers: 425 | result |= part 426 | return result 427 | 428 | def get_callback(self, hotkey): 429 | if self.verbose: 430 | print('Keybinds , key here -> ', tuple(hotkey)) 431 | pprint(self.keybinds) 432 | try: 433 | yield self.keybinds[tuple(hotkey)] 434 | except KeyError: 435 | # On Linux 436 | # The event gets sent a few times to us with different 437 | # Information about the modifyer, we just ignore failed attempts 438 | if self.verbose: 439 | print('MFERROR', hotkey) 440 | # Possible numpad key? The keysym can change if shift is pressed (only tested on linux) 441 | # TODO test numpad bindings on a non english system 442 | aliases = NUMPAD_ALIASES.get(hotkey[-1]) 443 | if aliases: 444 | for key in aliases: 445 | try: 446 | new_hotkey = hotkey[:-1] 447 | new_hotkey.append(key) 448 | yield self.keybinds[tuple(new_hotkey)] 449 | break 450 | except (KeyError, TypeError): 451 | if self.verbose: 452 | print('NUMERROR', new_hotkey) 453 | 454 | # this was for the event_type callback 455 | # def get_callback(self, hotkey ,event_type): 456 | # copy = list(hotkey) 457 | # copy.append(event_type) 458 | # if self.verbose: 459 | # print('Keybinds , key here -> ', tuple(copy)) 460 | # pprint(self.keybinds) 461 | # for func in self.keybinds[tuple(copy)]: 462 | # yield func 463 | 464 | 465 | def parse_event(self, event): 466 | ''' Turns an event back into a hotkeylist''' 467 | hotkey = [] 468 | if os.name == 'posix': 469 | try: 470 | hotkey += self.get_modifiersym(event.state) 471 | # When a new keyboard plugged in or numlock.. 472 | except AttributeError: 473 | return None 474 | 475 | hotkey.append(self._get_keysym(event.detail).lower()) 476 | else: 477 | keycode, modifiers = self.hk_ref[event.wParam][0], self.hk_ref[event.wParam][1] 478 | hotkey += self.get_modifiersym(modifiers) 479 | hotkey.append(self._get_keysym(keycode).lower()) 480 | 481 | if os.name == 'posix': 482 | # if you press keys at the same time as triggering an event 483 | # linux might send us a wrong key... 484 | if tuple(hotkey) not in self.keybinds: 485 | if self.verbose: 486 | print("bugger off spurious ", hotkey) 487 | return 488 | 489 | if self.verbose: 490 | print('hotkey ', hotkey) 491 | return hotkey 492 | 493 | 494 | def get_modifiersym(self, state): 495 | ret = [] 496 | if state & self.modders['control']: 497 | ret.append('control') 498 | if state & self.modders['shift']: 499 | ret.append('shift') 500 | if state & self.modders['alt']: 501 | ret.append('alt') 502 | if state & self.modders['super']: 503 | ret.append('super') 504 | return ret 505 | 506 | def _get_keysym(self, keycode): 507 | ''' 508 | given a keycode returns a keysym 509 | ''' 510 | # TODO 511 | # --quirks-- 512 | # linux: 513 | # numpad keys with multiple keysyms are currently undistinguishable 514 | # i.e kp_3 and kp_page_down look exactly the same, so we cannot implement our unite_kp.. 515 | 516 | 517 | class SystemHotkey(MixIn): 518 | ''' 519 | Cross platform System Wide Hotkeys 520 | 521 | Modifer oder doesn't matter, e.g 522 | binding to control shift k is the same as shift control k, 523 | limitation of the keyboard and operating systems not this library 524 | ''' 525 | hk_ref = {} 526 | keybinds = {} 527 | 528 | def __init__(self, consumer='callback', check_queue_interval=0.0001, use_xlib=False, conn=None, verbose=False, unite_kp=True): 529 | ''' 530 | if the consumer param = 'callback', -> All hotkeys will require 531 | a callback function 532 | 533 | Otherwise set consumer to a function to hanlde the event. 534 | the function signature: event, hotkey, args 535 | event is the xwindow/microsoft keyboard event 536 | hotkey is a tuple, 537 | args is a list of any ars parsed in at the time of registering 538 | 539 | check_queue_interval is in seconds and sets the sleep time on 540 | checking the queue for hotkey presses 541 | 542 | set use_xlib to true to use the xlib python bindings (GPL) instead of the xcb ones (BSD) 543 | You can pass an exisiting X display or connection using the conn keyword, 544 | otherwise one will be created for you. 545 | 546 | keybinds will work regardless if numlock/capslock are on/off. 547 | so kp_3 will also bind to kp_page_down 548 | If you do not want numpad keys to have the same function 549 | when numlock is on or off set unite_kp to False (only windows) 550 | TODO 551 | This is still under development, triggering the key with 552 | other modifyers such as shift or fn keys may or maynot work 553 | ''' 554 | # Changes the class methods to point to differenct functions 555 | # Depening on the operating system and library used 556 | # Consumer can be set to a function also, which will be sent the event 557 | # as well as the key and mask already broken out 558 | # Last option for consumer is False, then you have to listen to the queue yourself 559 | # data_queue 560 | self.verbose = verbose 561 | self.use_xlib = use_xlib 562 | self.consumer = consumer 563 | self.check_queue_interval = check_queue_interval 564 | self.unite_kp = unite_kp 565 | if os.name == 'posix' and not unite_kp: 566 | # see _get_keysym 567 | raise NotImplementedError 568 | 569 | def mark_event_type(event): 570 | # event gets an event_type attribute so the user has a portiabble way 571 | # actually on windows as far as i know you dont have the option of binding on keypress or release so... 572 | # anyway ahve to check it but for now u dont! 573 | if os.name == 'posix': 574 | if self.use_xlib: 575 | if event.type == X.KeyPress: 576 | event.event_type = 'keypress' 577 | elif event.type == X.KeyRelease: 578 | event.event_type = 'keyrelease' 579 | else: 580 | if isinstance(event, xproto.KeyPressEvent): 581 | event.event_type = 'keypress' 582 | if isinstance(event, xproto.KeyReleaseEvent): 583 | event.event_type = 'keyrelease' 584 | else: 585 | event.event_type = 'keypress' 586 | return event 587 | 588 | self.data_queue = queue.Queue() 589 | if os.name == 'nt': 590 | self.hk_action_queue = queue.Queue() 591 | self.modders = win_modders 592 | self.trivial_mods = win_trivial_mods 593 | self._the_grab = self._nt_the_grab 594 | self._get_keycode = self._nt_get_keycode 595 | self._get_keysym = self._nt_get_keysym 596 | 597 | thread.start_new_thread(self._nt_wait,(),) 598 | 599 | elif use_xlib: 600 | # Use the python-xlib library bindings, GPL License 601 | self.modders = xlib_modifiers 602 | self.trivial_mods = xlib_trivial_mods 603 | self._the_grab = self._xlib_the_grab 604 | self._get_keycode = self._xlib_get_keycode 605 | self._get_keysym = self._xlib_get_keysym 606 | if not conn: 607 | self.disp = Display() 608 | else: 609 | self.disp = conn 610 | self.xRoot = self.disp.screen().root 611 | self.xRoot.change_attributes(event_mask=X.KeyPressMask) 612 | 613 | thread.start_new_thread(self._xlib_wait,(),) 614 | 615 | else: 616 | # Using xcb and the xcffib python bindings Apache 2 http://stackoverflow.com/questions/40100/apache-license-vs-bsd-vs-mit 617 | self.modders = xcb_modifiers 618 | self.trivial_mods = xcb_trivial_mods 619 | self._the_grab = self._xcb_the_grab 620 | self._get_keycode = self._xcb_get_keycode 621 | self._get_keysym = self._xcb_get_keysym 622 | if not conn: 623 | self.conn = xcffib.connect() 624 | else: 625 | self.conn = conn 626 | self.root = self.conn.get_setup().roots[0].root 627 | 628 | thread.start_new_thread(self._xcb_wait,(),) 629 | 630 | if consumer == 'callback': 631 | if self.verbose: 632 | print('In Callback') 633 | def thread_me(): 634 | while 1: 635 | time.sleep(self.check_queue_interval) 636 | try: 637 | event = self.data_queue.get(block=False) 638 | except queue.Empty: 639 | pass 640 | else: 641 | event = mark_event_type(event) 642 | hotkey = self.parse_event(event) 643 | if not hotkey: 644 | continue 645 | #~ for cb in self.get_callback(hotkey, event.event_type): #when i was using the keypress / keyrelease shit 646 | for cb in self.get_callback(hotkey): 647 | if event.event_type == 'keypress': 648 | if self.verbose: 649 | print('calling ', repr(cb)) 650 | cb(event) # TODO either throw these up in a thread, or pass in a queue to be put onto 651 | thread.start_new_thread(thread_me,(),) 652 | 653 | elif callable(consumer): 654 | def thread_me(): 655 | while 1: 656 | time.sleep(self.check_queue_interval) 657 | try: 658 | event = self.data_queue.get(block=False) 659 | except queue.Empty: 660 | pass 661 | else: 662 | hotkey = self.parse_event(mark_event_type(event)) 663 | if not hotkey: 664 | continue 665 | if event.event_type == 'keypress': 666 | args = [cb for cb in self.get_callback(hotkey)] 667 | #~ callbacks = [cb for cb in self.get_callback(hotkey, event.event_type)] 668 | consumer(event, hotkey, args) 669 | thread.start_new_thread(thread_me,(),) 670 | else: 671 | print('You need to handle grabbing events yourself!') 672 | 673 | def _xlib_wait(self): 674 | # Pushes Event onto queue 675 | while 1: 676 | event = self.xRoot.display.next_event() 677 | self.data_queue.put(event) 678 | 679 | def _xcb_wait(self): 680 | # Pushes Event onto queue 681 | while 1: 682 | event = self.conn.wait_for_event() 683 | self.data_queue.put(event) 684 | 685 | def _nt_wait(self): 686 | # Pushes Event onto queue 687 | # I don't understand the windows msg system 688 | # I can only get hotkeys to work if they are registeed in the 689 | # Thread that is listening for them. 690 | # So any changes to the hotkeys have to be signaled to be done 691 | # By the thread. (including unregistering) 692 | # A new queue is checked and runs functions, either adding 693 | # or removing new hotkeys, then the windows msg queue is checked 694 | msg = ctypes.wintypes.MSG () 695 | while 1: 696 | try: 697 | remove_or_add = self.hk_action_queue.get(block=False) 698 | except queue.Empty: 699 | pass 700 | else: 701 | remove_or_add() 702 | # Checking the windows message Queue 703 | if user32.PeekMessageA(byref(msg), 0, 0, 0, PM_REMOVE): 704 | if msg.message == win32con.WM_HOTKEY: 705 | self.data_queue.put(msg) 706 | else: 707 | print('some other message') 708 | time.sleep(self.check_queue_interval) 709 | 710 | def _nt_get_keycode(self, key, disp=None): 711 | return vk_codes.get(key) 712 | 713 | def _nt_get_keysym(self, keycode): 714 | for key, value in vk_codes.items(): 715 | if value == keycode: 716 | return key 717 | 718 | def _nt_the_grab(self, keycode, masks, id, root=None): 719 | keysym = self._get_keysym(keycode) 720 | aliases = NUMPAD_ALIASES.get(keysym) 721 | # register numpad aliases for the keypad 722 | if aliases and self.unite_kp: 723 | for alias in aliases: 724 | if alias != keysym and self._get_keycode(alias): 725 | # Hack to avoid entering this control flow again.. 726 | self.unite_kp = False 727 | self._the_grab(self._get_keycode(alias), masks, id) 728 | self.unite_kp = True 729 | 730 | if not user32.RegisterHotKey(None, id, masks, keycode): 731 | keysym = self._nt_get_keysym(keycode) 732 | msg = 'The bind could be in use elsewhere: ' + keysym 733 | raise SystemRegisterError(msg) 734 | 735 | def _xlib_get_keycode(self, key) : 736 | keysym = XK.string_to_keysym(key) 737 | if keysym == 0: 738 | try: 739 | keysym = XK.string_to_keysym(special_X_keysyms[key]) 740 | except KeyError: 741 | return None 742 | keycode = self.disp.keysym_to_keycode(keysym) 743 | return keycode 744 | 745 | def _xlib_get_keysym(self, keycode, i=0): 746 | keysym = self.disp.keycode_to_keysym(keycode, i) 747 | # https://lists.gnu.org/archive/html/stumpwm-devel/2006-04/msg00033.html 748 | return keybind.keysym_strings.get(keysym, [None])[0] 749 | 750 | def _xlib_the_grab(self, keycode, masks): 751 | # TODO error handlig http://tronche.com/gui/x/xlib/event-handling/protocol-errors/default-handlers.html 752 | # try: 753 | for triv_mod in self.trivial_mods: 754 | self.xRoot.grab_key(keycode, triv_mod | masks, 1, X.GrabModeAsync, X.GrabModeAsync) 755 | # except Xlib.error.BadAccess: 756 | # raise SystemRegisterError('The bind is probably already in use elsewhere on the system') 757 | 758 | def _xcb_the_grab(self, keycode, masks): 759 | try: 760 | for triv_mod in self.trivial_mods: 761 | try: 762 | self.conn.core.GrabKeyChecked( 763 | True, 764 | self.root, triv_mod | masks, keycode, 765 | xproto.GrabMode.Async, xproto.GrabMode.Async).check() 766 | except struct.error as e: 767 | msg = 'Unable to Register, Key not understood by systemhotkey' 768 | raise InvalidKeyError(msg) from e 769 | except xproto.AccessError as e: 770 | keysym = self._xcb_get_keysym(keycode) 771 | msg = 'The bind could be in use elsewhere: ' + keysym 772 | raise SystemRegisterError(msg) from e 773 | 774 | def _xcb_get_keycode(self, key): 775 | return keybind.lookup_string(key) 776 | 777 | def _xcb_get_keysym(self, keycode, i=0): 778 | keysym = keybind.get_keysym(keycode, i) 779 | return keybind.keysym_strings.get(keysym, [None])[0] 780 | 781 | if __name__ == '__main__': 782 | # hk = SystemHotkey(use_xlib=True, verbose=1, unite_kp=1) 783 | # hk = SystemHotkey(use_xlib=False, verbose=0) # xcb 784 | # hk.register(('a',), callback=lambda e: print('hi')) 785 | # hk.register(('kp_3',), callback=lambda e: print('hi')) 786 | 787 | #hk.register(('left',), callback=lambda e: print('hi')) 788 | 789 | # hk.register(('k',), callback=lambda e: print('i am k')) 790 | # hk.register(['control', 'k'], callback=lambda e: print('i am control k')) 791 | # hk.register(('control','shift', 'k'), callback=lambda e: print('i am control shift k')) 792 | # hk.register(('control','super', 'k'), callback=lambda e: print('i am control win k')) 793 | # hk.register(['control','alt', 'k'], callback=lambda e: print('i am control alt k')) 794 | # hk.register(['control','shift','super','alt', 'k'], callback=lambda e: print('i am control alt shift win k')) 795 | 796 | # hk.unregister(['k']) 797 | # hk.unregister(('control','shift', 'k')) 798 | 799 | # hk.register(['k'], callback=lambda e: print('i am k2')) 800 | # hk.register(('control','shift', 'k'), callback=lambda e: print('i am con shift k2')) 801 | def consumer(event, hotkey, args): 802 | if hotkey != ["f4"]: 803 | print(1) 804 | # import pdb;pdb.set_trace() 805 | else: 806 | print(1) 807 | 808 | hk = SystemHotkey(use_xlib=False, consumer=consumer, verbose=0, 809 | check_queue_interval=0.001) 810 | hk.register(('escape',), callback=lambda e: print('hi')) 811 | while 1: 812 | pass 813 | 814 | # Refernces # 815 | #https://wiki.python.org/moin/AppsWithPythonScripting 816 | #vk codes http://msdn.microsoft.com/en-us/library/ms927178.aspx 817 | #vk codes http://www.kbdedit.com/manual/low_level_vk_list.html 818 | #http://stackoverflow.com/questions/14076207/simulating-a-key-press-event-in-python-2-7 #TODO WINDOWS KEY UP /DOWN ? 819 | #https://github.com/Tzbob/python-windows-tiler/blob/master/pwt/hotkey.py 820 | #TODO how to keep the lisetning alive if an error is encounterd? how to recover? 821 | -------------------------------------------------------------------------------- /system_hotkey/tests/test_system_hotkey.py: -------------------------------------------------------------------------------- 1 | from system_hotkey import SystemHotkey, \ 2 | SystemRegisterError, \ 3 | UnregisterError, \ 4 | InvalidKeyError 5 | 6 | def test_errors_raised_in_main(): 7 | hk = SystemHotkey() 8 | key = ('5',) 9 | cb = lambda e: print('hi') 10 | 11 | hk.register(key, callback=cb) 12 | try: 13 | hk.register(key, callback=cb) 14 | except SystemRegisterError: 15 | pass 16 | else: 17 | raise Exception('fail') 18 | 19 | hk.unregister(key) 20 | try: 21 | hk.unregister(key) 22 | except UnregisterError: 23 | pass 24 | else: 25 | raise Exception('fail') 26 | 27 | bad_key = ('badkey ..',) 28 | try: 29 | hk.register(bad_key, callback=cb) 30 | except InvalidKeyError: 31 | pass 32 | else: 33 | raise Exception('fail') 34 | 35 | try: 36 | hk.unregister(bad_key) 37 | except: 38 | pass 39 | else: 40 | raise Exception('fail') 41 | 42 | # input() # Hold program open until you press enter 43 | 44 | test_errors_raised_in_main() 45 | -------------------------------------------------------------------------------- /system_hotkey/util.py: -------------------------------------------------------------------------------- 1 | ''' 2 | system_hotkey.util 3 | 4 | general utilites.. 5 | ''' 6 | import _thread as thread 7 | import threading 8 | from queue import Queue 9 | import queue 10 | from functools import wraps 11 | import time 12 | 13 | 14 | def unique_int(values): 15 | ''' 16 | returns the first lowest integer 17 | that is not in the sequence passed in 18 | 19 | if a list looks like 3,6 20 | of the first call will return 1, and then 2 21 | and then 4 etc 22 | ''' 23 | last = 0 24 | for num in values: 25 | if last not in values: 26 | break 27 | else: 28 | last += 1 29 | return last 30 | 31 | 32 | class ExceptionSerializer(): 33 | def __init__(self): 34 | self.queue = queue.Queue() 35 | 36 | def catch_and_raise(self, func, timeout=0.5): 37 | ''' 38 | wait for a function to finish and raise any errors''' 39 | self.wait_event(func, timeout) 40 | self._check_for_errors(func) 41 | 42 | def mark_done(self, function): 43 | '''Wrap functions so that we can monitor when they are done''' 44 | self.init_wrap(function) 45 | @wraps(function) 46 | def decorator(*args, **kwargs): 47 | # Function has started running 48 | self.clear_event(function) 49 | try: 50 | results = function(*args, **kwargs) 51 | except Exception as err: 52 | self.queue.put(err) 53 | else: 54 | return results 55 | finally: 56 | # Function has finished running 57 | self.set_event(function) 58 | 59 | return decorator 60 | 61 | def put(self, x): 62 | self.queue.put(x) 63 | 64 | def init_wrap(self, func): 65 | name = self._make_event_name(func) 66 | event = threading.Event() 67 | setattr(self, name, event) 68 | 69 | def _check_for_errors(self, func): 70 | try: 71 | error = self.queue.get(block=False) 72 | except queue.Empty: 73 | pass 74 | else: 75 | raise error 76 | 77 | def _make_event_name(self, func): 78 | return '_event_' + func.__name__ 79 | 80 | def get_event(self, func): 81 | return getattr(self, self._make_event_name(func)) 82 | 83 | def set_event(self, func): 84 | self.get_event(func).set() 85 | 86 | def clear_event(self, func): 87 | self.get_event(func).clear() 88 | 89 | def wait_event(self, func, *args): 90 | self.get_event(func).wait(*args) 91 | 92 | 93 | class CallSerializer(): 94 | def __init__(self): 95 | self.queue = Queue() 96 | thread.start_new_thread(self.call_functions, (),) 97 | self.bug_catcher = ExceptionSerializer() 98 | 99 | def call_functions(self): 100 | while 1: 101 | func, args, kwargs = self.queue.get(block=True) 102 | func(*args, **kwargs) 103 | 104 | def serialize_call(self, timeout=0.5): 105 | ''' 106 | a call to a function decorated will not have 107 | overlapping calls, i.e thread safe 108 | ''' 109 | def state(function): 110 | @wraps(function) 111 | def decorator(*args, **kwargs): 112 | # Function will let us know when it is done running 113 | # This is done so we can catch exceptions raised 114 | # in functions that are run within threads 115 | mark_func = self.bug_catcher.mark_done(function) 116 | self.queue.put((mark_func, args, kwargs)) 117 | # wait for the function to finish and raise errors 118 | self.bug_catcher.catch_and_raise(function, timeout) 119 | return decorator 120 | return state 121 | 122 | -------------------------------------------------------------------------------- /system_hotkey/xpybutil_keybind.py: -------------------------------------------------------------------------------- 1 | """ 2 | A set of functions devoted to binding key presses and registering 3 | callbacks. This will automatically hook into the event callbacks 4 | in event.py. 5 | 6 | The two functions of interest here are 'bind_global_key' and 'bind_key'. Most 7 | of the other functions facilitate the use of those two, but you may need them 8 | if you're getting down and dirty. 9 | """ 10 | from collections import defaultdict 11 | import sys 12 | 13 | #~ import xcb.xproto as xproto 14 | import xcffib.xproto as xproto 15 | 16 | from xpybutil import conn, root, event 17 | from xpybutil.keysymdef import keysyms, keysym_strings 18 | 19 | __kbmap = None 20 | __keysmods = None 21 | 22 | __keybinds = defaultdict(list) 23 | __keygrabs = defaultdict(int) # Key grab key -> number of grabs 24 | 25 | EM = xproto.EventMask 26 | GM = xproto.GrabMode 27 | TRIVIAL_MODS = [ 28 | 0, 29 | xproto.ModMask.Lock, 30 | xproto.ModMask._2, 31 | xproto.ModMask.Lock | xproto.ModMask._2 32 | ] 33 | 34 | def bind_global_key(event_type, key_string, cb): 35 | """ 36 | An alias for ``bind_key(event_type, ROOT_WINDOW, key_string, cb)``. 37 | 38 | :param event_type: Either 'KeyPress' or 'KeyRelease'. 39 | :type event_type: str 40 | :param key_string: A string of the form 'Mod1-Control-a'. 41 | Namely, a list of zero or more modifiers separated by 42 | '-', followed by a single non-modifier key. 43 | :type key_string: str 44 | :param cb: A first class function with no parameters. 45 | :type cb: function 46 | :return: True if the binding was successful, False otherwise. 47 | :rtype: bool 48 | """ 49 | return bind_key(event_type, root, key_string, cb) 50 | 51 | def bind_key(event_type, wid, key_string, cb): 52 | """ 53 | Binds a function ``cb`` to a particular key press ``key_string`` on a 54 | window ``wid``. Whether it's a key release or key press binding is 55 | determined by ``event_type``. 56 | 57 | ``bind_key`` will automatically hook into the ``event`` module's dispatcher, 58 | so that if you're using ``event.main()`` for your main loop, everything 59 | will be taken care of for you. 60 | 61 | :param event_type: Either 'KeyPress' or 'KeyRelease'. 62 | :type event_type: str 63 | :param wid: The window to bind the key grab to. 64 | :type wid: int 65 | :param key_string: A string of the form 'Mod1-Control-a'. 66 | Namely, a list of zero or more modifiers separated by 67 | '-', followed by a single non-modifier key. 68 | :type key_string: str 69 | :param cb: A first class function with no parameters. 70 | :type cb: function 71 | :return: True if the binding was successful, False otherwise. 72 | :rtype: bool 73 | """ 74 | assert event_type in ('KeyPress', 'KeyRelease') 75 | 76 | mods, kc = parse_keystring(key_string) 77 | key = (wid, mods, kc) 78 | 79 | if not kc: 80 | print('Could not find a keycode for %s' % key_string, file=sys.stderr) 81 | return False 82 | 83 | if not __keygrabs[key] and not grab_key(wid, mods, kc): 84 | return False 85 | 86 | __keybinds[key].append(cb) 87 | __keygrabs[key] += 1 88 | 89 | if not event.is_connected(event_type, wid, __run_keybind_callbacks): 90 | event.connect(event_type, wid, __run_keybind_callbacks) 91 | 92 | return True 93 | 94 | def parse_keystring(key_string): 95 | """ 96 | A utility function to turn strings like 'Mod1-Mod4-a' into a pair 97 | corresponding to its modifiers and keycode. 98 | 99 | :param key_string: String starting with zero or more modifiers followed 100 | by exactly one key press. 101 | 102 | Available modifiers: Control, Mod1, Mod2, Mod3, Mod4, 103 | Mod5, Shift, Lock 104 | :type key_string: str 105 | :return: Tuple of modifier mask and keycode 106 | :rtype: (mask, int) 107 | """ 108 | modifiers = 0 109 | keycode = None 110 | 111 | for part in key_string.split('-'): 112 | if hasattr(xproto.KeyButMask, part): 113 | modifiers |= getattr(xproto.KeyButMask, part) 114 | else: 115 | if len(part) == 1: 116 | part = part.lower() 117 | keycode = lookup_string(part) 118 | 119 | return modifiers, keycode 120 | 121 | def lookup_string(kstr): 122 | """ 123 | Finds the keycode associated with a string representation of a keysym. 124 | 125 | :param kstr: English representation of a keysym. 126 | :return: Keycode, if one exists. 127 | :rtype: int 128 | """ 129 | if kstr in keysyms: 130 | return get_keycode(keysyms[kstr]) 131 | elif len(kstr) > 1 and kstr.capitalize() in keysyms: 132 | return get_keycode(keysyms[kstr.capitalize()]) 133 | 134 | return None 135 | 136 | def lookup_keysym(keysym): 137 | """ 138 | Finds the english string associated with a keysym. 139 | 140 | :param keysym: An X keysym. 141 | :return: English string representation of a keysym. 142 | :rtype: str 143 | """ 144 | return get_keysym_string(keysym) 145 | 146 | def get_min_max_keycode(): 147 | """ 148 | Return a tuple of the minimum and maximum keycode allowed in the 149 | current X environment. 150 | 151 | :rtype: (int, int) 152 | """ 153 | return conn.get_setup().min_keycode, conn.get_setup().max_keycode 154 | 155 | def get_keyboard_mapping(): 156 | """ 157 | Return a keyboard mapping cookie that can be used to fetch the table of 158 | keysyms in the current X environment. 159 | 160 | :rtype: xcb.xproto.GetKeyboardMappingCookie 161 | """ 162 | mn, mx = get_min_max_keycode() 163 | 164 | return conn.core.GetKeyboardMapping(mn, mx - mn + 1) 165 | 166 | def get_keyboard_mapping_unchecked(): 167 | """ 168 | Return an unchecked keyboard mapping cookie that can be used to fetch the 169 | table of keysyms in the current X environment. 170 | 171 | :rtype: xcb.xproto.GetKeyboardMappingCookie 172 | """ 173 | mn, mx = get_min_max_keycode() 174 | 175 | return conn.core.GetKeyboardMappingUnchecked(mn, mx - mn + 1) 176 | 177 | def get_keysym(keycode, col=0, kbmap=None): 178 | """ 179 | Get the keysym associated with a particular keycode in the current X 180 | environment. Although we get a list of keysyms from X in 181 | 'get_keyboard_mapping', this list is really a table with 182 | 'keysys_per_keycode' columns and ``mx - mn`` rows (where ``mx`` is the 183 | maximum keycode and ``mn`` is the minimum keycode). 184 | 185 | Thus, the index for a keysym given a keycode is: 186 | ``(keycode - mn) * keysyms_per_keycode + col``. 187 | 188 | In most cases, setting ``col`` to 0 will work. 189 | 190 | Witness the utter complexity: 191 | http://tronche.com/gui/x/xlib/input/keyboard-encoding.html 192 | 193 | You may also pass in your own keyboard mapping using the ``kbmap`` 194 | parameter, but xpybutil maintains an up-to-date version of this so you 195 | shouldn't have to. 196 | 197 | :param keycode: A physical key represented by an integer. 198 | :type keycode: int 199 | :param col: The column in the keysym table to use. 200 | Unless you know what you're doing, just use 0. 201 | :type col: int 202 | :param kbmap: The keyboard mapping to use. 203 | :type kbmap: xcb.xproto.GetKeyboardMapingReply 204 | """ 205 | if kbmap is None: 206 | kbmap = __kbmap 207 | 208 | mn, mx = get_min_max_keycode() 209 | per = kbmap.keysyms_per_keycode 210 | ind = (keycode - mn) * per + col 211 | 212 | return kbmap.keysyms[ind] 213 | 214 | def get_keysym_string(keysym): 215 | """ 216 | A simple wrapper to find the english string associated with a particular 217 | keysym. 218 | 219 | :param keysym: An X keysym. 220 | :rtype: str 221 | """ 222 | return keysym_strings.get(keysym, [None])[0] 223 | 224 | def get_keycode(keysym): 225 | """ 226 | Given a keysym, find the keycode mapped to it in the current X environment. 227 | It is necessary to search the keysym table in order to do this, including 228 | all columns. 229 | 230 | :param keysym: An X keysym. 231 | :return: A keycode or None if one could not be found. 232 | :rtype: int 233 | """ 234 | mn, mx = get_min_max_keycode() 235 | cols = __kbmap.keysyms_per_keycode 236 | for i in range(mn, mx + 1): 237 | for j in range(0, cols): 238 | ks = get_keysym(i, col=j) 239 | if ks == keysym: 240 | return i 241 | 242 | return None 243 | 244 | def get_mod_for_key(keycode): 245 | """ 246 | Finds the modifier that is mapped to the given keycode. 247 | This may be useful when analyzing key press events. 248 | 249 | :type keycode: int 250 | :return: A modifier identifier. 251 | :rtype: xcb.xproto.ModMask 252 | """ 253 | return __keysmods.get(keycode, 0) 254 | 255 | def get_keys_to_mods(): 256 | """ 257 | Fetches and creates the keycode -> modifier mask mapping. Typically, you 258 | shouldn't have to use this---xpybutil will keep this up to date if it 259 | changes. 260 | 261 | This function may be useful in that it should closely replicate the output 262 | of the ``xmodmap`` command. For example: 263 | 264 | :: 265 | 266 | keymods = get_keys_to_mods() 267 | for kc in sorted(keymods, key=lambda kc: keymods[kc]): 268 | print keymods[kc], hex(kc), get_keysym_string(get_keysym(kc)) 269 | 270 | Which will very closely replicate ``xmodmap``. I'm not getting precise 271 | results quite yet, but I do believe I'm getting at least most of what 272 | matters. (i.e., ``xmodmap`` returns valid keysym strings for some that 273 | I cannot.) 274 | 275 | :return: A dict mapping from keycode to modifier mask. 276 | :rtype: dict 277 | """ 278 | mm = xproto.ModMask 279 | modmasks = [mm.Shift, mm.Lock, mm.Control, 280 | mm._1, mm._2, mm._3, mm._4, mm._5] # order matters 281 | 282 | mods = conn.core.GetModifierMapping().reply() 283 | 284 | res = {} 285 | keyspermod = mods.keycodes_per_modifier 286 | for mmi in range(0, len(modmasks)): 287 | row = mmi * keyspermod 288 | for kc in mods.keycodes[row:row + keyspermod]: 289 | res[kc] = modmasks[mmi] 290 | 291 | return res 292 | 293 | def get_modifiers(state): 294 | """ 295 | Takes a ``state`` (typically found in key press or button press events) 296 | and returns a string list representation of the modifiers that were pressed 297 | when generating the event. 298 | 299 | :param state: Typically from ``some_event.state``. 300 | :return: List of modifier string representations. 301 | :rtype: [str] 302 | """ 303 | ret = [] 304 | 305 | if state & xproto.ModMask.Shift: 306 | ret.append('Shift') 307 | if state & xproto.ModMask.Lock: 308 | ret.append('Lock') 309 | if state & xproto.ModMask.Control: 310 | ret.append('Control') 311 | if state & xproto.ModMask._1: 312 | ret.append('Mod1') 313 | if state & xproto.ModMask._2: 314 | ret.append('Mod2') 315 | if state & xproto.ModMask._3: 316 | ret.append('Mod3') 317 | if state & xproto.ModMask._4: 318 | ret.append('Mod4') 319 | if state & xproto.ModMask._5: 320 | ret.append('Mod5') 321 | if state & xproto.KeyButMask.Button1: 322 | ret.append('Button1') 323 | if state & xproto.KeyButMask.Button2: 324 | ret.append('Button2') 325 | if state & xproto.KeyButMask.Button3: 326 | ret.append('Button3') 327 | if state & xproto.KeyButMask.Button4: 328 | ret.append('Button4') 329 | if state & xproto.KeyButMask.Button5: 330 | ret.append('Button5') 331 | 332 | return ret 333 | 334 | def grab_keyboard(grab_win): 335 | """ 336 | This will grab the keyboard. The effect is that further keyboard events 337 | will *only* be sent to the grabbing client. (i.e., ``grab_win``). 338 | 339 | N.B. There is an example usage of this in examples/window-marker. 340 | 341 | :param grab_win: A window identifier to report keyboard events to. 342 | :type grab_win: int 343 | :rtype: xcb.xproto.GrabStatus 344 | """ 345 | return conn.core.GrabKeyboard(False, grab_win, xproto.Time.CurrentTime, 346 | GM.Async, GM.Async).reply() 347 | 348 | def ungrab_keyboard(): 349 | """ 350 | This will release a grab initiated by ``grab_keyboard``. 351 | 352 | :rtype: void 353 | """ 354 | conn.core.UngrabKeyboardChecked(xproto.Time.CurrentTime).check() 355 | 356 | def grab_key(wid, modifiers, key): 357 | """ 358 | Grabs a key for a particular window and a modifiers/key value. 359 | If the grab was successful, return True. Otherwise, return False. 360 | If your client is grabbing keys, it is useful to notify the user if a 361 | key wasn't grabbed. Keyboard shortcuts not responding is disorienting! 362 | 363 | Also, this function will grab several keys based on varying modifiers. 364 | Namely, this accounts for all of the "trivial" modifiers that may have 365 | an effect on X events, but probably shouldn't effect key grabbing. (i.e., 366 | whether num lock or caps lock is on.) 367 | 368 | N.B. You should probably be using 'bind_key' or 'bind_global_key' instead. 369 | 370 | :param wid: A window identifier. 371 | :type wid: int 372 | :param modifiers: A modifier mask. 373 | :type modifiers: int 374 | :param key: A keycode. 375 | :type key: int 376 | :rtype: bool 377 | """ 378 | try: 379 | for mod in TRIVIAL_MODS: 380 | conn.core.GrabKeyChecked(True, wid, modifiers | mod, key, GM.Async, 381 | GM.Async).check() 382 | 383 | return True 384 | except xproto.BadAccess: 385 | return False 386 | 387 | def ungrab_key(wid, modifiers, key): 388 | """ 389 | Ungrabs a key that was grabbed by ``grab_key``. Similarly, it will return 390 | True on success and False on failure. 391 | 392 | When ungrabbing a key, the parameters to this function should be 393 | *precisely* the same as the parameters to ``grab_key``. 394 | 395 | :param wid: A window identifier. 396 | :type wid: int 397 | :param modifiers: A modifier mask. 398 | :type modifiers: int 399 | :param key: A keycode. 400 | :type key: int 401 | :rtype: bool 402 | """ 403 | try: 404 | for mod in TRIVIAL_MODS: 405 | conn.core.UngrabKeyChecked(key, wid, modifiers | mod).check() 406 | return True 407 | except xproto.BadAccess: 408 | return False 409 | 410 | def update_keyboard_mapping(e): 411 | """ 412 | Whenever the keyboard mapping is changed, this function needs to be called 413 | to update xpybutil's internal representing of the current keysym table. 414 | Indeed, xpybutil will do this for you automatically. 415 | 416 | Moreover, if something is changed that affects the current keygrabs, 417 | xpybutil will initiate a regrab with the changed keycode. 418 | 419 | :param e: The MappingNotify event. 420 | :type e: xcb.xproto.MappingNotifyEvent 421 | :rtype: void 422 | """ 423 | global __kbmap, __keysmods 424 | 425 | newmap = get_keyboard_mapping().reply() 426 | 427 | if e is None: 428 | __kbmap = newmap 429 | __keysmods = get_keys_to_mods() 430 | return 431 | 432 | if e.request == xproto.Mapping.Keyboard: 433 | changes = {} 434 | for kc in range(*get_min_max_keycode()): 435 | knew = get_keysym(kc, kbmap=newmap) 436 | oldkc = get_keycode(knew) 437 | if oldkc != kc: 438 | changes[oldkc] = kc 439 | 440 | __kbmap = newmap 441 | __regrab(changes) 442 | elif e.request == xproto.Mapping.Modifier: 443 | __keysmods = get_keys_to_mods() 444 | 445 | def __run_keybind_callbacks(e): 446 | """ 447 | A private function that intercepts all key press/release events, and runs 448 | their corresponding callback functions. Nothing much to see here, except 449 | that we must mask out the trivial modifiers from the state in order to 450 | find the right callback. 451 | 452 | Callbacks are called in the order that they have been added. (FIFO.) 453 | 454 | :param e: A Key{Press,Release} event. 455 | :type e: xcb.xproto.Key{Press,Release}Event 456 | :rtype: void 457 | """ 458 | kc, mods = e.detail, e.state 459 | for mod in TRIVIAL_MODS: 460 | mods &= ~mod 461 | 462 | key = (e.event, mods, kc) 463 | for cb in __keybinds.get(key, []): 464 | try: 465 | cb(e) 466 | except TypeError: 467 | cb() 468 | 469 | def __regrab(changes): 470 | """ 471 | Takes a dictionary of changes (mapping old keycode to new keycode) and 472 | regrabs any keys that have been changed with the updated keycode. 473 | 474 | :param changes: Mapping of changes from old keycode to new keycode. 475 | :type changes: dict 476 | :rtype: void 477 | """ 478 | for wid, mods, kc in __keybinds: 479 | if kc in changes: 480 | ungrab_key(wid, mods, kc) 481 | grab_key(wid, mods, changes[kc]) 482 | 483 | old = (wid, mods, kc) 484 | new = (wid, mods, changes[kc]) 485 | __keybinds[new] = __keybinds[old] 486 | del __keybinds[old] 487 | 488 | if conn is not None: 489 | update_keyboard_mapping(None) 490 | event.connect('MappingNotify', None, update_keyboard_mapping) 491 | 492 | if __name__ == '__main__': 493 | import xpybutil.event as event 494 | print(get_keysym_string(get_keysym(71))) 495 | #~ print(parse_keystring('f5')) 496 | #~ bind_global_key('KeyPress', 'F5', lambda: print('ass')) 497 | #~ event.main() 498 | 499 | --------------------------------------------------------------------------------