Files
acer-lighting-daemon/acer-rgb-tray.py

184 lines
5.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""Acer RGB Keyboard Tray Applet - Menu-based controls."""
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("AppIndicator3", "0.1")
from gi.repository import Gtk, AppIndicator3, Gdk
from daemon_client import (
RGBState, EFFECTS,
is_daemon_running, get_states, set_rgb
)
# Color presets (name, r, g, b)
COLOR_PRESETS = [
("Red", 255, 0, 0),
("Green", 0, 255, 0),
("Blue", 0, 0, 255),
("White", 255, 255, 255),
("Cyan", 0, 255, 255),
("Magenta", 255, 0, 255),
("Yellow", 255, 255, 0),
("Orange", 255, 128, 0),
]
class AcerRGBTray:
"""System tray application with menu-based controls."""
def __init__(self):
self.current_state = None
self.indicator = AppIndicator3.Indicator.new(
"acer-rgb-tray",
"media-view-subtitles-symbolic",
AppIndicator3.IndicatorCategory.HARDWARE
)
self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
self.load_state()
self.build_menu()
def build_menu(self):
"""Build the dropdown menu with all controls."""
menu = Gtk.Menu()
# Status line at top
self.status_item = Gtk.MenuItem(label="Loading...")
self.status_item.set_sensitive(False)
menu.append(self.status_item)
menu.append(Gtk.SeparatorMenuItem())
# Color presets directly in menu
for name, r, g, b in COLOR_PRESETS:
item = Gtk.MenuItem(label=name)
item.connect("activate", self.on_color_preset, r, g, b)
menu.append(item)
# Custom color option
custom_item = Gtk.MenuItem(label="Custom...")
custom_item.connect("activate", self.on_custom_color)
menu.append(custom_item)
menu.append(Gtk.SeparatorMenuItem())
# Effect submenu
effect_item = Gtk.MenuItem(label="Effect")
effect_submenu = Gtk.Menu()
self.effect_group = []
for effect_id, effect_name in EFFECTS:
item = Gtk.RadioMenuItem(label=effect_name)
if self.effect_group:
item.set_property("group", self.effect_group[0])
self.effect_group.append(item)
item.connect("toggled", self.on_effect_changed, effect_id)
effect_submenu.append(item)
effect_item.set_submenu(effect_submenu)
menu.append(effect_item)
menu.show_all()
self.indicator.set_menu(menu)
self.update_ui_from_state()
def load_state(self):
"""Load current state from daemon."""
if not is_daemon_running():
self.current_state = RGBState(device="keyboard")
return
try:
states = get_states()
if "keyboard" in states:
self.current_state = states["keyboard"]
else:
self.current_state = RGBState(device="keyboard")
except Exception:
self.current_state = RGBState(device="keyboard")
def update_ui_from_state(self):
"""Update menu items to reflect current state."""
if self.current_state is None:
self.status_item.set_label("Daemon not running")
return
state = self.current_state
# Update status line
color_name = self.get_color_name(state.r, state.g, state.b)
effect_name = dict(EFFECTS).get(state.effect, state.effect)
self.status_item.set_label(f"{color_name} | {effect_name}")
# Update effect radio buttons
for i, (effect_id, _) in enumerate(EFFECTS):
if effect_id == state.effect:
self.effect_group[i].set_active(True)
break
def get_color_name(self, r, g, b):
"""Get color name from RGB values, or hex if not a preset."""
for name, pr, pg, pb in COLOR_PRESETS:
if (r, g, b) == (pr, pg, pb):
return name
return f"#{r:02X}{g:02X}{b:02X}"
def on_color_preset(self, item, r, g, b):
"""Handle color preset selection."""
self.current_state.r = r
self.current_state.g = g
self.current_state.b = b
self.apply_state()
def on_custom_color(self, item):
"""Show color chooser dialog."""
dialog = Gtk.ColorChooserDialog(
title="Choose Color",
transient_for=None
)
dialog.set_use_alpha(False)
dialog.set_rgba(Gdk.RGBA(
self.current_state.r / 255,
self.current_state.g / 255,
self.current_state.b / 255,
1.0
))
if dialog.run() == Gtk.ResponseType.OK:
rgba = dialog.get_rgba()
self.current_state.r = int(rgba.red * 255)
self.current_state.g = int(rgba.green * 255)
self.current_state.b = int(rgba.blue * 255)
self.apply_state()
dialog.destroy()
def on_effect_changed(self, item, effect_id):
"""Handle effect selection."""
if not item.get_active():
return
self.current_state.effect = effect_id
if effect_id == "static":
self.current_state.speed = 0
else:
self.current_state.speed = 5
self.apply_state()
def apply_state(self):
"""Apply current state to daemon."""
self.current_state.device = "keyboard"
success, msg = set_rgb(self.current_state)
if success:
self.update_ui_from_state()
else:
self.status_item.set_label(f"Error: {msg}")
def run(self):
Gtk.main()
if __name__ == "__main__":
app = AcerRGBTray()
app.run()