"""Icons that stay sharp when the compositor scales the session. new_from_file_at_scale rasterises an SVG at its *logical* size. Under sway's 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 sway advertises the ceiling integer scale and scales the finished buffer down. Measured under sway: 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