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()
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()
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()
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()