added applet and updated README
This commit is contained in:
183
acer-rgb-tray.py
Executable file
183
acer-rgb-tray.py
Executable file
@@ -0,0 +1,183 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user