#!/usr/bin/env python3 import os import signal import sys import time import gi gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") from gi.repository import Adw, Gdk, GLib, Gtk try: gi.require_version("Gtk4LayerShell", "1.0") from gi.repository import Gtk4LayerShell except (ImportError, ValueError): Gtk4LayerShell = None BACKLIGHT_PATH = os.environ.get( "BRIGHTNESS_OSD_BACKLIGHT", "/sys/class/backlight/intel_backlight" ) PID_FILE = os.environ.get("BRIGHTNESS_OSD_PID", "/tmp/brightness-osd.pid") VALUE_FILE = os.environ.get("BRIGHTNESS_OSD_VALUE", "/tmp/brightness-osd.value") VISIBLE_MS = int(os.environ.get("BRIGHTNESS_OSD_VISIBLE_MS", "1400")) def read_percent(): if len(sys.argv) > 1: return max(0, min(100, int(round(float(sys.argv[1]))))) with open(os.path.join(BACKLIGHT_PATH, "brightness"), encoding="utf-8") as handle: current = int(handle.read().strip()) with open(os.path.join(BACKLIGHT_PATH, "max_brightness"), encoding="utf-8") as handle: maximum = int(handle.read().strip()) return max(0, min(100, round(current * 100 / maximum))) def read_existing_pid(): try: with open(PID_FILE, encoding="utf-8") as handle: return int(handle.read().strip()) except (FileNotFoundError, ValueError): return None def process_exists(pid): try: os.kill(pid, 0) return True except ProcessLookupError: return False except PermissionError: return True def signal_existing(percent): pid = None for _attempt in range(5): pid = read_existing_pid() if pid and pid != os.getpid() and process_exists(pid): break time.sleep(0.03) else: return False with open(VALUE_FILE, "w", encoding="utf-8") as handle: handle.write(str(percent)) try: os.kill(pid, signal.SIGUSR1) return True except ProcessLookupError: return False class BrightnessOsd(Adw.Application): def __init__(self, percent): super().__init__(application_id="local.brightness.osd") self.percent = percent self.label = None self.bar = None self.timeout_id = None def update_percent(self, percent): self.percent = max(0, min(100, percent)) if self.label: self.label.set_label(f"{self.percent}%") if self.bar: self.bar.set_value(self.percent) if self.timeout_id: GLib.source_remove(self.timeout_id) self.timeout_id = GLib.timeout_add(VISIBLE_MS, self.quit) def handle_refresh(self, *_args): try: with open(VALUE_FILE, encoding="utf-8") as handle: percent = int(handle.read().strip()) except (FileNotFoundError, ValueError): percent = read_percent() GLib.idle_add(self.update_percent, percent) def do_activate(self): with open(PID_FILE, "w", encoding="utf-8") as handle: handle.write(str(os.getpid())) window = Gtk.ApplicationWindow(application=self) window.set_title("Brightness") window.set_decorated(False) window.set_resizable(False) window.set_default_size(190, 58) window.set_opacity(0.92) window.set_hide_on_close(True) if Gtk4LayerShell is not None: Gtk4LayerShell.init_for_window(window) Gtk4LayerShell.set_layer(window, Gtk4LayerShell.Layer.OVERLAY) Gtk4LayerShell.set_anchor(window, Gtk4LayerShell.Edge.TOP, True) Gtk4LayerShell.set_margin(window, Gtk4LayerShell.Edge.TOP, 28) Gtk4LayerShell.set_keyboard_mode(window, Gtk4LayerShell.KeyboardMode.NONE) css = Gtk.CssProvider() css.load_from_data( b""" window { background: transparent; } .osd { background: alpha(@window_fg_color, 0.88); color: @window_bg_color; border-radius: 15px; padding: 10px 14px; box-shadow: 0 10px 34px alpha(black, 0.34); } .osd image { color: @window_bg_color; } .percent { font-size: 15px; font-weight: 700; } levelbar trough { min-height: 6px; border-radius: 999px; background: alpha(@window_bg_color, 0.24); } levelbar block.filled { min-height: 6px; border-radius: 999px; background: @window_bg_color; } """ ) Gtk.StyleContext.add_provider_for_display( Gdk.Display.get_default(), css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) box.add_css_class("osd") box.set_halign(Gtk.Align.CENTER) box.set_valign(Gtk.Align.START) box.set_margin_top(28) icon = Gtk.Image.new_from_icon_name("display-brightness-symbolic") icon.set_pixel_size(22) self.label = Gtk.Label(label=f"{self.percent}%") self.label.add_css_class("percent") self.label.set_width_chars(4) self.bar = Gtk.LevelBar.new_for_interval(0, 100) self.bar.set_value(self.percent) self.bar.set_size_request(96, -1) self.bar.set_valign(Gtk.Align.CENTER) box.append(icon) box.append(self.bar) box.append(self.label) window.set_child(box) signal.signal(signal.SIGUSR1, self.handle_refresh) window.present() self.timeout_id = GLib.timeout_add(VISIBLE_MS, self.quit) def main(): percent = read_percent() if signal_existing(percent): return 0 with open(PID_FILE, "w", encoding="utf-8") as handle: handle.write(str(os.getpid())) app = BrightnessOsd(percent) return app.run([]) if __name__ == "__main__": raise SystemExit(main())