diff --git a/daemon/openrazer_daemon/daemon.py b/daemon/openrazer_daemon/daemon.py index 51cc88dec1348516039011c6d39ce8f3903cb3ab..e67974fa67880505f7896bbe5b711b296db0e65c 100644 --- a/daemon/openrazer_daemon/daemon.py +++ b/daemon/openrazer_daemon/daemon.py @@ -29,6 +29,7 @@ import openrazer_daemon.hardware from openrazer_daemon.dbus_services.service import DBusService from openrazer_daemon.device import DeviceCollection from openrazer_daemon.misc.screensaver_monitor import ScreensaverMonitor +from openrazer_daemon.misc.autosave_persistence import PersistenceAutoSave class RazerDaemon(DBusService): @@ -46,7 +47,7 @@ class RazerDaemon(DBusService): BUS_NAME = 'org.razer' - def __init__(self, verbose=False, log_dir=None, console_log=False, run_dir=None, config_file=None, test_dir=None): + def __init__(self, verbose=False, log_dir=None, console_log=False, run_dir=None, config_file=None, persistence_file=None, test_dir=None): setproctitle.setproctitle('openrazer-daemon') # pylint: disable=no-member @@ -68,12 +69,24 @@ class RazerDaemon(DBusService): print("Config file {} does not exist.".format(config_file), file=sys.stderr) sys.exit(1) + if persistence_file is not None: + persistence_file = os.path.expanduser(persistence_file) + if not os.path.exists(persistence_file): + print("Persistence file {} does not exist.".format(persistence_file), file=sys.stderr) + sys.exit(1) + self._test_dir = test_dir self._run_dir = run_dir + self._config_file = config_file self._config = configparser.ConfigParser() self.read_config(config_file) + self._persistence_file = persistence_file + self._persistence = configparser.ConfigParser() + self._persistence.status = {"changed": False} + self.read_persistence(persistence_file) + # Logging log_level = logging.INFO if verbose or self._config.getboolean('General', 'verbose_logging'): @@ -126,6 +139,8 @@ class RazerDaemon(DBusService): self._collecting_udev = False self._collecting_udev_devices = [] + self._init_autosave_persistence() + # TODO remove self.sync_effects(self._config.getboolean('Startup', 'sync_effects_enabled')) # TODO ====== @@ -200,6 +215,16 @@ class RazerDaemon(DBusService): except dbus.exceptions.DBusException as e: self.logger.error("Failed to init ScreensaverMonitor: {}".format(e)) + def _init_autosave_persistence(self): + if not self._persistence: + self.logger.debug("Persistence unspecified. Will not create auto save thread") + return + + self._autosave_persistence = PersistenceAutoSave(self._persistence, self._persistence_file, self._persistence.status, self.logger, 10, self.write_persistence) + self._autosave_persistence.thread = threading.Thread(target=self._autosave_persistence.watch) + self._autosave_persistence.thread.daemon = True + self._autosave_persistence.thread.start() + def _init_signals(self): """ Heinous hack to properly handle signals on the mainloop. Necessary @@ -254,16 +279,64 @@ class RazerDaemon(DBusService): for section in ('General', 'Startup', 'Statistics'): self._config[section] = {} - self._config['DEFAULT'] = { + self._config['General'] = { 'verbose_logging': False, + } + self._config['Startup'] = { 'sync_effects_enabled': True, 'devices_off_on_screensaver': True, - 'key_statistics': False, + 'mouse_battery_notifier': True, + } + self._config['Statistics'] = { + 'key_statistics': True, } if config_file is not None and os.path.exists(config_file): self._config.read(config_file) + def read_persistence(self, persistence_file): + """ + Read the persistence file and set states into memory + + :param persistence_file: Persistence file + :type persistence_file: str or None + """ + if persistence_file is not None and os.path.exists(persistence_file): + self._persistence.read(persistence_file) + + def write_persistence(self, persistence_file): + """ + Write in the persistence file + + :param persistence_file: Persistence file + :type persistence_file: str or None + """ + if not persistence_file: + return + + self.logger.debug('Writing persistence config') + + for device in self._razer_devices: + self._persistence[device.dbus.storage_name] = {} + if 'set_dpi_xy' in device.dbus.METHODS: + self._persistence[device.dbus.storage_name]['dpi_x'] = str(device.dbus.dpi[0]) + self._persistence[device.dbus.storage_name]['dpi_y'] = str(device.dbus.dpi[1]) + + if 'set_poll_rate' in device.dbus.METHODS: + self._persistence[device.dbus.storage_name]['poll_rate'] = str(device.dbus.poll_rate) + + for i in device.dbus.ZONES: + if device.dbus.zone[i]["present"]: + self._persistence[device.dbus.storage_name][i + '_active'] = str(device.dbus.zone[i]["active"]) + self._persistence[device.dbus.storage_name][i + '_brightness'] = str(device.dbus.zone[i]["brightness"]) + self._persistence[device.dbus.storage_name][i + '_effect'] = device.dbus.zone[i]["effect"] + self._persistence[device.dbus.storage_name][i + '_colors'] = ' '.join(str(i) for i in device.dbus.zone[i]["colors"]) + self._persistence[device.dbus.storage_name][i + '_speed'] = str(device.dbus.zone[i]["speed"]) + self._persistence[device.dbus.storage_name][i + '_wave_dir'] = str(device.dbus.zone[i]["wave_dir"]) + + with open(persistence_file, 'w') as cf: + self._persistence.write(cf) + def get_off_on_screensaver(self): """ Returns if turn off on screensaver @@ -406,7 +479,7 @@ class RazerDaemon(DBusService): self.logger.critical("Could not access {0}/device_type, file is not owned by plugdev".format(sys_path)) break - razer_device = device_class(sys_path, device_number, self._config, testing=self._test_dir is not None, additional_interfaces=sorted(additional_interfaces)) + razer_device = device_class(sys_path, device_number, self._config, self._persistence, testing=self._test_dir is not None, additional_interfaces=sorted(additional_interfaces)) # Wireless devices sometimes don't listen count = 0 @@ -442,7 +515,7 @@ class RazerDaemon(DBusService): if device_class.match(sys_name, sys_path): # Check it matches sys/ ID format and has device_type file self.logger.info('Found valid device.%d: %s', device_number, sys_name) - razer_device = device_class(sys_path, device_number, self._config, testing=self._test_dir is not None) + razer_device = device_class(sys_path, device_number, self._config, self._persistence, testing=self._test_dir is not None) # Its a udev event so currently the device hasn't been chmodded yet time.sleep(0.2) @@ -479,6 +552,7 @@ class RazerDaemon(DBusService): device.dbus.close() device.dbus.remove_from_connection() + self.write_persistence(self._persistence_file) self.logger.warning("Removing %s", device_id) # Delete device @@ -555,3 +629,6 @@ class RazerDaemon(DBusService): for device in self._razer_devices: device.dbus.close() + + # Write config + self.write_persistence(self._persistence_file) diff --git a/daemon/openrazer_daemon/dbus_services/dbus_methods/bw2013.py b/daemon/openrazer_daemon/dbus_services/dbus_methods/bw2013.py index bd67c7305e5b7c439ca046fd9cc7ab4b02097a4f..3fc7e4576548f090587293c77d0afbcf28a7e411 100644 --- a/daemon/openrazer_daemon/dbus_services/dbus_methods/bw2013.py +++ b/daemon/openrazer_daemon/dbus_services/dbus_methods/bw2013.py @@ -4,23 +4,6 @@ BlackWidow Ultimate 2013 effects from openrazer_daemon.dbus_services import endpoint -@endpoint('razer.device.lighting.bw2013', 'getEffect', out_sig='y') -def bw_get_effect(self): - """ - Get current effect - - :return: Brightness - :rtype: int - """ - self.logger.debug("DBus call bw_get_effect") - - driver_path = self.get_driver_path('matrix_effect_pulsate') - - with open(driver_path, 'r') as driver_file: - brightness = int(driver_file.read().strip()) - return brightness - - @endpoint('razer.device.lighting.bw2013', 'setPulsate') def bw_set_pulsate(self): """ @@ -30,6 +13,9 @@ def bw_set_pulsate(self): driver_path = self.get_driver_path('matrix_effect_pulsate') + # remember effect + self.set_persistence("backlight", "effect", 'pulsate') + with open(driver_path, 'w') as driver_file: driver_file.write('1') @@ -46,6 +32,9 @@ def bw_set_static(self): driver_path = self.get_driver_path('matrix_effect_static') + # remember effect + self.set_persistence("backlight", "effect", 'static') + with open(driver_path, 'w') as driver_file: driver_file.write('1') diff --git a/daemon/openrazer_daemon/dbus_services/dbus_methods/chroma_keyboard.py b/daemon/openrazer_daemon/dbus_services/dbus_methods/chroma_keyboard.py index cdee1185331365dac987057a4d04f12270c79663..7acbc51ca72b085744a0afd48529cdf278b41338 100644 --- a/daemon/openrazer_daemon/dbus_services/dbus_methods/chroma_keyboard.py +++ b/daemon/openrazer_daemon/dbus_services/dbus_methods/chroma_keyboard.py @@ -15,14 +15,7 @@ def get_brightness(self): """ self.logger.debug("DBus call get_brightness") - driver_path = self.get_driver_path('matrix_brightness') - - with open(driver_path, 'r') as driver_file: - brightness = round(float(driver_file.read()) * (100.0 / 255.0), 2) - - self.method_args['brightness'] = brightness - - return brightness + return self.zone["backlight"]["brightness"] @endpoint('razer.device.lighting.brightness', 'setBrightness', in_sig='d') @@ -39,12 +32,15 @@ def set_brightness(self, brightness): self.method_args['brightness'] = brightness - brightness = int(round(brightness * (255.0 / 100.0))) - if brightness > 255: - brightness = 255 + if brightness > 100: + brightness = 100 elif brightness < 0: brightness = 0 + self.set_persistence("backlight", "brightness", brightness) + + brightness = int(round(brightness * (255.0 / 100.0))) + with open(driver_path, 'w') as driver_file: driver_file.write(str(brightness)) @@ -181,6 +177,10 @@ def set_wave_effect(self, direction): # Notify others self.send_effect_event('setWave', direction) + # remember effect + self.set_persistence("backlight", "effect", 'wave') + self.set_persistence("backlight", "wave_dir", int(direction)) + driver_path = self.get_driver_path('matrix_effect_wave') if direction not in self.WAVE_DIRS: @@ -209,6 +209,10 @@ def set_static_effect(self, red, green, blue): # Notify others self.send_effect_event('setStatic', red, green, blue) + # remember effect + self.set_persistence("backlight", "effect", 'static') + self.zone["backlight"]["colors"][0:3] = int(red), int(green), int(blue) + driver_path = self.get_driver_path('matrix_effect_static') payload = bytes([red, green, blue]) @@ -236,6 +240,10 @@ def set_blinking_effect(self, red, green, blue): # Notify others self.send_effect_event('setBlinking', red, green, blue) + # remember effect + self.set_persistence("backlight", "effect", 'blinking') + self.zone["backlight"]["colors"][0:3] = int(red), int(green), int(blue) + driver_path = self.get_driver_path('matrix_effect_blinking') payload = bytes([red, green, blue]) @@ -254,6 +262,9 @@ def set_spectrum_effect(self): # Notify others self.send_effect_event('setSpectrum') + # remember effect + self.set_persistence("backlight", "effect", 'spectrum') + driver_path = self.get_driver_path('matrix_effect_spectrum') with open(driver_path, 'w') as driver_file: @@ -270,6 +281,9 @@ def set_none_effect(self): # Notify others self.send_effect_event('setNone') + # remember effect + self.set_persistence("backlight", "effect", 'none') + driver_path = self.get_driver_path('matrix_effect_none') with open(driver_path, 'w') as driver_file: @@ -316,9 +330,15 @@ def set_reactive_effect(self, red, green, blue, speed): # Notify others self.send_effect_event('setReactive', red, green, blue, speed) + # remember effect + self.set_persistence("backlight", "effect", 'reactive') + self.zone["backlight"]["colors"][0:3] = int(red), int(green), int(blue) + if speed not in (1, 2, 3, 4): speed = 4 + self.set_persistence("backlight", "speed", int(speed)) + payload = bytes([speed, red, green, blue]) with open(driver_path, 'wb') as driver_file: @@ -335,6 +355,9 @@ def set_breath_random_effect(self): # Notify others self.send_effect_event('setBreathRandom') + # remember effect + self.set_persistence("backlight", "effect", 'breathRandom') + driver_path = self.get_driver_path('matrix_effect_breath') payload = b'1' @@ -362,6 +385,10 @@ def set_breath_single_effect(self, red, green, blue): # Notify others self.send_effect_event('setBreathSingle', red, green, blue) + # remember effect + self.set_persistence("backlight", "effect", 'breathSingle') + self.zone["backlight"]["colors"][0:3] = int(red), int(green), int(blue) + driver_path = self.get_driver_path('matrix_effect_breath') payload = bytes([red, green, blue]) @@ -398,6 +425,10 @@ def set_breath_dual_effect(self, red1, green1, blue1, red2, green2, blue2): # Notify others self.send_effect_event('setBreathDual', red1, green1, blue1, red2, green2, blue2) + # remember effect + self.set_persistence("backlight", "effect", 'breathDual') + self.zone["backlight"]["colors"][0:6] = int(red1), int(green1), int(blue1), int(red2), int(green2), int(blue2) + driver_path = self.get_driver_path('matrix_effect_breath') payload = bytes([red1, green1, blue1, red2, green2, blue2]) @@ -443,6 +474,10 @@ def set_breath_triple_effect(self, red1, green1, blue1, red2, green2, blue2, red # Notify others self.send_effect_event('setBreathTriple', red1, green1, blue1, red2, green2, blue2, red3, green3, blue3) + # remember effect + self.set_persistence("backlight", "effect", 'breathTriple') + self.zone["backlight"]["colors"][0:9] = int(red1), int(green1), int(blue1), int(red2), int(green2), int(blue2), int(red3), int(green3), int(blue3) + driver_path = self.get_driver_path('matrix_effect_breath') payload = bytes([red1, green1, blue1, red2, green2, blue2, red3, green3, blue3]) @@ -513,6 +548,10 @@ def set_ripple_effect(self, red, green, blue, refresh_rate): # Notify others self.send_effect_event('setRipple', red, green, blue, refresh_rate) + # remember effect + self.set_persistence("backlight", "effect", 'ripple') + self.zone["backlight"]["colors"][0:3] = int(red), int(green), int(blue) + @endpoint('razer.device.lighting.custom', 'setRippleRandomColour', in_sig='d') def set_ripple_effect_random_colour(self, refresh_rate): @@ -527,6 +566,9 @@ def set_ripple_effect_random_colour(self, refresh_rate): # Notify others self.send_effect_event('setRipple', None, None, None, refresh_rate) + # remember effect + self.set_persistence("backlight", "effect", 'rippleRandomColour') + @endpoint('razer.device.lighting.chroma', 'setStarlightRandom', in_sig='y') def set_starlight_random_effect(self, speed): @@ -543,6 +585,10 @@ def set_starlight_random_effect(self, speed): # Notify others self.send_effect_event('setStarlightRandom') + # remember effect + self.set_persistence("backlight", "effect", 'starlightRandom') + self.set_persistence("backlight", "speed", speed) + @endpoint('razer.device.lighting.chroma', 'setStarlightSingle', in_sig='yyyy') def set_starlight_single_effect(self, red, green, blue, speed): @@ -559,6 +605,11 @@ def set_starlight_single_effect(self, red, green, blue, speed): # Notify others self.send_effect_event('setStarlightSingle', red, green, blue, speed) + # remember effect + self.set_persistence("backlight", "effect", 'starlightSingle') + self.set_persistence("backlight", "speed", speed) + self.zone["backlight"]["colors"][0:3] = int(red), int(green), int(blue) + @endpoint('razer.device.lighting.chroma', 'setStarlightDual', in_sig='yyyyyyy') def set_starlight_dual_effect(self, red1, green1, blue1, red2, green2, blue2, speed): @@ -574,3 +625,8 @@ def set_starlight_dual_effect(self, red1, green1, blue1, red2, green2, blue2, sp # Notify others self.send_effect_event('setStarlightDual', red1, green1, blue1, red2, green2, blue2, speed) + + # remember effect + self.set_persistence("backlight", "effect", 'starlightDual') + self.set_persistence("backlight", "speed", speed) + self.zone["backlight"]["colors"][0:6] = int(red1), int(green1), int(blue1), int(red2), int(green2), int(blue2) diff --git a/daemon/openrazer_daemon/dbus_services/dbus_methods/deathadder_chroma.py b/daemon/openrazer_daemon/dbus_services/dbus_methods/deathadder_chroma.py index 65e627b2841f2262a3850aafff9a09bcfb60cd4a..688837342b59c8eee28d788e49021a3903858d53 100644 --- a/daemon/openrazer_daemon/dbus_services/dbus_methods/deathadder_chroma.py +++ b/daemon/openrazer_daemon/dbus_services/dbus_methods/deathadder_chroma.py @@ -29,11 +29,7 @@ def get_backlight_active(self): """ self.logger.debug("DBus call get_backlight_active") - driver_path = self.get_driver_path('backlight_led_state') - - with open(driver_path, 'r') as driver_file: - active = int(driver_file.read().strip()) - return active == 1 + return self.zone["backlight"]["active"] @endpoint('razer.device.lighting.backlight', 'setBacklightActive', in_sig='b') @@ -46,6 +42,9 @@ def set_backlight_active(self, active): """ self.logger.debug("DBus call set_backlight_active") + # remember status + self.set_persistence("backlight", "active", bool(active)) + driver_path = self.get_driver_path('backlight_led_state') with open(driver_path, 'w') as driver_file: @@ -65,11 +64,7 @@ def get_logo_active(self): """ self.logger.debug("DBus call get_logo_active") - driver_path = self.get_driver_path('logo_led_state') - - with open(driver_path, 'r') as driver_file: - active = int(driver_file.read().strip()) - return active == 1 + return self.zone["logo"]["active"] @endpoint('razer.device.lighting.logo', 'setLogoActive', in_sig='b') @@ -82,29 +77,15 @@ def set_logo_active(self, active): """ self.logger.debug("DBus call set_logo_active") + # remember status + self.set_persistence("logo", "active", bool(active)) + driver_path = self.get_driver_path('logo_led_state') with open(driver_path, 'w') as driver_file: driver_file.write('1' if active else '0') -@endpoint('razer.device.lighting.logo', 'getLogoEffect', out_sig='y') -def get_logo_effect(self): - """ - Get logo effect - - :return: Active - :rtype: bool - """ - self.logger.debug("DBus call get_logo_effect") - - driver_path = self.get_driver_path('logo_led_effect') - - with open(driver_path, 'r') as driver_file: - effect = int(driver_file.read().strip()) - return effect - - @endpoint('razer.device.lighting.logo', 'getLogoBrightness', out_sig='d') def get_logo_brightness(self): """ @@ -115,12 +96,7 @@ def get_logo_brightness(self): """ self.logger.debug("DBus call get_logo_brightness") - driver_path = self.get_driver_path('logo_led_brightness') - - with open(driver_path, 'r') as driver_file: - brightness = round(float(driver_file.read()) * (100.0 / 255.0), 2) - - return brightness + return self.zone["logo"]["brightness"] @endpoint('razer.device.lighting.logo', 'setLogoBrightness', in_sig='d') @@ -137,12 +113,15 @@ def set_logo_brightness(self, brightness): self.method_args['brightness'] = brightness - brightness = int(round(brightness * (255.0 / 100.0))) if brightness > 255: brightness = 255 elif brightness < 0: brightness = 0 + self.set_persistence("logo", "brightness", brightness) + + brightness = int(round(brightness * (255.0 / 100.0))) + with open(driver_path, 'w') as driver_file: driver_file.write(str(brightness)) @@ -169,6 +148,10 @@ def set_logo_static(self, red, green, blue): # Notify others self.send_effect_event('setStatic', red, green, blue) + # remember effect + self.set_persistence("logo", "effect", 'static') + self.zone["logo"]["colors"][0:3] = int(red), int(green), int(blue) + set_led_effect_color_common(self, 'logo', '0', red, green, blue) @@ -188,7 +171,7 @@ def set_logo_static_mono(self): @endpoint('razer.device.lighting.logo', 'setLogoBlinking', in_sig='yyy') def set_logo_blinking(self, red, green, blue): """ - Set the device to pulsate + Set the device to blinking :param red: Red component :type red: int @@ -204,6 +187,10 @@ def set_logo_blinking(self, red, green, blue): # Notify others self.send_effect_event('setLogoBlinking', red, green, blue) + # remember effect + self.set_persistence("logo", "effect", 'blinking') + self.zone["logo"]["colors"][0:3] = int(red), int(green), int(blue) + set_led_effect_color_common(self, 'logo', '1', red, green, blue) @@ -226,6 +213,10 @@ def set_logo_pulsate(self, red, green, blue): # Notify others self.send_effect_event('setPulsate', red, green, blue) + # remember effect + self.set_persistence("logo", "effect", 'pulsate') + self.zone["logo"]["colors"][0:3] = int(red), int(green), int(blue) + set_led_effect_color_common(self, 'logo', '2', red, green, blue) @@ -245,7 +236,7 @@ def set_logo_pulsate_mono(self): @endpoint('razer.device.lighting.logo', 'setLogoSpectrum') def set_logo_spectrum(self): """ - Set the device to pulsate + Set the device to spectrum :param red: Red component :type red: int @@ -261,6 +252,9 @@ def set_logo_spectrum(self): # Notify others self.send_effect_event('setSpectrum') + # remember effect + self.set_persistence("logo", "effect", 'spectrum') + set_led_effect_common(self, 'logo', '4') @@ -274,11 +268,7 @@ def get_scroll_active(self): """ self.logger.debug("DBus call get_scroll_active") - driver_path = self.get_driver_path('scroll_led_state') - - with open(driver_path, 'r') as driver_file: - active = int(driver_file.read().strip()) - return active == 1 + return self.zone["scroll"]["active"] @endpoint('razer.device.lighting.scroll', 'setScrollActive', in_sig='b') @@ -291,29 +281,15 @@ def set_scroll_active(self, active): """ self.logger.debug("DBus call set_scroll_active") + # remember status + self.set_persistence("scroll", "active", bool(active)) + driver_path = self.get_driver_path('scroll_led_state') with open(driver_path, 'w') as driver_file: driver_file.write('1' if active else '0') -@endpoint('razer.device.lighting.scroll', 'getScrollEffect', out_sig='y') -def get_scroll_effect(self): - """ - Get scroll effect - - :return: Active - :rtype: bool - """ - self.logger.debug("DBus call get_scroll_effect") - - driver_path = self.get_driver_path('scroll_led_effect') - - with open(driver_path, 'r') as driver_file: - effect = int(driver_file.read().strip()) - return effect - - @endpoint('razer.device.lighting.scroll', 'getScrollBrightness', out_sig='d') def get_scroll_brightness(self): """ @@ -324,12 +300,7 @@ def get_scroll_brightness(self): """ self.logger.debug("DBus call get_scroll_brightness") - driver_path = self.get_driver_path('scroll_led_brightness') - - with open(driver_path, 'r') as driver_file: - brightness = round(float(driver_file.read()) * (100.0 / 255.0), 2) - - return brightness + return self.zone["scroll"]["brightness"] @endpoint('razer.device.lighting.scroll', 'setScrollBrightness', in_sig='d') @@ -346,12 +317,15 @@ def set_scroll_brightness(self, brightness): self.method_args['brightness'] = brightness - brightness = int(round(brightness * (255.0 / 100.0))) if brightness > 255: brightness = 255 elif brightness < 0: brightness = 0 + self.set_persistence("scroll", "brightness", brightness) + + brightness = int(round(brightness * (255.0 / 100.0))) + with open(driver_path, 'w') as driver_file: driver_file.write(str(brightness)) @@ -378,6 +352,10 @@ def set_scroll_static(self, red, green, blue): # Notify others self.send_effect_event('setStatic', red, green, blue) + # remember effect + self.set_persistence("scroll", "effect", 'static') + self.zone["scroll"]["colors"][0:3] = int(red), int(green), int(blue) + set_led_effect_color_common(self, 'scroll', '0', red, green, blue) @@ -397,7 +375,7 @@ def set_scroll_static_mono(self): @endpoint('razer.device.lighting.scroll', 'setScrollBlinking', in_sig='yyy') def set_scroll_blinking(self, red, green, blue): """ - Set the device to pulsate + Set the device to blinking :param red: Red component :type red: int @@ -413,6 +391,10 @@ def set_scroll_blinking(self, red, green, blue): # Notify others self.send_effect_event('setPulsate', red, green, blue) + # remember effect + self.set_persistence("scroll", "effect", 'blinking') + self.zone["scroll"]["colors"][0:3] = int(red), int(green), int(blue) + set_led_effect_color_common(self, 'scroll', '1', red, green, blue) @@ -435,6 +417,10 @@ def set_scroll_pulsate(self, red, green, blue): # Notify others self.send_effect_event('setPulsate', red, green, blue) + # remember effect + self.set_persistence("scroll", "effect", 'pulsate') + self.zone["scroll"]["colors"][0:3] = int(red), int(green), int(blue) + set_led_effect_color_common(self, 'scroll', '2', red, green, blue) @@ -454,20 +440,14 @@ def set_scroll_pulsate_mono(self): @endpoint('razer.device.lighting.scroll', 'setScrollSpectrum') def set_scroll_spectrum(self): """ - Set the device to pulsate - - :param red: Red component - :type red: int - - :param green: Green component - :type green: int - - :param blue: Blue component - :type blue: int + Set the device to spectrum """ self.logger.debug("DBus call set_scroll_spectrum") # Notify others self.send_effect_event('setSpectrum') + # remember effect + self.set_persistence("scroll", "effect", 'spectrum') + set_led_effect_common(self, 'scroll', '4') diff --git a/daemon/openrazer_daemon/dbus_services/dbus_methods/kraken.py b/daemon/openrazer_daemon/dbus_services/dbus_methods/kraken.py index d234b60c498275def87e360867982330a576e01e..f137ea41a0c44bd67dfce21b37720a6095a23f5c 100644 --- a/daemon/openrazer_daemon/dbus_services/dbus_methods/kraken.py +++ b/daemon/openrazer_daemon/dbus_services/dbus_methods/kraken.py @@ -4,72 +4,6 @@ Module for kraken methods from openrazer_daemon.dbus_services import endpoint -@endpoint('razer.device.lighting.kraken', 'getCurrentEffect', out_sig='y') -def get_current_effect_kraken(self): - """ - Get the device's current effect - - :return: The internal bitfield like 05 (in hex) - :rtype: int - """ - self.logger.debug("DBus call matrix_current_effect") - - driver_path = self.get_driver_path('matrix_current_effect') - - with open(driver_path, 'r') as driver_file: - return int(driver_file.read().strip(), 16) - - -@endpoint('razer.device.lighting.kraken', 'getStaticArgs', out_sig='ai') -def get_static_effect_args_kraken(self): - """ - Get the static effect arguments - - :return: List of args used for static effect - :rtype: int - """ - self.logger.debug("DBus call get_static_effect_args") - - driver_path = self.get_driver_path('matrix_effect_static') - - with open(driver_path, 'rb') as driver_file: - bytestring = driver_file.read() - if len(bytestring) != 4: - raise ValueError("Response from driver is not valid, should be length 4 got: {0}".format(len(bytestring))) - else: - return list(bytestring[:3]) # We cut off the intensity value in the end, aint letting people mess with that. - - -@endpoint('razer.device.lighting.kraken', 'getBreathArgs', out_sig='ai') -def get_breath_effect_args_kraken(self): - """ - Get the breath effect arguments - - :return: List of args used for breathing effect - :rtype: int - """ - self.logger.debug("DBus call get_breath_effect_args") - - driver_path = self.get_driver_path('matrix_effect_breath') - - with open(driver_path, 'rb') as driver_file: - bytestring = driver_file.read() - if len(bytestring) % 4 != 0: - raise ValueError("Response from driver is not valid, should be length 4 got: {0}".format(len(bytestring))) - else: - result = [] - - # Result could be 4 bytes (breathing1), 8 bytes (breathing2), 12 bytes (breathing3) and we need to cut of the - # intensity value, so i thought it easier to cut it into chunks of 4, append the first 3 values to `result` - for chunk in [bytestring[i:i + 4] for i in range(0, len(bytestring), 4)]: - # Get first 3 values - values = list(chunk)[:3] - # Add those 3 values into the list - result.extend(values) - - return result # We cut off the intensity value in the end, aint letting people mess with that. - - @endpoint('razer.device.lighting.kraken', 'setCustom', in_sig='ai') def set_custom_kraken(self, rgbi): """ diff --git a/daemon/openrazer_daemon/dbus_services/dbus_methods/lanceheadte.py b/daemon/openrazer_daemon/dbus_services/dbus_methods/lanceheadte.py index 4d224471e75e5a784cd7d6b287d0f263efdf7663..dc3fe10883f6872979463f4c4981ed34f79ff80d 100644 --- a/daemon/openrazer_daemon/dbus_services/dbus_methods/lanceheadte.py +++ b/daemon/openrazer_daemon/dbus_services/dbus_methods/lanceheadte.py @@ -13,6 +13,10 @@ def set_logo_wave(self, direction): # Notify others self.send_effect_event('setWave', direction) + # remember effect + self.set_persistence("logo", "effect", 'wave') + self.set_persistence("logo", "wave_dir", int(direction)) + driver_path = self.get_driver_path('logo_matrix_effect_wave') if direction not in self.WAVE_DIRS: @@ -34,6 +38,10 @@ def set_scroll_wave(self, direction): # Notify others self.send_effect_event('setWave', direction) + # remember effect + self.set_persistence("scroll", "effect", 'wave') + self.set_persistence("scroll", "wave_dir", int(direction)) + driver_path = self.get_driver_path('scroll_matrix_effect_wave') if direction not in self.WAVE_DIRS: @@ -52,12 +60,7 @@ def get_left_brightness(self): """ self.logger.debug("DBus call get_left_brightness") - driver_path = self.get_driver_path('left_led_brightness') - - with open(driver_path, 'r') as driver_file: - brightness = round(float(driver_file.read()) * (100.0 / 255.0), 2) - - return brightness + return self.zone["left"]["brightness"] @endpoint('razer.device.lighting.left', 'setLeftBrightness', in_sig='d') @@ -73,12 +76,15 @@ def set_left_brightness(self, brightness): self.method_args['brightness'] = brightness - brightness = int(round(brightness * (255.0 / 100.0))) if brightness > 255: brightness = 255 elif brightness < 0: brightness = 0 + self.set_persistence("left", "brightness", brightness) + + brightness = int(round(brightness * (255.0 / 100.0))) + with open(driver_path, 'w') as driver_file: driver_file.write(str(brightness)) @@ -98,6 +104,10 @@ def set_left_wave(self, direction): # Notify others self.send_effect_event('setWave', direction) + # remember effect + self.set_persistence("left", "effect", 'wave') + self.set_persistence("left", "wave_dir", int(direction)) + driver_path = self.get_driver_path('left_matrix_effect_wave') if direction not in self.WAVE_DIRS: @@ -126,6 +136,10 @@ def set_left_static(self, red, green, blue): # Notify others self.send_effect_event('setStatic', red, green, blue) + # remember effect + self.set_persistence("left", "effect", 'static') + self.zone["left"]["colors"][0:3] = int(red), int(green), int(blue) + rgb_driver_path = self.get_driver_path('left_matrix_effect_static') payload = bytes([red, green, blue]) @@ -144,6 +158,9 @@ def set_left_spectrum(self): # Notify others self.send_effect_event('setSpectrum') + # remember effect + self.set_persistence("left", "effect", 'spectrum') + effect_driver_path = self.get_driver_path('left_matrix_effect_spectrum') with open(effect_driver_path, 'w') as effect_driver_file: @@ -160,6 +177,9 @@ def set_left_none(self): # Notify others self.send_effect_event('setNone') + # remember effect + self.set_persistence("left", "effect", 'none') + driver_path = self.get_driver_path('left_matrix_effect_none') with open(driver_path, 'w') as driver_file: @@ -190,9 +210,15 @@ def set_left_reactive(self, red, green, blue, speed): # Notify others self.send_effect_event('setReactive', red, green, blue, speed) + # remember effect + self.set_persistence("left", "effect", 'reactive') + self.zone["left"]["colors"][0:3] = int(red), int(green), int(blue) + if speed not in (1, 2, 3, 4): speed = 4 + self.set_persistence("left", "speed", int(speed)) + payload = bytes([speed, red, green, blue]) with open(driver_path, 'wb') as driver_file: @@ -209,6 +235,9 @@ def set_left_breath_random(self): # Notify others self.send_effect_event('setBreathRandom') + # remember effect + self.set_persistence("left", "effect", 'breathRandom') + driver_path = self.get_driver_path('left_matrix_effect_breath') payload = b'1' @@ -236,6 +265,10 @@ def set_left_breath_single(self, red, green, blue): # Notify others self.send_effect_event('setBreathSingle', red, green, blue) + # remember effect + self.set_persistence("left", "effect", 'breathSingle') + self.zone["left"]["colors"][0:3] = int(red), int(green), int(blue) + driver_path = self.get_driver_path('left_matrix_effect_breath') payload = bytes([red, green, blue]) @@ -272,6 +305,10 @@ def set_left_breath_dual(self, red1, green1, blue1, red2, green2, blue2): # Notify others self.send_effect_event('setBreathDual', red1, green1, blue1, red2, green2, blue2) + # remember effect + self.set_persistence("left", "effect", 'breathDual') + self.zone["left"]["colors"][0:6] = int(red1), int(green1), int(blue1), int(red2), int(green2), int(blue2) + driver_path = self.get_driver_path('left_matrix_effect_breath') payload = bytes([red1, green1, blue1, red2, green2, blue2]) @@ -289,12 +326,7 @@ def get_right_brightness(self): """ self.logger.debug("DBus call get_right_brightness") - driver_path = self.get_driver_path('right_led_brightness') - - with open(driver_path, 'r') as driver_file: - brightness = round(float(driver_file.read()) * (100.0 / 255.0), 2) - - return brightness + return self.zone["right"]["brightness"] @endpoint('razer.device.lighting.right', 'setRightBrightness', in_sig='d') @@ -310,12 +342,15 @@ def set_right_brightness(self, brightness): self.method_args['brightness'] = brightness - brightness = int(round(brightness * (255.0 / 100.0))) if brightness > 255: brightness = 255 elif brightness < 0: brightness = 0 + self.set_persistence("right", "brightness", brightness) + + brightness = int(round(brightness * (255.0 / 100.0))) + with open(driver_path, 'w') as driver_file: driver_file.write(str(brightness)) @@ -335,6 +370,10 @@ def set_right_wave(self, direction): # Notify others self.send_effect_event('setWave', direction) + # remember effect + self.set_persistence("right", "effect", 'wave') + self.set_persistence("right", "wave_dir", int(direction)) + driver_path = self.get_driver_path('right_matrix_effect_wave') if direction not in self.WAVE_DIRS: @@ -363,6 +402,10 @@ def set_right_static(self, red, green, blue): # Notify others self.send_effect_event('setStatic', red, green, blue) + # remember effect + self.set_persistence("right", "effect", 'static') + self.zone["right"]["colors"][0:3] = int(red), int(green), int(blue) + rgb_driver_path = self.get_driver_path('right_matrix_effect_static') payload = bytes([red, green, blue]) @@ -381,6 +424,9 @@ def set_right_spectrum(self): # Notify others self.send_effect_event('setSpectrum') + # remember effect + self.set_persistence("right", "effect", 'spectrum') + effect_driver_path = self.get_driver_path('right_matrix_effect_spectrum') with open(effect_driver_path, 'w') as effect_driver_file: @@ -397,6 +443,9 @@ def set_right_none(self): # Notify others self.send_effect_event('setNone') + # remember effect + self.set_persistence("right", "effect", 'none') + driver_path = self.get_driver_path('right_matrix_effect_none') with open(driver_path, 'w') as driver_file: @@ -427,9 +476,15 @@ def set_right_reactive(self, red, green, blue, speed): # Notify others self.send_effect_event('setReactive', red, green, blue, speed) + # remember effect + self.set_persistence("right", "effect", 'reactive') + self.zone["right"]["colors"][0:3] = int(red), int(green), int(blue) + if speed not in (1, 2, 3, 4): speed = 4 + self.set_persistence("right", "speed", int(speed)) + payload = bytes([speed, red, green, blue]) with open(driver_path, 'wb') as driver_file: @@ -446,6 +501,9 @@ def set_right_breath_random(self): # Notify others self.send_effect_event('setBreathRandom') + # remember effect + self.set_persistence("right", "effect", 'breathRandom') + driver_path = self.get_driver_path('right_matrix_effect_breath') payload = b'1' @@ -473,6 +531,10 @@ def set_right_breath_single(self, red, green, blue): # Notify others self.send_effect_event('setBreathSingle', red, green, blue) + # remember effect + self.set_persistence("right", "effect", 'breathSingle') + self.zone["right"]["colors"][0:3] = int(red), int(green), int(blue) + driver_path = self.get_driver_path('right_matrix_effect_breath') payload = bytes([red, green, blue]) @@ -509,6 +571,10 @@ def set_right_breath_dual(self, red1, green1, blue1, red2, green2, blue2): # Notify others self.send_effect_event('setBreathDual', red1, green1, blue1, red2, green2, blue2) + # remember effect + self.set_persistence("right", "effect", 'breathDual') + self.zone["right"]["colors"][0:6] = int(red1), int(green1), int(blue1), int(red2), int(green2), int(blue2) + driver_path = self.get_driver_path('right_matrix_effect_breath') payload = bytes([red1, green1, blue1, red2, green2, blue2]) diff --git a/daemon/openrazer_daemon/dbus_services/dbus_methods/mamba.py b/daemon/openrazer_daemon/dbus_services/dbus_methods/mamba.py index 1fb7cb746851832b6a043a1f03fb5e7c5f0ed148..010677af3d619cbebc568ef9383a6a277baffbc9 100644 --- a/daemon/openrazer_daemon/dbus_services/dbus_methods/mamba.py +++ b/daemon/openrazer_daemon/dbus_services/dbus_methods/mamba.py @@ -1,6 +1,3 @@ -""" -BlackWidow Chroma Effects -""" import math import struct from openrazer_daemon.dbus_services import endpoint @@ -181,6 +178,19 @@ def set_dpi_xy(self, dpi_x, dpi_y): else: dpi_bytes = struct.pack('>HH', dpi_x, dpi_y) + self.dpi[0] = dpi_x + self.dpi[1] = dpi_y + + self.set_persistence(None, "dpi_x", dpi_x) + self.set_persistence(None, "dpi_y", dpi_y) + + # constrain DPI to maximum + if hasattr(self, 'DPI_MAX'): + if self.dpi[0] > self.DPI_MAX: + self.dpi[0] = self.DPI_MAX + if self.dpi[1] > self.DPI_MAX: + self.dpi[1] = self.DPI_MAX + with open(driver_path, 'wb') as driver_file: driver_file.write(dpi_bytes) @@ -197,9 +207,15 @@ def get_dpi_xy(self): driver_path = self.get_driver_path('dpi') - with open(driver_path, 'r') as driver_file: - result = driver_file.read() - dpi = [int(dpi) for dpi in result.strip().split(':')] + # try retrieving DPI from the hardware. + # if we can't (e.g. because the mouse has been disconnected) + # return the value in local storage. + try: + with open(driver_path, 'r') as driver_file: + result = driver_file.read() + dpi = [int(dpi) for dpi in result.strip().split(':')] + except FileNotFoundError: + return self.dpi return dpi @@ -238,6 +254,9 @@ def set_poll_rate(self, rate): if rate in (1000, 500, 125): driver_path = self.get_driver_path('poll_rate') + # remember poll rate + self.poll_rate = rate + with open(driver_path, 'w') as driver_file: driver_file.write(str(rate)) else: @@ -254,10 +273,4 @@ def get_poll_rate(self): """ self.logger.debug("DBus call get_poll_rate") - driver_path = self.get_driver_path('poll_rate') - - with open(driver_path, 'r') as driver_file: - result = driver_file.read() - result = int(result.strip()) - - return result + return int(self.poll_rate) diff --git a/daemon/openrazer_daemon/dbus_services/dbus_methods/nagahexv2.py b/daemon/openrazer_daemon/dbus_services/dbus_methods/nagahexv2.py index 7ae655f1e7d152a11b0556054788721d5aaf6fd0..614b89637456c2bae1bbe79f5c1f12a006f07494 100644 --- a/daemon/openrazer_daemon/dbus_services/dbus_methods/nagahexv2.py +++ b/daemon/openrazer_daemon/dbus_services/dbus_methods/nagahexv2.py @@ -20,6 +20,10 @@ def set_logo_static_naga_hex_v2(self, red, green, blue): # Notify others self.send_effect_event('setStatic', red, green, blue) + # remember effect + self.set_persistence("logo", "effect", 'static') + self.zone["logo"]["colors"][0:3] = int(red), int(green), int(blue) + rgb_driver_path = self.get_driver_path('logo_matrix_effect_static') payload = bytes([red, green, blue]) @@ -38,6 +42,9 @@ def set_logo_spectrum_naga_hex_v2(self): # Notify others self.send_effect_event('setSpectrum') + # remember effect + self.set_persistence("logo", "effect", 'spectrum') + effect_driver_path = self.get_driver_path('logo_matrix_effect_spectrum') with open(effect_driver_path, 'w') as effect_driver_file: @@ -54,6 +61,9 @@ def set_logo_none_naga_hex_v2(self): # Notify others self.send_effect_event('setNone') + # remember effect + self.set_persistence("logo", "effect", 'none') + driver_path = self.get_driver_path('logo_matrix_effect_none') with open(driver_path, 'w') as driver_file: @@ -84,6 +94,11 @@ def set_logo_reactive_naga_hex_v2(self, red, green, blue, speed): # Notify others self.send_effect_event('setReactive', red, green, blue, speed) + # remember effect + self.set_persistence("logo", "effect", 'reactive') + self.zone["logo"]["colors"][0:3] = int(red), int(green), int(blue) + self.set_persistence("logo", "speed", int(speed)) + if speed not in (1, 2, 3, 4): speed = 4 @@ -103,6 +118,9 @@ def set_logo_breath_random_naga_hex_v2(self): # Notify others self.send_effect_event('setBreathRandom') + # remember effect + self.set_persistence("logo", "effect", 'breathRandom') + driver_path = self.get_driver_path('logo_matrix_effect_breath') payload = b'1' @@ -130,6 +148,10 @@ def set_logo_breath_single_naga_hex_v2(self, red, green, blue): # Notify others self.send_effect_event('setBreathSingle', red, green, blue) + # remember effect + self.set_persistence("logo", "effect", 'breathSingle') + self.zone["logo"]["colors"][0:3] = int(red), int(green), int(blue) + driver_path = self.get_driver_path('logo_matrix_effect_breath') payload = bytes([red, green, blue]) @@ -166,6 +188,10 @@ def set_logo_breath_dual_naga_hex_v2(self, red1, green1, blue1, red2, green2, bl # Notify others self.send_effect_event('setBreathDual', red1, green1, blue1, red2, green2, blue2) + # remember effect + self.set_persistence("logo", "effect", 'breathDual') + self.zone["logo"]["colors"][0:6] = int(red1), int(green1), int(blue1), int(red2), int(green2), int(blue2) + driver_path = self.get_driver_path('logo_matrix_effect_breath') payload = bytes([red1, green1, blue1, red2, green2, blue2]) @@ -193,6 +219,10 @@ def set_scroll_static_naga_hex_v2(self, red, green, blue): # Notify others self.send_effect_event('setStatic', red, green, blue) + # remember effect + self.set_persistence("scroll", "effect", 'static') + self.zone["scroll"]["colors"][0:3] = int(red), int(green), int(blue) + rgb_driver_path = self.get_driver_path('scroll_matrix_effect_static') payload = bytes([red, green, blue]) @@ -211,6 +241,8 @@ def set_scroll_spectrum_naga_hex_v2(self): # Notify others self.send_effect_event('setSpectrum') + self.set_persistence("scroll", "effect", 'spectrum') + effect_driver_path = self.get_driver_path('scroll_matrix_effect_spectrum') with open(effect_driver_path, 'w') as effect_driver_file: @@ -227,6 +259,8 @@ def set_scroll_none_naga_hex_v2(self): # Notify others self.send_effect_event('setNone') + self.set_persistence("scroll", "effect", 'none') + driver_path = self.get_driver_path('scroll_matrix_effect_none') with open(driver_path, 'w') as driver_file: @@ -257,6 +291,11 @@ def set_scroll_reactive_naga_hex_v2(self, red, green, blue, speed): # Notify others self.send_effect_event('setReactive', red, green, blue, speed) + # remember effect + self.set_persistence("scroll", "effect", 'reactive') + self.zone["scroll"]["colors"][0:3] = int(red), int(green), int(blue) + self.set_persistence("scroll", "speed", int(speed)) + if speed not in (1, 2, 3, 4): speed = 4 @@ -276,6 +315,9 @@ def set_scroll_breath_random_naga_hex_v2(self): # Notify others self.send_effect_event('setBreathRandom') + # remember effect + self.set_persistence("scroll", "effect", 'breathRandom') + driver_path = self.get_driver_path('scroll_matrix_effect_breath') payload = b'1' @@ -303,6 +345,10 @@ def set_scroll_breath_single_naga_hex_v2(self, red, green, blue): # Notify others self.send_effect_event('setBreathSingle', red, green, blue) + # remember effect + self.set_persistence("scroll", "effect", 'breathSingle') + self.zone["scroll"]["colors"][0:3] = int(red), int(green), int(blue) + driver_path = self.get_driver_path('scroll_matrix_effect_breath') payload = bytes([red, green, blue]) @@ -339,6 +385,10 @@ def set_scroll_breath_dual_naga_hex_v2(self, red1, green1, blue1, red2, green2, # Notify others self.send_effect_event('setBreathDual', red1, green1, blue1, red2, green2, blue2) + # remember effect + self.set_persistence("scroll", "effect", 'breathDual') + self.zone["scroll"]["colors"][0:6] = int(red1), int(green1), int(blue1), int(red2), int(green2), int(blue2) + driver_path = self.get_driver_path('scroll_matrix_effect_breath') payload = bytes([red1, green1, blue1, red2, green2, blue2]) diff --git a/daemon/openrazer_daemon/hardware/device_base.py b/daemon/openrazer_daemon/hardware/device_base.py index 10bf9baa7706fa58f9d3f8c1bb02be5d5f9adf97..988e41f1949ad1bcd35455e6d89adee0c12fd042 100644 --- a/daemon/openrazer_daemon/hardware/device_base.py +++ b/daemon/openrazer_daemon/hardware/device_base.py @@ -4,6 +4,7 @@ Hardware base class import re import os import types +import inspect import logging import time import json @@ -15,6 +16,8 @@ from openrazer_daemon.misc import effect_sync # pylint: disable=too-many-instance-attributes +# pylint: disable=E1102 +# See https://github.com/PyCQA/pylint/issues/1493 class RazerDevice(DBusService): """ Base class @@ -35,9 +38,11 @@ class RazerDevice(DBusService): WAVE_DIRS = (1, 2) + ZONES = ('backlight', 'logo', 'scroll', 'left', 'right') + DEVICE_IMAGE = None - def __init__(self, device_path, device_number, config, testing=False, additional_interfaces=None, additional_methods=[]): + def __init__(self, device_path, device_number, config, persistence, testing=False, additional_interfaces=None, additional_methods=[]): self.logger = logging.getLogger('razer.device{0}'.format(device_number)) self.logger.info("Initialising device.%d %s", device_number, self.__class__.__name__) @@ -45,6 +50,9 @@ class RazerDevice(DBusService): # Serial cache self._serial = None + # Local storage key name + self.storage_name = "UnknownDevice" + self._observer_list = [] self._effect_sync_propagate_up = False self._disable_notifications = False @@ -53,12 +61,40 @@ class RazerDevice(DBusService): self.additional_interfaces.extend(additional_interfaces) self.config = config + self.persistence = persistence self._testing = testing self._parent = None self._device_path = device_path self._device_number = device_number self.serial = self.get_serial() + if self.USB_PID == 0x0f07: + self.storage_name = "ChromaMug" + elif self.USB_PID == 0x0013: + self.storage_name = "Orochi2011" + elif self.USB_PID == 0x0016: + self.storage_name = "DeathAdder35G" + elif self.USB_PID == 0x0024 or self.USB_PID == 0x0025: + self.storage_name = "Mamba2012" + else: + self.storage_name = self.serial + + self.zone = dict() + + for i in self.ZONES: + self.zone[i] = { + "present": False, + "active": True, + "brightness": 75.0, + "effect": 'spectrum', + "colors": [0, 255, 0, 0, 255, 255, 0, 0, 255], + "speed": 1, + "wave_dir": 1, + } + + self.dpi = [1800, 1800] + self.poll_rate = 500 + self._effect_sync = effect_sync.EffectSync(self, device_number) self._is_closed = False @@ -98,18 +134,169 @@ class RazerDevice(DBusService): ('razer.device.misc', 'getVidPid', self.get_vid_pid, None, 'ai'), ('razer.device.misc', 'getDriverVersion', openrazer_daemon.dbus_services.dbus_methods.version, None, 's'), ('razer.device.misc', 'hasDedicatedMacroKeys', self.dedicated_macro_keys, None, 'b'), - # Deprecated API, but kept for backwards compatibility - ('razer.device.misc', 'getRazerUrls', self.get_image_json, None, 's') + ('razer.device.misc', 'getRazerUrls', self.get_image_json, None, 's'), + + ('razer.device.lighting.chroma', 'restoreLastEffect', self.restore_effect, None, None), + } + + effect_methods = { + "backlight": { + ('razer.device.lighting.chroma', 'getEffect', self.get_current_effect, None, 's'), + ('razer.device.lighting.chroma', 'getEffectColors', self.get_current_effect_colors, None, 'ay'), + ('razer.device.lighting.chroma', 'getEffectSpeed', self.get_current_effect_speed, None, 'i'), + ('razer.device.lighting.chroma', 'getWaveDir', self.get_current_wave_dir, None, 'i'), + }, + + "logo": { + ('razer.device.lighting.logo', 'getLogoEffect', self.get_current_logo_effect, None, 's'), + ('razer.device.lighting.logo', 'getLogoEffectColors', self.get_current_logo_effect_colors, None, 'ay'), + ('razer.device.lighting.logo', 'getLogoEffectSpeed', self.get_current_logo_effect_speed, None, 'i'), + ('razer.device.lighting.logo', 'getLogoWaveDir', self.get_current_logo_wave_dir, None, 'i'), + }, + + "scroll": { + ('razer.device.lighting.scroll', 'getScrollEffect', self.get_current_scroll_effect, None, 's'), + ('razer.device.lighting.scroll', 'getScrollEffectColors', self.get_current_scroll_effect_colors, None, 'ay'), + ('razer.device.lighting.scroll', 'getScrollEffectSpeed', self.get_current_scroll_effect_speed, None, 'i'), + ('razer.device.lighting.scroll', 'getScrollWaveDir', self.get_current_scroll_wave_dir, None, 'i'), + }, + + "left": { + ('razer.device.lighting.left', 'getLeftEffect', self.get_current_left_effect, None, 's'), + ('razer.device.lighting.left', 'getLeftEffectColors', self.get_current_left_effect_colors, None, 'ay'), + ('razer.device.lighting.left', 'getLeftEffectSpeed', self.get_current_left_effect_speed, None, 'i'), + ('razer.device.lighting.left', 'getLeftWaveDir', self.get_current_left_wave_dir, None, 'i'), + }, + + "right": { + ('razer.device.lighting.right', 'getRightEffect', self.get_current_right_effect, None, 's'), + ('razer.device.lighting.right', 'getRightEffectColors', self.get_current_right_effect_colors, None, 'ay'), + ('razer.device.lighting.right', 'getRightEffectSpeed', self.get_current_right_effect_speed, None, 'i'), + ('razer.device.lighting.right', 'getRightWaveDir', self.get_current_right_wave_dir, None, 'i'), + } } for m in methods: self.logger.debug("Adding {}.{} method to DBus".format(m[0], m[1])) self.add_dbus_method(m[0], m[1], m[2], in_signature=m[3], out_signature=m[4]) + # this check is separate from the rest because backlight effects don't have prefixes in their names + if 'set_static_effect' in self.METHODS or 'bw_set_static' in self.METHODS: + self.zone["backlight"]["present"] = True + for m in effect_methods["backlight"]: + self.logger.debug("Adding {}.{} method to DBus".format(m[0], m[1])) + self.add_dbus_method(m[0], m[1], m[2], in_signature=m[3], out_signature=m[4]) + + for i in self.ZONES[1:]: + if 'set_' + i + '_static' in self.METHODS or 'set_' + i + '_static_naga_hex_v2' in self.METHODS or '`set_' + i + 'active' in self.METHODS: + self.zone[i]["present"] = True + for m in effect_methods[i]: + self.logger.debug("Adding {}.{} method to DBus".format(m[0], m[1])) + self.add_dbus_method(m[0], m[1], m[2], in_signature=m[3], out_signature=m[4]) + # Load additional DBus methods self.load_methods() + # load last DPI/poll rate state + if self.persistence.has_section(self.storage_name): + if 'set_dpi_xy' in self.METHODS: + try: + self.dpi[0] = int(self.persistence[self.storage_name]['dpi_x']) + self.dpi[1] = int(self.persistence[self.storage_name]['dpi_y']) + except KeyError: + pass + + if 'set_poll_rate' in self.METHODS: + try: + self.poll_rate = int(self.persistence[self.storage_name]['poll_rate']) + except KeyError: + pass + + dpi_func = getattr(self, "setDPI", None) + if dpi_func is not None: + dpi_func(self.dpi[0], self.dpi[1]) + + poll_rate_func = getattr(self, "setPollRate", None) + if poll_rate_func is not None: + poll_rate_func(self.poll_rate) + + # load last effects + for i in self.ZONES: + if self.zone[i]["present"]: + # check if we have the device in the persistence file + if self.persistence.has_section(self.storage_name): + # try reading the effect name from the persistence + try: + self.zone[i]["effect"] = self.persistence[self.storage_name][i + '_effect'] + except KeyError: + pass + + # zone active status + try: + self.zone[i]["active"] = bool(self.persistence[self.storage_name][i + '_active']) + except KeyError: + pass + + # brightness + try: + self.zone[i]["brightness"] = float(self.persistence[self.storage_name][i + '_brightness']) + except KeyError: + pass + + # colors. + # these are stored as a string that must contain 9 numbers, separated with spaces. + try: + for index, item in enumerate(self.persistence[self.storage_name][i + '_colors'].split(" ")): + self.zone[i]["colors"][index] = int(item) + # check if the color is in range + if not 0 <= self.zone[i]["colors"][index] <= 255: + raise ValueError('Color out of range') + + # check if we have exactly 9 colors + if len(self.zone[i]["colors"]) != 9: + raise ValueError('There must be exactly 9 colors') + + except ValueError: + # invalid colors. reinitialize + self.zone[i]["colors"] = [0, 255, 0, 0, 255, 255, 0, 0, 255] + self.logger.info("%s: Invalid colors; restoring to defaults.", self.__class__.__name__) + pass + + except KeyError: + pass + + # speed + try: + self.zone[i]["speed"] = int(self.persistence[self.storage_name][i + '_speed']) + + except KeyError: + pass + + # wave direction + try: + self.zone[i]["wave_dir"] = int(self.persistence[self.storage_name][i + '_wave_dir']) + + except KeyError: + pass + + if 'set_' + i + '_active' in self.METHODS: + active_func = getattr(self, "set" + self.capitalize_first_char(i) + "Active", None) + if active_func is not None: + active_func(self.zone[i]["active"]) + + # load brightness level + bright_func = None + if i == "backlight": + bright_func = getattr(self, "setBrightness", None) + elif 'set_' + i + '_brightness' in self.METHODS: + bright_func = getattr(self, "set" + self.capitalize_first_char(i) + "Brightness", None) + + if bright_func is not None: + bright_func(self.zone[i]["brightness"]) + + self.restore_effect() + def send_effect_event(self, effect_name, *args): """ Send effect event @@ -134,6 +321,317 @@ class RazerDevice(DBusService): """ return self.DEDICATED_MACRO_KEYS + def restore_effect(self): + """ + Set the device to the current effect + + This is used at launch time and can be called by applications + that use custom matrix frames after they exit + """ + for i in self.ZONES: + if self.zone[i]["present"]: + # prepare the effect method name + # yes, we need to handle the backlight zone separately too. + # the backlight effect methods don't have a prefix. + if i == "backlight": + effect_func_name = 'set' + self.capitalize_first_char(self.zone[i]["effect"]) + else: + effect_func_name = 'set' + self.capitalize_first_char(i) + self.capitalize_first_char(self.zone[i]["effect"]) + + # find the effect method + effect_func = getattr(self, effect_func_name, None) + + # check if the effect method exists only if we didn't look for spectrum (because resetting to Spectrum when the effect is Spectrum is in vain) + if effect_func == None and not self.zone[i]["effect"] == "spectrum": + # not found. restoring to Spectrum + self.logger.info("%s: Invalid effect name %s; restoring to Spectrum.", self.__class__.__name__, effect_func_name) + self.zone[i]["effect"] = 'spectrum' + if i == "backlight": + effect_func_name = 'setSpectrum' + else: + effect_func_name = 'set' + self.capitalize_first_char(i) + 'Spectrum' + effect_func = getattr(self, effect_func_name, None) + + # we check again here because there is a possibility the device may not even have Spectrum + if effect_func is not None: + effect = self.zone[i]["effect"] + colors = self.zone[i]["colors"] + speed = self.zone[i]["speed"] + wave_dir = self.zone[i]["wave_dir"] + if self.get_num_arguments(effect_func) == 0: + effect_func() + elif self.get_num_arguments(effect_func) == 1: + # there are 2 effects which require 1 argument. + # these are: Starlight (Random) and Wave. + if effect == 'starlightRandom': + effect_func(speed) + elif effect == 'wave': + effect_func(wave_dir) + elif effect == 'rippleRandomColour': + # do nothing. this is handled in the ripple manager. + pass + else: + self.logger.error("%s: Effect requires 1 argument but don't know how to handle it!", self.__class__.__name__) + elif self.get_num_arguments(effect_func) == 3: + effect_func(colors[0], colors[1], colors[2]) + elif self.get_num_arguments(effect_func) == 4: + # starlight/reactive have different arguments. + if effect == 'starlightSingle' or effect == 'reactive': + effect_func(colors[0], colors[1], colors[2], speed) + elif effect == 'ripple': + # do nothing. this is handled in the ripple manager. + pass + else: + self.logger.error("%s: Effect requires 4 arguments but don't know how to handle it!", self.__class__.__name__) + elif self.get_num_arguments(effect_func) == 6: + effect_func(colors[0], colors[1], colors[2], colors[3], colors[4], colors[5]) + elif self.get_num_arguments(effect_func) == 7: + effect_func(colors[0], colors[1], colors[2], colors[3], colors[4], colors[5], speed) + elif self.get_num_arguments(effect_func) == 9: + effect_func(colors[0], colors[1], colors[2], colors[3], colors[4], colors[5], colors[6], colors[7], colors[8]) + else: + self.logger.error("%s: Couldn't detect effect argument count!", self.__class__.__name__) + + def set_persistence(self, zone, key, value): + """ + Set a device's current state for persisting across sessions. + + :param zone: Zone + :type zone: string + + :param key: Key + :type key: string + + :param value: Value + :type value: string + """ + self.persistence.status["changed"] = True + + if zone: + self.zone[zone][key] = value + else: + self.zone[key] = value + + def get_current_effect(self): + """ + Get the device's current effect + + :return: Effect + :rtype: string + """ + self.logger.debug("DBus call get_current_effect") + + return self.zone["backlight"]["effect"] + + def get_current_effect_colors(self): + """ + Get the device's current effect's colors + + :return: 3 colors + :rtype: list of byte + """ + self.logger.debug("DBus call get_current_effect_colors") + + return self.zone["backlight"]["colors"] + + def get_current_effect_speed(self): + """ + Get the device's current effect's speed + + :return: Speed + :rtype: int + """ + self.logger.debug("DBus call get_current_effect_speed") + + return self.zone["backlight"]["speed"] + + def get_current_wave_dir(self): + """ + Get the device's current wave direction + + :return: Direction + :rtype: int + """ + self.logger.debug("DBus call get_current_wave_dir") + + return self.zone["backlight"]["wave_dir"] + + def get_current_logo_effect(self): + """ + Get the device's current logo effect + + :return: Effect + :rtype: string + """ + self.logger.debug("DBus call get_current_logo_effect") + + return self.zone["logo"]["effect"] + + def get_current_logo_effect_colors(self): + """ + Get the device's current logo effect's colors + + :return: 3 colors + :rtype: list of byte + """ + self.logger.debug("DBus call get_current_logo_effect_colors") + + return self.zone["logo"]["colors"] + + def get_current_logo_effect_speed(self): + """ + Get the device's current logo effect's speed + + :return: Speed + :rtype: int + """ + self.logger.debug("DBus call get_current_logo_effect_speed") + + return self.zone["logo"]["speed"] + + def get_current_logo_wave_dir(self): + """ + Get the device's current logo wave direction + + :return: Direction + :rtype: int + """ + self.logger.debug("DBus call get_current_logo_wave_dir") + + return self.zone["logo"]["wave_dir"] + + def get_current_scroll_effect(self): + """ + Get the device's current scroll effect + + :return: Effect + :rtype: string + """ + self.logger.debug("DBus call get_current_scroll_effect") + + return self.zone["scroll"]["effect"] + + def get_current_scroll_effect_colors(self): + """ + Get the device's current scroll effect's colors + + :return: 3 colors + :rtype: list of byte + """ + self.logger.debug("DBus call get_current_scroll_effect_colors") + + return self.zone["scroll"]["colors"] + + def get_current_scroll_effect_speed(self): + """ + Get the device's current scroll effect's speed + + :return: Speed + :rtype: int + """ + self.logger.debug("DBus call get_current_scroll_effect_speed") + + return self.zone["scroll"]["speed"] + + def get_current_scroll_wave_dir(self): + """ + Get the device's current scroll wave direction + + :return: Direction + :rtype: int + """ + self.logger.debug("DBus call get_current_scroll_wave_dir") + + return self.zone["scroll"]["wave_dir"] + + def get_current_left_effect(self): + """ + Get the device's current left effect + + :return: Effect + :rtype: string + """ + self.logger.debug("DBus call get_current_left_effect") + + return self.zone["left"]["effect"] + + def get_current_left_effect_colors(self): + """ + Get the device's current left effect's colors + + :return: 3 colors + :rtype: list of byte + """ + self.logger.debug("DBus call get_current_left_effect_colors") + + return self.zone["left"]["colors"] + + def get_current_left_effect_speed(self): + """ + Get the device's current left effect's speed + + :return: Speed + :rtype: int + """ + self.logger.debug("DBus call get_current_left_effect_speed") + + return self.zone["left"]["speed"] + + def get_current_left_wave_dir(self): + """ + Get the device's current left wave direction + + :return: Direction + :rtype: int + """ + self.logger.debug("DBus call get_current_left_wave_dir") + + return self.zone["left"]["wave_dir"] + + def get_current_right_effect(self): + """ + Get the device's current right effect + + :return: Effect + :rtype: string + """ + self.logger.debug("DBus call get_current_right_effect") + + return self.zone["right"]["effect"] + + def get_current_right_effect_colors(self): + """ + Get the device's current right effect's colors + + :return: 3 colors + :rtype: list of byte + """ + self.logger.debug("DBus call get_current_right_effect_colors") + + return self.zone["right"]["colors"] + + def get_current_right_effect_speed(self): + """ + Get the device's current right effect's speed + + :return: Speed + :rtype: int + """ + self.logger.debug("DBus call get_current_right_effect_speed") + + return self.zone["right"]["speed"] + + def get_current_right_wave_dir(self): + """ + Get the device's current right wave direction + + :return: Direction + :rtype: int + """ + self.logger.debug("DBus call get_current_right_wave_dir") + + return self.zone["right"]["wave_dir"] + @property def effect_sync(self): """ @@ -349,6 +847,14 @@ class RazerDevice(DBusService): Close any resources opened by subclasses """ if not self._is_closed: + # If this is a mouse, retrieve current DPI for local storage + # in case the user has changed the DPI on-the-fly + # (e.g. the DPI buttons) + if 'get_dpi_xy' in self.METHODS: + dpi_func = getattr(self, "getDPI", None) + if dpi_func is not None: + self.dpi = dpi_func() + self._close() self._is_closed = True @@ -434,6 +940,24 @@ class RazerDevice(DBusService): return False + @staticmethod + def get_num_arguments(func): + """ + Get number of arguments in a function + + :param func: Function + :type func: callable + + :return: Number of arguments + :rtype: int + """ + func_sig = inspect.signature(func) + return len(func_sig.parameters) + + @staticmethod + def capitalize_first_char(string): + return string[0].upper() + string[1:] + def __del__(self): self.close() @@ -448,8 +972,8 @@ class RazerDeviceSpecialBrightnessSuspend(RazerDevice): Suspend functions """ - def __init__(self, device_path, device_number, config, testing=False, additional_interfaces=None, additional_methods=[]): - super().__init__(device_path, device_number, config, testing, additional_interfaces, additional_methods) + def __init__(self, device_path, device_number, config, persistence, testing=False, additional_interfaces=None, additional_methods=[]): + super().__init__(device_path, device_number, config, persistence, testing, additional_interfaces, additional_methods) def _suspend_device(self): """ @@ -484,6 +1008,6 @@ class RazerDeviceBrightnessSuspend(RazerDeviceSpecialBrightnessSuspend): Inherits from RazerDeviceSpecialBrightnessSuspend """ - def __init__(self, device_path, device_number, config, testing=False, additional_interfaces=None, additional_methods=[]): + def __init__(self, device_path, device_number, config, persistence, testing=False, additional_interfaces=None, additional_methods=[]): additional_methods.extend(['get_brightness', 'set_brightness']) - super().__init__(device_path, device_number, config, testing, additional_interfaces, additional_methods) + super().__init__(device_path, device_number, config, persistence, testing, additional_interfaces, additional_methods) diff --git a/daemon/openrazer_daemon/hardware/headsets.py b/daemon/openrazer_daemon/hardware/headsets.py index d6bebdacae78e2b79d09a95798badbfb8298b34c..591b4723bce1764ebeacd901f1351a9c27006880 100644 --- a/daemon/openrazer_daemon/hardware/headsets.py +++ b/daemon/openrazer_daemon/hardware/headsets.py @@ -16,7 +16,7 @@ class RazerKraken71(__RazerDevice): USB_VID = 0x1532 USB_PID = 0x0501 METHODS = ['get_device_type_headset', - 'set_static_effect', 'set_none_effect', 'get_current_effect_kraken'] + 'set_static_effect', 'set_none_effect'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/229/229_kraken_71.png" @@ -39,13 +39,7 @@ class RazerKraken71(__RazerDevice): """ self.suspend_args.clear() - current_effect = _dbus_kraken.get_current_effect_kraken(self) - dec = self.decode_bitfield(current_effect) - - if dec['state'] == 0x00: - self.suspend_args['effect'] = 'none' - elif dec['state'] == 0x01: - self.suspend_args['effect'] = 'static' + self.suspend_args['effect'] = self.zone["backlight"]["effect"] self.disable_notify = True _dbus_chroma.set_none_effect(self) @@ -84,7 +78,7 @@ class RazerKraken71Chroma(__RazerDevice): USB_PID = 0x0504 METHODS = ['get_device_type_headset', 'set_static_effect', 'set_spectrum_effect', 'set_none_effect', 'set_breath_single_effect', - 'get_current_effect_kraken', 'get_static_effect_args_kraken', 'get_breath_effect_args_kraken', 'set_custom_kraken'] + 'set_custom_kraken'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/280/280_kraken_71_chroma.png" @@ -107,17 +101,8 @@ class RazerKraken71Chroma(__RazerDevice): """ self.suspend_args.clear() - current_effect = _dbus_kraken.get_current_effect_kraken(self) - dec = self.decode_bitfield(current_effect) - - if dec['breathing1']: - self.suspend_args['effect'] = 'breathing1' - self.suspend_args['args'] = _dbus_kraken.get_breath_effect_args_kraken(self) - elif dec['spectrum']: - self.suspend_args['effect'] = 'spectrum' - elif dec['state']: - self.suspend_args['effect'] = 'static' - self.suspend_args['args'] = _dbus_kraken.get_static_effect_args_kraken(self) + self.suspend_args['effect'] = self.zone["backlight"]["effect"] + self.suspend_args['args'] = self.zone["backlight"]["colors"][0:3] self.disable_notify = True _dbus_chroma.set_none_effect(self) @@ -138,7 +123,7 @@ class RazerKraken71Chroma(__RazerDevice): _dbus_chroma.set_spectrum_effect(self) elif effect == 'static': _dbus_chroma.set_static_effect(self, *args) - elif effect == 'breathing1': + elif effect == 'breathSingle': _dbus_chroma.set_breath_single_effect(self, *args) self.disable_notify = False @@ -154,7 +139,7 @@ class RazerKraken71V2(__RazerDevice): USB_PID = 0x0510 METHODS = ['get_device_type_headset', 'set_static_effect', 'set_spectrum_effect', 'set_none_effect', 'set_breath_single_effect', 'set_breath_dual_effect', 'set_breath_triple_effect', - 'get_current_effect_kraken', 'get_static_effect_args_kraken', 'get_breath_effect_args_kraken', 'set_custom_kraken'] + 'set_custom_kraken'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/729/729_kraken_71_v2.png" @@ -177,23 +162,13 @@ class RazerKraken71V2(__RazerDevice): """ self.suspend_args.clear() - current_effect = _dbus_kraken.get_current_effect_kraken(self) - dec = self.decode_bitfield(current_effect) - - if dec['breathing1']: - self.suspend_args['effect'] = 'breathing1' - self.suspend_args['args'] = _dbus_kraken.get_breath_effect_args_kraken(self) - elif dec['breathing2']: - self.suspend_args['effect'] = 'breathing2' - self.suspend_args['args'] = _dbus_kraken.get_breath_effect_args_kraken(self) - elif dec['breathing3']: - self.suspend_args['effect'] = 'breathing3' - self.suspend_args['args'] = _dbus_kraken.get_breath_effect_args_kraken(self) - elif dec['spectrum']: - self.suspend_args['effect'] = 'spectrum' - elif dec['state']: - self.suspend_args['effect'] = 'static' - self.suspend_args['args'] = _dbus_kraken.get_static_effect_args_kraken(self) + self.suspend_args['effect'] = self.zone["backlight"]["effect"] + if self.suspend_args['effect'] == "breathDual": + self.suspend_args['args'] = self.zone["backlight"]["colors"][0:6] + elif self.suspend_args['effect'] == "breathTriple": + self.suspend_args['args'] = self.zone["backlight"]["colors"][0:9] + else: + self.suspend_args['args'] = self.zone["backlight"]["colors"][0:3] self.disable_notify = True _dbus_chroma.set_none_effect(self) @@ -214,11 +189,11 @@ class RazerKraken71V2(__RazerDevice): _dbus_chroma.set_spectrum_effect(self) elif effect == 'static': _dbus_chroma.set_static_effect(self, *args) - elif effect == 'breathing1': + elif effect == 'breathSingle': _dbus_chroma.set_breath_single_effect(self, *args) - elif effect == 'breathing2': + elif effect == 'breathDual': _dbus_chroma.set_breath_dual_effect(self, *args) - elif effect == 'breathing3': + elif effect == 'breathTriple': _dbus_chroma.set_breath_triple_effect(self, *args) self.disable_notify = False @@ -234,8 +209,8 @@ class RazerKrakenUltimate(__RazerDevice): USB_PID = 0x0527 METHODS = ['get_device_type_headset', 'set_static_effect', 'set_spectrum_effect', 'set_none_effect', 'set_breath_single_effect', - 'set_breath_dual_effect', 'set_breath_triple_effect', 'get_current_effect_kraken', - 'get_static_effect_args_kraken', 'get_breath_effect_args_kraken', 'set_custom_kraken'] + 'set_breath_dual_effect', 'set_breath_triple_effect', + 'get_static_effect_args_kraken', 'set_custom_kraken'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/1603/rzr_kraken_ultimate_render01_2019_resized.png" @@ -258,23 +233,13 @@ class RazerKrakenUltimate(__RazerDevice): """ self.suspend_args.clear() - current_effect = _dbus_kraken.get_current_effect_kraken(self) - dec = self.decode_bitfield(current_effect) - - if dec['breathing1']: - self.suspend_args['effect'] = 'breathing1' - self.suspend_args['args'] = _dbus_kraken.get_breath_effect_args_kraken(self) - elif dec['breathing2']: - self.suspend_args['effect'] = 'breathing2' - self.suspend_args['args'] = _dbus_kraken.get_breath_effect_args_kraken(self) - elif dec['breathing3']: - self.suspend_args['effect'] = 'breathing3' - self.suspend_args['args'] = _dbus_kraken.get_breath_effect_args_kraken(self) - elif dec['spectrum']: - self.suspend_args['effect'] = 'spectrum' - elif dec['state']: - self.suspend_args['effect'] = 'static' - self.suspend_args['args'] = _dbus_kraken.get_static_effect_args_kraken(self) + self.suspend_args['effect'] = self.zone["backlight"]["effect"] + if self.suspend_args['effect'] == "breathDual": + self.suspend_args['args'] = self.zone["backlight"]["colors"][0:6] + elif self.suspend_args['effect'] == "breathTriple": + self.suspend_args['args'] = self.zone["backlight"]["colors"][0:9] + else: + self.suspend_args['args'] = self.zone["backlight"]["colors"][0:3] self.disable_notify = True _dbus_chroma.set_none_effect(self) @@ -295,11 +260,11 @@ class RazerKrakenUltimate(__RazerDevice): _dbus_chroma.set_spectrum_effect(self) elif effect == 'static': _dbus_chroma.set_static_effect(self, *args) - elif effect == 'breathing1': + elif effect == 'breathSingle': _dbus_chroma.set_breath_single_effect(self, *args) - elif effect == 'breathing2': + elif effect == 'breathDual': _dbus_chroma.set_breath_dual_effect(self, *args) - elif effect == 'breathing3': + elif effect == 'breathTriple': _dbus_chroma.set_breath_triple_effect(self, *args) self.disable_notify = False diff --git a/daemon/openrazer_daemon/hardware/keyboards.py b/daemon/openrazer_daemon/hardware/keyboards.py index a47a2431742a632070845d8f47d3a29d7ef13ad2..5973169f15ef16ff4f2427c29fb7e6c0074a5bc3 100644 --- a/daemon/openrazer_daemon/hardware/keyboards.py +++ b/daemon/openrazer_daemon/hardware/keyboards.py @@ -55,6 +55,18 @@ class _RippleKeyboard(_MacroKeyboard): self.ripple_manager = _RippleManager(self, self._device_number) + # we need to set the effect to ripple (if needed) after the ripple manager has started + # otherwise it doesn't work + if self.zone["backlight"]["effect"] == "ripple" or self.zone["backlight"]["effect"] == "rippleRandomColour": + effect_func_name = 'set' + self.capitalize_first_char(self.zone["backlight"]["effect"]) + effect_func = getattr(self, effect_func_name, None) + + if effect_func is not None: + if effect_func_name == 'setRipple': + effect_func(self.zone["backlight"]["colors"][0], self.zone["backlight"]["colors"][1], self.zone["backlight"]["colors"][2], self.ripple_manager._ripple_thread._refresh_rate) + elif effect_func_name == 'setRippleRandomColour': + effect_func(self.ripple_manager._ripple_thread._refresh_rate) + def _close(self): super(_RippleKeyboard, self)._close() @@ -285,7 +297,7 @@ class RazerBlackWidowUltimate2012(_MacroKeyboard): DEDICATED_MACRO_KEYS = True MATRIX_DIMS = [6, 22] METHODS = ['get_device_type_keyboard', 'get_game_mode', 'set_game_mode', 'set_macro_mode', 'get_macro_mode', - 'get_macro_effect', 'set_macro_effect', 'bw_get_effect', 'bw_set_pulsate', 'bw_set_static', 'get_macros', 'delete_macro', 'add_macro'] + 'get_macro_effect', 'set_macro_effect', 'bw_set_pulsate', 'bw_set_static', 'get_macros', 'delete_macro', 'add_macro'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/563/563_blackwidow_ultimate_classic.png" @@ -301,7 +313,7 @@ class RazerBlackWidowStealth(_MacroKeyboard): DEDICATED_MACRO_KEYS = True MATRIX_DIMS = [6, 22] METHODS = ['get_device_type_keyboard', 'get_game_mode', 'set_game_mode', 'set_macro_mode', 'get_macro_mode', - 'get_macro_effect', 'set_macro_effect', 'bw_get_effect', 'bw_set_pulsate', 'bw_set_static', 'get_macros', 'delete_macro', 'add_macro'] + 'get_macro_effect', 'set_macro_effect', 'bw_set_pulsate', 'bw_set_static', 'get_macros', 'delete_macro', 'add_macro'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/products/17559/razer-blackwidow-gallery-01.png" @@ -317,7 +329,7 @@ class RazerBlackWidowStealthEdition(_MacroKeyboard): DEDICATED_MACRO_KEYS = True MATRIX_DIMS = [6, 22] METHODS = ['get_device_type_keyboard', 'get_game_mode', 'set_game_mode', 'set_macro_mode', 'get_macro_mode', - 'get_macro_effect', 'set_macro_effect', 'bw_get_effect', 'bw_set_pulsate', 'bw_set_static', 'get_macros', 'delete_macro', 'add_macro'] + 'get_macro_effect', 'set_macro_effect', 'bw_set_pulsate', 'bw_set_static', 'get_macros', 'delete_macro', 'add_macro'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/products/17559/razer-blackwidow-gallery-01.png" @@ -333,7 +345,7 @@ class RazerBlackWidowUltimate2013(_MacroKeyboard): DEDICATED_MACRO_KEYS = True MATRIX_DIMS = [6, 22] METHODS = ['get_device_type_keyboard', 'get_game_mode', 'set_game_mode', 'set_macro_mode', 'get_macro_mode', - 'get_macro_effect', 'set_macro_effect', 'bw_get_effect', 'bw_set_pulsate', 'bw_set_static', 'get_macros', 'delete_macro', 'add_macro'] + 'get_macro_effect', 'set_macro_effect', 'bw_set_pulsate', 'bw_set_static', 'get_macros', 'delete_macro', 'add_macro'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/245/438_blackwidow_ultimate_2014.png" @@ -836,7 +848,7 @@ class RazerDeathStalkerExpert(_MacroKeyboard): USB_VID = 0x1532 USB_PID = 0x0202 METHODS = ['get_device_type_keyboard', 'get_game_mode', 'set_game_mode', 'set_macro_mode', 'get_macro_mode', - 'get_macro_effect', 'set_macro_effect', 'bw_get_effect', 'bw_set_pulsate', 'bw_set_static', 'get_macros', 'delete_macro', 'add_macro'] + 'get_macro_effect', 'set_macro_effect', 'bw_set_pulsate', 'bw_set_static', 'get_macros', 'delete_macro', 'add_macro'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/49/49_razer_deathstalker.png" diff --git a/daemon/openrazer_daemon/hardware/mouse.py b/daemon/openrazer_daemon/hardware/mouse.py index fdc64d69f1e5860c53c4158909bcdf9548ff89ae..d68885df1eb349271e3a816b0fc8369a1d2b6220 100644 --- a/daemon/openrazer_daemon/hardware/mouse.py +++ b/daemon/openrazer_daemon/hardware/mouse.py @@ -620,8 +620,8 @@ class RazerDeathAdderChroma(__RazerDeviceSpecialBrightnessSuspend): USB_VID = 0x1532 USB_PID = 0x0043 METHODS = ['get_device_type_mouse', - 'set_logo_active', 'get_logo_active', 'get_logo_effect', 'get_logo_brightness', 'set_logo_brightness', 'set_logo_static', 'set_logo_pulsate', 'set_logo_blinking', 'set_logo_spectrum', - 'set_scroll_active', 'get_scroll_active', 'get_scroll_effect', 'get_scroll_brightness', 'set_scroll_brightness', 'set_scroll_static', 'set_scroll_pulsate', 'set_scroll_blinking', 'set_scroll_spectrum', + 'set_logo_active', 'get_logo_active', 'get_logo_brightness', 'set_logo_brightness', 'set_logo_static', 'set_logo_pulsate', 'set_logo_blinking', 'set_logo_spectrum', + 'set_scroll_active', 'get_scroll_active', 'get_scroll_brightness', 'set_scroll_brightness', 'set_scroll_static', 'set_scroll_pulsate', 'set_scroll_blinking', 'set_scroll_spectrum', 'max_dpi', 'get_dpi_xy', 'set_dpi_xy', 'get_poll_rate', 'set_poll_rate'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/278/278_deathadder_chroma.png" @@ -1232,7 +1232,7 @@ class RazerMamba2012Wireless(__RazerDeviceSpecialBrightnessSuspend): USB_PID = 0x0025 METHODS = ['get_device_type_mouse', 'get_battery', 'is_charging', 'set_idle_time', 'set_low_battery_threshold', 'max_dpi', 'get_dpi_xy', 'set_dpi_xy', 'get_poll_rate', 'set_poll_rate', - 'set_scroll_active', 'get_scroll_active', 'get_scroll_effect', 'get_scroll_brightness', 'set_scroll_brightness', 'set_scroll_static', 'set_scroll_spectrum'] + 'set_scroll_active', 'get_scroll_active', 'get_scroll_brightness', 'set_scroll_brightness', 'set_scroll_static', 'set_scroll_spectrum'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/192/192_mamba_2012.png" @@ -1288,7 +1288,7 @@ class RazerMamba2012Wired(__RazerDevice): USB_PID = 0x0024 METHODS = ['get_device_type_mouse', 'set_idle_time', 'set_low_battery_threshold', 'max_dpi', 'get_dpi_xy', 'set_dpi_xy', 'get_poll_rate', 'set_poll_rate', - 'set_scroll_active', 'get_scroll_active', 'get_scroll_effect', 'get_scroll_brightness', 'set_scroll_brightness', 'set_scroll_static', 'set_scroll_spectrum', + 'set_scroll_active', 'get_scroll_active', 'get_scroll_brightness', 'set_scroll_brightness', 'set_scroll_static', 'set_scroll_spectrum', 'get_battery', 'is_charging'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/192/192_mamba_2012.png" @@ -1497,8 +1497,8 @@ class RazerAbyssusV2(__RazerDeviceSpecialBrightnessSuspend): USB_VID = 0x1532 USB_PID = 0x005B METHODS = ['get_device_type_mouse', - 'set_logo_active', 'get_logo_active', 'get_logo_effect', 'get_logo_brightness', 'set_logo_brightness', 'set_logo_static', 'set_logo_pulsate', 'set_logo_blinking', 'set_logo_spectrum', - 'set_scroll_active', 'get_scroll_active', 'get_scroll_effect', 'get_scroll_brightness', 'set_scroll_brightness', 'set_scroll_static', 'set_scroll_pulsate', 'set_scroll_blinking', 'set_scroll_spectrum', + 'set_logo_active', 'get_logo_active', 'get_logo_brightness', 'set_logo_brightness', 'set_logo_static', 'set_logo_pulsate', 'set_logo_blinking', 'set_logo_spectrum', + 'set_scroll_active', 'get_scroll_active', 'get_scroll_brightness', 'set_scroll_brightness', 'set_scroll_static', 'set_scroll_pulsate', 'set_scroll_blinking', 'set_scroll_spectrum', 'max_dpi', 'get_dpi_xy', 'set_dpi_xy', 'get_poll_rate', 'set_poll_rate'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/721/721_abyssusv2.png" @@ -1631,8 +1631,8 @@ class RazerDeathAdder3500(__RazerDeviceSpecialBrightnessSuspend): USB_VID = 0x1532 USB_PID = 0x0054 METHODS = ['get_device_type_mouse', - 'get_logo_effect', 'get_logo_brightness', 'set_logo_brightness', 'set_logo_static', 'set_logo_pulsate', 'set_logo_blinking', - 'get_scroll_effect', 'get_scroll_brightness', 'set_scroll_brightness', 'set_scroll_static', 'set_scroll_pulsate', 'set_scroll_blinking', + 'get_logo_brightness', 'set_logo_brightness', 'set_logo_static', 'set_logo_pulsate', 'set_logo_blinking', + 'get_scroll_brightness', 'set_scroll_brightness', 'set_scroll_static', 'set_scroll_pulsate', 'set_scroll_blinking', 'max_dpi', 'get_dpi_xy', 'set_dpi_xy', 'get_poll_rate', 'set_poll_rate'] DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/561/561_deathadder_classic.png" diff --git a/daemon/openrazer_daemon/misc/autosave_persistence.py b/daemon/openrazer_daemon/misc/autosave_persistence.py new file mode 100644 index 0000000000000000000000000000000000000000..f1d1c155e16b496dae700bca386515d9d7417708 --- /dev/null +++ b/daemon/openrazer_daemon/misc/autosave_persistence.py @@ -0,0 +1,36 @@ +""" +A class that writes persistence data to disk when device state is updated. + +Due to the architecture of the daemon, it's not as simple to know when +something has changed, so for now, an interval will periodically check for +changes in memory and write them to disk. This also avoids excessive +writes when lots of variables change at once. + +This is essential because many desktop environments actually kill off +the daemon upon logout/shutdown, thereby persistence isn't retained across +sessions. + +A known issue is that this doesn't monitor DPI changes via hardware buttons, +so this won't be persisted until the state is updated via the API. +""" +import time + + +class PersistenceAutoSave(object): + def __init__(self, persistence, persistence_file, persistence_status, logger, interval, persistence_save_fn): + self.persistence = persistence + self.persistence_file = persistence_file + self.persistence_status = persistence_status + self.persistence_save_fn = persistence_save_fn + self.logger = logger + self.interval = interval + + def watch(self): + # Run indefinitely until process is terminated + while True: + time.sleep(self.interval) + + if self.persistence_status["changed"]: + self.logger.debug("State recently changed, writing to disk") + self.persistence_status["changed"] = False + self.persistence_save_fn(self.persistence_file) diff --git a/daemon/resources/man/openrazer-daemon.8 b/daemon/resources/man/openrazer-daemon.8 index 980a54d96eead0bba7bcbe08c960e83d4cae679f..a42d7211c34752a6825c028c71ac990578546b91 100644 --- a/daemon/resources/man/openrazer-daemon.8 +++ b/daemon/resources/man/openrazer-daemon.8 @@ -1,10 +1,11 @@ -.\" Generated by scdoc 1.8.1 +.\" Generated by scdoc 1.11.0 +.\" Complete documentation for this program is not available as a GNU info page .ie \n(.g .ds Aq \(aq .el .ds Aq ' .nh .ad l .\" Begin generated content: -.TH "openrazer-daemon" "8" "2019-02-06" +.TH "openrazer-daemon" "8" "2020-11-11" .P .SH NAME .P @@ -54,6 +55,11 @@ Allow the daemon to be started as root. Specifies the location of the config file. If this is not provided it will default to ~/.config/openrazer/razer.conf and create it if needed from the example config. .P .RE +\fB--persistence\fR=\fIpersistence_file\fR +.RS 4 +Specifies the location of the persistence file. This will be created if non-existent. This file will store device states so they persist between reboots. +.P +.RE \fB--run-dir\fR=\fIrun_directory\fR .RS 4 Tells the daemon what directory is its run directory, the directory it will change to once started. It will default to \fB$XDG_RUNTIME_DIR\fR, if not set it falls back to ~/.local/share/openrazer/. diff --git a/daemon/resources/man/openrazer-daemon.8.scd b/daemon/resources/man/openrazer-daemon.8.scd index 449fa6ea6dc243dd9863871d65a33fb00c0d03c9..1757dc9947a31e4bd488ddc8c7af15f356c33907 100644 --- a/daemon/resources/man/openrazer-daemon.8.scd +++ b/daemon/resources/man/openrazer-daemon.8.scd @@ -36,6 +36,9 @@ Note, that all paths shown as defaults, eg ~/.local/share/ or ~/.config/ can be *--config*=_config\_file_ Specifies the location of the config file. If this is not provided it will default to ~/.config/openrazer/razer.conf and create it if needed from the example config. +*--persistence*=_persistence\_file_ + Specifies the location of the persistence file. This will be created if non-existent. This file will store device states so they persist between reboots. + *--run-dir*=_run\_directory_ Tells the daemon what directory is its run directory, the directory it will change to once started. It will default to *$XDG_RUNTIME_DIR*, if not set it falls back to ~/.local/share/openrazer/. diff --git a/daemon/run_openrazer_daemon.py b/daemon/run_openrazer_daemon.py index fd64377b8e9b14286e74ced22352840cf3a39944..fd3680152627e02fc8f0f9859c6c45426e286984 100755 --- a/daemon/run_openrazer_daemon.py +++ b/daemon/run_openrazer_daemon.py @@ -26,6 +26,7 @@ RAZER_RUNTIME_DIR = XDG_RUNTIME_DIR EXAMPLE_CONF_FILE = '/usr/share/openrazer/razer.conf.example' CONF_FILE = os.path.join(RAZER_CONFIG_HOME, 'razer.conf') +PERSISTENCE_FILE = os.path.join(RAZER_CONFIG_HOME, 'persistence.conf') LOG_PATH = os.path.join(RAZER_DATA_HOME, 'logs') args = None @@ -45,6 +46,7 @@ def parse_args(): parser.add_argument('--as-root', action='store_true', help='Allow the daemon to be started as root') parser.add_argument('--config', type=str, help='Location of the config file', default=CONF_FILE) + parser.add_argument('--persistence', type=str, help='Location to file for storing device persistence data', default=PERSISTENCE_FILE) parser.add_argument('--run-dir', type=str, help='Location of the run directory', default=RAZER_RUNTIME_DIR) parser.add_argument('--log-dir', type=str, help='Location of the log directory', default=LOG_PATH) @@ -116,12 +118,30 @@ def install_example_config_file(config_file): sys.exit(1) +def init_persistence_config(persistence_file): + """ + Creates a new file for persistence, if it does not exist. + """ + if os.path.exists(persistence_file): + return + + try: + os.makedirs(os.path.dirname(persistence_file), exist_ok=True) + with open(persistence_file, "w") as f: + f.writelines("") + + except NotADirectoryError as e: + print("Failed to create {}".format(e.filename), file=sys.stderr) + sys.exit(1) + + def run_daemon(): global args daemon = RazerDaemon(verbose=args.verbose, log_dir=args.log_dir, console_log=args.foreground, config_file=args.config, + persistence_file=args.persistence, test_dir=args.test_dir) try: daemon.run() @@ -161,6 +181,7 @@ def run(): logger.setLevel(logging.DEBUG) install_example_config_file(args.config) + init_persistence_config(args.persistence) os.makedirs(args.run_dir, exist_ok=True) daemon = Daemonize(app="openrazer-daemon", diff --git a/examples/custom_starlight.py b/examples/custom_starlight.py index ca8f452a5a12305b76bc3562f5fba77263b41d81..36b20050a724dc4bcf3984acbd9e66407c990a8b 100644 --- a/examples/custom_starlight.py +++ b/examples/custom_starlight.py @@ -1,12 +1,16 @@ from collections import defaultdict import colorsys import random +import sys import time import threading from openrazer.client import DeviceManager from openrazer.client import constants as razer_constants +# Set a quit flag that will be used when the user quits to restore effects. +quit = False + # Create a DeviceManager. This is used to get specific devices device_manager = DeviceManager() @@ -76,6 +80,11 @@ def starlight_effect(device): time.sleep(0.1) + if quit: + break + + device.fx.advanced.restore() + # Spawn a manager thread for each device and wait on all of them. threads = [] @@ -86,7 +95,15 @@ for device in devices: # If there are still threads, update each device. -while any(t.isAlive() for t in threads): - for device in devices: - device.fx.advanced.draw() - time.sleep(1 / 60) +try: + while any(t.isAlive() for t in threads): + for device in devices: + device.fx.advanced.draw() + time.sleep(1 / 60) +except KeyboardInterrupt: + quit = True + + for t in threads: + t.join() + + sys.exit(0) diff --git a/pylib/openrazer/client/fx.py b/pylib/openrazer/client/fx.py index e5fb90b1acb57cae7e0e79302e4db8200aa4484d..9442ab53608fb4efbc0fcc1a66a5ca732a5956b2 100644 --- a/pylib/openrazer/client/fx.py +++ b/pylib/openrazer/client/fx.py @@ -68,6 +68,46 @@ class RazerFX(BaseRazerFX): self.misc = MiscLighting(serial, capabilities, self._dbus) + @property + def effect(self) -> str: + """ + Get current effect + + :return: Effect name ("static", "spectrum", etc.) + :rtype: str + """ + return self._lighting_dbus.getEffect() + + @property + def colors(self) -> bytearray: + """ + Get current effect colors + + :return: Effect colors (an array of 9 bytes, for 3 colors in RGB format) + :rtype: bytearray + """ + return bytes(self._lighting_dbus.getEffectColors()) + + @property + def speed(self) -> int: + """ + Get current effect speed + + :return: Effect speed (a value between 0 and 3) + :rtype: int + """ + return self._lighting_dbus.getEffectSpeed() + + @property + def wave_dir(self) -> int: + """ + Get current wave direction + + :return: Wave direction (WAVE_LEFT or WAVE_RIGHT) + :rtype: int + """ + return self._lighting_dbus.getWaveDir() + def none(self) -> bool: """ No effect @@ -612,6 +652,12 @@ class RazerAdvancedFX(BaseRazerFX): else: raise ValueError("RGB must be an RGB tuple") + def restore(self): + """ + Restore the device to the last effect + """ + self._lighting_dbus.restoreLastEffect() + class SingleLed(BaseRazerFX): def __init__(self, serial: str, capabilities: dict, daemon_dbus=None, led_name='logo'): @@ -644,6 +690,46 @@ class SingleLed(BaseRazerFX): else: func(False) + @property + def effect(self) -> str: + """ + Get current effect + + :return: Effect name ("static", "spectrum", etc.) + :rtype: str + """ + return str(self._getattr('get#Effect')()) + + @property + def colors(self) -> bytearray: + """ + Get current effect colors + + :return: Effect colors (an array of 9 bytes, for 3 colors in RGB format) + :rtype: bytearray + """ + return bytes(self._getattr('get#EffectColors')()) + + @property + def speed(self) -> int: + """ + Get current effect speed + + :return: Effect speed (a value between 0 and 3) + :rtype: int + """ + return int(self._getattr('get#EffectSpeed')()) + + @property + def wave_dir(self) -> int: + """ + Get current wave direction + + :return: Wave direction (WAVE_LEFT or WAVE_RIGHT) + :rtype: int + """ + return int(self._getattr('get#WaveDir')()) + @property def brightness(self): if self._shas('brightness'):