Skip to content

Commit 5f2b09d

Browse files
authored
Add files via upload
0 parents  commit 5f2b09d

3 files changed

Lines changed: 259 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "legion-led"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
clap = { version = "4.4", features = ["derive"] }
8+
zbus = { version = "4.0", features = ["tokio"] }
9+
tokio = { version = "1.0", features = ["full"] }
10+
anyhow = "1.0"

install.sh

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/bin/bash
2+
3+
# --- CONFIGURATION ---
4+
# Nazwa pliku wygenerowanego przez cargo (w target/release/)
5+
SOURCE_BINARY_NAME="legion-led"
6+
# Nazwa pod jaką zainstalujemy program w systemie (krótsza)
7+
FINAL_BINARY_NAME="legion-led"
8+
INSTALL_PATH="/usr/local/bin/$FINAL_BINARY_NAME"
9+
SERVICE_NAME="legion-led.service"
10+
11+
# 1. Root Check
12+
if [ "$EUID" -ne 0 ]; then
13+
echo "ERROR: Please run with sudo!"
14+
exit 1
15+
fi
16+
17+
# --- MODE: UNINSTALL ---
18+
# If the user runs "./install.sh uninstall", we go here
19+
if [ "$1" == "uninstall" ]; then
20+
echo ">>> [UNINSTALL] Stopping and removing service..."
21+
systemctl stop $SERVICE_NAME 2>/dev/null
22+
systemctl disable $SERVICE_NAME 2>/dev/null
23+
rm -f /etc/systemd/system/$SERVICE_NAME
24+
systemctl daemon-reload
25+
26+
echo ">>> [UNINSTALL] Unlocking filesystem..."
27+
steamos-readonly disable 2>/dev/null
28+
29+
echo ">>> [UNINSTALL] Removing binary..."
30+
rm -f "$INSTALL_PATH"
31+
32+
echo ">>> [UNINSTALL] Securing filesystem..."
33+
steamos-readonly enable 2>/dev/null
34+
35+
echo ">>> SUCCESS. Uninstalled completely."
36+
exit 0
37+
fi
38+
39+
# --- MODE: INSTALL (Default) ---
40+
41+
# 2. Find the binary
42+
# Check current directory first, then target/release
43+
if [ -f "./$SOURCE_BINARY_NAME" ]; then
44+
BINARY_TO_INSTALL="./$SOURCE_BINARY_NAME"
45+
elif [ -f "./target/release/$SOURCE_BINARY_NAME" ]; then
46+
BINARY_TO_INSTALL="./target/release/$SOURCE_BINARY_NAME"
47+
else
48+
echo "ERROR: Binary file '$SOURCE_BINARY_NAME' not found."
49+
echo "Did you run 'cargo build --release'?"
50+
exit 1
51+
fi
52+
53+
echo "Found binary at: $BINARY_TO_INSTALL"
54+
55+
echo ">>> [1/5] Stopping running service..."
56+
systemctl stop $SERVICE_NAME 2>/dev/null
57+
58+
echo ">>> [2/5] Unlocking filesystem (SteamOS)..."
59+
steamos-readonly disable 2>/dev/null
60+
61+
echo ">>> [3/5] Installing binary to $INSTALL_PATH..."
62+
cp -f "$BINARY_TO_INSTALL" "$INSTALL_PATH"
63+
chmod +x "$INSTALL_PATH"
64+
chown root:root "$INSTALL_PATH"
65+
66+
echo ">>> [4/5] Creating Hardened Systemd Service..."
67+
cat <<EOF > /etc/systemd/system/$SERVICE_NAME
68+
[Unit]
69+
Description=Legion Go S LED Sleep Controller
70+
After=multi-user.target
71+
72+
[Service]
73+
Type=simple
74+
# Load the kernel module before starting the daemon
75+
ExecStartPre=/usr/sbin/modprobe ec_sys write_support=1
76+
# Run the binary with the 'daemon' subcommand
77+
ExecStart=$INSTALL_PATH daemon
78+
Restart=always
79+
RestartSec=5
80+
User=root
81+
82+
# --- PERMISSIONS ---
83+
# Crucial: Allow writing to EC debugfs
84+
ReadWritePaths=/sys/kernel/debug/ec
85+
86+
# --- SECURITY HARDENING ---
87+
ProtectHome=true
88+
ProtectSystem=full
89+
PrivateTmp=true
90+
NoNewPrivileges=true
91+
92+
[Install]
93+
WantedBy=multi-user.target
94+
EOF
95+
96+
echo ">>> [5/5] Activating service..."
97+
systemctl daemon-reload
98+
systemctl enable $SERVICE_NAME
99+
systemctl restart $SERVICE_NAME
100+
101+
echo ">>> Securing filesystem..."
102+
steamos-readonly enable 2>/dev/null
103+
104+
echo ">>> SUCCESS. Legion LED service installed."
105+
echo " Status check: systemctl status $SERVICE_NAME"
106+
echo " Manual control: sudo legion-led on / off"

src/main.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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

Comments
 (0)