rzr/rzr
2025-02-01 15:49:12 +01:00

279 lines
8.6 KiB
Python
Executable file

#!/usr/bin/env python
from argparse import RawTextHelpFormatter
from colour import Color
from openrazer.client import DeviceManager
from pathlib import Path
import argparse
import os
import time
import toml
import sys
def error(msg, e=True):
"""Print error to stderr and exit eventually."""
print("ERROR: {}".format(msg), file=sys.stderr)
if e:
exit(1)
def get_color_tuple(color_string):
"""Convert a color string to a RGB tuple in range [0, 255]."""
# Convert color string to RGB tuple
try:
color = Color(color_string).rgb
except:
# Try again with leading #
try:
color = Color("#{}".format(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_device_profile(device_profile):
"""Apply a profile section on a device."""
global device_manager, lightmap_directory
# Get openrazer device
device = next(
(
d
for d in device_manager.devices
if d.name.lower().strip() == device_profile["name"].lower().strip()
),
None,
)
if not device:
error("device '{}' not available".format(device_profile["name"]))
elif not device.has("lighting_led_matrix"):
error("device '{}' not supported".format(device_profile["name"]))
# Open lightmap
try:
lightmap = toml.load(
"{}/{}.toml".format(lightmap_directory, device_profile["lightmap"])
)
except FileNotFoundError:
error(
"the lightmap '{}' for device '{}' doesn't exist".format(
device_profile["lightmap"], device_profile["name"]
),
False,
)
list_lightmaps()
exit(1)
except Exception as e:
error(
"failed to load lightmap '{}' for device '{}': {}".format(
device_profile["lightmap"], device_profile["name"], e
)
)
# Set light colors
try:
# If lights are declared apply profile, else turn lights off
if "lights" in device_profile:
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
)
else:
device.fx.none()
# Apply light colors
device.fx.advanced.draw()
except KeyError:
error(
"light '{}' is not available in lightmap '{}' for device '{}'".format(
light, device_profile["lightmap"], device_profile["name"]
)
)
except Exception as e:
error(
"failed to set light '{}' for device '{}': {}".format(
light, device_profile["name"], e
)
)
def apply_profile(name):
"""Apply a profile by name."""
global profile_directory
try:
profile = toml.load("{}/{}.toml".format(profile_directory, name))
except FileNotFoundError:
error("the profile '{}' doesn't exist".format(name), False)
list_profiles()
exit(1)
except Exception as e:
error("couldn't load profile '{}': {}".format(name, e))
for device in profile:
# Check if mandatory attributes "name" and "lightmap" exist
if "name" not in profile[device]:
error("'name' attribute is missing for device '{}'".format(device))
if "lightmap" not in profile[device]:
error("'lightmap' attribute is missing for device '{}'".format(device))
apply_device_profile(profile[device])
print("Profile '{}' applied".format(name))
def iterate_lights():
"""Iterate through every matrix position of every device and light one key after another."""
global device_manager
try:
# Turn all lights off
for device in device_manager.devices:
device.fx.none()
device.fx.advanced.draw()
# Iterate through all devices
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)
# Turn on one light every second
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)
except Exception as e:
error("failed to iterate device '{}': {}".format(device.name, e))
def list_devices():
"""Print a list of all available and supported devices."""
print("The following devices are available and supported:")
for device in device_manager.devices:
if device.has("lighting_led_matrix"):
print(" - {}".format(device.name))
def list_lightmaps():
"""Print a list of all available lightmaps."""
global lightmap_directory
if len(os.listdir(lightmap_directory)) > 0:
print("Available lightmaps:")
for lightmap_file in sorted(os.listdir(lightmap_directory)):
print(" - {}".format(lightmap_file[:-5]))
else:
print("No lightmaps available")
def list_profiles():
"""Print a list of all available profiles."""
global profile_directory
if len(os.listdir(profile_directory)) > 0:
print("Available profiles:")
for profile_file in sorted(os.listdir(profile_directory)):
print(" - {}".format(profile_file[:-5]))
else:
print("No profiles available")
def main():
global device_manager, lightmap_directory, profile_directory
# Parse arguments
parser = argparse.ArgumentParser(
description="A simple command line frontend for OpenRazer.",
formatter_class=RawTextHelpFormatter,
)
parser.add_argument(
"command",
metavar="COMMAND",
nargs="?",
default=None,
help="one of the following:\n list-devices - list available devices\n list-lightmaps - list available lightmaps\n list-profiles - list available profiles\n iterate-lights - iterate though all lights of all devices\n <PROFILE> - apply the given profile",
)
parser.add_argument(
"-ld",
"--lightmap-directory",
default="{}/.config/rzr/lightmaps".format(Path.home()),
help="path to directory with lightmaps (default: ~/.config/rzr/lightmaps)",
)
parser.add_argument(
"-pd",
"--profile-directory",
default="{}/.config/rzr/profiles".format(Path.home()),
help="path to directory with profiles (default: ~/.config/rzr/profiles)",
)
args = parser.parse_args()
# Create device manager
try:
device_manager = DeviceManager()
except Exception as e:
error("failed to load device manager: {}".format(e), False)
print("Is the openrazer-daemon running?")
exit(1)
# Check if directories exist
directories_available = True
lightmap_directory = args.lightmap_directory
profile_directory = args.profile_directory
if not os.path.exists(lightmap_directory):
error("lightmap directory '{}' doesn't exist".format(lightmap_directory), False)
create = input("Create the directory? [y/N] ")
if create in ["y", "Y"]:
Path(lightmap_directory).mkdir(parents=True, exist_ok=True)
else:
directories_available = False
if not os.path.exists(profile_directory):
error("profile directory '{}' doesn't exist".format(profile_directory), False)
create = input("Create the directory? [y/N] ")
if create in ["y", "Y"]:
Path(profile_directory).mkdir(parents=True, exist_ok=True)
else:
directories_available = False
if not directories_available:
exit(1)
if not args.command:
# Apply 'default' profile if existent, else print help
if "default.toml" in os.listdir(profile_directory):
apply_profile("default")
else:
parser.print_help()
# Execute command
elif args.command == "list-devices":
list_devices()
elif args.command == "list-lightmaps":
list_lightmaps()
elif args.command == "list-profiles":
list_profiles()
elif args.command == "iterate-lights":
iterate_lights()
elif args.command:
apply_profile(args.command)
if __name__ == "__main__":
main()