205 lines
5.9 KiB
Python
Executable File
205 lines
5.9 KiB
Python
Executable File
#!/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())
|