Пример #1
0
class RazerDaemon(DBusService):
    """
    Daemon class

    This class sets up the main run loop which serves DBus messages. The logger is initialised
    in this module as well as finding and initialising devices.

    Serves the following functions via DBus
    * getDevices - Returns a list of serial numbers
    * enableTurnOffOnScreensaver - Starts/Continues the run loop on the screensaver thread
    * disableTurnOffOnScreensaver - Pauses the run loop on the screensaver thread
    """

    BUS_PATH = 'org.razer'

    def __init__(self,
                 verbose=False,
                 log_dir=None,
                 console_log=False,
                 run_dir=None,
                 config_file=None,
                 test_dir=None):

        # Check if process exists
        exit_code = subprocess.call(['pgrep', 'razer-service'],
                                    stderr=subprocess.DEVNULL,
                                    stdout=subprocess.DEVNULL)

        if exit_code == 0:
            print("Daemon already exists. Please stop that one.",
                  file=sys.stderr)
            exit(-1)

        setproctitle.setproctitle('razer-service')

        # Expanding ~ as python doesnt do it by default, also creating dirs if needed
        if log_dir is not None:
            log_dir = os.path.expanduser(log_dir)
            os.makedirs(log_dir, mode=0o750, exist_ok=True)
        if run_dir is not None:
            run_dir = os.path.expanduser(run_dir)
            os.makedirs(run_dir, mode=0o750, exist_ok=True)
        if config_file is not None:
            config_file = os.path.expanduser(config_file)
            os.makedirs(os.path.dirname(config_file),
                        mode=0o750,
                        exist_ok=True)

        self._test_dir = test_dir
        self._data_dir = run_dir
        self._config_file = config_file
        self._config = configparser.ConfigParser()
        self.read_config(config_file)

        # Setup DBus to use gobject main loop
        dbus.mainloop.glib.threads_init()
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        DBusService.__init__(self, self.BUS_PATH, '/org/razer')

        self._init_signals()
        self._main_loop = GObject.MainLoop()

        # Listen for input events from udev
        self._udev_context = Context()
        udev_monitor = Monitor.from_netlink(self._udev_context)
        udev_monitor.filter_by(subsystem='input')
        self._udev_observer = MonitorObserver(udev_monitor,
                                              callback=self._udev_input_event,
                                              name='device-monitor')

        # Logging
        logging_level = logging.INFO
        if verbose or self._config.getboolean('General', 'verbose_logging'):
            logging_level = logging.DEBUG

        self.logger = logging.getLogger('razer')
        self.logger.setLevel(logging_level)
        formatter = logging.Formatter(
            '%(asctime)s | %(name)-30s | %(levelname)-8s | %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S')
        # Dont propagate to default logger
        self.logger.propagate = 0

        if console_log:
            console_logger = logging.StreamHandler()
            console_logger.setLevel(logging_level)
            console_logger.setFormatter(formatter)
            self.logger.addHandler(console_logger)

        if log_dir is not None:
            log_file = os.path.join(log_dir, 'razer.log')
            file_logger = logging.handlers.RotatingFileHandler(
                log_file, maxBytes=16777216, backupCount=10)  # 16MiB
            file_logger.setLevel(logging_level)
            file_logger.setFormatter(formatter)
            self.logger.addHandler(file_logger)

        self.logger.info("Initialising Daemon (v%s). Pid: %d", __version__,
                         os.getpid())

        # Setup screensaver thread
        self._screensaver_thread = ScreensaverThread(
            self,
            active=self._config.getboolean('Startup',
                                           'devices_off_on_screensaver'))
        self._screensaver_thread.start()

        self._razer_devices = DeviceCollection()
        self._load_devices(first_run=True)

        # Add DBus methods
        self.logger.info("Adding razer.devices.getDevices method to DBus")
        self.add_dbus_method('razer.devices',
                             'getDevices',
                             self.get_serial_list,
                             out_signature='as')
        self.logger.info(
            "Adding razer.devices.enableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'enableTurnOffOnScreensaver',
                             self.enable_turn_off_on_screensaver)
        self.logger.info(
            "Adding razer.devices.disableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'disableTurnOffOnScreensaver',
                             self.disable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.syncEffects method to DBus")
        self.add_dbus_method('razer.devices',
                             'syncEffects',
                             self.sync_effects,
                             in_signature='b')
        self.logger.info("Adding razer.daemon.version method to DBus")
        self.add_dbus_method('razer.daemon',
                             'version',
                             self.version,
                             out_signature='s')
        self.logger.info("Adding razer.daemon.stop method to DBus")
        self.add_dbus_method('razer.daemon', 'stop', self.stop)

        # TODO remove
        self.sync_effects(
            self._config.getboolean('Startup', 'sync_effects_enabled'))
        # TODO ======

    def _init_signals(self):
        """
        Heinous hack to properly handle signals on the mainloop. Necessary
        if we want to use the mainloop run() functionality.
        """
        def signal_action(signum):
            """
            Action to take when a signal is trapped
            """
            self.quit(signum)

        def idle_handler():
            """
            GLib idle handler to propagate signals
            """
            GLib.idle_add(signal_action, priority=GLib.PRIORITY_HIGH)

        def handler(*args):
            """
            Unix signal handler
            """
            signal_action(args[0])

        def install_glib_handler(sig):
            """
            Choose a compatible method and install the handler
            """
            unix_signal_add = None

            if hasattr(GLib, "unix_signal_add"):
                unix_signal_add = GLib.unix_signal_add
            elif hasattr(GLib, "unix_signal_add_full"):
                unix_signal_add = GLib.unix_signal_add_full

            if unix_signal_add:
                unix_signal_add(GLib.PRIORITY_HIGH, sig, handler, sig)
            else:
                print("Can't install GLib signal handler!")

        for sig in signal.SIGINT, signal.SIGTERM, signal.SIGHUP:
            signal.signal(sig, idle_handler)
            GLib.idle_add(install_glib_handler,
                          sig,
                          priority=GLib.PRIORITY_HIGH)

    def read_config(self, config_file):
        """
        Read in the config file and set the defaults

        :param config_file: Config file
        :type config_file: str or None
        """
        # Generate sections as trying to access a value even if a default exists will die if the section does not
        for section in ('General', 'Startup', 'Statistics'):
            self._config[section] = {}

        self._config['DEFAULT'] = {
            'verbose_logging': True,
            'sync_effects_enabled': True,
            'devices_off_on_screensaver': True,
            'key_statistics': False,
        }

        if config_file is not None and os.path.exists(config_file):
            self._config.read(config_file)

    def enable_turn_off_on_screensaver(self):
        """
        Enable the turning off of devices when the screensaver is active
        """
        self._screensaver_thread.active = True

    def disable_turn_off_on_screensaver(self):
        """
        Disable the turning off of devices when the screensaver is active
        """
        self._screensaver_thread.active = False

    def version(self):
        """
        Get the daemon version

        :return: Version string
        :rtype: str
        """
        return __version__

    def suspend_devices(self):
        """
        Suspend all devices
        """
        for device in self._razer_devices:
            device.dbus.suspend_device()

    def resume_devices(self):
        """
        Resume all devices
        """
        for device in self._razer_devices:
            device.dbus.resume_device()

    def get_serial_list(self):
        """
        Get list of devices serials
        """
        serial_list = self._razer_devices.serials()
        self.logger.debug('DBus called get_serial_list')
        return serial_list

    def sync_effects(self, enabled):
        """
        Sync the effects across the devices

        :param enabled: True to sync effects
        :type enabled: bool
        """
        # Todo perhaps move logic to device collection
        for device in self._razer_devices.devices:
            device.dbus.effect_sync = enabled

    def _load_devices(self, first_run=False):
        """
        Go through supported devices and load them

        Loops through the available hardware classes, loops through
        each device in the system and adds it if needs be.
        """
        classes = razer_daemon.hardware.get_device_classes()

        if first_run:
            # Just some pretty output
            max_name_len = max([len(cls.__name__) for cls in classes]) + 2
            for cls in classes:
                format_str = 'Loaded device specification: {0:-<' + str(
                    max_name_len) + '} ({1:04x}:{2:04X})'

                self.logger.debug(
                    format_str.format(cls.__name__ + ' ', cls.USB_VID,
                                      cls.USB_PID))

        for device in self._udev_context.list_devices(subsystem='hid'):
            device_number = 0

            for device_class in classes:
                if device.sys_name in self._razer_devices:
                    continue

                if device_class.match(
                        device.sys_name, device.parent.sys_path
                ):  # Check it matches sys/ ID format and has device_type file
                    self.logger.info('Found device.%d: %s', device_number,
                                     device.sys_name)
                    razer_device = device_class(device.sys_path,
                                                device_number,
                                                self._config,
                                                testing=self._test_dir
                                                is not None)

                    # Wireless devices sometimes dont listen
                    count = 0
                    while count < 3:
                        # Loop to get serial, exit early if it gets one
                        device_serial = razer_device.get_serial()
                        if len(device_serial) > 0:
                            break
                        count += 1
                    else:
                        logging.warning(
                            "Could not get serial for device {0}. Skipping".
                            format(device.sys_name))
                        continue

                    self._razer_devices.add(device.sys_name, device_serial,
                                            razer_device)

                    device_number += 1

    def _remove_devices(self):
        """
        Go through the list of current devices and if they no longer exist then remove them
        """
        hid_devices = [
            dev.sys_name
            for dev in self._udev_context.list_devices(subsystem='hid')
        ]
        devices_to_remove = [
            dev for dev in self._razer_devices if dev not in hid_devices
        ]
        for device in devices_to_remove:
            if self._test_dir is not None:
                # Remove from DBus
                device.dbus.remove_from_connection()

            # Remove device
            self.logger.warning("Device %s is missing. Removing from DBus",
                                device.device_id)
            del self._razer_devices[device.device_id]

    def _udev_input_event(self, device):
        self.logger.debug('Device event [%s]: %s', device.action,
                          device.device_path)
        if device.action == 'add':
            self._load_devices()
        elif device.action == 'remove':
            self._remove_devices()

    def run(self):
        """
        Run the daemon
        """
        self.logger.info('Serving DBus')

        # Start listening for device changes
        self._udev_observer.start()

        # Start the mainloop
        try:
            self._main_loop.run()
        except KeyboardInterrupt:
            self.logger.debug('Shutting down')

    def stop(self):
        """
        Wrapper for quit
        """
        self.quit(None)

    def quit(self, signum):
        """
        Quit by stopping the main loop, observer, and screensaver thread
        """
        # pylint: disable=unused-argument
        if signum is None:
            self.logger.info('Stopping daemon.')
        else:
            self.logger.info('Stopping daemon on signal %d', signum)

        self._main_loop.quit()

        # Stop udev monitor
        self._udev_observer.send_stop()

        # Stop screensaver
        self._screensaver_thread.shutdown = True
        self._screensaver_thread.join(timeout=2)
        if self._screensaver_thread.is_alive():
            self.logger.warning('Could not stop the screensaver thread')

        for device in self._razer_devices:
            device.dbus.close()
Пример #2
0
    def __init__(self,
                 verbose=False,
                 log_dir=None,
                 console_log=False,
                 run_dir=None,
                 config_file=None,
                 test_dir=None):

        # Check if process exists
        exit_code = subprocess.call(['pgrep', 'razer-service'],
                                    stderr=subprocess.DEVNULL,
                                    stdout=subprocess.DEVNULL)

        if exit_code == 0:
            print("Daemon already exists. Please stop that one.",
                  file=sys.stderr)
            exit(-1)

        setproctitle.setproctitle('razer-service')

        # Expanding ~ as python doesnt do it by default, also creating dirs if needed
        if log_dir is not None:
            log_dir = os.path.expanduser(log_dir)
            os.makedirs(log_dir, mode=0o750, exist_ok=True)
        if run_dir is not None:
            run_dir = os.path.expanduser(run_dir)
            os.makedirs(run_dir, mode=0o750, exist_ok=True)
        if config_file is not None:
            config_file = os.path.expanduser(config_file)
            os.makedirs(os.path.dirname(config_file),
                        mode=0o750,
                        exist_ok=True)

        self._test_dir = test_dir
        self._data_dir = run_dir
        self._config_file = config_file
        self._config = configparser.ConfigParser()
        self.read_config(config_file)

        # Setup DBus to use gobject main loop
        dbus.mainloop.glib.threads_init()
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        DBusService.__init__(self, self.BUS_PATH, '/org/razer')

        self._init_signals()
        self._main_loop = GObject.MainLoop()

        # Listen for input events from udev
        self._udev_context = Context()
        udev_monitor = Monitor.from_netlink(self._udev_context)
        udev_monitor.filter_by(subsystem='input')
        self._udev_observer = MonitorObserver(udev_monitor,
                                              callback=self._udev_input_event,
                                              name='device-monitor')

        # Logging
        logging_level = logging.INFO
        if verbose or self._config.getboolean('General', 'verbose_logging'):
            logging_level = logging.DEBUG

        self.logger = logging.getLogger('razer')
        self.logger.setLevel(logging_level)
        formatter = logging.Formatter(
            '%(asctime)s | %(name)-30s | %(levelname)-8s | %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S')
        # Dont propagate to default logger
        self.logger.propagate = 0

        if console_log:
            console_logger = logging.StreamHandler()
            console_logger.setLevel(logging_level)
            console_logger.setFormatter(formatter)
            self.logger.addHandler(console_logger)

        if log_dir is not None:
            log_file = os.path.join(log_dir, 'razer.log')
            file_logger = logging.handlers.RotatingFileHandler(
                log_file, maxBytes=16777216, backupCount=10)  # 16MiB
            file_logger.setLevel(logging_level)
            file_logger.setFormatter(formatter)
            self.logger.addHandler(file_logger)

        self.logger.info("Initialising Daemon (v%s). Pid: %d", __version__,
                         os.getpid())

        # Setup screensaver thread
        self._screensaver_thread = ScreensaverThread(
            self,
            active=self._config.getboolean('Startup',
                                           'devices_off_on_screensaver'))
        self._screensaver_thread.start()

        self._razer_devices = DeviceCollection()
        self._load_devices(first_run=True)

        # Add DBus methods
        self.logger.info("Adding razer.devices.getDevices method to DBus")
        self.add_dbus_method('razer.devices',
                             'getDevices',
                             self.get_serial_list,
                             out_signature='as')
        self.logger.info(
            "Adding razer.devices.enableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'enableTurnOffOnScreensaver',
                             self.enable_turn_off_on_screensaver)
        self.logger.info(
            "Adding razer.devices.disableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'disableTurnOffOnScreensaver',
                             self.disable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.syncEffects method to DBus")
        self.add_dbus_method('razer.devices',
                             'syncEffects',
                             self.sync_effects,
                             in_signature='b')
        self.logger.info("Adding razer.daemon.version method to DBus")
        self.add_dbus_method('razer.daemon',
                             'version',
                             self.version,
                             out_signature='s')
        self.logger.info("Adding razer.daemon.stop method to DBus")
        self.add_dbus_method('razer.daemon', 'stop', self.stop)

        # TODO remove
        self.sync_effects(
            self._config.getboolean('Startup', 'sync_effects_enabled'))
Пример #3
0
    def __init__(self, verbose=False, log_dir=None, console_log=False, run_dir=None, config_file=None, test_dir=None):

        # Check if process exists
        exit_code = subprocess.call(['pgrep', 'razer-service'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)

        if exit_code == 0:
            print("Daemon already exists. Please stop that one.", file=sys.stderr)
            exit(-1)

        setproctitle.setproctitle('razer-service')

        # Expanding ~ as python doesnt do it by default, also creating dirs if needed
        if log_dir is not None:
            log_dir = os.path.expanduser(log_dir)
            os.makedirs(log_dir, mode=0o750, exist_ok=True)
        if run_dir is not None:
            run_dir = os.path.expanduser(run_dir)
            os.makedirs(run_dir, mode=0o750, exist_ok=True)
        if config_file is not None:
            config_file = os.path.expanduser(config_file)
            os.makedirs(os.path.dirname(config_file), mode=0o750, exist_ok=True)

        self._test_dir = test_dir
        self._data_dir = run_dir
        self._config_file = config_file
        self._config = configparser.ConfigParser()
        self.read_config(config_file)

        # Setup DBus to use gobject main loop
        dbus.mainloop.glib.threads_init()
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        DBusService.__init__(self, self.BUS_PATH, '/org/razer')

        self._init_signals()
        self._main_loop = GObject.MainLoop()

        # Listen for input events from udev
        self._udev_context = Context()
        udev_monitor = Monitor.from_netlink(self._udev_context)
        udev_monitor.filter_by(subsystem='input')
        self._udev_observer = MonitorObserver(udev_monitor, callback=self._udev_input_event, name='device-monitor')

        # Logging
        logging_level = logging.INFO
        if verbose or self._config.getboolean('General', 'verbose_logging'):
            logging_level = logging.DEBUG

        self.logger = logging.getLogger('razer')
        self.logger.setLevel(logging_level)
        formatter = logging.Formatter('%(asctime)s | %(name)-30s | %(levelname)-8s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
        # Dont propagate to default logger
        self.logger.propagate = 0

        if console_log:
            console_logger = logging.StreamHandler()
            console_logger.setLevel(logging_level)
            console_logger.setFormatter(formatter)
            self.logger.addHandler(console_logger)

        if log_dir is not None:
            log_file = os.path.join(log_dir, 'razer.log')
            file_logger = logging.handlers.RotatingFileHandler(log_file, maxBytes=16777216, backupCount=10) # 16MiB
            file_logger.setLevel(logging_level)
            file_logger.setFormatter(formatter)
            self.logger.addHandler(file_logger)

        self.logger.info("Initialising Daemon (v%s). Pid: %d", __version__, os.getpid())

        # Setup screensaver thread
        self._screensaver_thread = ScreensaverThread(self, active=self._config.getboolean('Startup', 'devices_off_on_screensaver'))
        self._screensaver_thread.start()

        self._razer_devices = DeviceCollection()
        self._load_devices(first_run=True)

        # Add DBus methods
        self.logger.info("Adding razer.devices.getDevices method to DBus")
        self.add_dbus_method('razer.devices', 'getDevices', self.get_serial_list, out_signature='as')
        self.logger.info("Adding razer.devices.enableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'enableTurnOffOnScreensaver', self.enable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.disableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'disableTurnOffOnScreensaver', self.disable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.syncEffects method to DBus")
        self.add_dbus_method('razer.devices', 'syncEffects', self.sync_effects, in_signature='b')
        self.logger.info("Adding razer.daemon.version method to DBus")
        self.add_dbus_method('razer.daemon', 'version', self.version, out_signature='s')
        self.logger.info("Adding razer.daemon.stop method to DBus")
        self.add_dbus_method('razer.daemon', 'stop', self.stop)

        # TODO remove
        self.sync_effects(self._config.getboolean('Startup', 'sync_effects_enabled'))
Пример #4
0
class RazerDaemon(DBusService):
    """
    Daemon class

    This class sets up the main run loop which serves DBus messages. The logger is initialised
    in this module as well as finding and initialising devices.

    Serves the following functions via DBus
    * getDevices - Returns a list of serial numbers
    * enableTurnOffOnScreensaver - Starts/Continues the run loop on the screensaver thread
    * disableTurnOffOnScreensaver - Pauses the run loop on the screensaver thread
    """

    BUS_PATH = 'org.razer'

    def __init__(self, verbose=False, log_dir=None, console_log=False, run_dir=None, config_file=None, test_dir=None):

        # Check if process exists
        exit_code = subprocess.call(['pgrep', 'razer-service'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)

        if exit_code == 0:
            print("Daemon already exists. Please stop that one.", file=sys.stderr)
            exit(-1)

        setproctitle.setproctitle('razer-service')

        # Expanding ~ as python doesnt do it by default, also creating dirs if needed
        if log_dir is not None:
            log_dir = os.path.expanduser(log_dir)
            os.makedirs(log_dir, mode=0o750, exist_ok=True)
        if run_dir is not None:
            run_dir = os.path.expanduser(run_dir)
            os.makedirs(run_dir, mode=0o750, exist_ok=True)
        if config_file is not None:
            config_file = os.path.expanduser(config_file)
            os.makedirs(os.path.dirname(config_file), mode=0o750, exist_ok=True)

        self._test_dir = test_dir
        self._data_dir = run_dir
        self._config_file = config_file
        self._config = configparser.ConfigParser()
        self.read_config(config_file)

        # Setup DBus to use gobject main loop
        dbus.mainloop.glib.threads_init()
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        DBusService.__init__(self, self.BUS_PATH, '/org/razer')

        self._init_signals()
        self._main_loop = GObject.MainLoop()

        # Listen for input events from udev
        self._udev_context = Context()
        udev_monitor = Monitor.from_netlink(self._udev_context)
        udev_monitor.filter_by(subsystem='input')
        self._udev_observer = MonitorObserver(udev_monitor, callback=self._udev_input_event, name='device-monitor')

        # Logging
        logging_level = logging.INFO
        if verbose or self._config.getboolean('General', 'verbose_logging'):
            logging_level = logging.DEBUG

        self.logger = logging.getLogger('razer')
        self.logger.setLevel(logging_level)
        formatter = logging.Formatter('%(asctime)s | %(name)-30s | %(levelname)-8s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
        # Dont propagate to default logger
        self.logger.propagate = 0

        if console_log:
            console_logger = logging.StreamHandler()
            console_logger.setLevel(logging_level)
            console_logger.setFormatter(formatter)
            self.logger.addHandler(console_logger)

        if log_dir is not None:
            log_file = os.path.join(log_dir, 'razer.log')
            file_logger = logging.handlers.RotatingFileHandler(log_file, maxBytes=16777216, backupCount=10) # 16MiB
            file_logger.setLevel(logging_level)
            file_logger.setFormatter(formatter)
            self.logger.addHandler(file_logger)

        self.logger.info("Initialising Daemon (v%s). Pid: %d", __version__, os.getpid())

        # Setup screensaver thread
        self._screensaver_thread = ScreensaverThread(self, active=self._config.getboolean('Startup', 'devices_off_on_screensaver'))
        self._screensaver_thread.start()

        self._razer_devices = DeviceCollection()
        self._load_devices(first_run=True)

        # Add DBus methods
        self.logger.info("Adding razer.devices.getDevices method to DBus")
        self.add_dbus_method('razer.devices', 'getDevices', self.get_serial_list, out_signature='as')
        self.logger.info("Adding razer.devices.enableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'enableTurnOffOnScreensaver', self.enable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.disableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'disableTurnOffOnScreensaver', self.disable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.syncEffects method to DBus")
        self.add_dbus_method('razer.devices', 'syncEffects', self.sync_effects, in_signature='b')
        self.logger.info("Adding razer.daemon.version method to DBus")
        self.add_dbus_method('razer.daemon', 'version', self.version, out_signature='s')
        self.logger.info("Adding razer.daemon.stop method to DBus")
        self.add_dbus_method('razer.daemon', 'stop', self.stop)

        # TODO remove
        self.sync_effects(self._config.getboolean('Startup', 'sync_effects_enabled'))
        # TODO ======

    def _init_signals(self):
        """
        Heinous hack to properly handle signals on the mainloop. Necessary
        if we want to use the mainloop run() functionality.
        """
        def signal_action(signum):
            """
            Action to take when a signal is trapped
            """
            self.quit(signum)

        def idle_handler():
            """
            GLib idle handler to propagate signals
            """
            GLib.idle_add(signal_action, priority=GLib.PRIORITY_HIGH)

        def handler(*args):
            """
            Unix signal handler
            """
            signal_action(args[0])

        def install_glib_handler(sig):
            """
            Choose a compatible method and install the handler
            """
            unix_signal_add = None

            if hasattr(GLib, "unix_signal_add"):
                unix_signal_add = GLib.unix_signal_add
            elif hasattr(GLib, "unix_signal_add_full"):
                unix_signal_add = GLib.unix_signal_add_full

            if unix_signal_add:
                unix_signal_add(GLib.PRIORITY_HIGH, sig, handler, sig)
            else:
                print("Can't install GLib signal handler!")

        for sig in signal.SIGINT, signal.SIGTERM, signal.SIGHUP:
            signal.signal(sig, idle_handler)
            GLib.idle_add(install_glib_handler, sig, priority=GLib.PRIORITY_HIGH)

    def read_config(self, config_file):
        """
        Read in the config file and set the defaults

        :param config_file: Config file
        :type config_file: str or None
        """
        # Generate sections as trying to access a value even if a default exists will die if the section does not
        for section in ('General', 'Startup', 'Statistics'):
            self._config[section] = {}

        self._config['DEFAULT'] = {
            'verbose_logging': True,
            'sync_effects_enabled': True,
            'devices_off_on_screensaver': True,
            'key_statistics': False,
        }

        if config_file is not None and os.path.exists(config_file):
            self._config.read(config_file)

    def enable_turn_off_on_screensaver(self):
        """
        Enable the turning off of devices when the screensaver is active
        """
        self._screensaver_thread.active = True

    def disable_turn_off_on_screensaver(self):
        """
        Disable the turning off of devices when the screensaver is active
        """
        self._screensaver_thread.active = False

    def version(self):
        """
        Get the daemon version

        :return: Version string
        :rtype: str
        """
        return __version__

    def suspend_devices(self):
        """
        Suspend all devices
        """
        for device in self._razer_devices:
            device.dbus.suspend_device()

    def resume_devices(self):
        """
        Resume all devices
        """
        for device in self._razer_devices:
            device.dbus.resume_device()

    def get_serial_list(self):
        """
        Get list of devices serials
        """
        serial_list = self._razer_devices.serials()
        self.logger.debug('DBus called get_serial_list')
        return serial_list

    def sync_effects(self, enabled):
        """
        Sync the effects across the devices

        :param enabled: True to sync effects
        :type enabled: bool
        """
        # Todo perhaps move logic to device collection
        for device in self._razer_devices.devices:
            device.dbus.effect_sync = enabled

    def _load_devices(self, first_run=False):
        """
        Go through supported devices and load them

        Loops through the available hardware classes, loops through
        each device in the system and adds it if needs be.
        """
        classes = razer_daemon.hardware.get_device_classes()

        if first_run:
            # Just some pretty output
            max_name_len = max([len(cls.__name__) for cls in classes]) + 2
            for cls in classes:
                format_str = 'Loaded device specification: {0:-<' + str(max_name_len) + '} ({1:04x}:{2:04X})'

                self.logger.debug(format_str.format(cls.__name__ + ' ', cls.USB_VID, cls.USB_PID))

        for device in self._udev_context.list_devices(subsystem='hid'):
            device_number = 0

            for device_class in classes:
                if device.sys_name in self._razer_devices:
                    continue

                if device_class.match(device.sys_name, device.parent.sys_path):  # Check it matches sys/ ID format and has device_type file
                    self.logger.info('Found device.%d: %s', device_number, device.sys_name)
                    razer_device = device_class(device.sys_path, device_number, self._config, testing=self._test_dir is not None)

                    # Wireless devices sometimes dont listen
                    count = 0
                    while count < 3:
                        # Loop to get serial, exit early if it gets one
                        device_serial = razer_device.get_serial()
                        if len(device_serial) > 0:
                            break
                        count += 1
                    else:
                        logging.warning("Could not get serial for device {0}. Skipping".format(device.sys_name))
                        continue

                    self._razer_devices.add(device.sys_name, device_serial, razer_device)

                    device_number += 1

    def _remove_devices(self):
        """
        Go through the list of current devices and if they no longer exist then remove them
        """
        hid_devices = [dev.sys_name for dev in self._udev_context.list_devices(subsystem='hid')]
        devices_to_remove = [dev for dev in self._razer_devices if dev not in hid_devices]
        for device in devices_to_remove:
            if self._test_dir is not None:
                # Remove from DBus
                device.dbus.remove_from_connection()

            # Remove device
            self.logger.warning("Device %s is missing. Removing from DBus", device.device_id)
            del self._razer_devices[device.device_id]

    def _udev_input_event(self, device):
        self.logger.debug('Device event [%s]: %s', device.action, device.device_path)
        if device.action == 'add':
            self._load_devices()
        elif device.action == 'remove':
            self._remove_devices()

    def run(self):
        """
        Run the daemon
        """
        self.logger.info('Serving DBus')

        # Start listening for device changes
        self._udev_observer.start()

        # Start the mainloop
        try:
            self._main_loop.run()
        except KeyboardInterrupt:
            self.logger.debug('Shutting down')

    def stop(self):
        """
        Wrapper for quit
        """
        self.quit(None)

    def quit(self, signum):
        """
        Quit by stopping the main loop, observer, and screensaver thread
        """
        # pylint: disable=unused-argument
        if signum is None:
            self.logger.info('Stopping daemon.')
        else:
            self.logger.info('Stopping daemon on signal %d', signum)

        self._main_loop.quit()

        # Stop udev monitor
        self._udev_observer.send_stop()

        # Stop screensaver
        self._screensaver_thread.shutdown = True
        self._screensaver_thread.join(timeout=2)
        if self._screensaver_thread.is_alive():
            self.logger.warning('Could not stop the screensaver thread')

        for device in self._razer_devices:
            device.dbus.close()
Пример #5
0
class RazerDaemon(DBusService):
    """
    Daemon class

    This class sets up the main run loop which serves DBus messages. The logger is initialised
    in this module as well as finding and initialising devices.

    Serves the following functions via DBus
    * getDevices - Returns a list of serial numbers
    * enableTurnOffOnScreensaver - Starts/Continues the run loop on the screensaver thread
    * disableTurnOffOnScreensaver - Pauses the run loop on the screensaver thread
    """

    BUS_PATH = 'org.razer'

    def __init__(self, verbose=False, log_dir=None, console_log=False, run_dir=None, config_file=None):

        # Check if process exists
        exit_code = subprocess.call(['pgrep', 'razerdaemon'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)

        if exit_code == 0:
            print("Daemon already exists. Please stop that one.", file=sys.stderr)
            exit(-1)

        setproctitle.setproctitle('razerdaemon')

        # Expanding ~ as python doesnt do it by default, also creating dirs if needed
        if log_dir is not None:
            log_dir = os.path.expanduser(log_dir)
            os.makedirs(log_dir, mode=0o750, exist_ok=True)
        if run_dir is not None:
            run_dir = os.path.expanduser(run_dir)
            os.makedirs(run_dir, mode=0o750, exist_ok=True)
        if config_file is not None:
            config_file = os.path.expanduser(config_file)
            os.makedirs(os.path.dirname(config_file), mode=0o750, exist_ok=True)

        self._data_dir = run_dir
        self._config_file = config_file
        self._config = configparser.ConfigParser()
        self.read_config(config_file)

        # Setup DBus to use gobject main loop
        dbus.mainloop.glib.threads_init()
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        DBusService.__init__(self, self.BUS_PATH, '/org/razer')

        self._main_loop = gi.repository.GObject.MainLoop()

        # Logging
        logging_level = logging.INFO
        if verbose or self._config.getboolean('General', 'verbose_logging'):
            logging_level = logging.DEBUG

        self.logger = logging.getLogger('razer')
        self.logger.setLevel(logging_level)
        formatter = logging.Formatter('%(asctime)s | %(name)-30s | %(levelname)-8s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
        # Dont propagate to default logger
        self.logger.propagate = 0

        if console_log:
            console_logger = logging.StreamHandler()
            console_logger.setLevel(logging_level)
            console_logger.setFormatter(formatter)
            self.logger.addHandler(console_logger)

        if log_dir is not None:
            log_file = os.path.join(log_dir, 'razer.log')
            file_logger = logging.handlers.RotatingFileHandler(log_file, maxBytes=16777216, backupCount=10) # 16MiB
            file_logger.setLevel(logging_level)
            file_logger.setFormatter(formatter)
            self.logger.addHandler(file_logger)

        self.logger.info("Initialising Daemon. Pid: %d", os.getpid())

        # Setup screensaver thread
        self._screensaver_thread = ScreensaverThread(self, active=self._config.getboolean('Startup', 'devices_off_on_screensaver'))
        self._screensaver_thread.start()

        self._razer_devices = DeviceCollection()
        self._load_devices()

        # Add DBus methods
        self.logger.info("Adding razer.devices.getDevices method to DBus")
        self.add_dbus_method('razer.devices', 'getDevices', self.get_serial_list, out_signature='as')
        self.logger.info("Adding razer.devices.enableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'enableTurnOffOnScreensaver', self.enable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.disableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'disableTurnOffOnScreensaver', self.disable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.syncEffects method to DBus")
        self.add_dbus_method('razer.devices', 'syncEffects', self.sync_effects, in_signature='b')
        self.logger.info("Adding razer.daemon.stop method to DBus")
        self.add_dbus_method('razer.daemon', 'stop', self.stop)

        # TODO remove
        self.sync_effects(self._config.getboolean('Startup', 'sync_effects_enabled'))
        # TODO ======

        # Setup quit signals
        signal.signal(signal.SIGINT, self.quit)
        signal.signal(signal.SIGTERM, self.quit)

    def read_config(self, config_file):
        """
        Read in the config file and set the defaults

        :param config_file: Config file
        :type config_file: str or None
        """
        # Generate sections as trying to access a value even if a default exists will die if the section does not
        for section in ('General', 'Startup', 'Statistics'):
            self._config[section] = {}

        self._config['DEFAULT'] = {
            'verbose_logging': True,
            'sync_effects_enabled': True,
            'devices_off_on_screensaver': True,
            'key_statistics': False,
        }

        if config_file is not None and os.path.exists(config_file):
            self._config.read(config_file)



    def enable_turn_off_on_screensaver(self):
        """
        Enable the turning off of devices when the screensaver is active
        """
        self._screensaver_thread.active = True

    def disable_turn_off_on_screensaver(self):
        """
        Disable the turning off of devices when the screensaver is active
        """
        self._screensaver_thread.active = False

    def suspend_devices(self):
        """
        Suspend all devices
        """
        for device in self._razer_devices:
            device.dbus.suspend_device()

    def resume_devices(self):
        """
        Resume all devices
        """
        for device in self._razer_devices:
            device.dbus.resume_device()

    def get_serial_list(self):
        """
        Get list of devices serials
        """
        serial_list = self._razer_devices.serials()
        self.logger.debug('DBus called get_serial_list')
        return serial_list

    def sync_effects(self, enabled):
        """
        Sync the effects across the devices

        :param enabled: True to sync effects
        :type enabled: bool
        """
        # Todo perhaps move logic to device collection
        for device in self._razer_devices.devices:
            device.dbus.effect_sync = enabled

    def _load_devices(self):
        """
        Go through supported devices and load them

        Loops through the available hardware classes, loops through
        each device in the system and adds it if needs be.
        """
        try:
            devices = os.listdir('/sys/bus/hid/devices')
        except FileNotFoundError:
            devices = []
        classes = razer_daemon.hardware.get_device_classes()

        device_number = 0
        for device_class in classes:
            for device_id in devices:
                if device_id in self._razer_devices:
                    continue

                if device_class.match(device_id):
                    self.logger.info('Found device.%d: %s', device_number, device_id)
                    device_path = os.path.join('/sys/bus/hid/devices', device_id)
                    razer_device = device_class(device_path, device_number, self._config)

                    # Wireless devices sometimes dont listen
                    count = 0
                    while count < 3:
                        # Loop to get serial, exit early if it gets one
                        device_serial = razer_device.get_serial()
                        if len(device_serial) > 0:
                            break
                        count += 1
                    else:
                        logging.warning("Could not get serial for device {0}. Skipping".format(device_id))
                        continue

                    self._razer_devices.add(device_id, device_serial, razer_device)

                    device_number += 1

    def _remove_devices(self):
        """
        Go through the list of current devices and if they no longer exist then remove them
        """
        devices_to_remove = []

        for device in self._razer_devices:
            device_path = os.path.join('/sys/bus/hid/devices', device.device_id)
            if not os.path.exists(device_path):
                # Remove from DBus
                device.dbus.remove_from_connection()
                devices_to_remove.append(device.device_id)

        for device_id in devices_to_remove:
            # Remove device
            self.logger.warning("Device %s is missing. Removing from DBus", device_id)
            del self._razer_devices[device_id]

    def run(self):
        """
        Run the daemon
        """
        self.logger.info('Serving DBus')

        # Counter for managing periodic tasks
        counter = 0

        # Can't just use mainloop.run() as that blocks and
        # then signaling exit doesn't work
        main_loop_context = self._main_loop.get_context()
        while self._main_loop is not None:
            if main_loop_context.pending():
                main_loop_context.iteration()
            else:
                time.sleep(0.001)

            if counter > DEVICE_CHECK_INTERVAL: # Time sleeps 1ms so DEVICE_CHECK_INTERVAL is in milliseconds
                self._remove_devices()
                self._load_devices()
                counter = 0
            counter += 1

    def stop(self):
        """
        Wrapper for quit
        """
        self.quit(None, None)

    def quit(self, signum, frame):
        """
        Quit by stopping the main loop and screensaver thread
        """
        # pylint: disable=unused-argument
        self.logger.info('Stopping daemon.')
        self._main_loop = None

        # Stop screensaver
        self._screensaver_thread.shutdown = True
        self._screensaver_thread.join(timeout=2)
        if self._screensaver_thread.is_alive():
            self.logger.warning('Could not stop the screensaver thread')

        for device in self._razer_devices:
            device.dbus.close()
Пример #6
0
    def __init__(self, verbose=False, log_dir=None, console_log=False, run_dir=None, config_file=None):
        self._data_dir = run_dir
        self._config_file = config_file
        self._config = configparser.ConfigParser()
        self.read_config(config_file)

        # Setup DBus to use gobject main loop
        dbus.mainloop.glib.threads_init()
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        DBusService.__init__(self, self.BUS_PATH, '/org/razer')

        self._main_loop = GObject.MainLoop()

        # Logging
        logging_level = logging.INFO
        if verbose or self._config.getboolean('General', 'verbose_logging'):
            logging_level = logging.DEBUG

        self.logger = logging.getLogger('razer')
        self.logger.setLevel(logging_level)
        formatter = logging.Formatter('%(asctime)s | %(name)-30s | %(levelname)-8s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
        # Dont propagate to default logger
        self.logger.propagate = 0

        if console_log:
            console_logger = logging.StreamHandler()
            console_logger.setLevel(logging_level)
            console_logger.setFormatter(formatter)
            self.logger.addHandler(console_logger)

        if log_dir is not None:
            log_file = os.path.join(log_dir, 'razer.log')
            file_logger = logging.handlers.RotatingFileHandler(log_file, maxBytes=16777216, backupCount=10) # 16MiB
            file_logger.setLevel(logging_level)
            file_logger.setFormatter(formatter)
            self.logger.addHandler(file_logger)

        self.logger.info("Initialising Daemon. Pid: %d", os.getpid())

        # Setup screensaver thread
        self._screensaver_thread = ScreensaverThread(self, active=self._config.getboolean('Startup', 'devices_off_on_screensaver'))
        self._screensaver_thread.start()

        self._razer_devices = DeviceCollection()
        self._load_devices()

        # Add DBus methods
        self.logger.info("Adding razer.devices.getDevices method to DBus")
        self.add_dbus_method('razer.devices', 'getDevices', self.get_serial_list, out_signature='as')
        self.logger.info("Adding razer.devices.enableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'enableTurnOffOnScreensaver', self.enable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.disableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'disableTurnOffOnScreensaver', self.disable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.syncEffects method to DBus")
        self.add_dbus_method('razer.devices', 'syncEffects', self.sync_effects, in_signature='b')
        self.logger.info("Adding razer.daemon.stop method to DBus")
        self.add_dbus_method('razer.daemon', 'stop', self.stop)

        # TODO remove
        self.sync_effects(self._config.getboolean('Startup', 'sync_effects_enabled'))
        # TODO ======

        # Setup quit signals
        signal.signal(signal.SIGINT, self.quit)
        signal.signal(signal.SIGTERM, self.quit)
Пример #7
0
class RazerDaemon(DBusService):
    """
    Daemon class

    This class sets up the main run loop which serves DBus messages. The logger is initialised
    in this module as well as finding and initialising devices.

    Serves the following functions via DBus
    * getDevices - Returns a list of serial numbers
    * enableTurnOffOnScreensaver - Starts/Continues the run loop on the screensaver thread
    * disableTurnOffOnScreensaver - Pauses the run loop on the screensaver thread
    """

    BUS_PATH = 'org.razer'

    def __init__(self, verbose=False, log_dir=None, console_log=False, run_dir=None, config_file=None, test_dir=None):

        # Check if process exists
        exit_code = subprocess.call(['pgrep', 'razerdaemon'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)

        if exit_code == 0:
            print("Daemon already exists. Please stop that one.", file=sys.stderr)
            exit(-1)

        setproctitle.setproctitle('razerdaemon')

        # Expanding ~ as python doesnt do it by default, also creating dirs if needed
        if log_dir is not None:
            log_dir = os.path.expanduser(log_dir)
            os.makedirs(log_dir, mode=0o750, exist_ok=True)
        if run_dir is not None:
            run_dir = os.path.expanduser(run_dir)
            os.makedirs(run_dir, mode=0o750, exist_ok=True)
        if config_file is not None:
            config_file = os.path.expanduser(config_file)
            os.makedirs(os.path.dirname(config_file), mode=0o750, exist_ok=True)

        self._test_dir = test_dir
        self._data_dir = run_dir
        self._config_file = config_file
        self._config = configparser.ConfigParser()
        self.read_config(config_file)

        # Setup DBus to use gobject main loop
        dbus.mainloop.glib.threads_init()
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        DBusService.__init__(self, self.BUS_PATH, '/org/razer')

        self._main_loop = gi.repository.GObject.MainLoop()

        # Logging
        logging_level = logging.INFO
        if verbose or self._config.getboolean('General', 'verbose_logging'):
            logging_level = logging.DEBUG

        self.logger = logging.getLogger('razer')
        self.logger.setLevel(logging_level)
        formatter = logging.Formatter('%(asctime)s | %(name)-30s | %(levelname)-8s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
        # Dont propagate to default logger
        self.logger.propagate = 0

        if console_log:
            console_logger = logging.StreamHandler()
            console_logger.setLevel(logging_level)
            console_logger.setFormatter(formatter)
            self.logger.addHandler(console_logger)

        if log_dir is not None:
            log_file = os.path.join(log_dir, 'razer.log')
            file_logger = logging.handlers.RotatingFileHandler(log_file, maxBytes=16777216, backupCount=10) # 16MiB
            file_logger.setLevel(logging_level)
            file_logger.setFormatter(formatter)
            self.logger.addHandler(file_logger)

        self.logger.info("Initialising Daemon (v%s). Pid: %d", __version__, os.getpid())

        # Setup screensaver thread
        self._screensaver_thread = ScreensaverThread(self, active=self._config.getboolean('Startup', 'devices_off_on_screensaver'))
        self._screensaver_thread.start()

        self._razer_devices = DeviceCollection()
        self._load_devices()

        # Add DBus methods
        self.logger.info("Adding razer.devices.getDevices method to DBus")
        self.add_dbus_method('razer.devices', 'getDevices', self.get_serial_list, out_signature='as')
        self.logger.info("Adding razer.devices.enableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'enableTurnOffOnScreensaver', self.enable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.disableTurnOffOnScreensaver method to DBus")
        self.add_dbus_method('razer.devices', 'disableTurnOffOnScreensaver', self.disable_turn_off_on_screensaver)
        self.logger.info("Adding razer.devices.syncEffects method to DBus")
        self.add_dbus_method('razer.devices', 'syncEffects', self.sync_effects, in_signature='b')
        self.logger.info("Adding razer.daemon.version method to DBus")
        self.add_dbus_method('razer.daemon', 'version', self.version, out_signature='s')
        self.logger.info("Adding razer.daemon.stop method to DBus")
        self.add_dbus_method('razer.daemon', 'stop', self.stop)

        # TODO remove
        self.sync_effects(self._config.getboolean('Startup', 'sync_effects_enabled'))
        # TODO ======

        # Setup quit signals
        signal.signal(signal.SIGINT, self.quit)
        signal.signal(signal.SIGTERM, self.quit)

    def read_config(self, config_file):
        """
        Read in the config file and set the defaults

        :param config_file: Config file
        :type config_file: str or None
        """
        # Generate sections as trying to access a value even if a default exists will die if the section does not
        for section in ('General', 'Startup', 'Statistics'):
            self._config[section] = {}

        self._config['DEFAULT'] = {
            'verbose_logging': True,
            'sync_effects_enabled': True,
            'devices_off_on_screensaver': True,
            'key_statistics': False,
        }

        if config_file is not None and os.path.exists(config_file):
            self._config.read(config_file)

    def enable_turn_off_on_screensaver(self):
        """
        Enable the turning off of devices when the screensaver is active
        """
        self._screensaver_thread.active = True

    def disable_turn_off_on_screensaver(self):
        """
        Disable the turning off of devices when the screensaver is active
        """
        self._screensaver_thread.active = False

    def version(self):
        """
        Get the daemon version

        :return: Version string
        :rtype: str
        """
        return __version__

    def suspend_devices(self):
        """
        Suspend all devices
        """
        for device in self._razer_devices:
            device.dbus.suspend_device()

    def resume_devices(self):
        """
        Resume all devices
        """
        for device in self._razer_devices:
            device.dbus.resume_device()

    def get_serial_list(self):
        """
        Get list of devices serials
        """
        serial_list = self._razer_devices.serials()
        self.logger.debug('DBus called get_serial_list')
        return serial_list

    def sync_effects(self, enabled):
        """
        Sync the effects across the devices

        :param enabled: True to sync effects
        :type enabled: bool
        """
        # Todo perhaps move logic to device collection
        for device in self._razer_devices.devices:
            device.dbus.effect_sync = enabled

    def _load_devices(self):
        """
        Go through supported devices and load them

        Loops through the available hardware classes, loops through
        each device in the system and adds it if needs be.
        """
        if self._test_dir is not None:
            devices = os.listdir(self._test_dir)
            dev_path = self._test_dir
        else:
            try:
                devices = os.listdir('/sys/bus/hid/devices')
            except FileNotFoundError:
                devices = []
            finally:
                dev_path = '/sys/bus/hid/devices'


        classes = razer_daemon.hardware.get_device_classes()

        device_number = 0
        for device_class in classes:
            for device_id in devices:
                if device_id in self._razer_devices:
                    continue

                if device_class.match(device_id, dev_path=dev_path):  # Check it matches sys/ ID format and has device_type file
                    self.logger.info('Found device.%d: %s', device_number, device_id)
                    device_path = os.path.join(dev_path, device_id)
                    razer_device = device_class(device_path, device_number, self._config, testing=self._test_dir is not None)

                    # Wireless devices sometimes dont listen
                    count = 0
                    while count < 3:
                        # Loop to get serial, exit early if it gets one
                        device_serial = razer_device.get_serial()
                        if len(device_serial) > 0:
                            break
                        count += 1
                    else:
                        logging.warning("Could not get serial for device {0}. Skipping".format(device_id))
                        continue

                    self._razer_devices.add(device_id, device_serial, razer_device)

                    device_number += 1

    def _remove_devices(self):
        """
        Go through the list of current devices and if they no longer exist then remove them
        """
        devices_to_remove = []

        for device in self._razer_devices:
            if self._test_dir is not None:
                device_path = os.path.join(self._test_dir, device.device_id)
            else:
                device_path = os.path.join('/sys/bus/hid/devices', device.device_id)

            if not os.path.exists(device_path):
                # Remove from DBus
                device.dbus.remove_from_connection()
                devices_to_remove.append(device.device_id)

        for device_id in devices_to_remove:
            # Remove device
            self.logger.warning("Device %s is missing. Removing from DBus", device_id)
            del self._razer_devices[device_id]

    def run(self):
        """
        Run the daemon
        """
        self.logger.info('Serving DBus')

        # Counter for managing periodic tasks
        counter = 0

        # Can't just use mainloop.run() as that blocks and
        # then signaling exit doesn't work
        main_loop_context = self._main_loop.get_context()
        while self._main_loop is not None:
            if main_loop_context.pending():
                main_loop_context.iteration()
            else:
                time.sleep(0.05)

            if counter > DEVICE_CHECK_INTERVAL: # Time sleeps 1ms so DEVICE_CHECK_INTERVAL is in milliseconds
                self._remove_devices()
                self._load_devices()
                counter = 0
            counter += 1

    def stop(self):
        """
        Wrapper for quit
        """
        self.quit(None, None)

    def quit(self, signum, frame):
        """
        Quit by stopping the main loop and screensaver thread
        """
        # pylint: disable=unused-argument
        self.logger.info('Stopping daemon.')
        self._main_loop = None

        # Stop screensaver
        self._screensaver_thread.shutdown = True
        self._screensaver_thread.join(timeout=2)
        if self._screensaver_thread.is_alive():
            self.logger.warning('Could not stop the screensaver thread')

        for device in self._razer_devices:
            device.dbus.close()