184 lines
5.5 KiB
Python
Executable File
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()
|