feat: scale the whole session, not just the classroom's own windows
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]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
37f8166401
commit
8bed3d186a
@@ -6,6 +6,11 @@ set $mod Mod4
|
||||
# Wallpaper
|
||||
output * bg /usr/share/backgrounds/ekklesia/classroom-default.png fill
|
||||
|
||||
# UI scale first, so every surface below comes up at the right size
|
||||
# instead of being resized underneath itself. This scales the whole
|
||||
# session — Chrome, VLC and the dock alike — not just our own windows.
|
||||
exec --no-startup-id /usr/local/bin/classroom-scale
|
||||
|
||||
# Notifications, started before the dock so nothing can race D-Bus into
|
||||
# activating an unstyled mako first. The wrapper resolves the styling at
|
||||
# the room's UI scale and execs mako, so the process is still just mako.
|
||||
|
||||
+15
-6
@@ -21,14 +21,21 @@ before promoting to stable with `./update_stable.sh`.
|
||||
|
||||
## UI scale
|
||||
|
||||
- [ ] `UI_SCALE=1.0` looks exactly as it did before scaling existed
|
||||
- [ ] `UI_SCALE=1.25`, then `sudo systemctl restart getty@tty1`:
|
||||
dock, icons, clock and notifications all grow together
|
||||
- [ ] `UI_SCALE=0.8` shrinks them together
|
||||
- [ ] The reserved bottom strip grows and shrinks with the dock, and a
|
||||
maximised Chrome window still stops above it
|
||||
UI_SCALE sets sway's output scale, so it scales the whole session.
|
||||
Change it, then `sudo systemctl restart getty@tty1`.
|
||||
|
||||
- [ ] `UI_SCALE=1.0` is identical to a session with no scaling
|
||||
- [ ] `swaymsg -t get_outputs` shows the mode still 1920x1080 at every
|
||||
scale, with only the logical size changing
|
||||
- [ ] At 1.25 and 1.5, **Chrome** grows: tabs, omnibox, toolbar icons,
|
||||
menus and page content
|
||||
- [ ] Chrome's buttons are still easy to hit with the mouse
|
||||
- [ ] Dock, clock, notifications and the DVD bar grow by the same
|
||||
amount — once, not twice
|
||||
- [ ] `UI_SCALE=0.8` shrinks everything together
|
||||
- [ ] A nonsense value (`UI_SCALE=banana`) still gives a working shell
|
||||
- [ ] An extreme value (`UI_SCALE=9`) is clamped, not obeyed
|
||||
- [ ] `journalctl` or the sway log shows `classroom-scale: session scale N`
|
||||
|
||||
## Reserved bottom shell (regression)
|
||||
|
||||
@@ -128,6 +135,8 @@ With `DVD_ENABLED=true`:
|
||||
clock included — no wallpaper visible anywhere, at any point
|
||||
- [ ] A spinner and "Loading DVD…" show while the disc spins up
|
||||
- [ ] No partial VLC window is ever visible against the wallpaper
|
||||
- [ ] The loading surface stays up until the disc menu is actually
|
||||
showing — VLC's tiled black startup window is never visible
|
||||
- [ ] Video or the disc menu replaces the loading surface when ready
|
||||
- [ ] A disc that never opens still gives the desktop back (20s)
|
||||
- [ ] No VLC chrome anywhere: no menu bar, no fullscreen controller,
|
||||
|
||||
+71
-35
@@ -2,7 +2,7 @@
|
||||
|
||||
Every classroom gets the same design at a size that suits its TV. A 32"
|
||||
panel a few feet from the children and a 65" set across a hall want
|
||||
different sizes of the same dock, not different docks.
|
||||
different sizes of the same room, not different rooms.
|
||||
|
||||
## Changing it
|
||||
|
||||
@@ -18,43 +18,79 @@ Then restart the classroom shell:
|
||||
sudo systemctl restart getty@tty1
|
||||
```
|
||||
|
||||
That ends the session and the autologin brings it straight back with the
|
||||
new size. A reboot does the same thing. Nothing else needs touching, and
|
||||
no CSS or source file is ever edited to change a room's size.
|
||||
|
||||
## Values
|
||||
|
||||
| UI_SCALE | Roughly |
|
||||
| --- | --- |
|
||||
| 0.8 | Small or close-up panel |
|
||||
| 1.0 | Default. 1080p classroom TV, 32–50" |
|
||||
| 1.25 | Large room, or a set viewed from further back |
|
||||
| 1.5 | 65"+ |
|
||||
|
||||
Anything outside 0.8–1.5 is clamped to that range, and anything that is
|
||||
not a number at all falls back to 1.0, so a typo in device.conf cannot
|
||||
produce an unusable shell.
|
||||
That ends the session and the autologin brings it straight back at the
|
||||
new size. A reboot does the same. Nothing else needs touching, and no
|
||||
CSS or source file is ever edited to change a room's size.
|
||||
|
||||
## What it scales
|
||||
|
||||
- Dock height, and the reserved strip at the bottom of the screen
|
||||
- Dock corner radius, padding and the spacing between buttons
|
||||
- Button size, padding and corner radius
|
||||
- Every dock icon
|
||||
- Clock text size, padding, corner radius and its inset from the edge
|
||||
- Notification width, height, padding, margin, corner radius, text size
|
||||
and icon size
|
||||
**Everything.** UI_SCALE is a property of the television, not of one
|
||||
program, so it is applied where it reaches the whole session: sway's
|
||||
output scale.
|
||||
|
||||
Colours, translucency and the hairline borders do not scale: 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 scaling existed.
|
||||
- Chrome's tabs, omnibox, toolbar icons and menus
|
||||
- VLC, and any other application's own interface
|
||||
- The dock, the clock, notifications and the DVD control bar
|
||||
- Mouse hit targets, because they scale with the widgets
|
||||
|
||||
## How it works
|
||||
The display mode is **not** changed. The panel stays at its native
|
||||
1920x1080. Only the logical coordinate space shrinks, which is what
|
||||
makes everything drawn in it larger:
|
||||
|
||||
`/etc/ekklesia-classroom/dock/style.css.in` and
|
||||
`/etc/ekklesia-classroom/mako/config.in` are 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 hands it to mako. Neither template is
|
||||
ever rewritten on disk, so an update cannot lose a room's setting and a
|
||||
room's setting cannot drift from the packaged design.
|
||||
| UI_SCALE | Logical space | Roughly |
|
||||
| --- | --- | --- |
|
||||
| 0.8 | 2400x1350 | Small or close-up panel |
|
||||
| 1.0 | 1920x1080 | Default. 32–50" classroom TV |
|
||||
| 1.25 | 1536x864 | Large room, or a set viewed from further back |
|
||||
| 1.5 | 1280x720 | 65"+ |
|
||||
|
||||
Anything outside 0.8–1.5 is clamped, and anything that is not a number
|
||||
falls back to 1.0, so a typo in device.conf cannot produce an unusable
|
||||
shell.
|
||||
|
||||
## Measured
|
||||
|
||||
Against 1920x1080 under sway, with Chrome 152:
|
||||
|
||||
| | 1.0 | 1.25 | 1.5 |
|
||||
| --- | --- | --- | --- |
|
||||
| Chrome tab strip | 40px | 49px (x1.23) | 59px (x1.48) |
|
||||
| sway title bar | 25px | 31px (x1.24) | 38px (x1.52) |
|
||||
| Classroom dock | 74px | 92px (x1.24) | 110px (x1.49) |
|
||||
|
||||
Everything scales once, together.
|
||||
|
||||
## Why our own surfaces do not scale themselves
|
||||
|
||||
`classroom-scale` runs first in the sway config and sets the output
|
||||
scale. From then on the dock, the notifications and the DVD control bar
|
||||
are measured in the same logical pixels sway is already scaling, so they
|
||||
must render **unscaled** — `SHELL_SCALE` in `classroom_config.py` is
|
||||
`1.0`. Scaling them again would apply UI_SCALE twice and the dock would
|
||||
come out half again too big.
|
||||
|
||||
The stylesheet templates still take a scale, and `SHELL_SCALE` is the
|
||||
one place it comes from. If compositor scaling ever has to be abandoned,
|
||||
setting it back to `ui_scale(config)` restores per-surface scaling
|
||||
without touching a template.
|
||||
|
||||
## Tradeoffs
|
||||
|
||||
**GTK 3 has no fractional scaling.** At UI_SCALE=1.5 sway advertises an
|
||||
integer scale of 2 to GTK, which renders at 2x, and sway scales the
|
||||
result down to 1.5x. The dock is the right size but very slightly softer
|
||||
than at 1.0. On a TV at classroom distance this is not visible.
|
||||
|
||||
**XWayland is softer at fractional scale.** X11 clients have no concept
|
||||
of a fractional output scale, so sway renders them at the logical size
|
||||
and scales the buffer. Measured on terminal text, scale 1.5 produced
|
||||
about 50% more intermediate-tone pixels than scale 1.0 — real, modest
|
||||
softening.
|
||||
|
||||
VLC is the only XWayland application in the classroom, and in DVD mode
|
||||
none of its interface is on screen: only video, from a 720x480 disc
|
||||
being upscaled regardless. Chrome runs on Wayland natively and is sharp
|
||||
at every scale, which is what matters for reading.
|
||||
|
||||
At UI_SCALE=1.0 nothing is scaled at all and the session is identical to
|
||||
one with no scaling.
|
||||
|
||||
+15
-1
@@ -60,6 +60,20 @@ def ui_scale(config):
|
||||
return min(max(value, UI_SCALE_MIN), UI_SCALE_MAX)
|
||||
|
||||
|
||||
# The scale the classroom's own GTK surfaces render at.
|
||||
#
|
||||
# UI_SCALE is applied by sway to the whole output (see classroom-scale),
|
||||
# which scales Chrome, VLC and every other application along with the
|
||||
# dock. Our surfaces are measured in the same logical pixels sway is
|
||||
# already scaling, so scaling them here as well would apply UI_SCALE
|
||||
# twice and the dock would come out half again too big.
|
||||
#
|
||||
# This is the seam: if compositor scaling ever has to be abandoned,
|
||||
# changing this back to ui_scale(config) restores per-surface scaling
|
||||
# without touching the templates.
|
||||
SHELL_SCALE = 1.0
|
||||
|
||||
|
||||
def scaled(value, scale):
|
||||
"""A pixel measurement at the configured scale, never smaller than 1."""
|
||||
"""A pixel measurement at the given scale, never smaller than 1."""
|
||||
return max(1, int(round(value * scale)))
|
||||
|
||||
@@ -24,6 +24,7 @@ chmod 0755 /usr/local/bin/classroom-dvd-session
|
||||
chmod 0755 /usr/local/bin/classroom-dvd-css-setup
|
||||
chmod 0755 /usr/local/bin/classroom-notify-test
|
||||
chmod 0755 /usr/local/bin/classroom-notifications
|
||||
chmod 0755 /usr/local/bin/classroom-scale
|
||||
chmod 0755 /usr/local/bin/classroom-chrome-setup
|
||||
chmod 0755 /usr/local/bin/classroom-provision
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ sys.path.insert(0, "/usr/local/lib/ekklesia-classroom")
|
||||
|
||||
import classroom_style as style # noqa: E402
|
||||
from classroom_config import ( # noqa: E402
|
||||
SHELL_SCALE,
|
||||
enabled,
|
||||
read_device_config,
|
||||
scaled,
|
||||
ui_scale,
|
||||
)
|
||||
|
||||
# Optional on purpose. The dock is the one thing the classroom cannot
|
||||
@@ -107,7 +107,7 @@ class ClassroomShell(Gtk.Window):
|
||||
self.set_resizable(False)
|
||||
|
||||
self.config = read_device_config()
|
||||
self.scale = ui_scale(self.config)
|
||||
self.scale = SHELL_SCALE
|
||||
|
||||
self.dvd_image = None
|
||||
self.disc_ready = None
|
||||
@@ -383,7 +383,7 @@ def load_css(scale):
|
||||
|
||||
|
||||
def main():
|
||||
load_css(ui_scale(read_device_config()))
|
||||
load_css(SHELL_SCALE)
|
||||
|
||||
shell = ClassroomShell()
|
||||
shell.connect(
|
||||
|
||||
@@ -13,7 +13,7 @@ from pathlib import Path
|
||||
|
||||
sys.path.insert(0, "/usr/local/lib/ekklesia-classroom")
|
||||
|
||||
from classroom_config import read_device_config, scaled, ui_scale # noqa: E402
|
||||
from classroom_config import SHELL_SCALE, scaled # noqa: E402
|
||||
|
||||
MAKO = "/usr/bin/mako"
|
||||
TEMPLATE = Path("/etc/ekklesia-classroom/mako/config.in")
|
||||
@@ -53,7 +53,7 @@ def render(scale):
|
||||
|
||||
|
||||
def main():
|
||||
scale = ui_scale(read_device_config())
|
||||
scale = SHELL_SCALE
|
||||
|
||||
try:
|
||||
path = runtime_directory() / "mako.config"
|
||||
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Apply the room's UI scale to the whole classroom session.
|
||||
|
||||
UI_SCALE is a property of the television, not of one program, so it is
|
||||
applied where it reaches everything: sway's output scale. Chrome's tabs
|
||||
and omnibox, its menus, VLC, and the classroom's own dock and
|
||||
notifications are then all measured in the same scaled logical pixels.
|
||||
|
||||
The display mode is untouched — the panel stays at its native
|
||||
resolution. Only the logical coordinate space shrinks, which is what
|
||||
makes everything drawn in it larger.
|
||||
|
||||
Run from the sway config before anything else starts, so surfaces come
|
||||
up at the right size rather than being resized underneath themselves.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "/usr/local/lib/ekklesia-classroom")
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "lib"),
|
||||
)
|
||||
|
||||
from classroom_config import read_device_config, ui_scale # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
scale = ui_scale(read_device_config())
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["swaymsg", "output", "*", "scale", f"{scale:g}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
except OSError as error:
|
||||
print(f"classroom-scale: {error}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if result.returncode != 0:
|
||||
print(
|
||||
f"classroom-scale: {result.stderr.strip() or 'sway refused the scale'}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
print(f"classroom-scale: session scale {scale:g}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -121,6 +121,10 @@ install -Dm755 \
|
||||
"$ROOT/scripts/classroom-notifications" \
|
||||
"$STAGE/usr/local/bin/classroom-notifications"
|
||||
|
||||
install -Dm755 \
|
||||
"$ROOT/scripts/classroom-scale" \
|
||||
"$STAGE/usr/local/bin/classroom-scale"
|
||||
|
||||
install -Dm755 \
|
||||
"$ROOT/scripts/classroom-chrome-setup" \
|
||||
"$STAGE/usr/local/bin/classroom-chrome-setup"
|
||||
@@ -138,6 +142,10 @@ install -Dm644 \
|
||||
"$ROOT/lib/classroom_style.py" \
|
||||
"$STAGE/usr/local/lib/ekklesia-classroom/classroom_style.py"
|
||||
|
||||
install -Dm644 \
|
||||
"$ROOT/lib/classroom_sway.py" \
|
||||
"$STAGE/usr/local/lib/ekklesia-classroom/classroom_sway.py"
|
||||
|
||||
install -Dm644 \
|
||||
"$ROOT/lib/classroom_vlc.py" \
|
||||
"$STAGE/usr/local/lib/ekklesia-classroom/classroom_vlc.py"
|
||||
|
||||
Reference in New Issue
Block a user