From c3ed9af8a0a9565bb104013ba44306bb5ea933f1 Mon Sep 17 00:00:00 2001 From: Denis Lehmann Date: Wed, 21 Apr 2021 02:33:22 +0200 Subject: [PATCH] initial commit --- .gitignore | 138 +++++++++++++++++ README.org | 12 ++ lightmaps/razer_blackwidow_chroma_de.toml | 109 +++++++++++++ lightmaps/razer_mamba_elite.toml | 20 +++ main.py | 181 ++++++++++++++++++++++ profiles/template.toml | 20 +++ shell.nix | 39 +++++ 7 files changed, 519 insertions(+) create mode 100644 .gitignore create mode 100644 README.org create mode 100644 lightmaps/razer_blackwidow_chroma_de.toml create mode 100644 lightmaps/razer_mamba_elite.toml create mode 100644 main.py create mode 100644 profiles/template.toml create mode 100644 shell.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a81c8ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,138 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/README.org b/README.org new file mode 100644 index 0000000..95c61fc --- /dev/null +++ b/README.org @@ -0,0 +1,12 @@ +* rzr + + Apply lightmaps to Razer devices. + +** Troubleshooting + + If config file not found error: + + #+begin_example sh + systemctl --user stop openrazer-daemon.service + openrazer-daemon -Fv + #+end_example diff --git a/lightmaps/razer_blackwidow_chroma_de.toml b/lightmaps/razer_blackwidow_chroma_de.toml new file mode 100644 index 0000000..3730107 --- /dev/null +++ b/lightmaps/razer_blackwidow_chroma_de.toml @@ -0,0 +1,109 @@ +logo = [0, 20] +esc = [0, 1] +f1 = [0, 3] +f2 = [0, 4] +f3 = [0, 5] +f4 = [0, 6] +f5 = [0, 7] +f6 = [0, 8] +f7 = [0, 9] +f8 = [0, 10] +f9 = [0, 11] +f10 = [0, 12] +f11 = [0, 13] +f12 = [0, 14] +print = [0, 15] +roll = [0, 16] +pause = [0, 17] +m1 = [1, 0] +roof = [1, 1] +1 = [1, 2] +2 = [1, 3] +3 = [1, 4] +4 = [1, 5] +5 = [1, 6] +6 = [1, 7] +7 = [1, 8] +8 = [1, 9] +9 = [1, 10] +0 = [1, 11] +ß = [1, 12] +apostroph = [1, 13] +backspace = [1, 14] +insert = [1, 15] +pos1 = [1, 16] +pgup = [1, 17] +num = [1, 18] +div = [1, 19] +mult = [1, 20] +min = [1, 21] +m2 = [2, 0] +tab = [2, 1] +q = [2, 2] +w = [2, 3] +e = [2, 4] +r = [2, 5] +t = [2, 6] +z = [2, 7] +u = [2, 8] +i = [2, 9] +o = [2, 10] +p = [2, 11] +ü = [2, 12] ++ = [2, 13] +del = [2, 15] +end = [2, 16] +pgdown = [2, 17] +n7 = [2, 18] +n8 = [2, 19] +n9 = [2, 20] +plus = [2, 21] +m3 = [3, 0] +caps = [3, 1] +a = [3, 2] +s = [3, 3] +d = [3, 4] +f = [3, 5] +g = [3, 6] +h = [3, 7] +j = [3, 8] +k = [3, 9] +l = [3, 10] +ö = [3, 11] +ä = [3, 12] +hash = [3, 13] +ret = [3, 14] +n4 = [3, 18] +n5 = [3, 19] +n6 = [3, 20] +m4 = [4, 0] +lshift = [4, 1] +lower = [4, 2] +y = [4, 3] +x = [4, 4] +c = [4, 5] +v = [4, 6] +b = [4, 7] +n = [4, 8] +m = [4, 9] +comma = [4, 10] +dot = [4, 11] +dash = [4, 12] +rshift = [4, 14] +up = [4, 16] +n1 = [4, 18] +n2 = [4, 19] +n3 = [4, 20] +nreturn = [4, 21] +m5 = [5, 0] +lctrl = [5, 1] +super = [5, 2] +alt = [5, 3] +altgr = [5, 11] +menu = [5, 13] +rctrl = [5, 14] +left = [5, 15] +down = [5, 16] +right = [5, 17] +n0 = [5, 19] +ndel = [5, 20] \ No newline at end of file diff --git a/lightmaps/razer_mamba_elite.toml b/lightmaps/razer_mamba_elite.toml new file mode 100644 index 0000000..5ebcff8 --- /dev/null +++ b/lightmaps/razer_mamba_elite.toml @@ -0,0 +1,20 @@ +wheel = [0, 0] +logo = [0, 1] +l0 = [0, 2] +l1 = [0, 3] +l2 = [0, 4] +l3 = [0, 5] +l4 = [0, 6] +l5 = [0, 7] +l6 = [0, 8] +l7 = [0, 9] +l8 = [0, 10] +r0 = [0, 11] +r1 = [0, 12] +r2 = [0, 13] +r3 = [0, 14] +r4 = [0, 15] +r5 = [0, 16] +r6 = [0, 17] +r7 = [0, 18] +r8 = [0, 19] \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..93b1520 --- /dev/null +++ b/main.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python + +from colour import Color +from openrazer.client import DeviceManager +from openrazer.client import constants as razer_constants +from pathlib import Path + +import argparse +import os +import time +import toml + + +def get_color_tuple(color_string): + + # Convert color string to RGB tuple + try: + color = Color(color_string).rgb + except: + raise Exception("'{}' is not a valid color".format(color_string)) + + # Scale RGB tuple from [0, 1] to [0, 255] + color_tuple = tuple(map(lambda x: int(x * 255), color)) + + return color_tuple + + +def apply_lightmap(device_profile): + + global device_manager, lightmap_directory + + # Get openrazer device + device = next( + ( + d + for d in device_manager.devices + if d.name.lower() == device_profile["name"].lower() + ), + None, + ) + if device == None: + print("device '{}' not found".format(device_profile["device"])) + exit(1) + + # Open lightmap + try: + lightmap = toml.load( + "{}/{}.toml".format(lightmap_directory, device_profile["lightmap"]) + ) + except FileNotFoundError: + print("the lightmap '{}' doesn't exist".format(device_profile["lightmap"])) + if len(os.listdir(lightmap_directory)) > 0: + print("found the following lightmaps:") + for lightmap_file in os.listdir(lightmap_directory): + print(" - {}".format(lightmap_file[:-5])) + else: + print("found no lightmaps") + exit(1) + except Exception as e: + print("failed to load lightmap '{}': {}".format(device_profile["lightmap"], e)) + exit(1) + + # Set light colors + try: + for light in device_profile["lights"]: + color_tuple = get_color_tuple((device_profile["lights"][light])) + device.fx.advanced.matrix[ + lightmap[light][0], lightmap[light][1] + ] = color_tuple + except KeyError: + print( + "light '{}' is not available in lightmap '{}'".format( + light, device_profile["lightmap"] + ) + ) + exit(1) + except Exception as e: + print("failed to set light '{}': {}".format(light, e)) + exit(1) + + # Apply light colors + device.fx.advanced.draw() + + +def iterate_lights(): + + global device_manager + + # Print number of devices + print("found the following devices:") + for device in device_manager.devices: + print(" - {}".format(device.name)) + + # Turn all lights off + for device in device_manager.devices: + device.fx.none() + device.fx.advanced.draw() + + # Iterate through device matrices and turn on one light every second + for device in device_manager.devices: + + # Wait five seconds + for i in range(5, 0, -1): + print("{} will be iterated in {} seconds".format(device.name, i)) + time.sleep(1) + + for i in range(device.fx.advanced.rows): + for j in range(device.fx.advanced.cols): + device.fx.advanced.matrix[i, j] = (255, 255, 255) + device.fx.advanced.draw() + print("{}: [{}, {}]".format(device.name, i, j)) + time.sleep(1) + + +if __name__ == "__main__": + + global device_manager, lightmap_directory + + # Initialise variables + profile_directory = "{}/.config/rzr/profiles".format(Path.home()) + lightmap_directory = "{}/.config/rzr/lightmaps".format(Path.home()) + + # Create config folders if not existent + Path(profile_directory).mkdir(parents=True, exist_ok=True) + Path(lightmap_directory).mkdir(parents=True, exist_ok=True) + + # Create device manager + try: + device_manager = DeviceManager() + except Exception as e: + print("failed to load device manager: {}".format(e)) + print("is the openrazer-daemon running?") + exit(1) + + # Parse arguments + parser = argparse.ArgumentParser( + description="Set color profiles of your Razer devices." + ) + parser.add_argument( + "profile", + metavar="PROFILE", + nargs="?", + help="the profile which shall be applied (ignored if --iterate is set)", + ) + parser.add_argument( + "-i", + "--iterate", + action="store_true", + help="iterate through all Razer devices and turn on one light after another", + ) + args = parser.parse_args() + + # Print greeter + print("rzr") + + if args.iterate: + iterate_lights() + elif args.profile: + # Load profile + try: + profile = toml.load("{}/{}.toml".format(profile_directory, args.profile)) + except FileNotFoundError: + print("the profile '{}' doesn't exist".format(args.profile)) + if len(os.listdir(profile_directory)) > 0: + print("found the following profiles:") + for profile_file in os.listdir(profile_directory): + print(" - {}".format(profile_file[:-5])) + else: + print("found no profiles") + exit(1) + except Exception as e: + print(type(e)) + print("error while loading profile: {}".format(args.profile)) + exit(1) + for device in profile: + apply_lightmap(profile[device]) + + print("profile '{}' applied".format(args.profile)) + + else: + parser.error("either set a profile or --iterate") diff --git a/profiles/template.toml b/profiles/template.toml new file mode 100644 index 0000000..b819dbb --- /dev/null +++ b/profiles/template.toml @@ -0,0 +1,20 @@ +[mouse] +name = "Razer Mamba Elite" +lightmap = "razer_mamba_elite" + +[mouse.lights] +wheel = "red" +logo = "red" +l0 = "red" +r0 = "red" + +[keyboard] +name = "Razer BlackWidow Chroma" +lightmap = "razer_blackwidow_chroma_de" + +[keyboard.lights] +w = "red" +a = "red" +s = "red" +d = "red" +logo = "green" \ No newline at end of file diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..c790e10 --- /dev/null +++ b/shell.nix @@ -0,0 +1,39 @@ +{ pkgs ? import {} }: +pkgs.mkShell { + buildInputs = with pkgs; [ + python3 + python3Packages.virtualenv + python3Packages.colour + python3Packages.openrazer + python3Packages.toml + ]; + shellHook = '' + function log_header { + echo -ne "==> \e[32m\e[1m$1\e[0m\n" + } + function log_subheader { + echo -ne "--> \e[33m\e[1m$1\e[0m\n" + } + function log { + echo -ne " $1\n" + } + + echo "" + log_header "python_environment" + if [ ! -d .venv ]; then + python -m venv .venv + fi + source .venv/bin/activate + log_subheader "upgrading pip" + pip install --upgrade pip + echo "" + if [ -s requirements.txt ]; then + log_subheader "found requirements.txt, installing packages" + pip install -r requirements.txt + echo "" + fi + log_header "package versions" + log "$(python --version)" + log "$(pip --version)" + ''; +}