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:
ekklesia
2026-09-04 23:17:37 -05:00
co-authored by Claude Opus 5
parent 37f8166401
commit 8bed3d186a
9 changed files with 177 additions and 47 deletions
+3 -3
View File
@@ -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(
+2 -2
View File
@@ -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"
+57
View File
@@ -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())