__init__.py
Python script, ASCII text executable
1
"""
2
Screen brightness control applet for the Panorama panel.
3
Copyright 2025, roundabout-host.com <vlad@roundabout-host.com>
4
5
This program is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public Licence as published by
7
the Free Software Foundation, either version 3 of the Licence, or
8
(at your option) any later version.
9
10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public Licence for more details.
14
15
You should have received a copy of the GNU General Public Licence
16
along with this program. If not, see <https://www.gnu.org/licenses/>.
17
"""
18
import os
19
20
import panorama_panel
21
import pydbus
22
import locale
23
from pathlib import Path
24
from pyudev import Context, Monitor, MonitorObserver
25
26
import gi
27
gi.require_version("Gtk", "4.0")
28
29
from gi.repository import Gtk, GLib, Gio
30
31
32
module_directory = Path(__file__).resolve().parent
33
locale.bindtextdomain("panorama-panel-file-listing", module_directory / "locale")
34
_ = lambda x: locale.dgettext("panorama-panel-file-listing", x)
35
36
37
def open_file_and_read_int(path: Path):
38
with open(path) as f:
39
return int(f.read().strip())
40
41
42
class ScreenBrightness(panorama_panel.Applet):
43
name = _("Brightness")
44
description = _("Control display brightness")
45
46
def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None):
47
super().__init__(orientation=orientation, config=config)
48
if config is None:
49
config = {}
50
self.icon = Gtk.Image.new_from_icon_name("display-brightness-symbolic")
51
self.icon.set_pixel_size(config.get("icon_size", 24))
52
self.button = Gtk.MenuButton(child=self.icon, has_frame=False)
53
self.append(self.button)
54
self.popover = Gtk.Popover()
55
self.box = Gtk.Box()
56
self.popover.set_child(self.box)
57
self.button.set_popover(self.popover)
58
self.bus = pydbus.SystemBus()
59
self.login1 = self.bus.get("org.freedesktop.login1", "/org/freedesktop/login1")
60
self.manager = self.bus.get("org.freedesktop.login1", "/org/freedesktop/login1")
61
self.session = self.bus.get("org.freedesktop.login1", self.manager.GetSessionByPID(os.getpid()))
62
self.backlights = [path for path in Path("/sys/class/backlight").iterdir()]
63
if not self.backlights:
64
self.button.hide()
65
self.adjustments = {}
66
self.sessions = self.manager.ListSessions()
67
self.udev_context = Context()
68
self.monitor = Monitor.from_netlink(self.udev_context)
69
self.monitor.filter_by(subsystem="backlight")
70
self.observer = MonitorObserver(self.monitor, callback=self.process_notify)
71
self.observer.start()
72
self.update_backlights()
73
74
def process_notify(self, device):
75
# Called when the brightness is updated in any way, even externally
76
path = Path("/sys/class/backlight") / device.sys_name
77
adjustment = self.adjustments.get(path)
78
value = open_file_and_read_int(path / "brightness")
79
if not adjustment:
80
return
81
82
def update():
83
adjustment.handler_block(adjustment.handler_id)
84
adjustment.set_value(value)
85
adjustment.handler_unblock(adjustment.handler_id)
86
return False
87
88
GLib.idle_add(update)
89
90
def update_backlights(self):
91
for backlight in self.backlights:
92
max_brightness = open_file_and_read_int(backlight / "max_brightness")
93
current_brightness = open_file_and_read_int(backlight / "brightness")
94
adjustment = Gtk.Adjustment(lower=0, upper=max_brightness, value=current_brightness)
95
adjustment.backlight = backlight
96
self.adjustments[backlight] = adjustment
97
adjustment.handler_id = adjustment.connect("value-changed", self.value_changed)
98
scale = Gtk.Scale(adjustment=adjustment, width_request=200)
99
self.box.append(scale)
100
101
def value_changed(self, adjustment, *args):
102
name = adjustment.backlight.name
103
self.session.SetBrightness("backlight", name, int(adjustment.get_value()))
104
105
def get_config(self):
106
return {"icon_size": self.icon.get_pixel_size()}
107