Files
Classroom_Desktop/lib/classroom_icons.py
T
ekklesiaandClaude Opus 5 13aac85b87 docs: record Parent Call, the DVD migration and what is left to test
Three things in this window that a container cannot answer, and they are
now the top of the next-steps list: a real key pressed into Parent Call,
a television switched on after the computer, and a disc that plays.

Also brings the comments in the shared libraries up to date. The only
mention of sway left in the tree is the upgrade step that removes the
sway-era login shell from a room being upgraded.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 13:09:42 -05:00

97 lines
2.9 KiB
Python

"""Icons that stay sharp when the compositor scales the session.
new_from_file_at_scale rasterises an SVG at its *logical* size. Under
output scaling the compositor then enlarges that finished raster,
which is why icons went soft while the clock text and the dock surface
stayed crisp: those are drawn by GTK at the display's real pixel
density, and the icon was not.
GTK 3 has no fractional scaling, so the compositor advertises the ceiling
integer
scale and scales the finished buffer down. Measured: scale
factor 1 at output scale 1.0, and 2 at 1.25, 1.5 and 2.0.
So the fix is to rasterise at size * scale_factor physical pixels and
hand GTK a cairo surface carrying that device scale. The icon lands at
the real density, while its logical size — and therefore the whole dock
layout — is exactly what it was before.
"""
from pathlib import Path
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
gi.require_version("GdkPixbuf", "2.0")
from gi.repository import Gdk, GdkPixbuf, GLib, Gtk # noqa: E402
ICON_DIR = Path("/etc/ekklesia-classroom/icons")
def render_surface(path, size, scale, window=None):
"""An SVG at `size` logical pixels, drawn at `scale` times the density.
The box semantics match the old custom_icon(): the artwork is fitted
inside a square of the requested size with its aspect preserved, so
a wide logo like the DVD wordmark keeps the proportions it has now.
"""
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
str(path),
size * scale,
size * scale,
True,
)
return Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, window)
class ScalableIcon(Gtk.Image):
"""A custom SVG icon, redrawn whenever the display density changes."""
def __init__(self, filename, size, directory=None):
super().__init__()
self.directory = directory or ICON_DIR
self.size = size
self.drawn_at = None
self.usable = False
self.set_icon(filename)
# The scale factor is not known until the widget is on a display,
# and it changes if UI_SCALE is changed under a running session.
self.connect("realize", lambda *_: self.redraw())
self.connect("notify::scale-factor", lambda *_: self.redraw())
def set_icon(self, filename):
self.path = self.directory / filename
self.usable = self.path.exists()
self.drawn_at = None
self.redraw()
def redraw(self):
if not self.usable:
return
scale = self.get_scale_factor() or 1
if scale == self.drawn_at:
return
try:
surface = render_surface(
self.path,
self.size,
scale,
self.get_window(),
)
except (GLib.Error, MemoryError):
self.usable = False
return
self.set_from_surface(surface)
self.drawn_at = scale