Example #1
0
class GlobalKeyListener(EventSource):
    """
    Singleton class that listens to key presses not bound to a specific window.
    """

    _event_names = ("key-press", "key-release", "devices-updated")

    def __new__(cls, *args, **kwargs):
        """
        Singleton magic.
        """
        if not hasattr(cls, "self"):
            cls.self = object.__new__(cls, *args, **kwargs)
            cls.self.construct()
        return cls.self

    def __init__(self):
        """
        Called multiple times, don't use this.
        """
        pass

    def construct(self):
        """
        Singleton constructor, runs only once.
        """
        EventSource.__init__(self, self._event_names)

        self._device_manager = None
        self._keyboard_slave_devices = None

    def cleanup(self):
        """
        Should not need to be called as long connect and disconnect calls
        are balanced.
        """
        EventSource.cleanup(self)
        self._register_input_events_xinput(False)

    def connect(self, event_name, callback):
        EventSource.connect(self, event_name, callback)
        self._update_registered_events()

    def disconnect(self, event_name, callback):
        had_listeners = self.has_listeners()

        EventSource.disconnect(self, event_name, callback)
        self._update_registered_events()

        # help debugging disconnecting events on exit
        if had_listeners and not self.has_listeners():
            _logger.info("all listeners disconnected")

    def _update_registered_events(self):
        self._register_input_events(self.has_listeners())

    def _register_input_events(self, register):
        """
        Only for XInput currently. Extend this if we find out how to
        do it on other, non-X platforms (or with Gtk).
        """
        if not self._register_input_events_xinput(register):
            if register:
                _logger.warning("XInput event source failed to initialize.")

    def _register_input_events_xinput(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)
                self._unselect_xinput_devices()
                self._device_manager = None

        return success

    def _select_xinput_devices(self):
        """ Select keyboard devices and the events we want to listen to. """

        self._unselect_xinput_devices()

        event_mask = (XIEventMask.KeyPressMask | XIEventMask.KeyReleaseMask)

        devices = self._device_manager.get_client_keyboard_attached_slaves()
        _logger.info("listening to keyboard devices: {}".format([
            (d.name, d.id, d.get_config_string()) for d in devices
        ]))
        for device in devices:
            try:
                self._device_manager.select_events(None, device, event_mask)
            except Exception as ex:
                _logger.warning("Failed to select events for device "
                                "{id}: {ex}".format(id=device.id, ex=ex))
        self._keyboard_slave_devices = devices

    def _unselect_xinput_devices(self):
        if self._keyboard_slave_devices:
            for device in self._keyboard_slave_devices:
                try:
                    self._device_manager.unselect_events(None, device)
                except Exception as ex:
                    _logger.warning("Failed to unselect events for device "
                                    "{id}: {ex}".format(id=device.id, ex=ex))
            self._keyboard_slave_devices = None

    def _on_device_grab(self, device, event):
        """ Someone grabbed/relased a device. Update our device list. """
        self._select_xinput_devices()

    def _on_devices_updated(self):
        # re-select devices on changes to the device hierarchy
        self._select_xinput_devices()

        self.emit("devices-updated")

    def _on_device_event(self, event):
        """
        Handler for XI2 events.
        """
        event_type = event.xi_type

        if event_type == XIEventType.KeyPress:
            self.emit("key-press", event)

        elif event_type == XIEventType.KeyRelease:
            self.emit("key-release", event)

    def get_key_event_string(self, event, message=""):
        device = event.get_source_device()
        device_name = device.name if device else "None"
        return ((message + "global key-{}, keycode={}, keyval={} "
                 "from device '{}' ({})").format(
                     "press" if event.xi_type == XIEventType.KeyPress else
                     "release", event.keycode, event.keyval, device_name,
                     event.source_id))
Example #2
0
class AutoHide(EventSource):
    """
    Hide Onboard when a physical keyboard is being used.
    """
    def __init__(self, keyboard):
        # 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"])

        self._keyboard = keyboard
        self._device_manager = None
        self._keyboard_slave_devices = None

    def cleanup(self):
        self._register_xinput_events(False)

    def is_enabled(self):
        return self._device_manager is not None

    def enable(self, enable, use_gtk=False):
        self.register_input_events(enable, use_gtk)

    def register_input_events(self, register, use_gtk=False):
        self._register_xinput_events(False)
        if register:
            if not use_gtk:  # can't do this with gtk yet
                if not self._register_xinput_events(True):
                    _logger.warning(
                        "XInput event source failed to initialize, "
                        "falling back to GTK.")

    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._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._unselect_xinput_devices()
                self._device_manager = None

        return success

    def _select_xinput_devices(self):
        """ Select keyboard devices and the events we want to listen to. """

        self._unselect_xinput_devices()

        event_mask = XIEventMask.KeyPressMask | \
                     XIEventMask.KeyReleaseMask

        devices = self._device_manager.get_client_keyboard_attached_slaves()
        _logger.info("listening to keyboard devices: {}".format([
            (d.name, d.id, d.get_config_string()) for d in devices
        ]))
        for device in devices:
            try:
                self._device_manager.select_events(None, device, event_mask)
            except Exception as ex:
                _logger.warning("Failed to select events for device "
                                "{id}: {ex}".format(id=device.id, ex=ex))
        self._keyboard_slave_devices = devices

    def _unselect_xinput_devices(self):
        if self._keyboard_slave_devices:
            for device in self._keyboard_slave_devices:
                try:
                    self._device_manager.unselect_events(None, device)
                except Exception as ex:
                    _logger.warning("Failed to unselect events for device "
                                    "{id}: {ex}".format(id=device.id, ex=ex))
            self._keyboard_slave_devices = None

    def _on_device_grab(self, device, event):
        """ Someone grabbed/relased a device. Update our device list. """
        self._select_xinput_devices()

    def _on_device_event(self, event):
        """
        Handler for XI2 events.
        """
        event_type = event.xi_type

        # 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

        if event_type == XIEventType.KeyPress or \
           event_type == XIEventType.KeyRelease:

            if not self._keyboard.is_auto_show_paused():
                if _logger.isEnabledFor(logging.INFO):
                    device = event.get_source_device()
                    device_name = device.name if device else "None"
                    _logger.info("Hiding keyboard and pausing "
                                 "auto-show due to physical key-{} "
                                 "{} from device '{}' ({})".format(
                                     "press" if event_type
                                     == XIEventType.KeyPress else "release",
                                     event.keyval, device_name,
                                     event.source_id))

                if self._keyboard.is_visible():
                    if config.are_word_suggestions_enabled():
                        self._keyboard.discard_changes()

                    self._keyboard.set_visible(False)

            duration = config.auto_show.hide_on_key_press_pause
            if duration:
                if duration < 0.0:  # negative means auto-hide is off
                    duration = None
                self._keyboard.pause_auto_show(duration)

            return