class InputEventSource(EventSource, XIDeviceEventLogger): """ Setup and handle GTK or XInput device events. """ def __init__(self): # There is only button-release to subscribe to currently, # as this is all CSButtonRemapper needs to detect the end of a click. EventSource.__init__(self, ["button-release"]) XIDeviceEventLogger.__init__(self) self._gtk_handler_ids = None self._device_manager = None self._master_device = None # receives enter/leave events self._master_device_id = None # for convenience/performance self._slave_devices = None # receive pointer and touch events self._slave_device_ids = None # for convenience/performance self._xi_grab_active = False self._xi_grab_events_selected = False self._xi_event_handled = False self._touch_active = set() # set of id(XIDevice/GdkX11DeviceXI2) # For devices not contained here only # pointer events are considered. # Wacom devices with enabled gestures never # become touch-active, i.e. they don't # generate touch events. self.connect("realize", self._on_realize_event) self.connect("unrealize", self._on_unrealize_event) def cleanup(self): self._register_gtk_events(False) self._register_xinput_events(False) def _clear_touch_active(self): self._touch_active = set() def set_device_touch_active(self, device): """ Mark source device as actively receiving touch events """ self._touch_active.add(id(device)) def is_device_touch_active(self, device): """ Mark source device as actively receiving touch events """ return id(device) in self._touch_active def _on_realize_event(self, user_data): self.handle_realize_event() def _on_unrealize_event(self, user_data): self.handle_unrealize_event() def handle_realize_event(self): # register events in derived class pass def handle_unrealize_event(self): self.register_input_events(False) def grab_xi_pointer(self, active): """ Tell the xi event source a drag operation has started (ended) and we want to receive events of the whole screen. """ self._xi_grab_active = active # release simulated grab of slave device when the drag operation ends if not active and \ self._xi_grab_events_selected and \ self._device_manager: self._select_xi_grab_events(False) def set_xi_event_handled(self, handled): """ Tell the xi event source to stop/continue processing of handlers for the current event. """ self._xi_event_handled = handled def register_input_events(self, register, use_gtk=False): self._register_gtk_events(False) self._register_xinput_events(False) self._clear_touch_active() if register: if use_gtk: self._register_gtk_events(True) else: if not self._register_xinput_events(True): _logger.warning( "XInput event source failed to initialize, " "falling back to GTK.") self._register_gtk_events(True) def _register_gtk_events(self, register): """ Setup GTK event handling """ if register: event_mask = Gdk.EventMask.BUTTON_PRESS_MASK | \ Gdk.EventMask.BUTTON_RELEASE_MASK | \ Gdk.EventMask.POINTER_MOTION_MASK | \ Gdk.EventMask.LEAVE_NOTIFY_MASK | \ Gdk.EventMask.ENTER_NOTIFY_MASK if self._touch_events_enabled: event_mask |= Gdk.EventMask.TOUCH_MASK self.add_events(event_mask) self._gtk_handler_ids = [ self.connect("button-press-event", self._on_button_press_event), self.connect("button_release_event", self._on_button_release_event), self.connect("motion-notify-event", self._on_motion_event), self.connect("enter-notify-event", self._on_enter_notify), self.connect("leave-notify-event", self._on_leave_notify), self.connect("touch-event", self._on_touch_event), ] else: if self._gtk_handler_ids: for id in self._gtk_handler_ids: self.disconnect(id) self._gtk_handler_ids = None def _register_xinput_events(self, register): """ Setup XInput event handling """ success = True if register: self._device_manager = XIDeviceManager() if self._device_manager.is_valid(): self._device_manager.connect("device-event", self._on_device_event) self._device_manager.connect("device-grab", self._on_device_grab) self._device_manager.connect("devices-updated", self._on_devices_updated) self.select_xinput_devices() else: success = False self._device_manager = None else: if self._device_manager: self._device_manager.disconnect("device-event", self._on_device_event) self._device_manager.disconnect("device-grab", self._on_device_grab) self._device_manager.disconnect("devices-updated", self._on_devices_updated) if self._master_device: device = self._master_device try: self._device_manager.unselect_events(self, device) except Exception as ex: _logger.warning("Failed to unselect events for device " "{id}: {ex}".format(id=device.id, ex=ex)) self._master_device = None self._master_device_id = None if self._slave_devices: for device in self._slave_devices: try: self._device_manager.unselect_events(self, device) except Exception as ex: _logger.warning("Failed to unselect events for device " "{id}: {ex}".format(id=device.id, ex=ex)) self._slave_devices = None self._slave_device_ids = None return success def select_xinput_devices(self): """ Select pointer devices and their events we want to listen to. """ # Select events of the master pointer. # Enter/leave events aren't supported by the slaves. event_mask = XIEventMask.EnterMask | \ XIEventMask.LeaveMask device = self._device_manager.get_client_pointer() _logger.info("listening to XInput master: {}" \ .format((device.name, device.id, device.get_config_string()))) try: self._device_manager.select_events(self, device, event_mask) except Exception as ex: _logger.warning("Failed to select events for device " "{id}: {ex}".format(id=device.id, ex=ex)) self._master_device = device self._master_device_id = device.id # Select events of all attached (non-floating) slave pointers. event_mask = XIEventMask.ButtonPressMask | \ XIEventMask.ButtonReleaseMask | \ XIEventMask.EnterMask | \ XIEventMask.LeaveMask | \ XIEventMask.MotionMask if self._touch_events_enabled: event_mask |= XIEventMask.TouchMask devices = self._device_manager.get_client_pointer_attached_slaves() _logger.info("listening to XInput slaves: {}" \ .format([(d.name, d.id, d.get_config_string()) \ for d in devices])) for device in devices: try: self._device_manager.select_events(self, device, event_mask) except Exception as ex: _logger.warning("Failed to select events for device " "{id}: {ex}".format(id=device.id, ex=ex)) self._slave_devices = devices self._slave_device_ids = [d.id for d in devices] def _select_xi_grab_events(self, select): """ Select root window events for simulating a pointer grab. Only called when a drag was initiated, e.g. when moving/resizing the keyboard. """ if select: event_mask = XIEventMask.ButtonReleaseMask | \ XIEventMask.MotionMask for device in self._slave_devices: try: self._device_manager.select_events(None, device, event_mask) except Exception as ex: _logger.warning("Failed to select root events for device " "{id}: {ex}".format(id=device.id, ex=ex)) else: for device in self._slave_devices: try: self._device_manager.unselect_events(None, device) except Exception as ex: _logger.warning( "Failed to unselect root events for device " "{id}: {ex}".format(id=device.id, ex=ex)) self._xi_grab_events_selected = select def _on_device_grab(self, device, event): self.select_xinput_devices() def _on_devices_updated(self): self._clear_touch_active() def _on_device_event(self, event): """ Handler for XI2 events. """ event_type = event.xi_type device_id = event.device_id log = self._log_event_stub if _logger.isEnabledFor(logging.DEBUG) and \ event_type != XIEventType.Motion and \ event_type != XIEventType.TouchUpdate: self._log_device_event(event) log = self.log_event # re-select devices on changes to the device hierarchy if event_type in XIEventType.HierarchyEvents or \ event_type == XIEventType.DeviceChanged: self.select_xinput_devices() return log("_on_device_event1") if event_type == XIEventType.KeyPress or \ event_type == XIEventType.KeyRelease: return # check device_id, discard duplicate and unknown events if event_type == XIEventType.Enter or \ event_type == XIEventType.Leave: log("_on_device_event2 {} {}", device_id, self._master_device_id) # enter/leave are only expected from the master device if not device_id == self._master_device_id: log("_on_device_event3") return else: # all other pointer/touch events have to come from slaves log("_on_device_event4 {} {}", event.device_id, self._slave_device_ids) if not event.device_id in self._slave_device_ids: log("_on_device_event5") return # bail if the window isn't realized yet win = self.get_window() if not win: log("_on_device_event6") return # scale coordinates in response to changes to # org.gnome.desktop.interface scaling-factor try: scale = win.get_scale_factor() # from Gdk 3.10 log("_on_device_event7 {}", scale) if scale and scale != 1.0: scale = 1.0 / scale event.x = event.x * scale event.y = event.y * scale event.x_root = event.x_root * scale event.y_root = event.y_root * scale except AttributeError: pass # Slaves aren't grabbed for moving/resizing when simulating a drag # operation (drag click button), or when multiple slave devices are # involved (one for button press, another for motion). # -> Simulate pointer grab, select root events we can track even # outside the keyboard window. # None of these problems are assumed to exist for touch devices. log("_on_device_event8 {}", self._xi_grab_active) if self._xi_grab_active and \ (event_type == XIEventType.Motion or event_type == XIEventType.ButtonRelease): if not self._xi_grab_events_selected: self._select_xi_grab_events(True) log("_on_device_event9") # We only get root window coordinates for root window events, # so convert them to our target window's coordinates. rx, ry = win.get_root_coords(0, 0) event.x = event.x_root - rx event.y = event.y_root - ry else: # Is self the hit window? # We need this only for the multi-touch case with open # long press popup, e.g. while shift is held down with # one finger, touching anything in a long press popup must # not also affect the keyboard below. xid_event = event.xid_event xid_win = self.get_xid() log("_on_device_event10 {} {}", xid_event, xid_win) if xid_event != 0 and \ xid_event != xid_win: log("_on_device_event11") return # Dispatch events self._xi_event_handled = False if event_type == XIEventType.Motion: self._on_motion_event(self, event) elif event_type == XIEventType.TouchUpdate or \ event_type == XIEventType.TouchBegin or \ event_type == XIEventType.TouchEnd: self._on_touch_event(self, event) elif event_type == XIEventType.ButtonPress: self._on_button_press_event(self, event) elif event_type == XIEventType.ButtonRelease: self._on_button_release_event(self, event) # Notify CSButtonMapper, end remapped click. if not self._xi_event_handled: EventSource.emit(self, "button-release", event) elif event_type == XIEventType.Enter: self._on_enter_notify(self, event) elif event_type == XIEventType.Leave: self._on_leave_notify(self, event) def _log_device_event(self, event): if not event.xi_type in [XIEventType.TouchUpdate, XIEventType.Motion]: self.log_event("Device event: dev_id={} src_id={} xi_type={} " "xid_event={}({}) x={} y={} x_root={} y_root={} " "button={} state={} sequence={}" "".format( event.device_id, event.source_id, event.xi_type, event.xid_event, self.get_xid(), event.x, event.y, event.x_root, event.y_root, event.button, event.state, event.sequence, )) device = event.get_source_device() self.log_event("Source device: " + str(device)) @staticmethod def log_event(msg, *args): _logger.event(msg.format(*args)) @staticmethod def _log_event_stub(msg, *args): pass
class InputEventSource(EventSource, XIDeviceEventLogger): """ Setup and handle GTK or XInput device events. """ def __init__(self): # There is only button-release to subscribe to currently, # as this is all CSButtonRemapper needs to detect the end of a click. EventSource.__init__(self, ["button-release"]) XIDeviceEventLogger.__init__(self) self._gtk_handler_ids = None self._device_manager = None self._master_device = None # receives enter/leave events self._master_device_id = None # for convenience/performance self._slave_devices = None # receive pointer and touch events self._slave_device_ids = None # for convenience/performance self._xi_grab_active = False self._xi_grab_events_selected = False self._xi_event_handled = False self._touch_active = set() # set of id(XIDevice/GdkX11DeviceXI2) # For devices not contained here only # pointer events are considered. # Wacom devices with enabled gestures never # become touch-active, i.e. they don't # generate touch events. self.connect("realize", self._on_realize_event) self.connect("unrealize", self._on_unrealize_event) def cleanup(self): self._register_gtk_events(False) self._register_xinput_events(False) def _clear_touch_active(self): self._touch_active = set() def set_device_touch_active(self, device): """ Mark source device as actively receiving touch events """ self._touch_active.add(id(device)) def is_device_touch_active(self, device): """ Mark source device as actively receiving touch events """ return id(device) in self._touch_active def _on_realize_event(self, user_data): self.handle_realize_event() def _on_unrealize_event(self, user_data): self.handle_unrealize_event() def handle_realize_event(self): # register events in derived class pass def handle_unrealize_event(self): self.register_input_events(False) def grab_xi_pointer(self, active): """ Tell the xi event source a drag operation has started (ended) and we want to receive events of the whole screen. """ self._xi_grab_active = active # release simulated grab of slave device when the drag operation ends if not active and \ self._xi_grab_events_selected and \ self._device_manager: self._select_xi_grab_events(False) def set_xi_event_handled(self, handled): """ Tell the xi event source to stop/continue processing of handlers for the current event. """ self._xi_event_handled = handled def register_input_events(self, register, use_gtk = False): self._register_gtk_events(False) self._register_xinput_events(False) self._clear_touch_active() if register: if use_gtk: self._register_gtk_events(True) else: if not self._register_xinput_events(True): _logger.warning("XInput event source failed to initialize, " "falling back to GTK.") self._register_gtk_events(True) def _register_gtk_events(self, register): """ Setup GTK event handling """ if register: event_mask = Gdk.EventMask.BUTTON_PRESS_MASK | \ Gdk.EventMask.BUTTON_RELEASE_MASK | \ Gdk.EventMask.POINTER_MOTION_MASK | \ Gdk.EventMask.LEAVE_NOTIFY_MASK | \ Gdk.EventMask.ENTER_NOTIFY_MASK if self._touch_events_enabled: event_mask |= Gdk.EventMask.TOUCH_MASK self.add_events(event_mask) self._gtk_handler_ids = [ self.connect("button-press-event", self._on_button_press_event), self.connect("button_release_event", self._on_button_release_event), self.connect("motion-notify-event", self._on_motion_event), self.connect("enter-notify-event", self._on_enter_notify), self.connect("leave-notify-event", self._on_leave_notify), self.connect("touch-event", self._on_touch_event), ] else: if self._gtk_handler_ids: for id in self._gtk_handler_ids: self.disconnect(id) self._gtk_handler_ids = None def _register_xinput_events(self, register): """ Setup XInput event handling """ success = True if register: self._device_manager = XIDeviceManager() if self._device_manager.is_valid(): self._device_manager.connect("device-event", self._on_device_event) self._device_manager.connect("device-grab", self._on_device_grab) self._device_manager.connect("devices-updated", self._on_devices_updated) self.select_xinput_devices() else: success = False self._device_manager = None else: if self._device_manager: self._device_manager.disconnect("device-event", self._on_device_event) self._device_manager.disconnect("device-grab", self._on_device_grab) self._device_manager.disconnect("devices-updated", self._on_devices_updated) if self._master_device: device = self._master_device try: self._device_manager.unselect_events(self, device) except Exception as ex: _logger.warning("Failed to unselect events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) self._master_device = None self._master_device_id = None if self._slave_devices: for device in self._slave_devices: try: self._device_manager.unselect_events(self, device) except Exception as ex: _logger.warning("Failed to unselect events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) self._slave_devices = None self._slave_device_ids = None return success def select_xinput_devices(self): """ Select pointer devices and their events we want to listen to. """ # Select events of the master pointer. # Enter/leave events aren't supported by the slaves. event_mask = XIEventMask.EnterMask | \ XIEventMask.LeaveMask device = self._device_manager.get_client_pointer() _logger.info("listening to XInput master: {}" \ .format((device.name, device.id, device.get_config_string()))) try: self._device_manager.select_events(self, device, event_mask) except Exception as ex: _logger.warning("Failed to select events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) self._master_device = device self._master_device_id = device.id # Select events of all attached (non-floating) slave pointers. event_mask = XIEventMask.ButtonPressMask | \ XIEventMask.ButtonReleaseMask | \ XIEventMask.EnterMask | \ XIEventMask.LeaveMask | \ XIEventMask.MotionMask if self._touch_events_enabled: event_mask |= XIEventMask.TouchMask devices = self._device_manager.get_client_pointer_attached_slaves() _logger.info("listening to XInput slaves: {}" \ .format([(d.name, d.id, d.get_config_string()) \ for d in devices])) for device in devices: try: self._device_manager.select_events(self, device, event_mask) except Exception as ex: _logger.warning("Failed to select events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) self._slave_devices = devices self._slave_device_ids = [d.id for d in devices] def _select_xi_grab_events(self, select): """ Select root window events for simulating a pointer grab. Only called when a drag was initiated, e.g. when moving/resizing the keyboard. """ if select: event_mask = XIEventMask.ButtonReleaseMask | \ XIEventMask.MotionMask for device in self._slave_devices: try: self._device_manager.select_events(None, device, event_mask) except Exception as ex: _logger.warning("Failed to select root events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) else: for device in self._slave_devices: try: self._device_manager.unselect_events(None, device) except Exception as ex: _logger.warning("Failed to unselect root events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) self._xi_grab_events_selected = select def _on_device_grab(self, device, event): self.select_xinput_devices() def _on_devices_updated(self): self._clear_touch_active() def _on_device_event(self, event): """ Handler for XI2 events. """ event_type = event.xi_type device_id = event.device_id log = self._log_event_stub if _logger.isEnabledFor(logging.DEBUG) and \ event_type != XIEventType.Motion and \ event_type != XIEventType.TouchUpdate: self._log_device_event(event) log = self.log_event # re-select devices on changes to the device hierarchy if event_type in XIEventType.HierarchyEvents or \ event_type == XIEventType.DeviceChanged: self.select_xinput_devices() return log("_on_device_event1") if event_type == XIEventType.KeyPress or \ event_type == XIEventType.KeyRelease: return # check device_id, discard duplicate and unknown events if event_type == XIEventType.Enter or \ event_type == XIEventType.Leave: log("_on_device_event2 {} {}", device_id, self._master_device_id) # enter/leave are only expected from the master device if not device_id == self._master_device_id: log("_on_device_event3") return else: # all other pointer/touch events have to come from slaves log("_on_device_event4 {} {}", event.device_id, self._slave_device_ids) if not event.device_id in self._slave_device_ids: log("_on_device_event5") return # bail if the window isn't realized yet win = self.get_window() if not win: log("_on_device_event6") return # scale coordinates in response to changes to # org.gnome.desktop.interface scaling-factor try: scale = win.get_scale_factor() # from Gdk 3.10 log("_on_device_event7 {}", scale) if scale and scale != 1.0: scale = 1.0 / scale event.x = event.x * scale event.y = event.y * scale event.x_root = event.x_root * scale event.y_root = event.y_root * scale except AttributeError: pass # Slaves aren't grabbed for moving/resizing when simulating a drag # operation (drag click button), or when multiple slave devices are # involved (one for button press, another for motion). # -> Simulate pointer grab, select root events we can track even # outside the keyboard window. # None of these problems are assumed to exist for touch devices. log("_on_device_event8 {}", self._xi_grab_active) if self._xi_grab_active and \ (event_type == XIEventType.Motion or \ event_type == XIEventType.ButtonRelease): if not self._xi_grab_events_selected: self._select_xi_grab_events(True) log("_on_device_event9") # We only get root window coordinates for root window events, # so convert them to our target window's coordinates. rx, ry = win.get_root_coords(0, 0) event.x = event.x_root - rx event.y = event.y_root - ry else: # Is self the hit window? # We need this only for the multi-touch case with open # long press popup, e.g. while shift is held down with # one finger, touching anything in a long press popup must # not also affect the keyboard below. xid_event = event.xid_event xid_win = win.get_xid() log("_on_device_event10 {} {}", xid_event, xid_win) if xid_event != 0 and \ xid_event != xid_win: log("_on_device_event11") return # Dispatch events self._xi_event_handled = False if event_type == XIEventType.Motion: self._on_motion_event(self, event) elif event_type == XIEventType.TouchUpdate or \ event_type == XIEventType.TouchBegin or \ event_type == XIEventType.TouchEnd: self._on_touch_event(self, event) elif event_type == XIEventType.ButtonPress: self._on_button_press_event(self, event) elif event_type == XIEventType.ButtonRelease: self._on_button_release_event(self, event) # Notify CSButtonMapper, end remapped click. if not self._xi_event_handled: EventSource.emit(self, "button-release", event) elif event_type == XIEventType.Enter: self._on_enter_notify(self, event) elif event_type == XIEventType.Leave: self._on_leave_notify(self, event) def _log_device_event(self, event): win = self.get_window() if not event.xi_type in [ XIEventType.TouchUpdate, XIEventType.Motion]: self.log_event("Device event: dev_id={} src_id={} xi_type={} " "xid_event={}({}) x={} y={} x_root={} y_root={} " "button={} state={} sequence={}" "".format(event.device_id, event.source_id, event.xi_type, event.xid_event, win.get_xid() if win else 0, event.x, event.y, event.x_root, event.y_root, event.button, event.state, event.sequence, ) ) device = event.get_source_device() self.log_event("Source device: " + str(device)) @staticmethod def log_event(msg, *args): _logger.event(msg.format(*args)) @staticmethod def _log_event_stub(msg, *args): pass
class InputEventSource: """ Setup and handle GTK or XInput device events. """ def __init__(self): self._gtk_handler_ids = None self._device_manager = None self._master_device = None # receives enter/leave events self._master_device_id = None # for convenience/speed only self._slave_devices = None # receive pointer and touch events self._slave_device_ids = None # for convenience/speed only self._xi_drag_active = 0 self._xi_drag_events_selected = False self.connect("realize", self._on_realize_event) self.connect("unrealize", self._on_unrealize_event) def cleanup(self): self._register_gtk_events(False) self._register_xinput_events(False) def _on_realize_event(self, user_data): self.handle_realize_event() def _on_unrealize_event(self, user_data): self.handle_unrealize_event() def handle_realize_event(self): # register events in derived class pass def handle_unrealize_event(self): self.register_input_events(False) def set_xi_drag_active(self, active): """ Tell the xi event source a drag operation has started/ended. """ self._xi_drag_active = active # release slave device grab when the simulated grab ends if not active and \ self._xi_drag_events_selected and \ self._device_manager: self._select_xi_drag_events(False) def register_input_events(self, register, use_gtk = False): self._register_gtk_events(False) self._register_xinput_events(False) if register: if use_gtk: self._register_gtk_events(True) else: if not self._register_xinput_events(True): _logger.warning("XInput event source failed to initialize, " "falling back to GTK.") self._register_gtk_events(True) def _register_gtk_events(self, register): """ Setup GTK event handling """ if register: event_mask = Gdk.EventMask.BUTTON_PRESS_MASK | \ Gdk.EventMask.BUTTON_RELEASE_MASK | \ Gdk.EventMask.POINTER_MOTION_MASK | \ Gdk.EventMask.LEAVE_NOTIFY_MASK | \ Gdk.EventMask.ENTER_NOTIFY_MASK if self._touch_events_enabled: event_mask |= Gdk.EventMask.TOUCH_MASK self.add_events(event_mask) self._gtk_handler_ids = [ self.connect("button-press-event", self._on_button_press_event), self.connect("button_release_event", self._on_button_release_event), self.connect("motion-notify-event", self._on_motion_event), self.connect("enter-notify-event", self._on_enter_notify), self.connect("leave-notify-event", self._on_leave_notify), self.connect("touch-event", self._on_touch_event), ] else: if self._gtk_handler_ids: for id in self._gtk_handler_ids: self.disconnect(id) self._gtk_handler_ids = None def _register_xinput_events(self, register): """ Setup XInput event handling """ success = True if register: self._device_manager = XIDeviceManager() if self._device_manager.is_valid(): self._device_manager.connect("device-event", self._device_event_handler) self.select_xinput_devices() else: success = False self._device_manager = None else: if self._device_manager: self._device_manager.disconnect("device-event", self._device_event_handler) if self._master_device: device = self._master_device try: self._device_manager.unselect_events(self, device) except Exception as ex: _logger.warning("Failed to unselect events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) self._master_device = None self._master_device_id = None if self._slave_devices: for device in self._slave_devices: try: self._device_manager.unselect_events(self, device) except Exception as ex: _logger.warning("Failed to unselect events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) self._slave_devices = None self._slave_device_ids = None return success def select_xinput_devices(self): """ Select events of all slave pointer devices. """ # select events for the master pointer event_mask = XIEventMask.EnterMask | \ XIEventMask.LeaveMask device = self._device_manager.get_client_pointer() _logger.info("listening to XInput master: {}" \ .format((device.name, device.id, device.get_config_string()))) try: self._device_manager.select_events(self, device, event_mask) except Exception as ex: _logger.warning("Failed to select events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) self._master_device = device self._master_device_id = device.id # select events for all attached (non-floating) slave pointers event_mask = XIEventMask.ButtonPressMask | \ XIEventMask.ButtonReleaseMask | \ XIEventMask.EnterMask | \ XIEventMask.LeaveMask | \ XIEventMask.MotionMask if self._touch_events_enabled: event_mask |= XIEventMask.TouchMask devices = self._device_manager.get_client_slave_pointer_devices() devices = [d for d in devices if not d.is_floating()] _logger.info("listening to XInput devices: {}" \ .format([(d.name, d.id, d.get_config_string()) \ for d in devices])) for device in devices: try: self._device_manager.select_events(self, device, event_mask) except Exception as ex: _logger.warning("Failed to select events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) self._slave_devices = devices self._slave_device_ids = [d.id for d in devices] def _select_xi_drag_events(self, select): """ Select events for the root window to simulate a pointer grab. Only relevant when a drag was initiated, i.e. moving/resizing the keyboard. """ if select: event_mask = XIEventMask.ButtonReleaseMask | \ XIEventMask.MotionMask for device in self._slave_devices: try: self._device_manager.select_events(None, device, event_mask) except Exception as ex: _logger.warning("Failed to select root events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) else: for device in self._slave_devices: try: self._device_manager.unselect_events(None, device) except Exception as ex: _logger.warning("Failed to unselect root events for device " "{id}: {ex}" .format(id = device.id, ex = ex)) self._xi_drag_events_selected = select def _device_event_handler(self, event): """ Handler for XI2 events. """ event_type = event.xi_type device_id = event.device_id if _logger.isEnabledFor(logging.DEBUG): self._log_event(event) # re-select devices on changes to the device hierarchy if event_type == XIEventType.DeviceAdded or \ event_type == XIEventType.DeviceRemoved or \ event_type == XIEventType.DeviceChanged: self.select_xinput_devices() return # check device_id, stop duplicate and unknown events if event_type == XIEventType.Enter or \ event_type == XIEventType.Leave: # enter/leave are only expected from the master device if not device_id == self._master_device_id: return else: # all other pointer/touch events have to come from slaves if not event.device_id in self._slave_device_ids: return win = self.get_window() if not win: return # Slaves aren't grabbed for moving/resizing when simulating a drag # operation (drag click button), or multiple slave devices are # involved (one for button press, another for motion). # None of these problems are assumed to exist for touch devices. # -> select root events we can track even outside the keyboard window. if self._xi_drag_active and \ (event_type == XIEventType.Motion or \ event_type == XIEventType.ButtonRelease): if not self._xi_drag_events_selected: self._select_xi_drag_events(True) #print(self._xi_drag_active, event_type, event.state, event.device_id, self._master_device_id, event.xid_event) # We don't get window coordinates for root window events, # so convert them from the root window coordinates. rx, ry = win.get_root_coords(0, 0) event.x = event.x_root - rx event.y = event.y_root - ry else: # Is self the hit window? # We need this only for the multi touch case with open # long press popup, e.g. while shift is held down, touching # anything in a long press popup must not also affect # the keyboard below. xid_event = event.xid_event if xid_event != 0 and \ xid_event != win.get_xid(): return # Dispatch events if event_type == XIEventType.Motion: self._on_motion_event(self, event) elif event_type == XIEventType.TouchUpdate or \ event_type == XIEventType.TouchBegin or \ event_type == XIEventType.TouchEnd: self._on_touch_event(self, event) elif event_type == XIEventType.ButtonPress: self._on_button_press_event(self, event) elif event_type == XIEventType.ButtonRelease: self._on_button_release_event(self, event) elif event_type == XIEventType.Enter: self._on_enter_notify(self, event) elif event_type == XIEventType.Leave: self._on_leave_notify(self, event) def _log_event(self, event): win = self.get_window() if not event.xi_type in [ XIEventType.TouchUpdate, XIEventType.Motion]: _logger.debug("Device event: dev_id={} src_id={} xi_type={} " "xid_event={}({}) x={} y={} x_root={} y_root={} " "button={} state={} sequence={}" "".format(event.device_id, event.source_id, event.xi_type, event.xid_event, win.get_xid() if win else 0, event.x, event.y, event.x_root, event.y_root, event.button, event.state, event.sequence, ) )