From c7e479877194d0aa676eb085a29299e1413e6ed2 Mon Sep 17 00:00:00 2001 From: jawhng Date: Fri, 6 Feb 2026 18:56:26 +0200 Subject: [PATCH] added applet and updated README --- Makefile | 6 +- README.md | 40 ++++- __pycache__/acer-rgb-tray.cpython-312.pyc | Bin 0 -> 12352 bytes __pycache__/daemon_client.cpython-312.pyc | Bin 0 -> 5270 bytes acer-rgb-tray.desktop | 9 ++ acer-rgb-tray.py | 183 ++++++++++++++++++++++ daemon_client.py | 118 ++++++++++++++ 7 files changed, 352 insertions(+), 4 deletions(-) create mode 100644 __pycache__/acer-rgb-tray.cpython-312.pyc create mode 100644 __pycache__/daemon_client.cpython-312.pyc create mode 100644 acer-rgb-tray.desktop create mode 100755 acer-rgb-tray.py create mode 100644 daemon_client.py diff --git a/Makefile b/Makefile index 4dd5ea8..1a1cd34 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,10 @@ all: build # rgb: -# clang++ -std=c++23 ./rgb.cpp -o ./rgb +# clang++-14 -std=c++23 ./rgb.cpp -o ./rgb acer-rgb-cli: - clang++ -std=c++23 acer-rgb-cli.cpp -o acer-rgb-cli + clang++-14 -std=c++23 acer-rgb-cli.cpp -o acer-rgb-cli build-cli: acer-rgb-cli @@ -13,7 +13,7 @@ clean-cli: rm -f acer-rgb-cli acer-rgbd: - g++ -O2 -std=c++23 acer-rgbd.cpp -o acer-rgbd + g++-14 -O2 -std=c++23 acer-rgbd.cpp -o acer-rgbd build: acer-rgbd diff --git a/README.md b/README.md index 08bdd3d..ccb49db 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # acer-lighting +> **Fork Notice:** This is a fork of [fcrespo82/acer-lighting-daemon](https://github.com/fcrespo82/acer-lighting-daemon) with added GUI tray applet support. + **Thanks** This project benefited greatly from the work, testing and research of several community contributors — especially: @ZoeBattleSand, @0x189D7997, and @JakeBrxwn. Their reverse-engineering, testing, tooling and discussion (see https://github.com/0x7375646F/Linuwu-Sense/pull/65) made much of this possible. @@ -7,7 +9,7 @@ This project benefited greatly from the work, testing and research of several co Small tools to control Acer laptop RGB zones and a daemon that persists/apply states. > [!CAUTION] -> The code here was based on the work of others but it was written with the help of AI too +> The code here was based on the work of others but it was written with the help of AI too ## Build @@ -48,6 +50,41 @@ If you need to undo the install: sudo make uninstall ``` +## GUI Tray Applet + +A system tray applet is included for easy RGB control without using the command line. + +### Dependencies + +```sh +sudo apt install python3-gi gir1.2-appindicator3-0.1 +``` + +### Running + +```sh +python3 acer-rgb-tray.py +``` + +The applet appears in your system tray. Click it to access: +- **Color presets** (Red, Green, Blue, White, Cyan, Magenta, Yellow, Orange) +- **Custom...** - Opens a full color picker dialog +- **Effect** submenu - Static, Breathing, Neon, Wave, Ripple, Zoom, Snake, Disco, Shifting + +### Autostart + +To start the applet automatically on login: + +```sh +cp acer-rgb-tray.desktop ~/.config/autostart/ +``` + +Make sure the daemon is running before the applet starts: + +```sh +sudo systemctl enable acer-rgbd.service +``` + ## Usage - Send commands to the daemon using the `acer-rgb` helper (it talks to the daemon socket): @@ -85,3 +122,4 @@ sudo systemctl restart acer-rgbd.service - The installer writes an initial all-green state (keyboard/lid/button) to `/var/lib/acer-rgbd/state.txt` so newly installed systems show green LEDs by default. - Running the daemon requires root privileges (or appropriate udev rules) to access the HID device. +- The GUI applet communicates with the daemon via socket (`/run/acer-rgbd.sock`) and runs as a regular user. diff --git a/__pycache__/acer-rgb-tray.cpython-312.pyc b/__pycache__/acer-rgb-tray.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2b5c0854f9f4a6f59b12bfa123ad88185603ea9 GIT binary patch literal 12352 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

U;IY`o@*X<;mPDh0dGLesb*A zvD;nGgP^yrShss6x*W}&DR2epqx6UA+wBKIa41j=)UDJn*XQ^=lOMb>^!`x(Na2}+ z@zLyuvxSM9$xlcBaN+kCK0RG>GTwfw#BeV62J8WuY)VPXiOrsdV}bbSZQ`Renn#t; zTA=5ti}-Z5JQevMdgXJ378vkrW|q>ZC4QdSP9GPJO7j09ti8Z4aPzFhOm>1P_<3%g zj@hd$b%BK4B(e9pbQmPuCJEj(d6ou2;vkZM;i_=wxsS`U!u0!Umt&SJ;k(sj^v@2G3BivBf#Ec&}Yc%571 zilK&;Q_H8WpLvLOxmp(oi@w_Y6Ib3S`WuS7npQH)nL=CP)wNyWD=#b#Mf z?%kK;e&HzwLV5n$-kqrRwvK}SnF!Aot{dGeRP1<$S**_FJKYa3KQy4ira5^?JdM?ucqUlViX((2P#f^-{zTZFkg6Y&@ zHQaVNWKye57Fx4e-0@+U8-j(Pz{3J2Us+zq#)ed5B^nQ&sZzYP^B_76 zKRp2;i@p{DSNpE?{j&bXp7;0suKsTL`PK0AYeH{!u*mX1J@e+7_nPjoEoD3xTxncx zyxv^kRs-#K*p8wvcy;c|Tt4*kH}3k{SAFekz9+LUVC7S9p1ORdCiFD?(98yTR8L( zQC!_UU#P^u_(;cH=vzV!de!$>dfP3w z$<0;A(x^Ea?qk$;&QqBo2{{qs)6VC1OtOaEx!F9XjJ}!s5P5SKVFg>ba zs}6z_qLg_ZGOj z;(=B1z?%5owcs<0JfzmEN3R@R6PoS{9jgL(+8tqkG1ziH*pTnM(fxk+T4UGkVApL= z*Ej1zGfq55@A>-Hg;tC{eb0AbT?k>crRWR2m&mtXPcANe!8iX0r>2%iI&O>JI@0e& ze+&Tl%e0GS!oL&xKxYl&WVv?GGHtZz4Z3_1POb~lGLzxa^ zZW`xH9Ddr1nB$BL4Mhem#FD}OwMUNgwCw9 z str: + return ( + f"SET dev={self.device} hidraw={self.hidraw} " + f"effect={self.effect} bright={self.brightness} " + f"speed={self.speed} dir={self.direction} " + f"r={self.r} g={self.g} b={self.b} zone={self.zone}" + ) + + @classmethod + def from_line(cls, line: str) -> "RGBState": + state = cls() + for pair in line.split(): + if "=" not in pair: + continue + key, val = pair.split("=", 1) + if key == "dev": + state.device = val + elif key == "hidraw": + state.hidraw = val + elif key == "effect": + state.effect = val + elif key == "bright": + state.brightness = int(val) + elif key == "speed": + state.speed = int(val) + elif key == "dir": + state.direction = val + elif key == "r": + state.r = int(val) + elif key == "g": + state.g = int(val) + elif key == "b": + state.b = int(val) + elif key == "zone": + state.zone = val + return state + + +def send_command(cmd: str) -> str: + """Send command to daemon and return response.""" + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(SOCKET_PATH) + sock.sendall(cmd.encode() + b"\n") + response = sock.recv(8192).decode() + sock.close() + return response + + +def is_daemon_running() -> bool: + """Check if daemon is available.""" + try: + send_command("GET") + return True + except (FileNotFoundError, ConnectionRefusedError): + return False + + +def get_states() -> dict[str, RGBState]: + """Get current state for all devices.""" + response = send_command("GET") + states = {} + for line in response.strip().split("\n"): + if line.startswith("SET"): + state = RGBState.from_line(line) + states[state.device] = state + return states + + +def set_rgb(state: RGBState) -> tuple[bool, str]: + """Send RGB command to daemon. Returns (success, message).""" + try: + response = send_command(state.to_command()) + if response.strip().startswith("OK"): + return True, "OK" + else: + return False, response.strip() + except FileNotFoundError: + return False, "Daemon not running (socket not found)" + except ConnectionRefusedError: + return False, "Cannot connect to daemon" + except Exception as e: + return False, str(e)