/* * brightness.cpp * Panel brightness control: the icon that appears when the active display * has a controllable backlight (laptop panel or DDC/CI monitor), and the * slider popout that drives it. * * Copyright (c) 2026 Daniel Hammer */ #include "desktop_internal.hpp" #include using namespace gui; // ============================================================================ // Geometry // ============================================================================ static constexpr int BRIGHT_POPUP_W = 220; static constexpr int BRIGHT_SLIDER_INSET = 16; static constexpr int BRIGHT_SLIDER_H = 8; static constexpr int BRIGHT_KNOB_R = 8; static constexpr int BRIGHT_STEP = 5; // [-]/[+] and one wheel notch static constexpr int BRIGHT_MIN = 5; // keep the screen readable static constexpr int BRIGHT_POLL_MS = 2000; // follow the Display app static constexpr int BRIGHT_ABSENT_POLL_MS = 30000; // A DDC/CI monitor needs ~50ms per command, so a drag is sampled rather than // forwarding every mouse move; the release always sends the final level. static constexpr int BRIGHT_DRAG_SEND_MS = 100; static const char* DISPLAY_APP_PATH = "0:/apps/display/display.elf"; static int brightness_popup_x(const DesktopState* ds) { int x = ds->brightness_icon_rect.x + ds->brightness_icon_rect.w - BRIGHT_POPUP_W; return x < 4 ? 4 : x; } static int brightness_header_y(const DesktopState* ds) { (void)ds; return PANEL_HEIGHT + 2 + 12; } static Rect brightness_slider_rect(const DesktopState* ds) { int y = brightness_header_y(ds) + system_font_height() + 18; return {brightness_popup_x(ds) + BRIGHT_SLIDER_INSET, y, BRIGHT_POPUP_W - 2 * BRIGHT_SLIDER_INSET, BRIGHT_SLIDER_H}; } // [-] [+] row under the slider. static Rect brightness_minus_rect(const DesktopState* ds) { Rect s = brightness_slider_rect(ds); return {brightness_popup_x(ds) + (BRIGHT_POPUP_W - (36 * 2 + 8)) / 2, s.y + s.h + 16, 36, 24}; } static Rect brightness_plus_rect(const DesktopState* ds) { Rect m = brightness_minus_rect(ds); return {m.x + m.w + 8, m.y, m.w, m.h}; } static Rect brightness_settings_rect(const DesktopState* ds) { Rect m = brightness_minus_rect(ds); return {brightness_popup_x(ds) + 8, m.y + m.h + 12, BRIGHT_POPUP_W - 16, system_font_height() + 10}; } static Rect brightness_popup_rect(const DesktopState* ds) { Rect settings = brightness_settings_rect(ds); int y = PANEL_HEIGHT + 2; return {brightness_popup_x(ds), y, BRIGHT_POPUP_W, settings.y + settings.h + 8 - y}; } // ============================================================================ // Hardware access // ============================================================================ static int clamp_level(int level) { if (level < BRIGHT_MIN) return BRIGHT_MIN; if (level > 100) return 100; return level; } // Send `level` now. The kernel reports what the hardware ended up at, which // for a panel with coarse steps is not always exactly what was asked. static void brightness_apply(DesktopState* ds, int level) { ds->brightness_pending = -1; ds->brightness_last_send = montauk::get_milliseconds(); int result = montauk::display_brightness(level); if (result >= 0 && !ds->brightness_dragging) ds->brightness_level = result; } // While dragging, the slider tracks the pointer immediately and the hardware // catches up at most every BRIGHT_DRAG_SEND_MS. static void brightness_request(DesktopState* ds, int level) { level = clamp_level(level); ds->brightness_level = level; uint64_t now = montauk::get_milliseconds(); if (ds->brightness_dragging && now - ds->brightness_last_send < BRIGHT_DRAG_SEND_MS) { ds->brightness_pending = level; return; } brightness_apply(ds, level); } static int brightness_from_x(const DesktopState* ds, int mx) { Rect s = brightness_slider_rect(ds); int v = ((mx - s.x) * 100) / s.w; return clamp_level(v); } // ============================================================================ // Startup and polling // ============================================================================ void desktop_brightness_init(DesktopState* ds) { ds->icon_brightness = svg_load("0:/os/icons/display-brightness-symbolic.svg", 16, 16, colors::PANEL_TEXT); ds->brightness_popup_open = false; ds->brightness_icon_rect = {0, 0, 0, 0}; ds->brightness_dragging = false; ds->brightness_pending = -1; ds->brightness_last_send = 0; ds->brightness_last_poll = montauk::get_milliseconds(); int level = montauk::display_brightness(-1); ds->brightness_present = level >= 0; ds->brightness_level = level >= 0 ? level : 100; } bool desktop_brightness_poll(DesktopState* ds, uint64_t now) { // Flush a drag sample that was held back by the send throttle. if (ds->brightness_pending >= 0 && now - ds->brightness_last_send >= BRIGHT_DRAG_SEND_MS) brightness_apply(ds, ds->brightness_pending); if (ds->brightness_dragging) return false; // An absent backlight is re-checked now and then: an external monitor // can be switched to DDC/CI in its menu, and the kernel retries that. uint64_t interval = ds->brightness_present ? BRIGHT_POLL_MS : BRIGHT_ABSENT_POLL_MS; if (now - ds->brightness_last_poll < interval) return false; ds->brightness_last_poll = now; int level = montauk::display_brightness(-1); bool present = level >= 0; bool changed = present != ds->brightness_present; ds->brightness_present = present; if (!present) { if (changed) ds->brightness_popup_open = false; return changed; } if (level != ds->brightness_level) { ds->brightness_level = level; changed = true; } return changed; } bool desktop_brightness_dragging(const DesktopState* ds) { return ds->brightness_dragging; } // ============================================================================ // Popout // ============================================================================ static void draw_button(DesktopState* ds, const Rect& r, const char* label) { Framebuffer& fb = ds->fb; Color bg = r.contains(ds->mouse.x, ds->mouse.y) ? Color::from_rgb(0xD0, 0xD0, 0xD0) : Color::from_rgb(0xE0, 0xE0, 0xE0); fill_rounded_rect(fb, r.x, r.y, r.w, r.h, 6, bg); int tw = text_width(label); draw_text(fb, r.x + (r.w - tw) / 2, r.y + (r.h - system_font_height()) / 2, label, colors::TEXT_COLOR); } void desktop_draw_brightness_popup(DesktopState* ds) { Framebuffer& fb = ds->fb; Rect popup = brightness_popup_rect(ds); Color accent = ds->settings.accent_color; draw_shadow(fb, popup.x, popup.y, popup.w, popup.h, 4, colors::SHADOW); fill_rounded_rect(fb, popup.x, popup.y, popup.w, popup.h, 8, colors::MENU_BG); draw_rect(fb, popup.x, popup.y, popup.w, popup.h, colors::BORDER); int ty = brightness_header_y(ds); draw_text(fb, popup.x + 14, ty, "Brightness", colors::TEXT_COLOR); char value[8]; snprintf(value, sizeof(value), "%d%%", ds->brightness_level); draw_text(fb, popup.x + popup.w - 14 - text_width(value), ty, value, accent); Rect s = brightness_slider_rect(ds); fill_rounded_rect(fb, s.x, s.y, s.w, s.h, 4, Color::from_rgb(0xDD, 0xDD, 0xDD)); int fill_w = (ds->brightness_level * s.w) / 100; if (fill_w > 0) fill_rounded_rect(fb, s.x, s.y, fill_w, s.h, 4, accent); int knob_cx = s.x + fill_w; int knob_cy = s.y + s.h / 2; fill_circle(fb, knob_cx, knob_cy, BRIGHT_KNOB_R, accent); fill_circle(fb, knob_cx, knob_cy, BRIGHT_KNOB_R - 3, colors::WHITE); draw_button(ds, brightness_minus_rect(ds), "-"); draw_button(ds, brightness_plus_rect(ds), "+"); Rect settings = brightness_settings_rect(ds); if (settings.contains(ds->mouse.x, ds->mouse.y)) fill_rounded_rect(fb, settings.x, settings.y, settings.w, settings.h, 5, Color::from_rgb(0xF0, 0xF3, 0xF6)); draw_text(fb, settings.x + 6, settings.y + (settings.h - system_font_height()) / 2, "Display Settings", colors::TEXT_COLOR); } // Returns true when the event was consumed. bool desktop_brightness_handle_mouse(DesktopState* ds, int mx, int my, bool left_pressed, bool left_held, bool left_released, int scroll) { if (!ds->brightness_present) return false; if (ds->brightness_dragging) { if (left_held) { brightness_request(ds, brightness_from_x(ds, mx)); return true; } if (left_released) { ds->brightness_dragging = false; brightness_apply(ds, ds->brightness_level); return true; } } // The wheel works over the panel icon too, without opening anything. bool over_popup = ds->brightness_popup_open && brightness_popup_rect(ds).contains(mx, my); if (scroll != 0 && (over_popup || ds->brightness_icon_rect.contains(mx, my))) { brightness_request(ds, ds->brightness_level + scroll * BRIGHT_STEP); return true; } if (!ds->brightness_popup_open || !left_pressed) return false; if (!over_popup) { if (!ds->brightness_icon_rect.contains(mx, my)) ds->brightness_popup_open = false; return false; } Rect s = brightness_slider_rect(ds); if (my >= s.y - 10 && my <= s.y + s.h + 10 && mx >= s.x - BRIGHT_KNOB_R && mx <= s.x + s.w + BRIGHT_KNOB_R) { ds->brightness_dragging = true; brightness_request(ds, brightness_from_x(ds, mx)); return true; } if (brightness_minus_rect(ds).contains(mx, my)) { brightness_request(ds, ds->brightness_level - BRIGHT_STEP); return true; } if (brightness_plus_rect(ds).contains(mx, my)) { brightness_request(ds, ds->brightness_level + BRIGHT_STEP); return true; } if (brightness_settings_rect(ds).contains(mx, my)) { ds->brightness_popup_open = false; desktop_spawn_app(ds, DISPLAY_APP_PATH, nullptr); return true; } return true; // inside the popup, on nothing in particular }