← luna

Commit d3999a7

4
files
+108
added
-2
deleted
@@ -252,11 +252,12 @@ pub enum IconName {
252 252 User,
253 253 Info,
254 254 App,
255 + Brightness,
255 256 }
256 257
257 258 impl IconName {
258 259 /// Wszystkie ikony z pakietu (kolejność = kolejność w galerii).
259 - pub const ALL: [IconName; 28] = [
260 + pub const ALL: [IconName; 29] = [
260 261 IconName::Menu,
261 262 IconName::Search,
262 263 IconName::Files,
@@ -285,6 +286,7 @@ impl IconName {
285 286 IconName::User,
286 287 IconName::Info,
287 288 IconName::App,
289 + IconName::Brightness,
288 290 ];
289 291
290 292 /// Nazwa ikony (do galerii/diagnostyki).
@@ -318,6 +320,7 @@ impl IconName {
318 320 IconName::User => "User",
319 321 IconName::Info => "Info",
320 322 IconName::App => "App",
323 + IconName::Brightness => "Brightness",
321 324 }
322 325 }
323 326 }
@@ -634,6 +637,19 @@ pub fn paint_icon(paint: &mut PaintCtx, name: IconName, rect: Rect, color: Color
634 637 color,
635 638 );
636 639 }
640 + IconName::Brightness => {
641 + scene.stroke_circle(g.p(12.0, 12.0), g.d(4.0), w, color);
642 + for k in 0..8 {
643 + let a = (k as f64 * 45.0).to_radians();
644 + let (c, s) = (a.cos(), a.sin());
645 + scene.stroke_line(
646 + g.p(12.0 + 6.4 * c, 12.0 + 6.4 * s),
647 + g.p(12.0 + 9.2 * c, 12.0 + 9.2 * s),
648 + g.d(2.0),
649 + color,
650 + );
651 + }
652 + }
637 653 }
638 654 }
639 655
@@ -51,6 +51,8 @@ fn main() -> Result<(), Box<dyn Error>> {
51 51 // Daemon powiadomień D‑Bus (rejestrowany przy pierwszej klatce).
52 52 let mut bus: Option<NotificationsBus> = None;
53 53 let mut bus_tried = false;
54 + // Bieżąca jasność (procent) — sterowana klikiem w pigułkę jasności.
55 + let mut brightness_pct: u8 = 0;
54 56 // Lista okien z kompozytora (foreign-toplevel) — własne połączenie + wątek.
55 57 let toplevels = luna::toplevels::Toplevels::spawn();
56 58 // Przestrzenie robocze z kompozytora (ext-workspace) — własne połączenie + wątek.
@@ -98,6 +100,7 @@ fn main() -> Result<(), Box<dyn Error>> {
98 100 if let Ok(sys) = system.lock() {
99 101 state.user = sys.user.clone();
100 102 state.toggles = build_toggles(&sys);
103 + brightness_pct = sys.brightness.unwrap_or(0);
101 104 }
102 105 // Przestrzenie robocze z kompozytora.
103 106 match &workspaces {
@@ -200,6 +203,13 @@ fn main() -> Result<(), Box<dyn Error>> {
200 203 luna::system::set_wifi(!state.toggles[i].active);
201 204 }
202 205 ToggleAction::Volume => luna::system::toggle_mute(),
206 + ToggleAction::Brightness => {
207 + let next = luna::system::next_brightness_level(brightness_pct);
208 + luna::system::set_brightness(next);
209 + brightness_pct = next;
210 + osd.icon = IconName::Brightness;
211 + osd.show("Jasność", next as f64 / 100.0);
212 + }
203 213 ToggleAction::None => {}
204 214 }
205 215 } else if let Some(i) =
@@ -327,9 +337,13 @@ fn main() -> Result<(), Box<dyn Error>> {
327 337 .wrapping_add(u64::from(t.active) ^ t.title.len() as u64);
328 338 }
329 339 for tg in &state.toggles {
340 + let mut lh: u64 = 0;
341 + for b in tg.label.bytes() {
342 + lh = lh.wrapping_mul(31).wrapping_add(u64::from(b));
343 + }
330 344 th = th
331 345 .wrapping_mul(1_099_511_628_211)
332 - .wrapping_add(u64::from(tg.active) ^ tg.label.len() as u64);
346 + .wrapping_add(u64::from(tg.active) ^ lh);
333 347 }
334 348 th = th
335 349 .wrapping_mul(1_099_511_628_211)
@@ -403,6 +417,14 @@ fn build_toggles(s: &luna::system::SystemStatus) -> Vec<Toggle> {
403 417 action: ToggleAction::None,
404 418 });
405 419 }
420 + if let Some(pct) = s.brightness {
421 + v.push(Toggle {
422 + label: format!("{pct}%"),
423 + icon: IconName::Brightness,
424 + active: pct > 0,
425 + action: ToggleAction::Brightness,
426 + });
427 + }
406 428 v
407 429 }
408 430
@@ -39,6 +39,8 @@ pub enum ToggleAction {
39 39 None,
40 40 Wifi,
41 41 Volume,
42 + /// Jasność ekranu (klik cyklicznie zmienia poziom).
43 + Brightness,
42 44 }
43 45
44 46 /// Przełącznik szybkiego dostępu (Wi-Fi, dźwięk, bateria…).
@@ -15,6 +15,8 @@ pub struct SystemStatus {
15 15 pub wifi: Option<(bool, Option<String>)>,
16 16 /// `(procent, ładuje)`.
17 17 pub battery: Option<(u8, bool)>,
18 + /// Jasność ekranu (procent), gdy jest podświetlenie (`/sys/class/backlight`).
19 + pub brightness: Option<u8>,
18 20 }
19 21
20 22 impl SystemStatus {
@@ -25,6 +27,7 @@ impl SystemStatus {
25 27 volume: read_volume(),
26 28 wifi: wifi(),
27 29 battery: battery(),
30 + brightness: read_brightness(),
28 31 }
29 32 }
30 33 }
@@ -34,6 +37,60 @@ pub fn read_volume() -> Option<(u8, bool)> {
34 37 volume()
35 38 }
36 39
40 +/// Bieżąca jasność ekranu w procentach (z `/sys/class/backlight`), gdy dostępna.
41 +pub fn read_brightness() -> Option<u8> {
42 + let dir = backlight_dir()?;
43 + let cur: u32 = std::fs::read_to_string(dir.join("brightness"))
44 + .ok()?
45 + .trim()
46 + .parse()
47 + .ok()?;
48 + let max: u32 = std::fs::read_to_string(dir.join("max_brightness"))
49 + .ok()?
50 + .trim()
51 + .parse()
52 + .ok()?;
53 + if max == 0 {
54 + return None;
55 + }
56 + Some(((cur * 100 + max / 2) / max) as u8)
57 +}
58 +
59 +/// Ustawia jasność ekranu (procent). Bez uprawnień do sysfs cicho nic nie robi.
60 +pub fn set_brightness(percent: u8) {
61 + let Some(dir) = backlight_dir() else { return };
62 + let Ok(text) = std::fs::read_to_string(dir.join("max_brightness")) else {
63 + return;
64 + };
65 + let Ok(max) = text.trim().parse::<u32>() else {
66 + return;
67 + };
68 + if max == 0 {
69 + return;
70 + }
71 + let value = (percent as u32 * max + 50) / 100;
72 + let _ = std::fs::write(dir.join("brightness"), format!("{value}\n"));
73 +}
74 +
75 +/// Katalog pierwszego podświetlenia (pierwszy wpis `/sys/class/backlight`).
76 +fn backlight_dir() -> Option<std::path::PathBuf> {
77 + std::fs::read_dir("/sys/class/backlight")
78 + .ok()?
79 + .flatten()
80 + .map(|e| e.path())
81 + .next()
82 +}
83 +
84 +/// Kolejny poziom jasności po kliknięciu (cykl 25 → 50 → 75 → 100 → 25).
85 +pub fn next_brightness_level(current: u8) -> u8 {
86 + for level in [25u8, 50, 75, 100] {
87 + if current + 6 < level {
88 + return level;
89 + }
90 + }
91 + 25
92 +}
93 +
37 94 /// Współdzielony stan, aktualizowany w tle.
38 95 pub type Shared = Arc<Mutex<SystemStatus>>;
39 96
@@ -146,6 +203,15 @@ fn battery() -> Option<(u8, bool)> {
146 203 mod tests {
147 204 use super::*;
148 205
206 + #[test]
207 + fn brightness_cycles_levels() {
208 + assert_eq!(next_brightness_level(0), 25);
209 + assert_eq!(next_brightness_level(25), 50);
210 + assert_eq!(next_brightness_level(50), 75);
211 + assert_eq!(next_brightness_level(75), 100);
212 + assert_eq!(next_brightness_level(100), 25);
213 + }
214 +
149 215 #[test]
150 216 fn parses_volume_and_mute() {
151 217 assert_eq!(parse_volume("Volume: 0.40\n"), Some((40, false)));