fix: a room that was set up, a card where its button is, and larger cards
Parent Call told rooms they had never been set up. Nothing about the rooms was wrong. Read from the church broker: …/coordinator and …/pending are retained and …/mode is absent, and the coordinator's own source says why — it publishes the retained mode only in answer to an operator writing to …/mode/set, and nobody ever has. Every classroom waited a second and a half for a message that will never exist. A missing mode is now read as the coordinator's own default rather than as an unconfigured room: the room offers the call and lets the sanctuary end answer, because that is the end that knows. A mode that really is off still refuses, in the coordinator's words, and a broker that is missing or unreachable still says which of those it is. Proved against a rig broker made to match the church's exactly, with a call landing on the sanctuary mock. The card is anchored to the bottom-left corner instead of centred, by layer shell, which is what the disc player's cover has used under KWin for months. Anchoring rather than positioning is what makes it right at 1080p and at 4K without a coordinate anywhere. It falls back to the centred card if the library is missing. Notifications are sized for a room. Every Plasma measurement is a grid unit and a grid unit is the height of the general font, so the font is the lever — 15 point instead of Breeze's 10, which takes the card from about 350x110 to about 410x145 at 100% and doubles again at 200%. Text, icon, padding and buttons move together. The chime stays off. No shell package: that path cost the panel and is not coming back. And the product name is gone from everything a teacher sees. The notification card is from Classroom, the disc player's from DVD. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bd55c5abd9
commit
ba8768cee5
@@ -39,6 +39,37 @@ gi.require_version("Gdk", "3.0")
|
||||
|
||||
from gi.repository import Gdk, GLib, Gtk # noqa: E402
|
||||
|
||||
# Where the card sits on the screen.
|
||||
#
|
||||
# A Wayland client cannot place its own window, and the card used to be
|
||||
# centred because KWin centres everything. On a classroom television that
|
||||
# puts it in the middle of whatever the room is watching, a long way from
|
||||
# the button that opened it. Layer shell is the compositor's own answer:
|
||||
# the surface is anchored to an edge and the compositor does the
|
||||
# arithmetic, so bottom-left is bottom-left at 1080p and at 4K, at 100%
|
||||
# and at 200%, with no coordinates written down anywhere.
|
||||
#
|
||||
# The classroom already draws the disc player's cover and control bar
|
||||
# this way under KWin, so the mechanism is one this room has run for
|
||||
# months rather than a new dependency.
|
||||
#
|
||||
# Optional on purpose. If the library is missing the card still opens,
|
||||
# centred, exactly as it did before: a teacher can always reach it.
|
||||
try:
|
||||
gi.require_version("GtkLayerShell", "0.1")
|
||||
from gi.repository import GtkLayerShell # noqa: E402
|
||||
except (ImportError, ValueError):
|
||||
GtkLayerShell = None
|
||||
|
||||
# Logical pixels, so they mean the same thing at every scale.
|
||||
#
|
||||
# The bar is 76 logical pixels tall and reserves that space, so the
|
||||
# compositor already keeps a bottom-anchored surface clear of it; this
|
||||
# gap is the breathing room above it. The left margin lines the card up
|
||||
# near the launcher that opened it rather than hard against the edge.
|
||||
CARD_GAP_BOTTOM = 16
|
||||
CARD_GAP_LEFT = 16
|
||||
|
||||
sys.path.insert(0, "/usr/local/lib/ekklesia-classroom")
|
||||
sys.path.insert(
|
||||
0,
|
||||
@@ -395,11 +426,7 @@ class ParentCall(Gtk.Window):
|
||||
self.set_titlebar(Gtk.Box())
|
||||
self.set_resizable(False)
|
||||
self.set_default_size(460, -1)
|
||||
# Deliberately not set_position(): a Wayland client cannot place
|
||||
# itself, and asking for the centre put the card in the top-left
|
||||
# corner instead. KWin centres it, because kwinrc already asks for
|
||||
# every window to be centred — no rule of its own is needed, and
|
||||
# the one that was written for it turned out to be doing nothing.
|
||||
self._anchor_bottom_left()
|
||||
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
|
||||
# Belt and braces with the window rule: whichever of the two KWin
|
||||
# honours, the card must not end up behind the lesson a teacher
|
||||
@@ -499,6 +526,44 @@ class ParentCall(Gtk.Window):
|
||||
self.get_screen(), css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
)
|
||||
|
||||
def _anchor_bottom_left(self):
|
||||
"""Put the card in the corner its launcher is in.
|
||||
|
||||
Anchored rather than positioned: the compositor is told which two
|
||||
edges to hold the surface against and works the rest out itself,
|
||||
so this is right on a 1080p television at 100% and on a 4K one at
|
||||
200% without either number appearing here.
|
||||
|
||||
Keyboard interactivity is the part that matters and the part that
|
||||
has to be asked for: a teacher types a child's name into this.
|
||||
"""
|
||||
if GtkLayerShell is None:
|
||||
return
|
||||
|
||||
try:
|
||||
GtkLayerShell.init_for_window(self)
|
||||
# TOP, not OVERLAY. The disc player's cover is an overlay and
|
||||
# is meant to be in front of everything including this; a
|
||||
# teacher fetching a parent during a film is not a reason to
|
||||
# put a text box over the film's own controls.
|
||||
GtkLayerShell.set_layer(self, GtkLayerShell.Layer.TOP)
|
||||
GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.BOTTOM, True)
|
||||
GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.LEFT, True)
|
||||
GtkLayerShell.set_margin(
|
||||
self, GtkLayerShell.Edge.BOTTOM, CARD_GAP_BOTTOM)
|
||||
GtkLayerShell.set_margin(
|
||||
self, GtkLayerShell.Edge.LEFT, CARD_GAP_LEFT)
|
||||
# Reserve nothing. The card is a moment, not furniture, and a
|
||||
# surface that reserved space would push the lesson around
|
||||
# every time somebody fetched a parent.
|
||||
GtkLayerShell.set_exclusive_zone(self, 0)
|
||||
GtkLayerShell.set_keyboard_interactivity(self, True)
|
||||
except Exception as error: # noqa: BLE001
|
||||
# Any failure here is cosmetic. A centred card a teacher can
|
||||
# type into beats a perfectly placed one that never opened.
|
||||
print(f"classroom-parent-call: layer shell unavailable: {error}",
|
||||
file=sys.stderr)
|
||||
|
||||
def _set_status(self, text, tone=None, detail=None):
|
||||
style = self.status.get_style_context()
|
||||
|
||||
@@ -520,7 +585,8 @@ class ParentCall(Gtk.Window):
|
||||
and self.broker_reachable
|
||||
and contract.clean_child(self.entry.get_text()) is not None
|
||||
and contract.availability(
|
||||
self.mode, self.coordinator_online, self.broker_reachable
|
||||
self.mode, self.coordinator_online, self.broker_reachable,
|
||||
assume_default=self.settled,
|
||||
)[0]
|
||||
)
|
||||
self.primary.set_sensitive(bool(ready))
|
||||
@@ -585,7 +651,8 @@ class ParentCall(Gtk.Window):
|
||||
return
|
||||
|
||||
can, why = contract.availability(
|
||||
self.mode, self.coordinator_online, self.broker_reachable
|
||||
self.mode, self.coordinator_online, self.broker_reachable,
|
||||
assume_default=final,
|
||||
)
|
||||
|
||||
if can:
|
||||
@@ -642,8 +709,11 @@ class ParentCall(Gtk.Window):
|
||||
self._set_status("Enter the child's name", tone="bad")
|
||||
return
|
||||
|
||||
# The teacher is pressing the button, so the retained topics have
|
||||
# long since had their chance.
|
||||
can, why = contract.availability(
|
||||
self.mode, self.coordinator_online, self.broker_reachable
|
||||
self.mode, self.coordinator_online, self.broker_reachable,
|
||||
assume_default=True,
|
||||
)
|
||||
|
||||
if not can:
|
||||
@@ -673,11 +743,16 @@ class ParentCall(Gtk.Window):
|
||||
|
||||
def _retained_deadline(self):
|
||||
# Retained topics arrive at once or not at all. Now that both have
|
||||
# had their chance, a negative is safe to show.
|
||||
# had their chance, a negative is safe to show, and a mode nobody
|
||||
# has ever set is the coordinator's default rather than a room
|
||||
# that was never configured.
|
||||
self._settle(final=True)
|
||||
|
||||
if not self.settled:
|
||||
self._unavailable("Parent Call has not been set up yet")
|
||||
# Only reachable if nothing at all came back from the
|
||||
# sanctuary end, which the wording should say rather than
|
||||
# blaming this room's setup.
|
||||
self._unavailable("No answer from the sanctuary")
|
||||
|
||||
return False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user