1+ use anyhow:: { Context , Result } ;
2+ use clap:: { Parser , Subcommand } ;
3+ use std:: fs:: OpenOptions ;
4+ use std:: io:: { Read , Seek , SeekFrom , Write } ;
5+ use std:: process:: Command ;
6+ use zbus:: export:: futures_util:: StreamExt ;
7+ use zbus:: Connection ;
8+
9+ // --- KONFIGURACJA HARDWARE (Z Twoich plików) ---
10+ const EC_PATH : & str = "/sys/kernel/debug/ec/ec0/io" ;
11+ const LED_OFFSET : u64 = 0x10 ;
12+ const LED_BIT : u8 = 6 ; // Bit 6
13+ const LED_MASK : u8 = 1 << LED_BIT ; // 0x40 (64)
14+
15+ // --- KONFIGURACJA LOGIKI ---
16+ // Bit 1 (True) zazwyczaj wyłącza oddychanie (Static/Off w kontekście sleep)
17+ // Bit 0 (False) włącza oddychanie (Default)
18+ const VAL_SLEEP_MODE : bool = true ; // Ustaw bit na 1 przed snem (wyłącz miganie)
19+ const VAL_WAKE_MODE : bool = false ; // Ustaw bit na 0 po obudzeniu (powrót do systemu)
20+
21+ #[ derive( Parser ) ]
22+ #[ command( name = "legion-led" ) ]
23+ #[ command( about = "Legion Go S LED Controller & Daemon" , long_about = None ) ]
24+ struct Cli {
25+ #[ command( subcommand) ]
26+ command : Commands ,
27+ }
28+
29+ #[ derive( Subcommand ) ]
30+ enum Commands {
31+ /// Uruchamia proces w tle (nasłuchuje uśpienia/wybudzenia)
32+ Daemon ,
33+ /// Wymusza tryb "OFF" (przestaje migać/świecić statycznie)
34+ Off ,
35+ /// Przywraca tryb "ON" (domyślne oddychanie/sterowanie systemowe)
36+ On ,
37+ }
38+
39+ /// Sprawdza i ładuje moduł jądra, jeśli nie jest obecny
40+ fn ensure_ec_access ( ) -> Result < ( ) > {
41+ if !std:: path:: Path :: new ( EC_PATH ) . exists ( ) {
42+ println ! ( "Ładowanie modułu ec_sys..." ) ;
43+ let status = Command :: new ( "modprobe" )
44+ . arg ( "ec_sys" )
45+ . arg ( "write_support=1" )
46+ . status ( )
47+ . context ( "Nie udało się uruchomić modprobe" ) ?;
48+
49+ if !status. success ( ) {
50+ return Err ( anyhow:: anyhow!( "Błąd ładowania ec_sys. Uruchom jako root!" ) ) ;
51+ }
52+ }
53+ Ok ( ( ) )
54+ }
55+
56+ /// Bezpieczna funkcja Read-Modify-Write dla EC
57+ fn modify_ec_led ( disable_breathing : bool ) -> Result < ( ) > {
58+ let mut file = OpenOptions :: new ( )
59+ . read ( true )
60+ . write ( true )
61+ . open ( EC_PATH )
62+ . context ( format ! ( "Nie można otworzyć {}. Brak uprawnień root?" , EC_PATH ) ) ?;
63+
64+ // 1. Odczyt
65+ file. seek ( SeekFrom :: Start ( LED_OFFSET ) ) ?;
66+ let mut buf = [ 0u8 ; 1 ] ;
67+ file. read_exact ( & mut buf) ?;
68+ let original = buf[ 0 ] ;
69+
70+ // 2. Modyfikacja
71+ let new_val = if disable_breathing {
72+ original | LED_MASK // Ustaw bit na 1
73+ } else {
74+ original & !LED_MASK // Wyzeruj bit (powrót do 0)
75+ } ;
76+
77+ // 3. Zapis (tylko jeśli jest zmiana, żeby nie męczyć EC)
78+ if original != new_val {
79+ file. seek ( SeekFrom :: Start ( LED_OFFSET ) ) ?;
80+ file. write_all ( & [ new_val] ) ?;
81+ // println!("EC: 0x{:02X} -> 0x{:02X} (Bit {}={})", original, new_val, LED_BIT, disable_breathing);
82+ }
83+
84+ Ok ( ( ) )
85+ }
86+
87+ #[ tokio:: main]
88+ async fn main ( ) -> Result < ( ) > {
89+ let args = Cli :: parse ( ) ;
90+
91+ // Każda operacja wymaga dostępu do EC
92+ ensure_ec_access ( ) ?;
93+
94+ match args. command {
95+ Commands :: Off => {
96+ modify_ec_led ( true ) ?;
97+ println ! ( "Dioda: Tryb statyczny/OFF wymuszony." ) ;
98+ }
99+ Commands :: On => {
100+ modify_ec_led ( false ) ?;
101+ println ! ( "Dioda: Tryb domyślny/ON przywrócony." ) ;
102+ }
103+ Commands :: Daemon => {
104+ println ! ( "Start demona Legion LED..." ) ;
105+ println ! ( "Nasłuchiwanie sygnałów D-Bus (PrepareForSleep)..." ) ;
106+
107+ // Połączenie z systemową szyną D-Bus
108+ let conn = Connection :: system ( ) . await ?;
109+
110+ // Proxy do menedżera logowania systemd
111+ let proxy = zbus:: Proxy :: new (
112+ & conn,
113+ "org.freedesktop.login1" ,
114+ "/org/freedesktop/login1" ,
115+ "org.freedesktop.login1.Manager" ,
116+ )
117+ . await ?;
118+
119+ // Subskrypcja sygnału
120+ let mut stream = proxy. receive_signal ( "PrepareForSleep" ) . await ?;
121+
122+ // Pętla nasłuchująca (nic nie robi dopóki nie przyjdzie sygnał)
123+ while let Some ( msg) = stream. next ( ) . await {
124+ let body: ( bool , ) = msg. body ( ) . deserialize ( ) ?;
125+ let is_going_to_sleep = body. 0 ;
126+
127+ if is_going_to_sleep {
128+ // System IDZIE SPAĆ -> Wyłącz diodę
129+ if let Err ( e) = modify_ec_led ( VAL_SLEEP_MODE ) {
130+ eprintln ! ( "Błąd podczas usypiania: {}" , e) ;
131+ }
132+ } else {
133+ // System WSTAŁ -> Przywróć diodę
134+ if let Err ( e) = modify_ec_led ( VAL_WAKE_MODE ) {
135+ eprintln ! ( "Błąd podczas wybudzania: {}" , e) ;
136+ }
137+ }
138+ }
139+ }
140+ }
141+
142+ Ok ( ( ) )
143+ }
0 commit comments