feat: take the last of sway out of the classroom

The DVD player was the only thing still speaking a compositor the room
no longer runs. It asked sway's tree whether VLC's window had really
gone fullscreen, because VLC reports playing while its window is still
the black startup one the loading cover exists to hide.

VLC's own status was measured as a replacement and is not one: it
reports fullscreen true in the same 250ms poll as playing, because the
flag is the option it was started with and not a report about its
window. So the cover is held for a floor and capped by the deadline that
was already there, and the compositor is not asked anything.

Focus-or-launch went the same way. The session already takes an
exclusive lock, so a second one cannot start, and bringing an existing
one to the front is the panel's job.

classroom-window, classroom_sway and classroom_chrome are gone with it —
the last of them was reachable from nothing at all.

DVD also moves out of the status row, where it sat beside the clock as
though a disc were a machine reading. It is an application: it is in the
row with Wonder Ink and the browser, and classroom-provision removes it
from the layout on a machine with no optical drive, which retires
DVD_ENABLED. A computer that moves between rooms takes its drive with
it; nobody remembers to edit a file when it does.

The panel is rebuilt from the package at every login now. Plasma keeps
whatever panel it first built, so an update could change the bar in the
package and never change it in a room — measured, with an icon that
outlived the file it came from. Editing the live panel configuration was
tried instead and destroyed the panel, because the shell owns that file.
Removing it before the shell starts is safe and makes the desktop
stateless.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
ekklesia
2026-09-10 13:00:50 -05:00
co-authored by Claude Opus 5
parent 18cf0249dd
commit d2d30d6b0c
25 changed files with 402 additions and 737 deletions
+46 -22
View File
@@ -13,7 +13,7 @@ Layout, bottom to top:
loading surface layer-shell OVERLAY, covers the whole display
including the dock while the disc loads, removed
once playback is actually on screen
VLC fullscreen, drawn above the top layer by sway
VLC fullscreen, an ordinary window below both layers
control bar layer-shell OVERLAY, drawn above fullscreen video
"""
@@ -45,7 +45,6 @@ sys.path.insert(
import classroom_optical as optical # noqa: E402
import classroom_style as style # noqa: E402
import classroom_sway as sway # noqa: E402
import classroom_vlc as vlc # noqa: E402
from classroom_config import SHELL_SCALE, read_device_config, scaled # noqa: E402
from classroom_icons import ScalableIcon # noqa: E402
@@ -78,9 +77,21 @@ HOVER_STRIP = 14
IDLE_HIDE_SECONDS = 4
LEAVE_HIDE_SECONDS = 1
# How VLC is recognised in the sway tree. Qt5 puts it on XWayland, so
# the X11 class is what usually matches.
VLC_WINDOW = (r"vlc", r"vlc-.*")
# How long the cover stays up at the very least.
#
# This replaced a query to the window manager. Under sway the session
# walked the tree until VLC's window was really fullscreen, because VLC
# reports playing while its window is still the ordinary one — the black
# startup window the cover exists to hide.
#
# VLC's own status was measured as a replacement and is not one: it
# reports <fullscreen>true</fullscreen> in the same 250ms poll as
# <state>playing</state>, because the flag is the setting it was started
# with and not the state of its window. So the cover is held for a floor
# instead, and LOADING_DEADLINE_SECONDS still caps the wait. On real
# hardware the disc takes far longer than this to open, so the floor is
# almost never what decides.
MINIMUM_COVER_SECONDS = 1.2
# The loading surface goes away only once VLC has genuinely taken the
# screen. The deadline is a safety net: whatever happens, DVD mode must
@@ -188,15 +199,29 @@ class VlcControl:
def state(self):
"""playing, paused, stopped, or None while VLC is still coming up."""
return self.field(r"<state>(\w+)</state>")
def field(self, pattern):
body = self.request()
if not body:
return None
found = re.search(r"<state>(\w+)</state>", body)
found = re.search(pattern, body)
return found.group(1) if found else None
def wants_fullscreen(self):
"""Whether VLC believes it is fullscreen.
Deliberately named for what it is. VLC reports this true from the
moment it starts, because it is the option it was given, not a
report about its window — measured at 250ms resolution against a
real VLC. It is a precondition worth checking and not evidence
that anything is on screen.
"""
return self.field(r"<fullscreen>(\w+)</fullscreen>") == "true"
class LoadingSurface(Gtk.Window):
"""The whole display, while the disc spins up.
@@ -221,9 +246,9 @@ class LoadingSurface(Gtk.Window):
GtkLayerShell.init_for_window(self)
# OVERLAY rather than TOP. The dock is a TOP layer surface, and
# OVERLAY rather than TOP. The panel is a TOP layer surface and
# DVD mode has to cover it; OVERLAY is above it without depending
# on which surface sway happens to have created first. It is
# on which surface the compositor happened to create first. It is
# taken down the moment playback is on screen, so it never sits
# over the video.
GtkLayerShell.set_layer(self, GtkLayerShell.Layer.OVERLAY)
@@ -290,7 +315,8 @@ class ControlBar(Gtk.Window):
GtkLayerShell.init_for_window(self)
# OVERLAY is the one layer sway draws above a fullscreen window.
# OVERLAY is the one layer a compositor draws above a fullscreen
# window, so the classroom's controls stay over the video.
GtkLayerShell.set_layer(self, GtkLayerShell.Layer.OVERLAY)
for edge in (
@@ -564,25 +590,23 @@ class DvdSession:
self.pid = None
Gtk.main_quit()
def player_is_fullscreen(self):
"""True once VLC's window exists and has taken the whole output.
def player_is_ready(self):
"""True once there is something worth uncovering.
Playback state alone is not enough. VLC reports playing while its
window is still the ordinary tiled one sway gave it, which is the
black startup window the cover exists to hide. It only takes the
display when it goes fullscreen, and that is what the tree says.
Three things, none of which asks the window manager anything:
VLC is playing or paused, VLC was told to be fullscreen and still
thinks it is, and the cover has been up for its minimum.
"""
for window in sway.windows():
if sway.matches(window, VLC_WINDOW) and window["fullscreen"]:
return True
if not self.control.wants_fullscreen():
return False
return False
return self.elapsed >= MINIMUM_COVER_SECONDS
def watch_startup(self):
"""Hold the cover until VLC is genuinely ready to replace it.
Three things have to line up: VLC says it is playing, its window
is fullscreen, and a moment has passed for the disc menu to draw.
Three things have to line up: VLC says it is playing, it is still
set fullscreen, and a moment has passed for the disc menu to draw.
The deadline is the safety net: DVD mode must never leave a cover
over a screen with nothing behind it, even if the disc never
opens.
@@ -592,7 +616,7 @@ class DvdSession:
# The cheap local check first; the tree is only worth walking
# once VLC claims to be playing something.
playing = self.control.state() in ("playing", "paused")
ready = playing and self.player_is_fullscreen()
ready = playing and self.player_is_ready()
if ready or self.elapsed >= LOADING_DEADLINE_SECONDS:
GLib.timeout_add(STABILISE_MS, self.drop_loading)