| 1 |  |  | """Xlib-based global hotkey-binding code""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 2 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 3 |  |  | __author__ = "Stephan Sokolow (deitarion/SSokolow)" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 4 |  |  | __license__ = "GNU GPL 2.0 or later" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 5 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 6 |  |  | import logging | 
            
                                                                                                            
                            
            
                                    
            
            
                | 7 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 8 |  |  | import gobject, gtk | 
            
                                                                                                            
                            
            
                                    
            
            
                | 9 |  |  | from Xlib import X | 
            
                                                                                                            
                            
            
                                    
            
            
                | 10 |  |  | from Xlib.display import Display | 
            
                                                                                                            
                            
            
                                    
            
            
                | 11 |  |  | from Xlib.error import BadAccess, DisplayConnectionError | 
            
                                                                                                            
                            
            
                                    
            
            
                | 12 |  |  | from Xlib.protocol.event import KeyPress as XKeyPress | 
            
                                                                                                            
                            
            
                                    
            
            
                | 13 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 14 |  |  | from .util import powerset, XInitError | 
            
                                                                                                            
                            
            
                                    
            
            
                | 15 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 16 |  |  | # Allow MyPy to work without depending on the `typing` package | 
            
                                                                                                            
                            
            
                                    
            
            
                | 17 |  |  | # (And silence complaints from only using the imported types in comments) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 18 |  |  | try: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 19 |  |  |     # pylint: disable=unused-import | 
            
                                                                                                            
                            
            
                                    
            
            
                | 20 |  |  |     from typing import (Any, Callable, Dict, Iterable, Iterator, List,  # NOQA | 
            
                                                                                                            
                            
            
                                    
            
            
                | 21 |  |  |                         Optional, Sequence, Sized, Tuple) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 22 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 23 |  |  |     from Xlib.error import XError          # NOQA | 
            
                                                                                                            
                            
            
                                    
            
            
                | 24 |  |  |     from .commands import CommandRegistry  # NOQA | 
            
                                                                                                            
                            
            
                                    
            
            
                | 25 |  |  |     from .wm import WindowManager          # NOQA | 
            
                                                                                                            
                            
            
                                    
            
            
                | 26 |  |  |     from .util import CommandCB            # NOQA | 
            
                                                                                                            
                            
            
                                    
            
            
                | 27 |  |  | except:  # pylint: disable=bare-except | 
            
                                                                                                            
                            
            
                                    
            
            
                | 28 |  |  |     pass | 
            
                                                                                                            
                            
            
                                    
            
            
                | 29 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 30 |  |  | class KeyBinder(object): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 31 |  |  |     """A convenience class for wrapping C{XGrabKey}.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 32 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 33 |  |  |     #: @todo: Figure out how to set the modifier mask in X11 and use | 
            
                                                                                                            
                            
            
                                    
            
            
                | 34 |  |  |     #:        C{gtk.accelerator_get_default_mod_mask()} to feed said code. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 35 |  |  |     ignored_modifiers = ['Mod2Mask', 'LockMask'] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 36 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 37 |  |  |     #: Used to pass state from L{cb_xerror} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 38 |  |  |     keybind_failed = False | 
            
                                                                                                            
                            
            
                                    
            
            
                | 39 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 40 |  |  |     def __init__(self, xdisplay=None):  # type: (Optional[Display]) -> None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 41 |  |  |         """Connect to X11 and the Glib event loop. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 42 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 43 |  |  |         @param xdisplay: A C{python-xlib} display handle. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 44 |  |  |         @type xdisplay: C{Xlib.display.Display} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 45 |  |  |         """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 46 |  |  |         try: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 47 |  |  |             self.xdisp = xdisplay or Display() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 48 |  |  |         except (UnicodeDecodeError, DisplayConnectionError), err: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 49 |  |  |             raise XInitError("python-xlib failed with %s when asked to open" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 50 |  |  |                              " a connection to the X server. Cannot bind keys." | 
            
                                                                                                            
                            
            
                                    
            
            
                | 51 |  |  |                              "\n\tIt's unclear why this happens, but it is" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 52 |  |  |                              " usually fixed by deleting your ~/.Xauthority" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 53 |  |  |                              " file and rebooting." | 
            
                                                                                                            
                            
            
                                    
            
            
                | 54 |  |  |                              % err.__class__.__name__) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 55 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 56 |  |  |         self.xroot = self.xdisp.screen().root | 
            
                                                                                                            
                            
            
                                    
            
            
                | 57 |  |  |         self._keys = {}  # type: Dict[Tuple[int, int], Callable] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 58 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 59 |  |  |         # Resolve these at runtime to avoid NameErrors | 
            
                                                                                                            
                            
            
                                    
            
            
                | 60 |  |  |         self._ignored_modifiers = [getattr(X, name) for name in | 
            
                                                                                                            
                            
            
                                    
            
            
                | 61 |  |  |                                    self.ignored_modifiers]  # type: List[int] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 62 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 63 |  |  |         # We want to receive KeyPress events | 
            
                                                                                                            
                            
            
                                    
            
            
                | 64 |  |  |         self.xroot.change_attributes(event_mask=X.KeyPressMask) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 65 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 66 |  |  |         # Set up a handler to catch XGrabKey() failures | 
            
                                                                                                            
                            
            
                                    
            
            
                | 67 |  |  |         self.xdisp.set_error_handler(self.cb_xerror) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 68 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 69 |  |  |         # Merge python-xlib into the Glib event loop | 
            
                                                                                                            
                            
            
                                    
            
            
                | 70 |  |  |         # Source: http://www.pygtk.org/pygtk2tutorial/sec-MonitoringIO.html | 
            
                                                                                                            
                            
            
                                    
            
            
                | 71 |  |  |         gobject.io_add_watch(self.xroot.display, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 72 |  |  |                              gobject.IO_IN, self.cb_xevent) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 73 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 74 |  |  |     def bind(self, accel, callback):  # type: (str, Callable[[], None]) -> bool | 
            
                                                                                                            
                            
            
                                    
            
            
                | 75 |  |  |         """Bind a global key combination to a callback. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 76 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 77 |  |  |         @param accel: An accelerator as either a string to be parsed by | 
            
                                                                                                            
                            
            
                                    
            
            
                | 78 |  |  |             C{gtk.accelerator_parse()} or a tuple as returned by it.) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 79 |  |  |         @param callback: The function to call when the key is pressed. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 80 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 81 |  |  |         @type accel: C{str} or C{(int, gtk.gdk.ModifierType)} or C{(int, int)} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 82 |  |  |         @type callback: C{function} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 83 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 84 |  |  |         @returns: A boolean indicating whether the provided keybinding was | 
            
                                                                                                            
                            
            
                                    
            
            
                | 85 |  |  |             parsed successfully. (But not whether it was registered | 
            
                                                                                                            
                            
            
                                    
            
            
                | 86 |  |  |             successfully due to the asynchronous nature of the C{XGrabKey} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 87 |  |  |             request.) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 88 |  |  |         @rtype: C{bool} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 89 |  |  |         """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 90 |  |  |         keycode, modmask = self.parse_accel(accel) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 91 |  |  |         if keycode is None or modmask is None: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 92 |  |  |             return False | 
            
                                                                                                            
                            
            
                                    
            
            
                | 93 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 94 |  |  |         # Ignore modifiers like Mod2 (NumLock) and Lock (CapsLock) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 95 |  |  |         self._keys[(keycode, 0)] = callback  # Null modifiers seem to be a risk | 
            
                                                                                                            
                            
            
                                    
            
            
                | 96 |  |  |         for mmask in self._vary_modmask(modmask, self._ignored_modifiers): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 97 |  |  |             self._keys[(keycode, mmask)] = callback | 
            
                                                                                                            
                            
            
                                    
            
            
                | 98 |  |  |             self.xroot.grab_key(keycode, mmask, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 99 |  |  |                                 1, X.GrabModeAsync, X.GrabModeAsync) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 100 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 101 |  |  |         # If we don't do this, then nothing works. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 102 |  |  |         # I assume it flushes the XGrabKey calls to the server. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 103 |  |  |         self.xdisp.sync() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 104 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 105 |  |  |         # React to any cb_xerror that might have resulted from xdisp.sync() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 106 |  |  |         if self.keybind_failed: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 107 |  |  |             self.keybind_failed = False | 
            
                                                                                                            
                            
            
                                    
            
            
                | 108 |  |  |             logging.warning("Failed to bind key. It may already be in use: %s", | 
            
                                                                                                            
                            
            
                                    
            
            
                | 109 |  |  |                             accel) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 110 |  |  |             return False | 
            
                                                                                                            
                            
            
                                    
            
            
                | 111 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 112 |  |  |         return True | 
            
                                                                                                            
                            
            
                                    
            
            
                | 113 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 114 |  |  |     def cb_xerror(self, err, _):  # type: (XError, Any) -> None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 115 |  |  |         """Used to identify when attempts to bind keys fail. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 116 |  |  |         @note: If you can make python-xlib's C{CatchError} actually work or if | 
            
                                                                                                            
                            
            
                                    
            
            
                | 117 |  |  |                you can retrieve more information to show, feel free. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 118 |  |  |         """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 119 |  |  |         if isinstance(err, BadAccess): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 120 |  |  |             self.keybind_failed = True | 
            
                                                                                                            
                            
            
                                    
            
            
                | 121 |  |  |         else: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 122 |  |  |             self.xdisp.display.default_error_handler(err) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 123 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 124 |  |  |     def cb_xevent(self, src, cond, handle=None):  # pylint: disable=W0613 | 
            
                                                                                                            
                            
            
                                    
            
            
                | 125 |  |  |         # type: (Any, Any, Optional[Display]) -> bool | 
            
                                                                                                            
                            
            
                                    
            
            
                | 126 |  |  |         """Callback to dispatch X events to more specific handlers. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 127 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 128 |  |  |         @rtype: C{True} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 129 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 130 |  |  |         @todo: Make sure uncaught exceptions are prevented from making | 
            
                                                                                                            
                            
            
                                    
            
            
                | 131 |  |  |             quicktile unresponsive in the general case. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 132 |  |  |         """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 133 |  |  |         handle = handle or self.xroot.display | 
            
                                                                                                            
                            
            
                                    
            
            
                | 134 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 135 |  |  |         for _ in range(0, handle.pending_events()): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 136 |  |  |             xevent = handle.next_event() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 137 |  |  |             if xevent.type == X.KeyPress: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 138 |  |  |                 self.handle_keypress(xevent) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 139 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 140 |  |  |         # Necessary for proper function | 
            
                                                                                                            
                            
            
                                    
            
            
                | 141 |  |  |         return True | 
            
                                                                                                            
                            
            
                                    
            
            
                | 142 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 143 |  |  |     def handle_keypress(self, xevent):  # type: (XKeyPress) -> None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 144 |  |  |         """Dispatch C{XKeyPress} events to their callbacks.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 145 |  |  |         keysig = (xevent.detail, xevent.state) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 146 |  |  |         if keysig not in self._keys: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 147 |  |  |             logging.error("Received an event for an unrecognized keybind: " | 
            
                                                                                                            
                            
            
                                    
            
            
                | 148 |  |  |                           "%s, %s", xevent.detail, xevent.state) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 149 |  |  |             return | 
            
                                                                                                            
                            
            
                                    
            
            
                | 150 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 151 |  |  |         # Display a meaningful debug message | 
            
                                                                                                            
                            
            
                                    
            
            
                | 152 |  |  |         # FIXME: Only call this code if --debug | 
            
                                                                                                            
                            
            
                                    
            
            
                | 153 |  |  |         # FIXME: Proper "index" arg for keycode_to_keysym | 
            
                                                                                                            
                            
            
                                    
            
            
                | 154 |  |  |         keysym = self.xdisp.keycode_to_keysym(keysig[0], 0) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 155 |  |  |         kbstr = gtk.accelerator_name(keysym, keysig[1]) # pylint: disable=E1101 | 
            
                                                                                                            
                            
            
                                    
            
            
                | 156 |  |  |         logging.debug("Received keybind: %s", kbstr) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 157 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 158 |  |  |         # Call the associated callback | 
            
                                                                                                            
                            
            
                                    
            
            
                | 159 |  |  |         self._keys[keysig]() | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 160 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 161 |  |  |     def parse_accel(self, accel  # type: str | 
            
                                                                        
                            
            
                                    
            
            
                | 162 |  |  |                     ):  # type: (...) -> Tuple[Optional[int], Optional[int]] | 
            
                                                                        
                            
            
                                    
            
            
                | 163 |  |  |         """Convert an accelerator string into the form XGrabKey needs.""" | 
            
                                                                        
                            
            
                                    
            
            
                | 164 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 165 |  |  |         keysym, modmask = gtk.accelerator_parse(accel) | 
            
                                                                        
                            
            
                                    
            
            
                | 166 |  |  |         if not gtk.accelerator_valid(keysym, modmask):  # pylint: disable=E1101 | 
            
                                                                        
                            
            
                                    
            
            
                | 167 |  |  |             logging.error("Invalid keybinding: %s", accel) | 
            
                                                                        
                            
            
                                    
            
            
                | 168 |  |  |             return None, None | 
            
                                                                        
                            
            
                                    
            
            
                | 169 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 170 |  |  |         if modmask > 2**16 - 1: | 
            
                                                                        
                            
            
                                    
            
            
                | 171 |  |  |             logging.error("Modifier out of range for XGrabKey " | 
            
                                                                        
                            
            
                                    
            
            
                | 172 |  |  |                           "(int(modmask) > 65535). " | 
            
                                                                        
                            
            
                                    
            
            
                | 173 |  |  |                           "Did you use <Super> instead of <Mod4>?") | 
            
                                                                        
                            
            
                                    
            
            
                | 174 |  |  |             return None, None | 
            
                                                                        
                            
            
                                    
            
            
                | 175 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 176 |  |  |         # Convert to what XGrabKey expects | 
            
                                                                        
                            
            
                                    
            
            
                | 177 |  |  |         keycode = self.xdisp.keysym_to_keycode(keysym) | 
            
                                                                        
                            
            
                                    
            
            
                | 178 |  |  |         if isinstance(modmask, gtk.gdk.ModifierType): | 
            
                                                                        
                            
            
                                    
            
            
                | 179 |  |  |             modmask = modmask.real | 
            
                                                                        
                            
            
                                    
            
            
                | 180 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 181 |  |  |         return keycode, modmask | 
            
                                                                                                            
                            
            
                                    
            
            
                | 182 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 183 |  |  |     @staticmethod | 
            
                                                                                                            
                            
            
                                    
            
            
                | 184 |  |  |     def _vary_modmask(modmask, ignored): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 185 |  |  |         # type: (int, Sequence[int]) -> Iterator[int] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 186 |  |  |         """Generate all possible variations on C{modmask} that need to be | 
            
                                                                                                            
                            
            
                                    
            
            
                | 187 |  |  |         taken into consideration if we can't properly ignore the modifiers in | 
            
                                                                                                            
                            
            
                                    
            
            
                | 188 |  |  |         C{ignored}. (Typically NumLock and CapsLock) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 189 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 190 |  |  |         @param modmask: A bitfield to be combinatorically grown. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 191 |  |  |         @param ignored: Modifiers to be combined with C{modmask}. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 192 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 193 |  |  |         @type modmask: C{int} or C{gtk.gdk.ModifierType} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 194 |  |  |         @type ignored: C{list(int)} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 195 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 196 |  |  |         @rtype: generator of C{type(modmask)} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 197 |  |  |         """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 198 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 199 |  |  |         for ignored in powerset(ignored): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 200 |  |  |             imask = reduce(lambda x, y: x | y, ignored, 0) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 201 |  |  |             yield modmask | imask | 
            
                                                                                                            
                            
            
                                    
            
            
                | 202 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 203 |  |  | def init(modmask,   # type: Optional[str] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 204 |  |  |          mappings,  # type: Dict[str, CommandCB] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 205 |  |  |          commands,  # type: CommandRegistry | 
            
                                                                                                            
                            
            
                                    
            
            
                | 206 |  |  |          winman     # type: WindowManager | 
            
                                                                                                            
                            
            
                                    
            
            
                | 207 |  |  |          ):         # type: (...) -> Optional[KeyBinder] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 208 |  |  |     """Initialize the keybinder and bind the requested mappings""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 209 |  |  |     # Allow modmask to be empty for keybinds which don't share a common prefix | 
            
                                                                                                            
                            
            
                                    
            
            
                | 210 |  |  |     if not modmask or modmask.lower() == 'none': | 
            
                                                                                                            
                            
            
                                    
            
            
                | 211 |  |  |         modmask = '' | 
            
                                                                                                            
                            
            
                                    
            
            
                | 212 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 213 |  |  |     try: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 214 |  |  |         keybinder = KeyBinder() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 215 |  |  |     except XInitError as err: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 216 |  |  |         logging.error("%s", err) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 217 |  |  |         return None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 218 |  |  |     else: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 219 |  |  |         # TODO: Take a mapping dict with pre-modmasked keys | 
            
                                                                                                            
                            
            
                                    
            
            
                | 220 |  |  |         #       and pre-closured commands | 
            
                                                                                                            
                            
            
                                    
            
            
                | 221 |  |  |         for key, func in mappings.items(): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 222 |  |  |             def call(func=func): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 223 |  |  |                 """Closure to resolve `func` and call it on a | 
            
                                                                                                            
                            
            
                                    
            
            
                | 224 |  |  |                    `WindowManager` instance""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 225 |  |  |                 commands.call(func, winman) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 226 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 227 |  |  |             keybinder.bind(modmask + key, call) | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 228 |  |  |     return keybinder | 
            
                                                        
            
                                    
            
            
                | 229 |  |  |  |