├── .gitignore ├── examples ├── get-current-desktop.py ├── inactive-window-transparent.py └── window-marker.py ├── xpybutil ├── __init__.py ├── compat.py ├── font.py ├── xinerama.py ├── image.py ├── cursor.py ├── motif.py ├── rect.py ├── event.py ├── window.py ├── mousebind.py ├── render.py ├── util.py ├── keybind.py ├── icccm.py └── keysymdef.py ├── COPYING ├── setup.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | tests 2 | *.pyc 3 | */tags 4 | .*swp 5 | testing.py 6 | tags 7 | -------------------------------------------------------------------------------- /examples/get-current-desktop.py: -------------------------------------------------------------------------------- 1 | import xpybutil.ewmh as ewmh 2 | 3 | names = ewmh.get_desktop_names().reply() 4 | desk = ewmh.get_current_desktop().reply() 5 | 6 | print names[desk] if desk < len(names) else desk 7 | 8 | -------------------------------------------------------------------------------- /xpybutil/__init__.py: -------------------------------------------------------------------------------- 1 | from xpybutil.compat import xcb, xcb_ConnectException 2 | 3 | try: 4 | conn = xcb.connect() 5 | root = conn.get_setup().roots[0].root 6 | except xcb_ConnectException: 7 | conn = None 8 | root = None 9 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /xpybutil/compat.py: -------------------------------------------------------------------------------- 1 | try: 2 | import xcffib as xcb 3 | import xcffib.xproto as xproto 4 | import xcffib.xinerama as xinerama 5 | import xcffib.randr as randr 6 | import xcffib.render as render 7 | from xcffib import XcffibException as xcb_Exception 8 | from xcffib import ConnectionException as xcb_ConnectException 9 | 10 | except ImportError: 11 | import xcb 12 | import xcb.xproto as xproto 13 | import xcb.xinerama as xinerama 14 | import xcb.randr as randr 15 | import xcb.render as render 16 | from xcb import Exception as xcb_Exception 17 | from xcb import ConnectException as xcb_ConnectException 18 | -------------------------------------------------------------------------------- /xpybutil/font.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module is pretty inadequate. Mostly because if you find 3 | yourself wanting to use this module, you're probably doing something wrong. 4 | (i.e., use pycairo or PIL.) 5 | 6 | This module will likely be removed in the future. 7 | """ 8 | from xpybutil.compat import xproto 9 | 10 | def get_font_height(qfont): 11 | return qfont.max_bounds.ascent + qfont.max_bounds.descent 12 | 13 | def get_text_width(qfont, text): 14 | assert isinstance(qfont, xproto.QueryFontReply) 15 | 16 | return sum([qfont.char_infos[ord(i)].character_width for i in text]) 17 | 18 | def get_text_height(qfont, text): 19 | assert isinstance(qfont, xproto.QueryFontReply) 20 | 21 | cinfo = qfont.char_infos 22 | return max([cinfo[ord(i)].ascent + cinfo[ord(i)].descent for i in text]) 23 | 24 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | 4 | from setuptools import setup 5 | 6 | try: 7 | from xpybutil.compat import xproto, xinerama, randr 8 | except: 9 | print('') 10 | print('xpybutil requires the X Python Binding') 11 | print('See: http://cgit.freedesktop.org/xcb/xpyb/') 12 | print('More options: xpyb-ng:', 'https://github.com/dequis/xpyb-ng', 13 | 'and xcffib:', 'https://github.com/tych0/xcffib') 14 | sys.exit(1) 15 | 16 | setup( 17 | name="xpybutil", 18 | maintainer="Fenner Macrae", 19 | author_email="fmacrae.dev@gmail.com", 20 | version="0.0.6", 21 | license="WTFPL", 22 | description="An incomplete xcb-util port plus some extras", 23 | long_description="See README", 24 | url="http://github.com/BurntSushi/xpybutil", 25 | platforms='POSIX', 26 | packages=['xpybutil'], 27 | data_files=[('share/doc/xpybutil', ['README.md', 'COPYING'])] 28 | ) 29 | -------------------------------------------------------------------------------- /xpybutil/xinerama.py: -------------------------------------------------------------------------------- 1 | """ 2 | A couple of functions that support retrieving information about 3 | all active physical heads. This is done through the Xinerama 4 | extension, which implicitly supports RandR and TwinView. 5 | 6 | The 'get_physical_mapping' function will produce a list of 7 | monitor indices in a physical ordering (left to right, top to 8 | bottom). These indices can then be used in the list returned by 9 | 'get_monitors'. 10 | """ 11 | from xpybutil.compat import xinerama 12 | 13 | from xpybutil import conn 14 | 15 | ext = None 16 | if conn is not None: 17 | ext = conn(xinerama.key) 18 | 19 | def get_monitors(): 20 | ''' 21 | Returns a list of Xinerama screen rectangles. 22 | They come in the order that the Xinerama extension specifies. 23 | 24 | :rtype: List of (x, y, w, h) rectangles 25 | ''' 26 | retval = [] 27 | ms = ext.QueryScreens().reply() 28 | if ms: 29 | for m in ms.screen_info: 30 | retval.append((m.x_org, m.y_org, m.width, m.height)) 31 | 32 | return retval 33 | 34 | def get_physical_mapping(monitors): 35 | ''' 36 | Returns a list of Xinerama screen indices in their physical order. 37 | Where physical order is defined as left-to-right and then top-to-bottom. 38 | 39 | :param monitors: List of (x, y, w, h) rectangles 40 | :rtype: List of Xinerama indices 41 | ''' 42 | retval = [] 43 | 44 | tosort = [] 45 | for i, (x, y, w, h) in enumerate(monitors): 46 | tosort.append((x, y, i)) 47 | for x, y, xscreen in sorted(tosort): 48 | retval.append(xscreen) 49 | 50 | return retval 51 | 52 | -------------------------------------------------------------------------------- /examples/inactive-window-transparent.py: -------------------------------------------------------------------------------- 1 | # Makes all inactive windows transparent and keeps the active window opaque. 2 | # This should be run in the background as a daemon like so: 3 | # python2 inactive-window-transparent.py 4 | 5 | # Range of values: 0 <= opacity <= 1 6 | # where 1 is fully opaque and 0 is completely invisible 7 | opacity = 0.8 8 | 9 | import xpybutil 10 | import xpybutil.event as event 11 | import xpybutil.ewmh as ewmh 12 | import xpybutil.util as util 13 | import xpybutil.window as window 14 | 15 | def update_window_opacity(): 16 | activewin = ewmh.get_active_window().reply() 17 | if not activewin: 18 | return 19 | 20 | for client in clients: 21 | ewmh.set_wm_window_opacity(util.get_parent_window(client), 22 | 1 if client == activewin else opacity) 23 | 24 | xpybutil.conn.flush() 25 | 26 | def client_is_normal(client): 27 | wtype = ewmh.get_wm_window_type(client).reply() 28 | if not wtype or wtype[0] == util.get_atom('_NET_WM_WINDOW_TYPE_NORMAL'): 29 | return True 30 | return False 31 | 32 | def cb_property_notify(e): 33 | global clients 34 | 35 | aname = util.get_atom_name(e.atom) 36 | 37 | if aname == '_NET_ACTIVE_WINDOW': 38 | update_window_opacity(); 39 | elif aname == '_NET_CLIENT_LIST': 40 | clients = filter(client_is_normal, ewmh.get_client_list().reply()) 41 | 42 | clients = filter(client_is_normal, ewmh.get_client_list().reply()) 43 | update_window_opacity() 44 | 45 | window.listen(xpybutil.root, 'PropertyChange') 46 | event.connect('PropertyNotify', xpybutil.root, cb_property_notify) 47 | 48 | event.main() 49 | 50 | -------------------------------------------------------------------------------- /xpybutil/image.py: -------------------------------------------------------------------------------- 1 | """ 2 | An incomplete and haphazard collection of functions that can 3 | bridge a gap between PIL and drawing images with X. 4 | """ 5 | from xpybutil.compat import xproto 6 | 7 | from xpybutil import conn 8 | from PIL import Image 9 | 10 | def net_wm_icon_to_bgra(data): 11 | ret = [0] * (len(data) * 4) 12 | for i, d in enumerate(data): 13 | alpha = d >> 24 14 | red = ((alpha << 24) ^ d) >> 16 15 | green = (((alpha << 24) + (red << 16)) ^ d) >> 8 16 | blue = (((alpha << 24) + (red << 16) + (green << 8)) ^ d) >> 0 17 | 18 | pixel = i * 4 19 | ret[pixel] = blue 20 | ret[pixel + 1] = green 21 | ret[pixel + 2] = red 22 | ret[pixel + 3] = alpha 23 | 24 | return ret 25 | 26 | def color_humanize(clr): 27 | t = hex(clr).replace('0x', '') 28 | return '#%s' % ('0' * (6 - len(t)) + t) 29 | 30 | def hex_to_rgb(h): 31 | s = color_humanize(h) 32 | return (int(s[1:3], 16), int(s[3:5], 16), int(s[5:7], 16)) 33 | 34 | def get_image_from_pixmap(pid): 35 | try: 36 | geom = conn.core.GetGeometry(pid).reply() 37 | pimg = conn.core.GetImage(xproto.ImageFormat.ZPixmap, pid, 38 | 0, 0, geom.width, geom.height, 39 | 2**32 - 1).reply().data 40 | 41 | return geom.width, geom.height, pimg 42 | except xproto.BadDrawable: 43 | return 0, 0, [] 44 | 45 | def get_image(width, height, data): 46 | im = Image.fromstring('RGBA', (width, height), 47 | ''.join([chr(i) for i in data]), 'raw', 'BGRA') 48 | 49 | return im 50 | 51 | def get_bitmap(width, height, data): 52 | im = Image.fromstring('1', (width, height), 53 | ''.join([chr(i) for i in data]), 'raw', '1;R') 54 | 55 | return im 56 | 57 | def get_data(image): 58 | return [ord(s) for s in image.tostring('raw', 'BGRA')] 59 | 60 | -------------------------------------------------------------------------------- /xpybutil/cursor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provides a listing of all built-in X cursors, and provides a 3 | function 'create_font_cursor' to create one. This object can 4 | then be directly used in a call to CreateWindow or 5 | ChangeWindowAttributes. 6 | """ 7 | from xpybutil import conn 8 | 9 | class FontCursor: 10 | """Constants for all X cursors. To be used in ``create_font_cursor``.""" 11 | XCursor = 0 12 | Arrow = 2 13 | BasedArrowDown = 4 14 | BasedArrowUp = 6 15 | Boat = 8 16 | Bogosity = 10 17 | BottomLeftCorner = 12 18 | BottomRightCorner = 14 19 | BottomSide = 16 20 | BottomTee = 18 21 | BoxSpiral = 20 22 | CenterPtr = 22 23 | Circle = 24 24 | Clock = 26 25 | CoffeeMug = 28 26 | Cross = 30 27 | CrossReverse = 32 28 | Crosshair = 34 29 | DiamondCross = 36 30 | Dot = 38 31 | DotBoxMask = 40 32 | DoubleArrow = 42 33 | DraftLarge = 44 34 | DraftSmall = 46 35 | DrapedBox = 48 36 | Exchange = 50 37 | Fleur = 52 38 | Gobbler = 54 39 | Gumby = 56 40 | Hand1 = 58 41 | Hand2 = 60 42 | Heart = 62 43 | Icon = 64 44 | IronCross = 66 45 | LeftPtr = 68 46 | LeftSide = 70 47 | LeftTee = 72 48 | LeftButton = 74 49 | LLAngle = 76 50 | LRAngle = 78 51 | Man = 80 52 | MiddleButton = 82 53 | Mouse = 84 54 | Pencil = 86 55 | Pirate = 88 56 | Plus = 90 57 | QuestionArrow = 92 58 | RightPtr = 94 59 | RightSide = 96 60 | RightTee = 98 61 | RightButton = 100 62 | RtlLogo = 102 63 | Sailboat = 104 64 | SBDownArrow = 106 65 | SBHDoubleArrow = 108 66 | SBLeftArrow = 110 67 | SBRightArrow = 112 68 | SBUpArrow = 114 69 | SBVDoubleArrow = 116 70 | Shuttle = 118 71 | Sizing = 120 72 | Spider = 122 73 | Spraycan = 124 74 | Star = 126 75 | Target = 128 76 | TCross = 130 77 | TopLeftArrow = 132 78 | TopLeftCorner = 134 79 | TopRightCorner = 136 80 | TopSide = 138 81 | TopTee = 140 82 | Trek = 142 83 | ULAngle = 144 84 | Umbrella = 146 85 | URAngle = 148 86 | Watch = 150 87 | XTerm = 152 88 | 89 | def create_font_cursor(cursor_id, fore_red=0, fore_green=0, fore_blue=0, 90 | back_red=0xffff, back_green=0xffff, back_blue=0xffff): 91 | """ 92 | Function to create cursor resources on the X server. You can use the 93 | return value of this function with mousebind.grab_pointer to make the 94 | mouse change when it's being grabbed. 95 | 96 | The rest of the parameters are a way to colorize the cursor. 97 | 98 | :param cursor_id: A numeric identifier for a cursor. 99 | :type cursor_id: A class variable of FontCursor 100 | :type fore_red: int 101 | :type fore_green: int 102 | :type fore_blue: int 103 | :type back_red: int 104 | :type back_green: int 105 | :type back_blue: int 106 | :return: A cursor X identifier. 107 | :rtype: int 108 | """ 109 | font = conn.generate_id() 110 | cursor = conn.generate_id() 111 | 112 | conn.core.OpenFontChecked(font, len('cursor'), 'cursor').check() 113 | conn.core.CreateGlyphCursorChecked(cursor, font, font, cursor_id, 114 | cursor_id + 1, fore_red, fore_green, 115 | fore_blue, back_red, back_green, 116 | back_blue).check() 117 | conn.core.CloseFont(font) 118 | 119 | return cursor 120 | 121 | -------------------------------------------------------------------------------- /xpybutil/motif.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implements a subset of the Motif spec. This module exists 3 | because some window managers still use this as the only way of 4 | toggling window decorations via events. 5 | """ 6 | import struct 7 | 8 | from xpybutil.compat import xproto 9 | 10 | from xpybutil import conn as c 11 | from xpybutil import util 12 | 13 | __atoms = ['_MOTIF_WM_HINTS'] 14 | 15 | class Hint: 16 | Functions = 1 << 0 17 | Decorations = 1 << 1 18 | InputMode = 1 << 2 19 | Status = 1 << 3 20 | 21 | class Function: 22 | _None = 0 23 | All = 1 << 0 24 | Resize = 1 << 1 25 | Move = 1 << 2 26 | Minimize = 1 << 3 27 | Maximize = 1 << 4 28 | Close = 1 << 5 29 | 30 | class Decoration: 31 | _None = 0 32 | All = 1 << 0 33 | Border = 1 << 1 34 | ResizeH = 1 << 2 35 | Title = 1 << 3 36 | Menu = 1 << 4 37 | Minimize = 1 << 5 38 | Maximize = 1 << 6 39 | 40 | class Input: 41 | Modeless = 0 42 | PrimaryApplicationModal = 1 43 | SystemModal = 2 44 | FullApplicationModal = 3 45 | 46 | class Status: 47 | TearoffWindow = 1 << 0 48 | 49 | # Some aliases 50 | atom = util.get_atom 51 | preplace = xproto.PropMode.Replace 52 | 53 | # Build the atom cache for quicker access 54 | util.build_atom_cache(__atoms) 55 | 56 | # _MOTIF_WM_HINTS 57 | 58 | class MotifHintsCookie(util.PropertyCookie): 59 | def reply(self): 60 | v = util.PropertyCookie.reply(self) 61 | 62 | if not v: 63 | return None 64 | 65 | return { 66 | 'flags': { 67 | 'Functions': v[0] & Hint.Functions > 0, 68 | 'Decorations': v[0] & Hint.Decorations > 0, 69 | 'InputMode': v[0] & Hint.InputMode > 0, 70 | 'Status': v[0] & Hint.Status > 0 71 | }, 72 | 'function': v[1], 73 | 'decoration': v[2], 74 | 'input': v[3], 75 | 'status': v[4] 76 | } 77 | 78 | def get_hints(window): 79 | return MotifHintsCookie(util.get_property(window, '_MOTIF_WM_HINTS')) 80 | 81 | def get_hints_unchecked(window): 82 | cook = util.get_property_unchecked(window, '_MOTIF_WM_HINTS') 83 | return MotifHintsCookie(cook) 84 | 85 | def _pack_hints(flags, function, decoration, input, status): 86 | hints = [0] * 5 87 | 88 | if flags & Hint.Functions: 89 | hints[1] = function 90 | 91 | if flags & Hint.Decorations: 92 | hints[2] = decoration 93 | 94 | if flags & Hint.InputMode: 95 | hints[3] = input 96 | 97 | if flags & Hint.Status: 98 | hints[4] = status 99 | 100 | hints[0] = flags 101 | 102 | return struct.pack('I' * 5, *hints) 103 | 104 | def set_hints(window, flags, function=Function._None, 105 | decoration=Decoration._None, input=Input.Modeless, 106 | status=Status.TearoffWindow): 107 | packed = _pack_hints(flags, function, decoration, input, status) 108 | return c.core.ChangeProperty(preplace, window, atom('_MOTIF_WM_HINTS'), 109 | atom('_MOTIF_WM_HINTS'), 32, 5, packed) 110 | 111 | def set_hints_checked(window, flags, function=Function._None, 112 | decoration=Decoration._None, input=Input.Modeless, 113 | status=Status.TearoffWindow): 114 | packed = _pack_hints(flags, function, decoration, input, status) 115 | return c.core.ChangePropertyChecked(preplace, window, 116 | atom('_MOTIF_WM_HINTS'), 117 | atom('_MOTIF_WM_HINTS'), 32, 5, packed) 118 | 119 | -------------------------------------------------------------------------------- /examples/window-marker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import argparse 4 | import sys 5 | 6 | from xpybutil.compat import xproto 7 | 8 | import xpybutil 9 | import xpybutil.event as event 10 | import xpybutil.ewmh as ewmh 11 | import xpybutil.keybind as keybind 12 | 13 | epilog = ''' 14 | Using window-marker is exactly like using marks in vim. Namely, by pressing the 15 | keybinding for "mark", window-marker then listens for one more letter (a-z) to 16 | be entered. Whichever letter is entered will now be associated with the active 17 | window. 18 | If you now want to go back to that window (even if you're on a different 19 | desktop), simply press the keybinding for "goto" and then enter the letter you 20 | used to mark the window. 21 | To set your own keybinding, please consult /usr/include/X11/keysymdef.h 22 | and the output of `xmodmap`. The `xev` program may also be useful. 23 | ''' 24 | parser = argparse.ArgumentParser(description='Vim-like marks for windows.', 25 | epilog=epilog) 26 | parser.add_argument('-m', '--mark', default='Mod4-m', metavar='KEYBINDING', 27 | help='keybinding to add a mark (default: %(default)s)') 28 | parser.add_argument('-g', '--goto', default='Mod4-apostrophe', 29 | metavar='KEYBINDING', 30 | help='keybinding to goto a mark (default %(default)s)') 31 | args = parser.parse_args() 32 | 33 | keybinds = { args.mark: 'mark_window', args.goto: 'goto_window' } 34 | keybindmap = {} 35 | marked = {} 36 | grabbing = None 37 | 38 | def do_mark_window(letter): 39 | awin = ewmh.get_active_window().reply() 40 | if awin is not None: 41 | marked[letter] = awin 42 | 43 | def do_goto_window(letter): 44 | if letter not in marked: 45 | print >> sys.stderr, 'mark %s does not exist' % letter 46 | return 47 | 48 | wid = marked[letter] 49 | try: 50 | wdesk = ewmh.get_wm_desktop(wid).reply() 51 | desktop = ewmh.get_current_desktop().reply() 52 | visibles = ewmh.get_visible_desktops().reply() or [desktop] 53 | 54 | if wdesk is not None and wdesk not in visibles: 55 | ewmh.request_current_desktop_checked(wdesk).check() 56 | ewmh.request_active_window_checked(wid, source=1).check() 57 | except xproto.BadWindow: 58 | print >> sys.stderr, '%d no longer exists' % wid 59 | 60 | def mark_window(): 61 | start_get_letter(do_mark_window) 62 | 63 | def goto_window(): 64 | start_get_letter(do_goto_window) 65 | 66 | def start_get_letter(cb): 67 | global grabbing 68 | 69 | GS = xproto.GrabStatus 70 | if keybind.grab_keyboard(xpybutil.root).status == GS.Success: 71 | grabbing = cb 72 | 73 | def cb_get_letter(e): 74 | global grabbing 75 | 76 | if grabbing is not None: 77 | keybind.ungrab_keyboard() 78 | sym = keybind.get_keysym(e.detail) 79 | letter = keybind.get_keysym_string(sym) 80 | 81 | if len(letter) == 1 and ord(letter) in range(ord('a'), ord('z') + 1): 82 | grabbing(letter.lower()) 83 | 84 | grabbing = None 85 | 86 | # This has to come first so it is called first in the event loop 87 | event.connect('KeyPress', xpybutil.root, cb_get_letter) 88 | 89 | for key_str, fun_str in keybinds.iteritems(): 90 | if fun_str not in globals(): 91 | print >> sys.stderr, 'No such function %s for %s' % (fun_str, key_str) 92 | continue 93 | 94 | fun = globals()[fun_str] 95 | if not keybind.bind_global_key('KeyPress', key_str, fun): 96 | print >> sys.stderr, 'Could not bind %s to %s' % (key_str, fun_str) 97 | 98 | event.main() 99 | 100 | -------------------------------------------------------------------------------- /xpybutil/rect.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module has a few utility functions that perform math on rectangles. 3 | 4 | For example, finding the area of intersection of two rectangles with 5 | ``rect_intersect_area``, or getting the rectangle of a monitor after accounting 6 | for struts with ``monitor_rects``. 7 | """ 8 | from xpybutil.compat import xproto 9 | 10 | import xpybutil.ewmh as ewmh 11 | import xpybutil.window as window 12 | 13 | def rect_intersect_area(r1, r2): 14 | """ 15 | Returns the area of the intersection of two rectangles. If the rectangles 16 | do not intersect, the area returned is 0. 17 | 18 | :param r1: A 4-tuple representing a rectangle: 19 | 20 | (top_left_x, top_left_y, width, height) 21 | :param r2: A 4-tuple representing a rectangle: 22 | 23 | (top_left_x, top_left_y, width, height) 24 | :return: Area of intersection of r1 and r2. 25 | :rtype: Integer 26 | """ 27 | x1, y1, w1, h1 = r1 28 | x2, y2, w2, h2 = r2 29 | if x2 < x1 + w1 and x2 + w2 > x1 and y2 < y1 + h1 and y2 + h2 > y1: 30 | iw = min(x1 + w1 - 1, x2 + w2 - 1) - max(x1, x2) + 1 31 | ih = min(y1 + h1 - 1, y2 + h2 - 1) - max(y1, y2) + 1 32 | return iw * ih 33 | 34 | return 0 35 | 36 | def get_monitor_area(search, monitors): 37 | """ 38 | Returns the monitor with the most overlap with the 'search' rectangle. 39 | 40 | :param search: A 4-tuple representing a rectangle: 41 | (top_left_x, top_left_y, width, height) 42 | :param monitors: A list of 4-tuples representing each monitor's rectangle. 43 | :return: The monitor rectangle with the most overlap with 'search'. 44 | :rtype: (top_left_x, top_left_y, width, height) 45 | """ 46 | marea = 0 47 | mon = None 48 | for mx, my, mw, mh in monitors: 49 | a = rect_intersect_area((mx, my, mw, mh), search) 50 | if a > marea: 51 | marea = a 52 | mon = (mx, my, mw, mh) 53 | 54 | return mon 55 | 56 | def monitor_rects(monitors): 57 | """ 58 | Takes a list of monitors returned by ``xinerama.get_monitors`` and returns 59 | a new list of rectangles, in the same order, of monitor areas that account 60 | for all struts set by all windows. Duplicate struts are ignored. 61 | 62 | :param monitors: A list of 4-tuples representing monitor rectangles. 63 | :return: A list of 4-tuples representing monitor rectangles after 64 | subtracting strut areas. 65 | :rtype: [(top_left_x, top_left_y, width, height)] 66 | """ 67 | mons = monitors # alias 68 | wa = mons[:] 69 | 70 | clients = ewmh.get_client_list().reply() 71 | 72 | log = [] # Identical struts should be ignored 73 | 74 | for c in clients: 75 | try: 76 | cx, cy, cw, ch = window.get_geometry(c) 77 | except xproto.BadWindow: 78 | continue 79 | 80 | for i, (x, y, w, h) in enumerate(wa): 81 | if rect_intersect_area((x, y, w, h), (cx, cy, cw, ch)) > 0: 82 | struts = ewmh.get_wm_strut_partial(c).reply() 83 | if not struts: 84 | struts = ewmh.get_wm_strut(c).reply() 85 | 86 | key = (cx, cy, cw, ch, struts) 87 | if key in log: 88 | continue 89 | log.append(key) 90 | 91 | if struts and not all([v == 0 for v in struts.itervalues()]): 92 | if struts['left'] or struts['right']: 93 | if struts['left']: 94 | x += cw 95 | w -= cw 96 | if struts['top'] or struts['bottom']: 97 | if struts['top']: 98 | y += ch 99 | h -= ch 100 | elif struts: 101 | # x/y shouldn't be zero 102 | if cx > 0 and w == cx + cw: 103 | w -= cw 104 | elif cy > 0 and h == cy + ch: 105 | h -= ch 106 | elif cx > 0 and x == cx: 107 | x += cw 108 | w -= cw 109 | elif cy > 0 and y == cy: 110 | y += ch 111 | h -= ch 112 | 113 | wa[i] = (x, y, w, h) 114 | 115 | return wa 116 | 117 | -------------------------------------------------------------------------------- /xpybutil/event.py: -------------------------------------------------------------------------------- 1 | """ 2 | A module that can send client events to windows. It also allows 3 | registering callback functions to particular events. It can also 4 | run the main event loop. 5 | """ 6 | from collections import defaultdict, deque 7 | import struct 8 | import sys 9 | import traceback 10 | 11 | from xpybutil.compat import xcb_Exception, xproto 12 | 13 | from xpybutil import conn, root, util 14 | 15 | __queue = deque() 16 | __callbacks = defaultdict(list) 17 | EM = xproto.EventMask 18 | 19 | stringtype = str if sys.version_info[0] >= 3 else basestring 20 | 21 | class Event(object): 22 | KeyPressEvent = 2 23 | KeyReleaseEvent = 3 24 | ButtonPressEvent = 4 25 | ButtonReleaseEvent = 5 26 | MotionNotifyEvent = 6 27 | EnterNotifyEvent = 7 28 | LeaveNotifyEvent = 8 29 | FocusInEvent = 9 30 | FocusOutEvent = 10 31 | KeymapNotifyEvent = 11 32 | ExposeEvent = 12 33 | GraphicsExposureEvent = 13 34 | NoExposureEvent = 14 35 | VisibilityNotifyEvent = 15 36 | CreateNotifyEvent = 16 37 | DestroyNotifyEvent = 17 38 | UnmapNotifyEvent = 18 39 | MapNotifyEvent = 19 40 | MapRequestEvent = 20 41 | ReparentNotifyEvent = 21 42 | ConfigureNotifyEvent = 22 43 | ConfigureRequestEvent = 23 44 | GravityNotifyEvent = 24 45 | ResizeRequestEvent = 25 46 | CirculateNotifyEvent = 26 47 | CirculateRequestEvent = 27 48 | PropertyNotifyEvent = 28 49 | SelectionClearEvent = 29 50 | SelectionRequestEvent = 30 51 | SelectionNotifyEvent = 31 52 | ColormapNotifyEvent = 32 53 | ClientMessageEvent = 33 54 | MappingNotifyEvent = 34 55 | 56 | def replay_pointer(): 57 | conn.core.AllowEventsChecked(xproto.Allow.ReplayPointer, 58 | xproto.Time.CurrentTime).check() 59 | 60 | def send_event(destination, event_mask, event, propagate=False): 61 | return conn.core.SendEvent(propagate, destination, event_mask, event) 62 | 63 | def send_event_checked(destination, event_mask, event, propagate=False): 64 | return conn.core.SendEventChecked(propagate, destination, event_mask, event) 65 | 66 | def pack_client_message(window, message_type, *data): 67 | assert len(data) <= 5 68 | 69 | if isinstance(message_type, stringtype): 70 | message_type = util.get_atom(message_type) 71 | 72 | data = list(data) 73 | data += [0] * (5 - len(data)) 74 | 75 | # Taken from 76 | # http://xcb.freedesktop.org/manual/structxcb__client__message__event__t.html 77 | return struct.pack('BBH7I', Event.ClientMessageEvent, 32, 0, window, 78 | message_type, *data) 79 | 80 | def root_send_client_event(window, message_type, *data): 81 | mask = EM.SubstructureNotify | EM.SubstructureRedirect 82 | packed = pack_client_message(window, message_type, *data) 83 | return send_event(root, mask, packed) 84 | 85 | def root_send_client_event_checked(window, message_type, *data): 86 | mask = EM.SubstructureNotify | EM.SubstructureRedirect 87 | packed = pack_client_message(window, message_type, *data) 88 | return send_event_checked(root, mask, packed) 89 | 90 | def is_connected(event_name, window, callback): 91 | member = '%sEvent' % event_name 92 | assert hasattr(xproto, member) 93 | 94 | key = (getattr(xproto, member), window) 95 | return key in __callbacks and callback in __callbacks[key] 96 | 97 | def connect(event_name, window, callback): 98 | member = '%sEvent' % event_name 99 | assert hasattr(xproto, member) 100 | 101 | key = (getattr(xproto, member), window) 102 | __callbacks[key].append(callback) 103 | 104 | def disconnect(event_name, window): 105 | member = '%sEvent' % event_name 106 | assert hasattr(xproto, member) 107 | 108 | key = (getattr(xproto, member), window) 109 | if key in __callbacks: 110 | del __callbacks[key] 111 | 112 | def read(block=False): 113 | if block: 114 | e = conn.wait_for_event() 115 | __queue.appendleft(e) 116 | 117 | while True: 118 | e = conn.poll_for_event() 119 | 120 | if not e: 121 | break 122 | 123 | __queue.appendleft(e) 124 | 125 | def main(): 126 | try: 127 | while True: 128 | read(block=True) 129 | for e in queue(): 130 | w = None 131 | if isinstance(e, xproto.MappingNotifyEvent): 132 | w = None 133 | elif isinstance(e, xproto.MapRequestEvent): 134 | # Force all MapRequestEvents to go to the root window so 135 | # a window manager using xpybutil can get them. 136 | w = root 137 | elif hasattr(e, 'window'): 138 | w = e.window 139 | elif hasattr(e, 'event'): 140 | w = e.event 141 | elif hasattr(e, 'owner'): 142 | w = e.owner 143 | elif hasattr(e, 'requestor'): 144 | w = e.requestor 145 | 146 | key = (e.__class__, w) 147 | for cb in __callbacks.get(key, []): 148 | cb(e) 149 | except xcb_Exception: 150 | traceback.print_exc() 151 | sys.exit(1) 152 | 153 | def queue(): 154 | while len(__queue): 155 | yield __queue.pop() 156 | 157 | def peek(): 158 | return list(__queue) 159 | 160 | -------------------------------------------------------------------------------- /xpybutil/window.py: -------------------------------------------------------------------------------- 1 | """ 2 | A few utility functions related to client windows. In 3 | particular, getting an accurate geometry of a client window 4 | including the decorations (this can vary with the window 5 | manager). Also, a functon to move and/or resize a window 6 | accurately by the top-left corner. (Also can change based on 7 | the currently running window manager.) 8 | 9 | This module also contains a function 'listen' that must be used 10 | in order to receive certain events from a window. For example, 11 | if you wanted to run 'func' whenever a property on the root 12 | window changed, you could do something like: 13 | 14 | :: 15 | 16 | import xpybutil 17 | import xpybutil.event as event 18 | import xpybutil.ewmh as ewmh 19 | import xpybutil.util as util 20 | import xpybutil.window as window 21 | 22 | def func(e): 23 | if util.get_atom_name(e.atom) == '_NET_ACTIVE_WINDOW': 24 | # Do something whenever the active window changes 25 | active_window_id = ewmh.get_active_window().reply() 26 | 27 | window.listen(xpybutil.root, 'PropertyChange') 28 | event.connect('PropertyNotify', xpybutil.root, func) 29 | 30 | The idea here is to tell X that you want events that fall under 31 | the 'PropertyChange' category. Then you bind 'func' to the 32 | particular event 'PropertyNotify'. 33 | """ 34 | from xpybutil.compat import xproto 35 | 36 | from xpybutil import conn 37 | import xpybutil.ewmh as ewmh 38 | 39 | class WindowManagers(object): 40 | """ 41 | A list of window managers that xpybutil is aware of. 42 | 43 | These mostly modify how we determine the size of a client. In particular, 44 | KWin's decorations are in the parent of the parent of the client, whereas 45 | in Openbox, they are simply in the parent of the client. 46 | 47 | I am not sure whether I have plans to expand this list. 48 | """ 49 | Unknown = 0 50 | Openbox = 1 51 | KWin = 2 52 | 53 | def listen(window, *event_mask_names): 54 | """ 55 | Makes X report events for the masks provided. 56 | 57 | This function must be called in order to get X send you events about a 58 | particular window. (i.e., it is not simply enough to call 'event.connect'.) 59 | 60 | This page is required reading if you are to do any event processing: 61 | http://tronche.com/gui/x/xlib/events/processing-overview.html 62 | 63 | :param window: Window identifier. 64 | :type window: int 65 | :param event_mask_names: List of mask names. 66 | :type event_mask_names: List of xcb.xproto.EventMask class variable names 67 | :rtype: void 68 | """ 69 | masks = 0 70 | for mask_name in event_mask_names: 71 | assert hasattr(xproto.EventMask, mask_name) 72 | masks |= getattr(xproto.EventMask, mask_name) 73 | 74 | conn.core.ChangeWindowAttributesChecked(window, xproto.CW.EventMask, 75 | [masks]).check() 76 | 77 | def get_parent_window(window): 78 | """ 79 | Uses QueryTree() to find the parent of the given window. 80 | 81 | :param window: Window identifier. 82 | :return: Parent window identifier of 'window'. 83 | :rtype: int 84 | """ 85 | return conn.core.QueryTree(window).reply().parent 86 | 87 | def get_geometry(window, window_manager=None): 88 | """ 89 | Returns the geometry of a window. This function will do its best to get 90 | the real geometry of a window; typically by inspecting the window's 91 | decorations if there are any. 92 | 93 | Since decorations are different for each window manager, you'll get the 94 | best possible results by passing a supported window manager in. 95 | 96 | :param window: Window identifier. 97 | :param window_manager: A class variable from Window.WindowManagers 98 | :type window_manager: int 99 | :return: Real geometry of a client window starting from the top-left corner. 100 | :rtype: (top_left_x, top_left_y, width, height) 101 | """ 102 | 103 | if window_manager is WindowManagers.KWin: 104 | p = get_parent_window(window) 105 | return __get_geometry(get_parent_window(p)) 106 | else: 107 | return __get_geometry(get_parent_window(window)) 108 | 109 | def moveresize(win, x=None, y=None, w=None, h=None, window_manager=None): 110 | """ 111 | This function attempts to properly move/resize a window, accounting for 112 | its decorations. 113 | 114 | It doesn't rely upon _NET_FRAME_EXTENTS, but instead, uses the actual 115 | parent window to adjust the width and height. (I've found _NET_FRAME_EXTENTS 116 | to be wildly unreliable.) 117 | 118 | :param win: Window identifier. 119 | :param x: Top left x coordinate. 120 | :param y: Top left y coordinate. 121 | :param w: Client width. 122 | :param h: Client height. 123 | :param window_manager: A class variable from Window.WindowManagers 124 | :type window_manager: int 125 | :rtype: void 126 | """ 127 | if window_manager is WindowManagers.KWin: 128 | tomove = get_parent_window(get_parent_window(win)) 129 | else: 130 | tomove = get_parent_window(win) 131 | 132 | if tomove: 133 | cx, cy, cw, ch = __get_geometry(win) 134 | px, py, pw, ph = __get_geometry(tomove) 135 | 136 | w -= pw - cw 137 | h -= ph - ch 138 | 139 | ewmh.request_moveresize_window(win, x=x, y=y, width=max(1, w), 140 | height=max(1, h), source=2) 141 | 142 | def __get_geometry(win): 143 | """ 144 | Private function that abstracts the low level GetGeometry call. 145 | 146 | If you're looking for the size of a window including its decorations, 147 | please use ``window.get_geometry``. 148 | 149 | :param win: Window identifier. 150 | :return: X rectangle of the window. 151 | :rtype: (x, y, width, height) 152 | """ 153 | raw = conn.core.GetGeometry(win).reply() 154 | return raw.x, raw.y, raw.width, raw.height 155 | 156 | -------------------------------------------------------------------------------- /xpybutil/mousebind.py: -------------------------------------------------------------------------------- 1 | """ 2 | A set of functions to faciliate binding to mouse button presses. 3 | 4 | This is basically the companion module of 'keybind.py'. The good news is that 5 | we don't have to deal with the complexity of X's keysym table. The bad news is 6 | that mice binding and dragging comes with its own hairball of complexity. 7 | 8 | TODO: Mouse bindings that hook into the event dipatcher like the keybind 9 | module. Also, functions that abstract the process of using the mouse 10 | to drag something. 11 | """ 12 | from collections import defaultdict 13 | 14 | from xpybutil.compat import xproto 15 | 16 | from xpybutil import conn, root 17 | 18 | __mousebinds = defaultdict(list) 19 | __mousegrabs = defaultdict(int) # Mouse grab key -> number of grabs 20 | 21 | EM = xproto.EventMask 22 | GM = xproto.GrabMode 23 | TRIVIAL_MODS = [ 24 | 0, 25 | xproto.ModMask.Lock, 26 | xproto.ModMask._2, 27 | xproto.ModMask.Lock | xproto.ModMask._2 28 | ] 29 | 30 | def parse_buttonstring(button_string): 31 | """ 32 | A utility function to turn strings like 'Mod1-Shift-3' into a pair 33 | corresponding to its modifiers and button number. 34 | 35 | :param button_string: String starting with zero or more modifiers followed 36 | by exactly one button number. 37 | 38 | Available modifiers: Control, Mod1, Mod2, Mod3, Mod4, 39 | Mod5, Shift, Lock 40 | :type button_string: str 41 | :return: Tuple of modifier mask and button number 42 | :rtype: (mask, int) 43 | """ 44 | mods, button = 0, None 45 | for part in button_string.split('-'): 46 | if hasattr(xproto.KeyButMask, part): 47 | mods |= getattr(xproto.KeyButMask, part) 48 | else: 49 | button = int(part) 50 | 51 | return mods, button 52 | 53 | def grab_pointer(grab_win, confine, cursor): 54 | """ 55 | This will grab the pointer. The effect is that further pointer events will 56 | *only* be sent to the grabbing client (i.e., ``grab_win``). 57 | 58 | :param grab_win: A window identifier to report pointer events to. 59 | :type grab_win: int 60 | :param confine: Either a window identifier to confine the pointer to, or 61 | None if no confinement is desired. 62 | :type confine: int 63 | :param cursor: A cursor identifier. (See the ``cursor`` module.) 64 | :rtype: xcb.xproto.GrabStatus 65 | """ 66 | mask = EM.PointerMotion | EM.ButtonRelease | EM.ButtonPress 67 | return conn.core.GrabPointer(False, grab_win, mask, GM.Async, GM.Async, 68 | confine, cursor, 69 | xproto.Time.CurrentTime).reply() 70 | 71 | def ungrab_pointer(): 72 | """ 73 | This will release a grab initiated by ``grab_pointer``. 74 | 75 | :rtype: void 76 | """ 77 | conn.core.UngrabPointerChecked(xproto.Time.CurrentTime).check() 78 | 79 | def grab_button(wid, modifiers, button, propagate=False): 80 | """ 81 | Grabs a mouse button for a particular window and a modifiers/key value. 82 | If the grab was successful, return True. Otherwise, return False. 83 | If your client is grabbing buttons, it is useful to notify the user if a 84 | button wasn't grabbed. Mouse shortcuts not responding is disorienting! 85 | 86 | Also, this function will grab several buttons based on varying modifiers. 87 | Namely, this accounts for all of the "trivial" modifiers that may have 88 | an effect on X events, but probably shouldn't effect button grabbing. (i.e., 89 | whether num lock or caps lock is on.) 90 | 91 | If 'propagate' is True, then no further events can be processed until the 92 | grabbing client allows them to be. (Which is done via AllowEvents. Thus, 93 | if propagate is True, you *must* make some call to AllowEvents at some 94 | point, or else your client will lock.) 95 | 96 | N.B. You should probably be using 'bind_mouse' or 'bind_global_mouse' 97 | instead. 98 | 99 | :param wid: A window identifier. 100 | :type wid: int 101 | :param modifiers: A modifier mask. 102 | :type modifiers: int 103 | :param button: A button number. 104 | :type button: int 105 | :param propagate: Whether to grab synchronously (True) or 106 | asynchronously (False). When in doubt, keep it set 107 | to False. 108 | :type propagate: bool 109 | :rtype: bool 110 | """ 111 | mask = EM.ButtonPress | EM.ButtonRelease | EM.ButtonMotion 112 | 113 | try: 114 | for mod in TRIVIAL_MODS: 115 | conn.core.GrabButtonChecked(True, wid, mask, 116 | GM.Sync if propagate else GM.Async, 117 | GM.Async, 0, 0, 118 | button, modifiers | mod).check() 119 | 120 | return True 121 | except xproto.BadAccess: 122 | return False 123 | 124 | def ungrab_button(wid, modifiers, button): 125 | """ 126 | Ungrabs a button that was grabbed by ``grab_button``. Similarly, it 127 | will return True on success and False on failure. 128 | 129 | When ungrabbing a button, the parameters to this function should be 130 | *precisely* the same as the parameters to ``grab_button``. 131 | 132 | :param wid: A window identifier. 133 | :type wid: int 134 | :param modifiers: A modifier mask. 135 | :type modifiers: int 136 | :param button: A button number. 137 | :type button: int 138 | :rtype: bool 139 | """ 140 | try: 141 | for mod in TRIVIAL_MODS: 142 | conn.core.UngrabButtonChecked(button, wid, modifiers | mod).check() 143 | 144 | return True 145 | except xproto.BadAccess: 146 | return False 147 | 148 | def bind_global_mouse(event_type, key_string, cb): 149 | """ 150 | An alias for ``bind_mouse(event_type, ROOT_WINDOW, key_string, cb)``. 151 | 152 | :param event_type: Either 'ButtonPress' or 'ButtonRelease'. 153 | :type event_type: str 154 | :param key_string: A string of the form 'Mod1-Control-3'. 155 | Namely, a list of zero or more modifiers separated by 156 | '-', followed by a single button digit. 157 | :type key_string: str 158 | :param cb: A first class function with no parameters. 159 | :type cb: function 160 | :return: True if the binding was successful, False otherwise. 161 | :rtype: bool 162 | """ 163 | return bind_mouse(event_type, root, key_string, cb) 164 | 165 | def bind_mouse(event_type, wid, button_string, cb): 166 | raise NotImplemented 167 | 168 | -------------------------------------------------------------------------------- /xpybutil/render.py: -------------------------------------------------------------------------------- 1 | """ 2 | I once used the help of this module to implement a basic compositing manager 3 | using xpyb. It only did transparency, and not well. (Performance was actually 4 | quite nice.) It's called pycompmgr. 5 | 6 | This is meant to be a very close translation to the corresponding xcb-util 7 | module. Mostly because I lack a deep understanding of everything here. 8 | """ 9 | from xpybutil.compat import render 10 | 11 | class PictFormat: 12 | Id = 1 13 | Type = 2 14 | Depth = 4 15 | Red = 8 16 | RedMask = 16 17 | Green = 32 18 | GreenMask = 64 19 | Blue = 128 20 | BlueMask = 256 21 | Alpha = 512 22 | AlphaMask = 1024 23 | Colormap = 2048 24 | 25 | class PictStandard: 26 | Argb32 = 0 27 | Rgb24 = 1 28 | A8 = 2 29 | A4 = 3 30 | A1 = 4 31 | 32 | standardFormats = [ 33 | # ARGB_32 34 | { 35 | 'template': { 36 | 'id': 0, 37 | 'type': render.PictType.Direct, 38 | 'depth': 32, 39 | 'direct': { 40 | 'red': 16, 41 | 'red_mask': 0xff, 42 | 'green': 8, 43 | 'green_mask': 0xff, 44 | 'blue': 0, 45 | 'blue_mask': 0xff, 46 | 'alpha': 24, 47 | 'alpha_mask': 0xff 48 | }, 49 | 'colormap': 0 50 | }, 51 | 52 | 'mask': (PictFormat.Type | PictFormat.Depth | PictFormat.Red | 53 | PictFormat.RedMask | PictFormat.Green | PictFormat.GreenMask | 54 | PictFormat.Blue | PictFormat.BlueMask | PictFormat.Alpha | 55 | PictFormat.AlphaMask) 56 | }, 57 | 58 | # RGB_24 59 | { 60 | 'template': { 61 | 'id': 0, 62 | 'type': render.PictType.Direct, 63 | 'depth': 24, 64 | 'direct': { 65 | 'red': 16, 66 | 'red_mask': 0xff, 67 | 'green': 8, 68 | 'green_mask': 0xff, 69 | 'blue': 0, 70 | 'blue_mask': 0xff, 71 | 'alpha': 0, 72 | 'alpha_mask': 0x00 73 | }, 74 | 'colormap': 0 75 | }, 76 | 77 | 'mask': (PictFormat.Type | PictFormat.Depth | PictFormat.Red | 78 | PictFormat.RedMask | PictFormat.Green | PictFormat.GreenMask | 79 | PictFormat.Blue | PictFormat.BlueMask | PictFormat.AlphaMask) 80 | }, 81 | 82 | # A_8 83 | { 84 | 'template': { 85 | 'id': 0, 86 | 'type': render.PictType.Direct, 87 | 'depth': 8, 88 | 'direct': { 89 | 'red': 0, 90 | 'red_mask': 0x00, 91 | 'green': 0, 92 | 'green_mask': 0x00, 93 | 'blue': 0, 94 | 'blue_mask': 0x00, 95 | 'alpha': 0, 96 | 'alpha_mask': 0xff 97 | }, 98 | 'colormap': 0 99 | }, 100 | 101 | 'mask': (PictFormat.Type | PictFormat.Depth | PictFormat.RedMask | 102 | PictFormat.GreenMask | PictFormat.BlueMask | 103 | PictFormat.Alpha | PictFormat.AlphaMask) 104 | }, 105 | 106 | # A_4 107 | { 108 | 'template': { 109 | 'id': 0, 110 | 'type': render.PictType.Direct, 111 | 'depth': 4, 112 | 'direct': { 113 | 'red': 0, 114 | 'red_mask': 0x00, 115 | 'green': 0, 116 | 'green_mask': 0x00, 117 | 'blue': 0, 118 | 'blue_mask': 0x00, 119 | 'alpha': 0, 120 | 'alpha_mask': 0x0f 121 | }, 122 | 'colormap': 0 123 | }, 124 | 125 | 'mask': (PictFormat.Type | PictFormat.Depth | PictFormat.RedMask | 126 | PictFormat.GreenMask | PictFormat.BlueMask | 127 | PictFormat.Alpha | PictFormat.AlphaMask) 128 | }, 129 | 130 | # A_1 131 | { 132 | 'template': { 133 | 'id': 0, 134 | 'type': render.PictType.Direct, 135 | 'depth': 1, 136 | 'direct': { 137 | 'red': 0, 138 | 'red_mask': 0x00, 139 | 'green': 0, 140 | 'green_mask': 0x00, 141 | 'blue': 0, 142 | 'blue_mask': 0x00, 143 | 'alpha': 0, 144 | 'alpha_mask': 0x01 145 | }, 146 | 'colormap': 0 147 | }, 148 | 149 | 'mask': (PictFormat.Type | PictFormat.Depth | PictFormat.RedMask | 150 | PictFormat.GreenMask | PictFormat.BlueMask | 151 | PictFormat.Alpha | PictFormat.AlphaMask) 152 | } 153 | ] 154 | 155 | def find_visual_format(formats, visid): 156 | for screen in formats.screens: 157 | for depth in screen.depths: 158 | for visual in depth.visuals: 159 | if visual.visual == visid: 160 | return visual 161 | 162 | def find_format(formats, mask, template, count): 163 | if not formats: 164 | return None 165 | 166 | for format in formats.formats: 167 | if mask & PictFormat.Id: 168 | if template['id'] != format.id: 169 | continue 170 | if mask & PictFormat.Type: 171 | if template['type'] != format.type: 172 | continue 173 | if mask & PictFormat.Depth: 174 | if template['depth'] != format.depth: 175 | continue 176 | if mask & PictFormat.Red: 177 | if template['direct']['red'] != format.direct.red_shift: 178 | continue 179 | if mask & PictFormat.RedMask: 180 | if template['direct']['red_mask'] != format.direct.red_mask: 181 | continue 182 | if mask & PictFormat.Green: 183 | if template['direct']['green'] != format.direct.green_shift: 184 | continue 185 | if mask & PictFormat.GreenMask: 186 | if template['direct']['green_mask'] != format.direct.green_mask: 187 | continue 188 | if mask & PictFormat.Blue: 189 | if template['direct']['blue'] != format.direct.blue_shift: 190 | continue 191 | if mask & PictFormat.BlueMask: 192 | if template['direct']['blue_mask'] != format.direct.blue_mask: 193 | continue 194 | if mask & PictFormat.Alpha: 195 | if template['direct']['alpha'] != format.direct.alpha_shift: 196 | continue 197 | if mask & PictFormat.AlphaMask: 198 | if template['direct']['alpha_mask'] != format.direct.alpha_mask: 199 | continue 200 | if mask & PictFormat.Colormap: 201 | if template['colormap'] != format.colormap: 202 | continue 203 | 204 | if not count: 205 | return format 206 | count -= 1 207 | 208 | return None 209 | 210 | def find_standard_format(formats, format): 211 | if format < 0 or format >= len(standardFormats): 212 | return None 213 | 214 | return find_format( 215 | formats, 216 | standardFormats[format]['mask'], 217 | standardFormats[format]['template'], 218 | 0 219 | ) 220 | 221 | -------------------------------------------------------------------------------- /xpybutil/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | A vast assortment of utility functions. The ones of interest to 3 | you are probably 'get_atom' and 'get_atom_name'. The rest are 4 | heavily used throughout the rest of xpybutil. 5 | """ 6 | import struct 7 | import sys 8 | 9 | from xpybutil.compat import xproto 10 | 11 | from xpybutil import conn 12 | 13 | __atom_cache = {} 14 | __atom_nm_cache = {} 15 | 16 | class Cookie(object): 17 | """ 18 | The base Cookie class. The role of a cookie is to serve as an intermediary 19 | between you and the X server. After calling one of the many functions 20 | in the ewmh or icccm modules, the X server is typically not contacted 21 | immediately. Usually, you'll need to call the 'check()' or 'reply()' 22 | methods on the cookie returned by one of the functions in the ewmh or 23 | icccm modules. (Alternatively, you could flush the X buffer using 24 | ``conn_obj.flush()``.) 25 | """ 26 | def __init__(self, cookie): 27 | self.cookie = cookie 28 | 29 | def check(self): 30 | return self.cookie.check() 31 | 32 | class PropertyCookie(Cookie): 33 | """ 34 | A regular property cookie that uses 'get_property_value' to return a nicer 35 | version to you. (Instead of raw X data.) 36 | """ 37 | def reply(self): 38 | return get_property_value(self.cookie.reply()) 39 | 40 | class PropertyCookieSingle(Cookie): 41 | """ 42 | A cookie that should only be used when one is guaranteed a single logical 43 | result. Namely, 'get_property_value' will be stupid and return a single 44 | list. This class checks for that, and takes the head of that list. 45 | """ 46 | def reply(self): 47 | ret = get_property_value(self.cookie.reply()) 48 | 49 | if ret and isinstance(ret, list) and len(ret) == 1: 50 | return ret[0] 51 | return ret 52 | 53 | class AtomCookie(Cookie): 54 | """ 55 | Pulls the ATOM identifier out of the reply object. 56 | """ 57 | def reply(self): 58 | return self.cookie.reply().atom 59 | 60 | class AtomNameCookie(Cookie): 61 | """ 62 | Converts the null terminated list of characters (that represents an 63 | ATOM name) to a string. 64 | """ 65 | def reply(self): 66 | return bytes(self.cookie.reply().name.buf()).decode('utf-8') 67 | 68 | def get_property_value(property_reply): 69 | """ 70 | A function that takes a property reply object, and turns its value into 71 | something nice for us. 72 | 73 | In particular, if the format of the reply is '8', then assume that it 74 | is a string. Moreover, it could be a list of strings that are null 75 | terminated. In this case, return a list of Python strings. Otherwise, just 76 | convert it to a string and remove the null terminator if it exists. 77 | 78 | Otherwise, the format must be a list of integers that has to be unpacked. 79 | Sometimes, these integers are ATOM identifiers, so it is useful to map 80 | 'get_atom_name' over this list if that's the case. 81 | 82 | :param property_reply: An object returned by a cookie's "reply" method. 83 | :type property_reply: xcb.xproto.GetPropertyReply 84 | :return: Either a string, a list of strings or a list of integers depending 85 | upon the format of the property reply. 86 | """ 87 | if property_reply.format == 8: 88 | ret = bytes(property_reply.value.buf()).split(b'\0') 89 | if ret[-1] == '': ret.pop() 90 | ret = [ x.decode('utf-8') for x in ret ] 91 | return ret[0] if len(ret) == 1 else ret 92 | elif property_reply.format in (16, 32): 93 | return list(struct.unpack('I' * property_reply.value_len, 94 | property_reply.value.buf())) 95 | 96 | return None 97 | 98 | def get_property(window, atom): 99 | """ 100 | Abstracts the process of issuing a GetProperty request. 101 | 102 | You'll typically want to call the ``reply`` method on the return value of 103 | this function, and pass that result to 104 | 'get_property_value' so that the data is nicely formatted. 105 | 106 | :param window: A window identifier. 107 | :type window: int 108 | :param atom: An atom identifier. 109 | :type atom: int OR str 110 | :rtype: xcb.xproto.GetPropertyCookie 111 | """ 112 | stringtype = str if sys.version_info[0] >= 3 else basestring 113 | if isinstance(atom, stringtype): 114 | atom = get_atom(atom) 115 | return conn.core.GetProperty(False, window, atom, 116 | xproto.GetPropertyType.Any, 0, 117 | 2 ** 32 - 1) 118 | 119 | def get_property_unchecked(window, atom): 120 | """ 121 | Abstracts the process of issuing a GetPropertyUnchecked request. 122 | 123 | You'll typically want to call the ``reply`` method on the return value of 124 | this function, and pass that result to 125 | 'get_property_value' so that the data is nicely formatted. 126 | 127 | :param window: A window identifier. 128 | :type window: int 129 | :param atom: An atom identifier. 130 | :type atom: int OR str 131 | :rtype: xcb.xproto.GetPropertyCookie 132 | """ 133 | stringtype = str if sys.version_info[0] >= 3 else basestring 134 | if isinstance(atom, stringtype): 135 | atom = get_atom(atom) 136 | return conn.core.GetPropertyUnchecked(False, window, atom, 137 | xproto.GetPropertyType.Any, 0, 138 | 2 ** 32 - 1) 139 | 140 | def build_atom_cache(atoms): 141 | """ 142 | Quickly builds a cache of ATOM names to ATOM identifiers (and the reverse). 143 | You'll only need to use this function if you're using atoms not defined in 144 | the ewmh, icccm or motif modules. (Otherwise, those modules will build this 145 | cache for you.) 146 | 147 | The 'get_atom' and 'get_atom_name' function automatically use this cache. 148 | 149 | :param atoms: A list of atom names. 150 | :rtype: void 151 | """ 152 | global __atom_cache, __atom_nm_cache 153 | 154 | if conn is None: 155 | return 156 | 157 | for atom in atoms: 158 | __atom_cache[atom] = __get_atom_cookie(atom, only_if_exists=False) 159 | for atom in __atom_cache: 160 | if isinstance(__atom_cache[atom], AtomCookie): 161 | __atom_cache[atom] = __atom_cache[atom].reply() 162 | 163 | __atom_nm_cache = dict((v, k) for k, v in __atom_cache.items()) 164 | 165 | def get_atom(atom_name, only_if_exists=False): 166 | """ 167 | Queries the X server for an ATOM identifier using a name. If we've already 168 | cached the identifier, then we don't contact the X server. 169 | 170 | If the identifier is not cached, it is added to the cache. 171 | 172 | If 'only_if_exists' is false, then the atom is created if it does not exist 173 | already. 174 | 175 | :param atom_name: An atom name. 176 | :type atom_name: str 177 | :param only_if_exists: If false, the atom is created if it didn't exist. 178 | :type only_if_exists: bool 179 | :return: ATOM identifier. 180 | :rtype: int 181 | """ 182 | global __atom_cache 183 | 184 | a = __atom_cache.setdefault(atom_name, 185 | __get_atom_cookie(atom_name, 186 | only_if_exists).reply()) 187 | if isinstance(a, AtomCookie): 188 | a = a.reply() 189 | 190 | return a 191 | 192 | def get_atom_name(atom): 193 | """ 194 | Queries the X server for an ATOM name using the specified identifier. 195 | If we've already cached the name, then we don't contact the X server. 196 | 197 | If the atom name is not cached, it is added to the cache. 198 | 199 | :param atom: An atom identifier. 200 | :type atom: int 201 | :return: ATOM name. 202 | :rtype: str 203 | """ 204 | global __atom_nm_cache 205 | 206 | a = __atom_nm_cache.setdefault(atom, __get_atom_name_cookie(atom).reply()) 207 | 208 | if isinstance(a, AtomNameCookie): 209 | a = a.reply() 210 | 211 | return a 212 | 213 | def __get_atom_cookie(atom_name, only_if_exists=False): 214 | """ 215 | Private function that issues the xpyb call to intern an atom. 216 | 217 | :type atom_name: str 218 | :type only_if_exists: bool 219 | :rtype: xcb.xproto.InternAtomCookie 220 | """ 221 | atom_bytes = atom_name.encode('ascii') 222 | atom = conn.core.InternAtomUnchecked(only_if_exists, len(atom_bytes), 223 | atom_bytes) 224 | return AtomCookie(atom) 225 | 226 | def __get_atom_name_cookie(atom): 227 | """ 228 | Private function that issues the xpyb call to get an ATOM identifier's name. 229 | 230 | :type atom: int 231 | :rtype: xcb.xproto.GetAtomNameCookie 232 | """ 233 | return AtomNameCookie(conn.core.GetAtomNameUnchecked(atom)) 234 | 235 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | xpybutil is an abstraction over the X Python Binding (xpyb - the Python version 4 | of XCB). It exists because xpyb is a very low level library that communicates 5 | with X. 6 | 7 | The most mature portions of xpybutil are the ICCCM and EWMH modules. Each 8 | implement their respective specifications of the same name. The EWMH module 9 | also implements the '_NET_WM_WINDOW_OPACITY' and '_NET_VISIBLE_DESKTOPS' 10 | non-standard features. The former is widely used by compositing managers and 11 | other utilities (i.e., xcompmgr and transset-df) while the latter is used by my 12 | fork of Openbox called Openbox Multihead. 13 | 14 | Installation 15 | ============ 16 | xpybutil is compatible with xpyb and its various drop-in replacements (xcffib, xpyb-ng). Since 17 | there are several choices, it is up to you to choose and install one. 18 | [xcffib](https://github.com/tych0/xcffib) is the most actively maintained option. 19 | 20 | To install xpybutil from pip with xcffib: 21 | ```bash 22 | pip install xcffib xpybutil 23 | ``` 24 | 25 | Examples 26 | ======== 27 | There are a few examples included in the 'examples' directory of the source of 28 | xpybutil. You could also look at my 'window-marker' or 'pytyle3' utilities. The 29 | former is a small script but the latter is on the order of ~1000 lines and 30 | encompasses a wide variety of use cases of xpybutil. (And still yet, other use 31 | cases are probably more appropriate only for window manager development.) 32 | 33 | Another program that uses xpybutil is 'pager-multihead'. I would advise not 34 | looking to it for inspiration, as it mixes xpybutil and GTK. (Namely, it uses 35 | GTK's event dispatcher, but interacts with the window manager mostly using 36 | xpybutil.) 37 | 38 | 39 | Usable modules 40 | ============== 41 | cursor.py - Provides a listing of all built-in X cursors, and provides a 42 | function 'create_font_cursor' to create one. This object can 43 | then be directly used in a call to CreateWindow or 44 | ChangeWindowAttributes. 45 | 46 | ewmh.py - A module that implements the entire EWMH spec. 47 | 48 | event.py - A module that can send client events to windows. It also allows 49 | registering callback functions to particular events. It can also 50 | run the main event loop. 51 | 52 | icccm.py - A module that implements most of the ICCCM spec. 53 | 54 | image.py - An incomplete and haphazard collection of functions that can 55 | bridge a gap between PIL and drawing images with X. 56 | 57 | keybind.py - A set of functions devoted to binding key presses and registering 58 | callbacks. This will automatically hook into the event callbacks 59 | in event.py. 60 | 61 | keysymdef.py - The sole purpose of this module is to provide a mapping from 62 | X keysyms to english string representations (for easier binding 63 | by the user). This should probably never be used directly. It is 64 | used by the keysym module. 65 | 66 | motif.py - Implements a subset of the Motif spec. This module exists 67 | because some window managers still use this as the only way of 68 | toggling window decorations via events. 69 | 70 | mousebind.py - Similar to keybind.py, but for mice. 71 | 72 | rect.py - A few utility functions that do math associated with X style 73 | rectangles. (i.e., a 4-tuple of (top_left_x, top_left_y, width, 74 | height).) It can also calculate monitor rectangles after 75 | accounting for struts. 76 | 77 | render.py - A nearly exact port of the module by the same name from 78 | xcb-util. I used it once to help with a compositing manager that 79 | I wrote with xpyb. 80 | 81 | util.py - A vast assortment of utility functions. The ones of interest to 82 | you are probably 'get_atom' and 'get_atom_name'. The rest are 83 | heavily used throughout the rest of xpybutil. 84 | 85 | window.py - A few utility functions related to client windows. In 86 | particular, getting an accurate geometry of a client window 87 | including the decorations (this can vary with the window 88 | manager). Also, a functon to move and/or resize a window 89 | accurately by the top-left corner. (Also can change based on 90 | the currently running window manager.) 91 | 92 | This module also contains a function 'listen' that must be used 93 | in order to receive certain events from a window. For example, 94 | if you wanted to run 'func' whenever a property on the root 95 | window changed, you could do something like: 96 | 97 | import xpybutil 98 | import xpybutil.event as event 99 | import xpybutil.ewmh as ewmh 100 | import xpybutil.util as util 101 | import xpybutil.window as window 102 | 103 | def func(e): 104 | if util.get_atom_name(e.atom) == '_NET_ACTIVE_WINDOW': 105 | # Do something whenever the active window changes 106 | active_window_id = ewmh.get_active_window().reply() 107 | 108 | window.listen(xpybutil.root, 'PropertyChange') 109 | event.connect('PropertyNotify', xpybutil.root, func) 110 | 111 | The idea here is to tell X that you want events that fall under 112 | the 'PropertyChange' category. Then you bind 'func' to the 113 | particular event 'PropertyNotify'. 114 | 115 | xinerama.py - A couple of functions that support retrieving information about 116 | all active physical heads. This is done through the Xinerama 117 | extension, which implicitly supports RandR and TwinView. 118 | 119 | The 'get_physical_mapping' function will produce a list of 120 | monitor indices in a physical ordering (left to right, top to 121 | bottom). These indices can then be used in the list returned by 122 | 'get_monitors'. 123 | 124 | 125 | Unusable modules 126 | ================ 127 | font.py is pretty inadequate. Mostly because if you find 128 | yourself wanting to use font.py, you're probably doing something wrong. 129 | (i.e., use pycairo or PIL.) 130 | 131 | 132 | EWMH and ICCCM 133 | ============== 134 | xpybutil's primary purpose was to make accessing ICCCM and EWMH related 135 | information extremely easy. This can be done by using the ewmh or icccm 136 | modules. Here's a quick example (assuming xpybutil is installed): 137 | 138 | import xpybutil.ewmh as ewmh 139 | 140 | desktop_names = ewmh.get_desktop_names().reply() 141 | current_desktop = ewmh.get_current_desktop().reply() 142 | 143 | print names.get(current_desktop, current_desktop) 144 | 145 | This imports the ewmh module (and by extension, connects to the X server) and 146 | fetches a list of the current desktop names and the current desktop index 147 | (starting from 0). Since not every desktop must be named, the desktop index is 148 | printed if it has no name. 149 | 150 | Note that the functions in the ewmh and icccm module return *cookies*. In order 151 | to pull a response from the X server, call the 'reply()' method on a cookie 152 | object. To force a response sent to the X server, call the 'check()' method on 153 | the corresponding cookie. 154 | 155 | Much of the EWMH and ICCCM modules encapsulate packing data from convenient 156 | Python data types into C structs (using the 'struct' Python module). For 157 | example, if one wants to fetch the partial struts set by some window with 158 | identifier ID, you could do: 159 | 160 | import xpybutil.ewmh as ewmh 161 | 162 | print ewmh.get_wm_strut_partial(ID).reply() 163 | 164 | Which outputs a dictionary with 12 entries, where each corresponds to a value 165 | in the partial strut. (i.e., 'left', 'top_end_x', 'right_start_y', etc...). 166 | 167 | In general, particularly with the EWMH module, the ewmh module is very nearly 168 | representative of the spec itself. Functions that get property values start 169 | with 'get_', functions that set property values start with 'set_', and 170 | functions that send an event to a client (which typically requests the window 171 | manager to DO something) start with 'request_'. 172 | 173 | If a request has no reply (typically the 'set_' functions), then the 174 | default is to call it 'unchecked'. If you want to check the result (and force 175 | retrieval), then use the '_checked' variant. 176 | 177 | The reverse goes for the 'get_' functions. By default, they are checked, but 178 | you can use the '_unchecked' variant too. 179 | 180 | Basically, you should probably almost always use the 'checked' variant of 181 | everything. The cases where you don't are when you want to send a whole bunch 182 | of requests to the X server at once. You could use the unchecked invariant, and 183 | after you've initialized all the cookies, calling 'flush' will force 184 | communication with the X server. 185 | 186 | Finally, unless you're writing a window manager or creating a client window 187 | from scratch, you'll almost always want to use the 'get_' and 'request_' 188 | functions, not 'set_'. For example, if you want to change the current 189 | desktop... 190 | 191 | DON'T DO: ewmh.set_current_desktop_checked(2).check() 192 | 193 | DO: ewmh.request_current_desktop_checked(2).check() 194 | 195 | The former will probably not work, but the latter will. Just follow the EWMH 196 | spec :-) 197 | -------------------------------------------------------------------------------- /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 | from xpybutil.compat import xproto 14 | 15 | from xpybutil import conn, root, event 16 | from xpybutil.keysymdef import keysyms, keysym_strings 17 | 18 | __kbmap = None 19 | __keysmods = None 20 | 21 | __keybinds = defaultdict(list) 22 | __keygrabs = defaultdict(int) # Key grab key -> number of grabs 23 | 24 | EM = xproto.EventMask 25 | GM = xproto.GrabMode 26 | TRIVIAL_MODS = [ 27 | 0, 28 | xproto.ModMask.Lock, 29 | xproto.ModMask._2, 30 | xproto.ModMask.Lock | xproto.ModMask._2 31 | ] 32 | 33 | def bind_global_key(event_type, key_string, cb): 34 | """ 35 | An alias for ``bind_key(event_type, ROOT_WINDOW, key_string, cb)``. 36 | 37 | :param event_type: Either 'KeyPress' or 'KeyRelease'. 38 | :type event_type: str 39 | :param key_string: A string of the form 'Mod1-Control-a'. 40 | Namely, a list of zero or more modifiers separated by 41 | '-', followed by a single non-modifier key. 42 | :type key_string: str 43 | :param cb: A first class function with no parameters. 44 | :type cb: function 45 | :return: True if the binding was successful, False otherwise. 46 | :rtype: bool 47 | """ 48 | return bind_key(event_type, root, key_string, cb) 49 | 50 | def bind_key(event_type, wid, key_string, cb): 51 | """ 52 | Binds a function ``cb`` to a particular key press ``key_string`` on a 53 | window ``wid``. Whether it's a key release or key press binding is 54 | determined by ``event_type``. 55 | 56 | ``bind_key`` will automatically hook into the ``event`` module's dispatcher, 57 | so that if you're using ``event.main()`` for your main loop, everything 58 | will be taken care of for you. 59 | 60 | :param event_type: Either 'KeyPress' or 'KeyRelease'. 61 | :type event_type: str 62 | :param wid: The window to bind the key grab to. 63 | :type wid: int 64 | :param key_string: A string of the form 'Mod1-Control-a'. 65 | Namely, a list of zero or more modifiers separated by 66 | '-', followed by a single non-modifier key. 67 | :type key_string: str 68 | :param cb: A first class function with no parameters. 69 | :type cb: function 70 | :return: True if the binding was successful, False otherwise. 71 | :rtype: bool 72 | """ 73 | assert event_type in ('KeyPress', 'KeyRelease') 74 | 75 | mods, kc = parse_keystring(key_string) 76 | key = (wid, mods, kc) 77 | 78 | if not kc: 79 | print >> sys.stderr, 'Could not find a keycode for %s' % key_string 80 | return False 81 | 82 | if not __keygrabs[key] and not grab_key(wid, mods, kc): 83 | return False 84 | 85 | __keybinds[key].append(cb) 86 | __keygrabs[key] += 1 87 | 88 | if not event.is_connected(event_type, wid, __run_keybind_callbacks): 89 | event.connect(event_type, wid, __run_keybind_callbacks) 90 | 91 | return True 92 | 93 | def parse_keystring(key_string): 94 | """ 95 | A utility function to turn strings like 'Mod1-Mod4-a' into a pair 96 | corresponding to its modifiers and keycode. 97 | 98 | :param key_string: String starting with zero or more modifiers followed 99 | by exactly one key press. 100 | 101 | Available modifiers: Control, Mod1, Mod2, Mod3, Mod4, 102 | Mod5, Shift, Lock 103 | :type key_string: str 104 | :return: Tuple of modifier mask and keycode 105 | :rtype: (mask, int) 106 | """ 107 | modifiers = 0 108 | keycode = None 109 | 110 | for part in key_string.split('-'): 111 | if hasattr(xproto.KeyButMask, part): 112 | modifiers |= getattr(xproto.KeyButMask, part) 113 | else: 114 | if len(part) == 1: 115 | part = part.lower() 116 | keycode = lookup_string(part) 117 | 118 | return modifiers, keycode 119 | 120 | def lookup_string(kstr): 121 | """ 122 | Finds the keycode associated with a string representation of a keysym. 123 | 124 | :param kstr: English representation of a keysym. 125 | :return: Keycode, if one exists. 126 | :rtype: int 127 | """ 128 | if kstr in keysyms: 129 | return get_keycode(keysyms[kstr]) 130 | elif len(kstr) > 1 and kstr.capitalize() in keysyms: 131 | return get_keycode(keysyms[kstr.capitalize()]) 132 | 133 | return None 134 | 135 | def lookup_keysym(keysym): 136 | """ 137 | Finds the english string associated with a keysym. 138 | 139 | :param keysym: An X keysym. 140 | :return: English string representation of a keysym. 141 | :rtype: str 142 | """ 143 | return get_keysym_string(keysym) 144 | 145 | def get_min_max_keycode(): 146 | """ 147 | Return a tuple of the minimum and maximum keycode allowed in the 148 | current X environment. 149 | 150 | :rtype: (int, int) 151 | """ 152 | return conn.get_setup().min_keycode, conn.get_setup().max_keycode 153 | 154 | def get_keyboard_mapping(): 155 | """ 156 | Return a keyboard mapping cookie that can be used to fetch the table of 157 | keysyms in the current X environment. 158 | 159 | :rtype: xcb.xproto.GetKeyboardMappingCookie 160 | """ 161 | mn, mx = get_min_max_keycode() 162 | 163 | return conn.core.GetKeyboardMapping(mn, mx - mn + 1) 164 | 165 | def get_keyboard_mapping_unchecked(): 166 | """ 167 | Return an unchecked keyboard mapping cookie that can be used to fetch the 168 | table of keysyms in the current X environment. 169 | 170 | :rtype: xcb.xproto.GetKeyboardMappingCookie 171 | """ 172 | mn, mx = get_min_max_keycode() 173 | 174 | return conn.core.GetKeyboardMappingUnchecked(mn, mx - mn + 1) 175 | 176 | def get_keysym(keycode, col=0, kbmap=None): 177 | """ 178 | Get the keysym associated with a particular keycode in the current X 179 | environment. Although we get a list of keysyms from X in 180 | 'get_keyboard_mapping', this list is really a table with 181 | 'keysys_per_keycode' columns and ``mx - mn`` rows (where ``mx`` is the 182 | maximum keycode and ``mn`` is the minimum keycode). 183 | 184 | Thus, the index for a keysym given a keycode is: 185 | ``(keycode - mn) * keysyms_per_keycode + col``. 186 | 187 | In most cases, setting ``col`` to 0 will work. 188 | 189 | Witness the utter complexity: 190 | http://tronche.com/gui/x/xlib/input/keyboard-encoding.html 191 | 192 | You may also pass in your own keyboard mapping using the ``kbmap`` 193 | parameter, but xpybutil maintains an up-to-date version of this so you 194 | shouldn't have to. 195 | 196 | :param keycode: A physical key represented by an integer. 197 | :type keycode: int 198 | :param col: The column in the keysym table to use. 199 | Unless you know what you're doing, just use 0. 200 | :type col: int 201 | :param kbmap: The keyboard mapping to use. 202 | :type kbmap: xcb.xproto.GetKeyboardMapingReply 203 | """ 204 | if kbmap is None: 205 | kbmap = __kbmap 206 | 207 | mn, mx = get_min_max_keycode() 208 | per = kbmap.keysyms_per_keycode 209 | ind = (keycode - mn) * per + col 210 | 211 | return kbmap.keysyms[ind] 212 | 213 | def get_keysym_string(keysym): 214 | """ 215 | A simple wrapper to find the english string associated with a particular 216 | keysym. 217 | 218 | :param keysym: An X keysym. 219 | :rtype: str 220 | """ 221 | return keysym_strings.get(keysym, [None])[0] 222 | 223 | def get_keycode(keysym): 224 | """ 225 | Given a keysym, find the keycode mapped to it in the current X environment. 226 | It is necessary to search the keysym table in order to do this, including 227 | all columns. 228 | 229 | :param keysym: An X keysym. 230 | :return: A keycode or None if one could not be found. 231 | :rtype: int 232 | """ 233 | mn, mx = get_min_max_keycode() 234 | cols = __kbmap.keysyms_per_keycode 235 | for i in range(mn, mx + 1): 236 | for j in range(0, cols): 237 | ks = get_keysym(i, col=j) 238 | if ks == keysym: 239 | return i 240 | 241 | return None 242 | 243 | def get_mod_for_key(keycode): 244 | """ 245 | Finds the modifier that is mapped to the given keycode. 246 | This may be useful when analyzing key press events. 247 | 248 | :type keycode: int 249 | :return: A modifier identifier. 250 | :rtype: xcb.xproto.ModMask 251 | """ 252 | return __keysmods.get(keycode, 0) 253 | 254 | def get_keys_to_mods(): 255 | """ 256 | Fetches and creates the keycode -> modifier mask mapping. Typically, you 257 | shouldn't have to use this---xpybutil will keep this up to date if it 258 | changes. 259 | 260 | This function may be useful in that it should closely replicate the output 261 | of the ``xmodmap`` command. For example: 262 | 263 | :: 264 | 265 | keymods = get_keys_to_mods() 266 | for kc in sorted(keymods, key=lambda kc: keymods[kc]): 267 | print keymods[kc], hex(kc), get_keysym_string(get_keysym(kc)) 268 | 269 | Which will very closely replicate ``xmodmap``. I'm not getting precise 270 | results quite yet, but I do believe I'm getting at least most of what 271 | matters. (i.e., ``xmodmap`` returns valid keysym strings for some that 272 | I cannot.) 273 | 274 | :return: A dict mapping from keycode to modifier mask. 275 | :rtype: dict 276 | """ 277 | mm = xproto.ModMask 278 | modmasks = [mm.Shift, mm.Lock, mm.Control, 279 | mm._1, mm._2, mm._3, mm._4, mm._5] # order matters 280 | 281 | mods = conn.core.GetModifierMapping().reply() 282 | 283 | res = {} 284 | keyspermod = mods.keycodes_per_modifier 285 | for mmi in range(0, len(modmasks)): 286 | row = mmi * keyspermod 287 | for kc in mods.keycodes[row:row + keyspermod]: 288 | res[kc] = modmasks[mmi] 289 | 290 | return res 291 | 292 | def get_modifiers(state): 293 | """ 294 | Takes a ``state`` (typically found in key press or button press events) 295 | and returns a string list representation of the modifiers that were pressed 296 | when generating the event. 297 | 298 | :param state: Typically from ``some_event.state``. 299 | :return: List of modifier string representations. 300 | :rtype: [str] 301 | """ 302 | ret = [] 303 | 304 | if state & xproto.ModMask.Shift: 305 | ret.append('Shift') 306 | if state & xproto.ModMask.Lock: 307 | ret.append('Lock') 308 | if state & xproto.ModMask.Control: 309 | ret.append('Control') 310 | if state & xproto.ModMask._1: 311 | ret.append('Mod1') 312 | if state & xproto.ModMask._2: 313 | ret.append('Mod2') 314 | if state & xproto.ModMask._3: 315 | ret.append('Mod3') 316 | if state & xproto.ModMask._4: 317 | ret.append('Mod4') 318 | if state & xproto.ModMask._5: 319 | ret.append('Mod5') 320 | if state & xproto.KeyButMask.Button1: 321 | ret.append('Button1') 322 | if state & xproto.KeyButMask.Button2: 323 | ret.append('Button2') 324 | if state & xproto.KeyButMask.Button3: 325 | ret.append('Button3') 326 | if state & xproto.KeyButMask.Button4: 327 | ret.append('Button4') 328 | if state & xproto.KeyButMask.Button5: 329 | ret.append('Button5') 330 | 331 | return ret 332 | 333 | def grab_keyboard(grab_win): 334 | """ 335 | This will grab the keyboard. The effect is that further keyboard events 336 | will *only* be sent to the grabbing client. (i.e., ``grab_win``). 337 | 338 | N.B. There is an example usage of this in examples/window-marker. 339 | 340 | :param grab_win: A window identifier to report keyboard events to. 341 | :type grab_win: int 342 | :rtype: xcb.xproto.GrabStatus 343 | """ 344 | return conn.core.GrabKeyboard(False, grab_win, xproto.Time.CurrentTime, 345 | GM.Async, GM.Async).reply() 346 | 347 | def ungrab_keyboard(): 348 | """ 349 | This will release a grab initiated by ``grab_keyboard``. 350 | 351 | :rtype: void 352 | """ 353 | conn.core.UngrabKeyboardChecked(xproto.Time.CurrentTime).check() 354 | 355 | def grab_key(wid, modifiers, key): 356 | """ 357 | Grabs a key for a particular window and a modifiers/key value. 358 | If the grab was successful, return True. Otherwise, return False. 359 | If your client is grabbing keys, it is useful to notify the user if a 360 | key wasn't grabbed. Keyboard shortcuts not responding is disorienting! 361 | 362 | Also, this function will grab several keys based on varying modifiers. 363 | Namely, this accounts for all of the "trivial" modifiers that may have 364 | an effect on X events, but probably shouldn't effect key grabbing. (i.e., 365 | whether num lock or caps lock is on.) 366 | 367 | N.B. You should probably be using 'bind_key' or 'bind_global_key' instead. 368 | 369 | :param wid: A window identifier. 370 | :type wid: int 371 | :param modifiers: A modifier mask. 372 | :type modifiers: int 373 | :param key: A keycode. 374 | :type key: int 375 | :rtype: bool 376 | """ 377 | try: 378 | for mod in TRIVIAL_MODS: 379 | conn.core.GrabKeyChecked(True, wid, modifiers | mod, key, GM.Async, 380 | GM.Async).check() 381 | 382 | return True 383 | except xproto.BadAccess: 384 | return False 385 | 386 | def ungrab_key(wid, modifiers, key): 387 | """ 388 | Ungrabs a key that was grabbed by ``grab_key``. Similarly, it will return 389 | True on success and False on failure. 390 | 391 | When ungrabbing a key, the parameters to this function should be 392 | *precisely* the same as the parameters to ``grab_key``. 393 | 394 | :param wid: A window identifier. 395 | :type wid: int 396 | :param modifiers: A modifier mask. 397 | :type modifiers: int 398 | :param key: A keycode. 399 | :type key: int 400 | :rtype: bool 401 | """ 402 | try: 403 | for mod in TRIVIAL_MODS: 404 | conn.core.UngrabKeyChecked(key, wid, modifiers | mod).check() 405 | 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.keys(): 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 | -------------------------------------------------------------------------------- /xpybutil/icccm.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implements most of the ICCCM spec. The ICCCM spec can be found at: 3 | http://tronche.com/gui/x/icccm/ 4 | 5 | See the EWMH module for some relevant information that also generally applies 6 | to this module. 7 | """ 8 | from collections import defaultdict 9 | import struct 10 | 11 | from xpybutil.compat import xproto 12 | 13 | from xpybutil import conn as c 14 | from xpybutil import util 15 | 16 | __atoms = ['WM_PROTOCOLS', 'WM_TAKE_FOCUS', 'WM_SAVE_YOURSELF', 17 | 'WM_DELETE_WINDOW', 'WM_COLORMAP_WINDOWS', 'WM_STATE'] 18 | 19 | class Hint: 20 | Input = 1 21 | State = 2 22 | IconPixmap = 4 23 | IconWindow = 8 24 | IconPosition = 16 25 | IconMask = 32 26 | WindowGroup = 64 27 | Message = 128 28 | Urgency = 256 29 | 30 | class SizeHint: 31 | USPosition = 1 32 | USSize = 2 33 | PPosition = 4 34 | PSize = 8 35 | PMinSize = 16 36 | PMaxSize = 32 37 | PResizeInc = 64 38 | PAspect = 128 39 | PBaseSize = 256 40 | PWinGravity = 512 41 | 42 | class State: 43 | Withdrawn = 0 44 | Normal = 1 45 | Zoomed = 2 46 | Iconic = 3 47 | Inactive = 4 48 | 49 | # Some aliases 50 | atom = util.get_atom 51 | atoms = xproto.Atom 52 | 53 | # Build the atom cache for quicker access 54 | util.build_atom_cache(__atoms) 55 | 56 | # WM_NAME 57 | 58 | def get_wm_name(window): 59 | return util.PropertyCookie(util.get_property(window, atoms.WM_NAME)) 60 | 61 | def get_wm_name_unchecked(window): 62 | return util.PropertyCookie(util.get_property_unchecked(window, 63 | atoms.WM_NAME)) 64 | 65 | def set_wm_name(window, wm_name): 66 | return c.core.ChangeProperty(xproto.PropMode.Replace, window, 67 | atoms.WM_NAME, atoms.STRING, 8, len(wm_name), 68 | wm_name) 69 | 70 | def set_wm_name_checked(window, wm_name): 71 | return c.core.ChangePropertyChecked(xproto.PropMode.Replace, window, 72 | atoms.WM_NAME, atoms.STRING, 8, 73 | len(wm_name), wm_name) 74 | 75 | # WM_ICON_NAME 76 | 77 | def get_wm_icon_name(window): 78 | return util.PropertyCookie(util.get_property(window, atoms.WM_ICON_NAME)) 79 | 80 | def get_wm_icon_name_unchecked(window): 81 | return util.PropertyCookie(util.get_property_unchecked(window, 82 | atoms.WM_ICON_NAME)) 83 | 84 | def set_wm_icon_name(window, wm_icon_name): 85 | return c.core.ChangeProperty(xproto.PropMode.Replace, window, 86 | atoms.WM_ICON_NAME, atoms.STRING, 8, 87 | len(wm_icon_name), wm_icon_name) 88 | 89 | def set_wm_icon_name_checked(window, wm_icon_name): 90 | return c.core.ChangePropertyChecked(xproto.PropMode.Replace, window, 91 | atoms.WM_ICON_NAME, atoms.STRING, 8, 92 | len(wm_icon_name), wm_icon_name) 93 | 94 | # WM_NORMAL_HINTS 95 | 96 | class NormalHintsCookie(util.PropertyCookie): 97 | def reply(self): 98 | v = util.PropertyCookie.reply(self) 99 | 100 | if not v: 101 | return None 102 | 103 | fields = ['x', 'y', 'width', 'height', 'min_width', 'min_height', 104 | 'max_width', 'max_height', 'width_inc', 'height_inc', 105 | 'min_aspect_num', 'min_aspect_den', 'max_aspect_num', 106 | 'max_aspect_den', 'base_width', 'base_height', 'win_gravity'] 107 | retval = defaultdict(int) 108 | 109 | retval['flags'] = { 110 | 'USPosition': v[0] & SizeHint.USPosition > 0, 111 | 'USSize': v[0] & SizeHint.USSize > 0, 112 | 'PPosition': v[0] & SizeHint.PPosition > 0, 113 | 'PSize': v[0] & SizeHint.PSize > 0, 114 | 'PMinSize': v[0] & SizeHint.PMinSize > 0, 115 | 'PMaxSize': v[0] & SizeHint.PMaxSize > 0, 116 | 'PResizeInc': v[0] & SizeHint.PResizeInc > 0, 117 | 'PAspect': v[0] & SizeHint.PAspect > 0, 118 | 'PBaseSize': v[0] & SizeHint.PBaseSize > 0, 119 | 'PWinGravity': v[0] & SizeHint.PWinGravity > 0 120 | } 121 | 122 | for j, f in enumerate(fields): 123 | i = j + 1 # flags offset 124 | 125 | if i >= len(v): 126 | return 127 | 128 | if f == 'win_gravity' and v[i] <= 0: 129 | v[i] = xproto.Gravity.NorthWest 130 | 131 | retval[f] = v[i] 132 | 133 | return retval 134 | 135 | def get_wm_normal_hints(window): 136 | return NormalHintsCookie(util.get_property(window, atoms.WM_NORMAL_HINTS)) 137 | 138 | def get_wm_normal_hints_unchecked(window): 139 | return NormalHintsCookie(util.get_property_unchecked(window, 140 | atoms.WM_NORMAL_HINTS)) 141 | 142 | def _pack_normal_hints(flags, x, y, width, height, min_width, min_height, 143 | max_width, max_height, width_inc, height_inc, 144 | min_aspect_num, min_aspect_den, max_aspect_num, 145 | max_aspect_den, base_width, base_height, win_gravity): 146 | hints = [0] * 18 147 | 148 | if flags & SizeHint.USPosition: 149 | hints[1], hints[2] = x, y 150 | elif flags & SizeHint.PPosition: 151 | hints[1], hints[2] = x, y 152 | 153 | if flags & SizeHint.USSize: 154 | hints[3], hints[4] = width, height 155 | elif flags & SizeHint.PSize: 156 | hints[3], hints[4] = width, height 157 | 158 | if flags & SizeHint.PMinSize: 159 | hints[5], hints[6] = min_width, min_height 160 | 161 | if flags & SizeHint.PMaxSize: 162 | hints[7], hints[8] = max_width, max_height 163 | 164 | if flags & SizeHint.PResizeInc: 165 | hints[9], hints[10] = width_inc, height_inc 166 | 167 | if flags & SizeHint.PAspect: 168 | hints[11], hints[12] = min_aspect_num, min_aspect_den 169 | hints[13], hints[14] = max_aspect_num, max_aspect_den 170 | 171 | if flags & SizeHint.PBaseSize: 172 | hints[15], hints[16] = base_width, base_height 173 | 174 | if flags & SizeHint.PWinGravity: 175 | hints[17] = win_gravity 176 | 177 | hints[0] = flags 178 | 179 | return struct.pack('I' * 18, *hints) 180 | 181 | def set_wm_normal_hints(window, flags, x=0, y=0, width=0, 182 | height=0, min_width=0, min_height=0, 183 | max_width=0, max_height=0, width_inc=0, 184 | height_inc=0, min_aspect_num=0, 185 | min_aspect_den=0, max_aspect_num=0, 186 | max_aspect_den=0, base_width=0, base_height=0, 187 | win_gravity=xproto.Gravity.NorthWest): 188 | packed = _pack_normal_hints(flags, x, y, width, height, min_width, 189 | min_height, max_width, max_height, width_inc, 190 | height_inc, min_aspect_num, min_aspect_den, 191 | max_aspect_num, max_aspect_den, base_width, 192 | base_height, win_gravity) 193 | return c.core.ChangeProperty(xproto.PropMode.Replace, window, 194 | atoms.WM_NORMAL_HINTS, atoms.WM_SIZE_HINTS, 32, 195 | 18, packed) 196 | 197 | def set_wm_normal_hints_checked(window, flags, x=0, y=0, width=0, 198 | height=0, min_width=0, min_height=0, 199 | max_width=0, max_height=0, width_inc=0, 200 | height_inc=0, min_aspect_num=0, 201 | min_aspect_den=0, max_aspect_num=0, 202 | max_aspect_den=0, base_width=0, base_height=0, 203 | win_gravity=xproto.Gravity.NorthWest): 204 | packed = _pack_normal_hints(flags, x, y, width, height, min_width, 205 | min_height, max_width, max_height, width_inc, 206 | height_inc, min_aspect_num, min_aspect_den, 207 | max_aspect_num, max_aspect_den, base_width, 208 | base_height, win_gravity) 209 | return c.core.ChangePropertyChecked(xproto.PropMode.Replace, window, 210 | atoms.WM_NORMAL_HINTS, 211 | atoms.WM_SIZE_HINTS, 32, 18, packed) 212 | 213 | # WM_HINTS 214 | 215 | class HintsCookie(util.PropertyCookie): 216 | def reply(self): 217 | v = util.PropertyCookie.reply(self) 218 | 219 | if not v: 220 | return None 221 | 222 | return { 223 | 'flags': { 224 | 'Input': v[0] & Hint.Input > 0, 225 | 'State': v[0] & Hint.State > 0, 226 | 'IconPixmap': v[0] & Hint.IconPixmap > 0, 227 | 'IconWindow': v[0] & Hint.IconWindow > 0, 228 | 'IconPosition': v[0] & Hint.IconPosition > 0, 229 | 'IconMask': v[0] & Hint.IconMask > 0, 230 | 'WindowGroup': v[0] & Hint.WindowGroup > 0, 231 | 'Message': v[0] & Hint.Message > 0, 232 | 'Urgency': v[0] & Hint.Urgency > 0 233 | }, 234 | 'input': v[1], 235 | 'initial_state': v[2], 236 | 'icon_pixmap': v[3], 237 | 'icon_window': v[4], 238 | 'icon_x': v[5], 239 | 'icon_y': v[6], 240 | 'icon_mask': v[7], 241 | 'window_group': v[8], 242 | } 243 | 244 | def get_wm_hints(window): 245 | return HintsCookie(util.get_property(window, atoms.WM_HINTS)) 246 | 247 | def get_wm_hints_unchecked(window): 248 | return HintsCookie(util.get_property_unchecked(window, atoms.WM_HINTS)) 249 | 250 | def _pack_hints(flags, input, initial_state, icon_pixmap, icon_window, 251 | icon_x, icon_y, icon_mask, window_group): 252 | hints = [0] * 9 253 | 254 | if flags & Hint.Input: 255 | hints[1] = input 256 | 257 | if flags & Hint.State: 258 | hints[2] = initial_state 259 | 260 | if flags & Hint.IconPixmap: 261 | hints[3] = icon_pixmap 262 | 263 | if flags & Hint.IconWindow: 264 | hints[4] = icon_window 265 | 266 | if flags & Hint.IconPosition: 267 | hints[5], hints[6] = icon_x, icon_y 268 | 269 | if flags & Hint.IconMask: 270 | hints[7] = icon_mask 271 | 272 | if flags & Hint.WindowGroup: 273 | hints[8] = window_group 274 | 275 | hints[0] = flags 276 | 277 | return struct.pack('I' * 9, *hints) 278 | 279 | def set_wm_hints(window, flags, input=1, initial_state=State.Normal, 280 | icon_pixmap=0, icon_window=0, icon_x=0, icon_y=0, 281 | icon_mask=0, window_group=0): 282 | packed = _pack_hints(flags, input, initial_state, icon_pixmap, icon_window, 283 | icon_x, icon_y, icon_mask, window_group) 284 | return c.core.ChangeProperty(xproto.PropMode.Replace, window, 285 | atoms.WM_HINTS, atoms.WM_HINTS, 32, 9, packed) 286 | 287 | def set_wm_hints_checked(window, flags, input=1, 288 | initial_state=State.Normal, icon_pixmap=0, 289 | icon_window=0, icon_x=0, icon_y=0, icon_mask=0, 290 | window_group=0): 291 | packed = _pack_hints(flags, input, initial_state, icon_pixmap, icon_window, 292 | icon_x, icon_y, icon_mask, window_group) 293 | return c.core.ChangePropertyChecked(xproto.PropMode.Replace, window, 294 | atoms.WM_HINTS, atoms.WM_HINTS, 32, 295 | 9, packed) 296 | 297 | # WM_CLASS 298 | 299 | def get_wm_class(window): 300 | return util.PropertyCookie(util.get_property(window, atoms.WM_CLASS)) 301 | 302 | def get_wm_class_unchecked(window): 303 | return util.PropertyCookie(util.get_property_unchecked(window, 304 | atoms.WM_CLASS)) 305 | 306 | def set_wm_class(window, instance, cls): 307 | return c.core.ChangeProperty(xproto.PropMode.Replace, window, 308 | atoms.WM_CLASS, atoms.STRING, 8, 309 | len(instance) + len(cls) + 2, 310 | instance + chr(0) + cls + chr(0)) 311 | 312 | def set_wm_class_checked(window, instance, cls): 313 | return c.core.ChangePropertyChecked(xproto.PropMode.Replace, window, 314 | atoms.WM_CLASS, atoms.STRING, 8, 315 | len(instance) + len(cls) + 2, 316 | instance + chr(0) + cls + chr(0)) 317 | 318 | # WM_TRANSIENT_FOR 319 | 320 | def get_wm_transient_for(window): 321 | return util.PropertyCookie(util.get_property(window, 322 | atoms.WM_TRANSIENT_FOR)) 323 | 324 | def get_wm_transient_for_unchecked(window): 325 | cook = util.get_property_unchecked(window, atoms.WM_TRANSIENT_FOR) 326 | return util.PropertyCookie(cook) 327 | 328 | def set_wm_transient_for(window, transient_window): 329 | packed = struct.pack('I', transient_window) 330 | return c.core.ChangeProperty(xproto.PropMode.Replace, window, 331 | atoms.WM_TRANSIENT_FOR, atoms.WINDOW, 32, 332 | 1, packed) 333 | 334 | def set_wm_transient_for_checked(window, transient_window): 335 | packed = struct.pack('I', transient_window) 336 | return c.core.ChangePropertyChecked(xproto.PropMode.Replace, window, 337 | atoms.WM_TRANSIENT_FOR, 338 | atoms.WINDOW, 32, 1, packed) 339 | 340 | # WM_PROTOCOLS 341 | 342 | def get_wm_protocols(window): 343 | return util.PropertyCookie(util.get_property(window, 'WM_PROTOCOLS')) 344 | 345 | def get_wm_protocols_unchecked(window): 346 | return util.PropertyCookie(util.get_property_unchecked(window, 347 | 'WM_PROTOCOLS')) 348 | 349 | def set_wm_protocols(window, protocol_atoms): 350 | packed = struct.pack('I' * len(protocol_atoms), *protocol_atoms) 351 | return c.core.ChangeProperty(xproto.PropMode.Replace, window, 352 | atom('WM_PROTOCOLS'), atoms.ATOM, 32, 353 | len(protocol_atoms), packed) 354 | 355 | def set_wm_protocols_checked(window, protocol_atoms): 356 | packed = struct.pack('I' * len(protocol_atoms), *protocol_atoms) 357 | return c.core.ChangePropertyChecked(xproto.PropMode.Replace, window, 358 | atom('WM_PROTOCOLS'), atoms.ATOM, 32, 359 | len(protocol_atoms), packed) 360 | 361 | # WM_COLORMAP_WINDOWS 362 | 363 | def get_wm_colormap_windows(window): 364 | return util.PropertyCookie(util.get_property(window, 'WM_COLORMAP_WINDOWS')) 365 | 366 | def get_wm_colormap_windows_unchecked(window): 367 | cook = util.get_property_unchecked(window, 'WM_COLORMAP_WINDOWS') 368 | return util.PropertyCookie(cook) 369 | 370 | def set_wm_colormap_windows(window, colormap_windows): 371 | packed = struct.pack('I' * len(colormap_windows), *colormap_windows) 372 | return c.core.ChangeProperty(xproto.PropMode.Replace, window, 373 | atom('WM_COLORMAP_WINDOWS'), atoms.WINDOW, 32, 374 | len(colormap_windows), packed) 375 | 376 | def set_wm_colormap_windows_checked(window, colormap_windows): 377 | packed = struct.pack('I' * len(colormap_windows), *colormap_windows) 378 | return c.core.ChangePropertyChecked(xproto.PropMode.Replace, window, 379 | atom('WM_COLORMAP_WINDOWS'), 380 | atoms.WINDOW, 32, 381 | len(colormap_windows), packed) 382 | 383 | # WM_CLIENT_MACHINE 384 | 385 | def get_wm_client_machine(window): 386 | return util.PropertyCookie(util.get_property(window, 387 | atoms.WM_CLIENT_MACHINE)) 388 | 389 | def get_wm_client_machine_unchecked(window): 390 | cook = util.get_property_unchecked(window, atoms.WM_CLIENT_MACHINE) 391 | return util.PropertyCookie(cook) 392 | 393 | def set_wm_client_machine(window, client_machine): 394 | return c.core.ChangeProperty(xproto.PropMode.Replace, window, 395 | atoms.WM_CLIENT_MACHINE, atoms.STRING, 8, 396 | len(client_machine), client_machine) 397 | 398 | def set_wm_client_machine_checked(window, client_machine): 399 | return c.core.ChangePropertyChecked(xproto.PropMode.Replace, window, 400 | atoms.WM_CLIENT_MACHINE, 401 | atoms.STRING, 8, 402 | len(client_machine), client_machine) 403 | 404 | # WM_STATE 405 | 406 | class StateCookie(util.PropertyCookie): 407 | def reply(self): 408 | v = util.PropertyCookie.reply(self) 409 | 410 | if not v: 411 | return None 412 | 413 | return { 414 | 'state': v[0], 415 | 'icon': v[1] 416 | } 417 | 418 | def get_wm_state(window): 419 | return StateCookie(util.get_property(window, 'WM_STATE')) 420 | 421 | def get_wm_state_unchecked(window): 422 | return StateCookie(util.get_property_unchecked(window, 'WM_STATE')) 423 | 424 | def set_wm_state(window, state, icon): 425 | packed = struct.pack('II', state, icon) 426 | return c.core.ChangeProperty(xproto.PropMode.Replace, window, 427 | atom('WM_STATE'), atom('WM_STATE'), 32, 428 | 2, packed) 429 | 430 | def set_wm_state_checked(window, state, icon): 431 | packed = struct.pack('II', state, icon) 432 | return c.core.ChangePropertyChecked(xproto.PropMode.Replace, window, 433 | atom('WM_STATE'), atom('WM_STATE'), 32, 434 | 2, packed) 435 | 436 | # WM_ICON_SIZE 437 | 438 | class IconSizeCookie(util.PropertyCookie): 439 | def reply(self): 440 | v = util.PropertyCookie.reply(self) 441 | 442 | if not v: 443 | return None 444 | 445 | return { 446 | 'min_width': v[0], 447 | 'min_height': v[1], 448 | 'max_width': v[2], 449 | 'max_height': v[3], 450 | 'width_inc': v[4], 451 | 'height_inc': v[5] 452 | } 453 | 454 | def get_icon_size(window): 455 | return IconSizeCookie(util.get_property(window, atoms.WM_ICON_SIZE)) 456 | 457 | def get_icon_size_unchecked(window): 458 | return IconSizeCookie(util.get_property_unchecked(window, 459 | atoms.WM_ICON_SIZE)) 460 | 461 | def set_icon_size(window, min_width=0, min_height=0, max_width=0, 462 | max_height=0, width_inc=0, height_inc=0): 463 | packed = struct.pack('I' * 6, min_width, min_height, max_width, max_height, 464 | width_inc, height_inc) 465 | return c.core.ChangeProperty(xproto.PropMode.Replace, window, 466 | atoms.WM_ICON_SIZE, atoms.WM_ICON_SIZE, 32, 467 | 6, packed) 468 | 469 | def set_icon_size_checked(window, min_width=0, min_height=0, max_width=0, 470 | max_height=0, width_inc=0, height_inc=0): 471 | packed = struct.pack('I' * 6, min_width, min_height, max_width, max_height, 472 | width_inc, height_inc) 473 | return c.core.ChangePropertyChecked(xproto.PropMode.Replace, window, 474 | atoms.WM_ICON_SIZE, 475 | atoms.WM_ICON_SIZE, 32, 6, packed) 476 | -------------------------------------------------------------------------------- /xpybutil/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 keysyms.items(): 2023 | keysym_strings[keysym].append(keysym_string) 2024 | --------------------------------------------------------------------------------