Icons went soft under UI_SCALE while the clock text beside them stayed
sharp, which is visible on a 24" monitor even if it hides on a TV.
new_from_file_at_scale rasterises an SVG at its *logical* size. The
artwork is vector, but by the time sway scaled the session it was
already a fixed raster, and the compositor enlarged that. Text never had
the problem because GTK draws it at the real density.
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 each icon
is now rasterised at size x scale_factor physical pixels and handed to
GTK as a cairo surface carrying that device scale. The icon lands at the
real density; its logical size, and the whole dock layout, is unchanged.
Icons redraw on notify::scale-factor as well as at realize, since the
scale factor is not known until a widget is on a display and changes if
UI_SCALE is changed under a running session.
Browser, DVD, LIVE and all six DVD control icons go through it, in a
shared module so the dock and the DVD session cannot diverge. The
throbber is drawn rather than loaded and had the same flaw, so it draws
on a device-scaled surface too rather than being the one soft thing left
in a sharp dock.
The artwork is untouched. Verified by rendering the dock under sway at
1.0, 1.25, 1.5 and 2.0 against the previous build: apparent icon size is
identical at every scale (28, 35, 42, 56 px), 1.0 is unchanged
pixel-for-pixel, and peak edge contrast on the Chrome icon goes from 60
to 99 at 1.25, 49 to 83 at 1.5, and 37 to 82 at 2.0 out of 255. The DVD
control bar was checked the same way.
Also records that Wonder Ink session-only cookies are confirmed working
on the endpoint, which settles the question 0.1.34 shipped as unverified
and tells us the session is in cookies rather than localStorage.
Co-Authored-By: Claude Opus 5 <[email protected]>
Clicking Browser and watching nothing happen for several seconds is the
one place the classroom felt broken rather than quiet. The button now
becomes a throbber the instant it is pressed, and stays one until the
window actually appears in the sway tree.
Only a real launch spins. The dock asks sway whether Chrome is already
open first, so focusing or restoring an existing window stays immediate,
which is how it should feel. DVD is left alone: it has its own
full-screen loading surface and does not need two answers to one click.
A launch that never arrives gives up after thirty seconds, puts the icon
back and says so, so a broken start cannot spin all morning.
The throbber is drawn rather than a GtkSpinner: the stock one is capped
well below the size of the icon it replaces, so it read as a hesitation
rather than as work in progress. Pre-rendered frames swapped into the
same Gtk.Image keep the button an ordinary image with ordinary clicks,
and it falls back to GtkSpinner if cairo is ever missing.
The pointer also turns busy while it is over the dock. Wayland gives the
cursor to whichever client the pointer is over and sway offers no way to
set one globally, so this only holds on our own surface — which is
exactly where the pointer is at the moment a dock button is pressed.
Co-Authored-By: Claude Opus 5 <[email protected]>
UI_SCALE only ever reached the surfaces we draw ourselves. Chrome stayed
tiny — its tabs, omnibox and menus are Chrome's own pixels, and Ctrl+
only scales page content, which is no use to a volunteer reading a
toolbar from across the room.
UI_SCALE is a property of the television, not of one program, so it now
sets sway's output scale, which every client is measured against.
classroom-scale runs first in the session and applies it. The display
mode is untouched: the panel stays at 1920x1080 and only the logical
coordinate space shrinks, which is what makes everything in it larger.
Our own surfaces stop scaling themselves. They are measured in the same
logical pixels sway is already scaling, so applying UI_SCALE again would
double it. SHELL_SCALE is that decision in one named place, and setting
it back to ui_scale(config) would restore per-surface scaling without
touching a template.
Measured at 1920x1080 with Chrome 152. Chrome's tab strip 40 -> 49 ->
59px against 1.0, 1.25, 1.5; sway's title bar 25 -> 31 -> 38px; the
classroom dock 74 -> 92 -> 110px. Everything scales once, together, and
1.0 is identical to no scaling at all.
Two tradeoffs, both measured rather than assumed. GTK 3 has no
fractional scaling, so at 1.5 it renders at 2x and sway scales down —
very slightly softer, invisible at classroom distance. XWayland is
softer too: terminal text at 1.5 showed about 50% more intermediate-tone
pixels than at 1.0. VLC is the only XWayland application here, and in
DVD mode none of its interface is on screen, only video from a 720x480
disc that is being upscaled regardless. Chrome is a native Wayland
client and stays sharp, which is what matters for reading.
Co-Authored-By: Claude Opus 5 <[email protected]>
The DVD control bar has to look like the dock, which is best guaranteed
by drawing it from the same template with the same measurements rather
than by keeping two stylesheets in step by hand.
Co-Authored-By: Claude Opus 5 <[email protected]>
A 32" panel a few feet from the children and a 65" set across a hall
want the same design at different sizes. UI_SCALE in device.conf now
sets that, and nobody has to edit CSS or source to tune a room.
The stylesheet and the notification config become templates carrying
measurement tokens. classroom-dock resolves the stylesheet in memory at
startup; classroom-notifications resolves the notification config into
the session's runtime directory and execs mako on it, so the process is
still just mako and everything that looks for it by name still finds it.
Neither template is rewritten on disk, so an update cannot lose a room's
setting and a room cannot drift from the packaged design.
Dock height and the reserved strip, corner radii, padding, button size,
spacing, every icon, the clock and its inset, and the notification
geometry all follow the scale. Colours, translucency and the hairline
borders do not: those are the design, not the size.
At UI_SCALE=1.0 the rendered stylesheet is byte-identical to the one the
shell used before any of this existed, which is the property worth
keeping. Values outside 0.8 to 1.5 are clamped and anything that is not
a number falls back to 1.0, so a typo in device.conf cannot produce an
unusable classroom.
Device configuration moves into a shared module, since the dock and the
notification daemon now have to agree about what a setting means.
Co-Authored-By: Claude Opus 5 <[email protected]>
DVD support stays optional per room: DVD_ENABLED decides whether the
button is built at all, and rooms without it poll nothing. Upgrades
append a commented DVD_ENABLED=false so the option is discoverable
without changing what those rooms already do.
The dock polls the drive with CDROM_DRIVE_STATUS, which is a status
query rather than a read, so it does not spin the disc up. A disc that
becomes ready announces itself once and brings the DVD logo up to full
strength; an empty or unplugged drive holds it back, the same gray
versus colour language the LIVE icon already uses. Nothing autoplays —
playback always needs a deliberate press.
Playback is VLC rather than mpv because mpv dropped DVD menu support,
and menus are the part that matters for a commercial disc. It goes
through classroom-window like everything else, so the player minimizes
and restores from the dock exactly as the browser does.
Pressing the button with no drive, no disc, or a disc still spinning up
gives a plain-language notification instead of failing silently.
Reading the drive needs group cdrom, which the postinst grants and the
OTA reboot activates. Commercial CSS discs additionally need libdvdcss,
which Debian cannot ship; classroom-dvd-css-setup bootstraps it from
contrib. That is deliberately not run from the postinst, which executes
while dpkg already holds the apt lock.
The optical helpers live in a shared module both the dock and
classroom-dvd import, and the dock imports it defensively: if it ever
goes missing the shell loses disc detection rather than failing to
start.
Co-Authored-By: Claude Opus 5 <[email protected]>