def record_context(dpy, ev_ranges, dev_ranges): ev_ranges = coalesce_ranges(ev_ranges) dev_ranges = coalesce_ranges(dev_ranges) specs = max(len(ev_ranges), len(dev_ranges)) range_specs = (xlib.POINTER(xlib.XRecordRange) * specs)() for i in range(specs): range_specs[i] = xlib.XRecordAllocRange() if len(ev_ranges) > i: range_specs[i].contents.delivered_events.first = ev_ranges[i][0] range_specs[i].contents.delivered_events.last = ev_ranges[i][1] if len(dev_ranges) > i: range_specs[i].contents.device_events.first = dev_ranges[i][0] range_specs[i].contents.device_events.last = dev_ranges[i][1] rec_ctx = xlib.XRecordCreateContext( dpy, 0, xlib.byref(xlib.c_ulong(xlib.XRecordAllClients)), 1, range_specs, specs) for i in range(specs): xlib.XFree(range_specs[i]) return rec_ctx
def record_context(dpy, ev_range, dev_range): range_spec = xlib.XRecordAllocRange() range_spec.contents.delivered_events.first = ev_range[0] range_spec.contents.delivered_events.last = ev_range[1] range_spec.contents.device_events.first = dev_range[0] range_spec.contents.device_events.last = dev_range[1] rec_ctx = xlib.XRecordCreateContext( dpy, 0, xlib.byref(xlib.c_ulong(xlib.XRecordAllClients)), 1, xlib.byref(range_spec), 1) xlib.XFree(range_spec) return rec_ctx
def _kbd_process(self, ev): if ev.type == xlib.ClientMessage and \ ev.xclient.message_type == self.custom_atom: if ev.xclient.data[0] in [xlib.FocusIn, xlib.FocusOut]: # we do not keep track of multiple XICs, just reset xic = xlib.Xutf8ResetIC(self._kbd_replay_xic) if xic is not None: xlib.XFree(xic) return elif ev.type in [xlib.KeyPress, xlib.KeyRelease]: # fake keyboard event data for XFilterEvent ev.xkey.send_event = False ev.xkey.window = self.replay_win # pass _all_ events to XFilterEvent filtered = bool(xlib.XFilterEvent(ev, 0)) if ev.type == xlib.KeyRelease and \ phantom_release(self.replay_dpy, ev.xkey): return if ev.type not in [xlib.KeyPress, xlib.KeyRelease]: return # generate new keyboard event data = KeyData() data.filtered = filtered data.pressed = (ev.type == xlib.KeyPress) data.repeated = (ev.type == self._kbd_last_ev.type and ev.xkey.state == self._kbd_last_ev.xkey.state and ev.xkey.keycode == self._kbd_last_ev.xkey.keycode) data.mods_mask = ev.xkey.state data.keycod = ev.xkey.keycode self._event_modifiers(ev.xkey, data) if not data.filtered and data.pressed and self.kbd_translate: self._event_keypress(ev.xkey, data) else: self._event_lookup(ev.xkey, data) self._event_processed(data) self._kbd_last_ev = ev
def run(self): self.control_dpy = xlib.XOpenDisplay(None) xlib.XSynchronize(self.control_dpy, True) self.record_ctx = record_context(self.control_dpy, [xlib.FocusIn, xlib.FocusOut], [xlib.KeyPress, xlib.KeyRelease]) record_dpy = xlib.XOpenDisplay(None) record_fd = xlib.XConnectionNumber(record_dpy) # note that we never ever map the window self.replay_dpy = xlib.XOpenDisplay(None) self.custom_atom = xlib.XInternAtom(self.replay_dpy, "SCREENKEY", False) replay_fd = xlib.XConnectionNumber(self.replay_dpy) self.replay_win = create_replay_window(self.replay_dpy) if self.compose: style = xlib.XIMPreeditNothing | xlib.XIMStatusNothing else: style = xlib.XIMPreeditNone | xlib.XIMStatusNone # TODO: implement preedit callbacks for on-the-spot composition # (this would fix focus-stealing for the global IM state) replay_xim = xlib.XOpenIM(self.replay_dpy, None, None, None) if not replay_xim: raise Exception("Cannot initialize input method") self.replay_xic = xlib.XCreateIC(replay_xim, xlib.XNClientWindow, self.replay_win, xlib.XNInputStyle, style, None) xlib.XSetICFocus(self.replay_xic) # we need to keep the proc_ref alive proc_ref = record_enable(record_dpy, self.record_ctx, self._event_received) last_ev = xlib.XEvent() self.lock.release() while True: with self.lock: if self.stopped: break r_fd = [] if xlib.XPending(record_dpy): r_fd.append(record_fd) if xlib.XPending(self.replay_dpy): r_fd.append(replay_fd) if not r_fd: r_fd, _, _ = select.select([record_fd, replay_fd], [], []) if not r_fd: break if record_fd in r_fd: xlib.XRecordProcessReplies(record_dpy) xlib.XFlush(self.replay_dpy) if replay_fd in r_fd: ev = xlib.XEvent() xlib.XNextEvent(self.replay_dpy, xlib.byref(ev)) if ev.type == xlib.ClientMessage and \ ev.xclient.message_type == self.custom_atom: if ev.xclient.data[0] in [xlib.FocusIn, xlib.FocusOut]: # We do not keep track of multiple XICs, just reset xic = xlib.Xutf8ResetIC(self.replay_xic) if xic is not None: xlib.XFree(xic) continue elif ev.type in [xlib.KeyPress, xlib.KeyRelease]: ev.xkey.send_event = False ev.xkey.window = self.replay_win filtered = bool(xlib.XFilterEvent(ev, 0)) if ev.type not in [xlib.KeyPress, xlib.KeyRelease]: continue if ev.type == xlib.KeyRelease and \ phantom_release(self.replay_dpy, ev.xkey): continue data = KeyData() data.filtered = filtered data.pressed = (ev.type == xlib.KeyPress) data.repeated = (ev.type == last_ev.type and \ ev.xkey.state == last_ev.xkey.state and \ ev.xkey.keycode == last_ev.xkey.keycode) data.mods_mask = ev.xkey.state self._event_modifiers(ev.xkey, data) if not data.filtered and data.pressed and self.translate: self._event_keypress(ev.xkey, data) else: self._event_lookup(ev.xkey, data) self._event_processed(data) last_ev = ev xlib.XRecordFreeContext(self.control_dpy, self.record_ctx) xlib.XCloseDisplay(self.control_dpy) xlib.XCloseDisplay(record_dpy) del proc_ref xlib.XDestroyIC(self.replay_xic) xlib.XCloseIM(replay_xim) xlib.XDestroyWindow(self.replay_dpy, self.replay_win) xlib.XCloseDisplay(self.replay_dpy)