Ctrl+Alt+Delete opened Plasma's session screen and a teacher hibernated a classroom computer from it. A hibernated room cannot be recovered by anybody standing in front of it, which on a Sunday morning is everybody. logout=false was already set and did not stop it. That restriction governs Plasma's own actions — what plasmashell draws in a menu or an applet. The chord is a KGlobalAccel shortcut registered by ksmserver and it asks D-Bus for the greeter without consulting a Plasma action at all. A restriction on a menu cannot see a key. Three layers, because each one alone has a way past it. The key. kglobalshortcutsrc unbinds every ksmserver session action and PowerDevil's Sleep, Hibernate, PowerDown and PowerOff, each entry marked immutable. It is deliberately not in the xdg directory with the other settings: kglobalacceld opens that file as a SimpleConfig, so it reads one file in the teacher's home and no system directory at all. Measured — with it in the cascade, kreadconfig6 reported the chord unbound while the shortcut server went on answering Ctrl+Alt+Del. classroom-desktop-reset copies the package's copy into place before the shortcut server starts, the same way the panel is copied out of the package at every login. The policy. A polkit rule refuses login1.suspend* and .hibernate* for the classroom user and for nobody else. This is the layer that matters: a key can be unbound and anything reaching logind can still ask. Refusing here rather than masking sleep.target keeps a technician's own account working, and is also what tells Plasma to stop drawing the button — CanSuspend and CanHibernate come back no. Hybrid sleep and suspend-then-hibernate have no action of their own in systemd 257; both are authorised through hibernate, so denying it denies all three. The greeter. Its D-Bus activation is shadowed with /bin/false, the same mechanism already used for the old notification daemon. Layer one removes the caller, this removes the callee. Measured in the lab, from the package, after a reboot: the escape reproduced first with the shadow removed — the greeter really does start — and then refused, exited 1, with it back. The chord reports nothing bound. systemctl suspend as the classroom user is denied. The maintenance chord and the display chord both still answer, by key code, after the shortcut file is replaced wholesale. A root process may still suspend, hibernate, reboot and power off. 182 checks pass across a logout and back. Screen locking stays disabled, the power button still does nothing, and reboot and shut down are not newly denied to anybody. Co-Authored-By: Claude Opus 5 <[email protected]>
231 lines
9.6 KiB
Bash
Executable File
231 lines
9.6 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# Make the desktop come from the package, every time.
|
|
#
|
|
# Plasma builds the panel from the classroom's layout script whenever it
|
|
# starts with no panel configuration, and keeps whatever it built after
|
|
# that. That means an update could change the bar in the package and
|
|
# never change it in a room — which is exactly what happened with an icon
|
|
# that outlived the file it came from.
|
|
#
|
|
# So the configuration is removed before the shell starts and the bar is
|
|
# rebuilt from the layout at every login. The desktop becomes stateless:
|
|
# there is nothing in a teacher's home that an update cannot reach, and a
|
|
# panel damaged somehow is repaired by the next login rather than by a
|
|
# technician.
|
|
#
|
|
# Nothing is lost by this. The panel is immutable to teachers by Kiosk
|
|
# policy, so there is never anything of theirs in that file.
|
|
#
|
|
# This runs before plasmashell, as a user service ordered Before it.
|
|
|
|
CLASSROOM_XDG=/etc/ekklesia-classroom/xdg
|
|
CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
|
|
|
|
say() {
|
|
echo "classroom-desktop-reset: $1"
|
|
}
|
|
|
|
# THE CASCADE.
|
|
#
|
|
# Everything below this line is pointless if Plasma is not reading the
|
|
# classroom's settings directory at all: without it the room gets stock
|
|
# Plasma, no Kiosk restrictions and a lock screen. The session environment
|
|
# is set by /etc/xdg/plasma-workspace/env/zz-ekklesia-classroom.sh, which
|
|
# is the mechanism Plasma itself provides. This is the assertion, not the
|
|
# mechanism: if the variable arrives here wrong, say so out loud and fix it
|
|
# for the shell that is about to start rather than build a wrong desktop
|
|
# quietly.
|
|
case ":${XDG_CONFIG_DIRS:-}:" in
|
|
*:"$CLASSROOM_XDG":*)
|
|
;;
|
|
*)
|
|
say "XDG_CONFIG_DIRS did not contain $CLASSROOM_XDG — repairing it"
|
|
export XDG_CONFIG_DIRS="$CLASSROOM_XDG:${XDG_CONFIG_DIRS:-/etc/xdg}"
|
|
systemctl --user set-environment \
|
|
"XDG_CONFIG_DIRS=$XDG_CONFIG_DIRS" 2>/dev/null || true
|
|
;;
|
|
esac
|
|
|
|
# FOREIGN APPEARANCE STATE.
|
|
#
|
|
# A classroom machine that has been through another desktop arrives with a
|
|
# profile that answers questions the package means to answer. Two places
|
|
# sit ahead of the classroom's own directory in KConfig's cascade:
|
|
#
|
|
# ~/.config/kdedefaults what the last look and feel applied wrote.
|
|
# Plasma also treats it as the record of which
|
|
# look and feel is in force: remove it and the
|
|
# next session applies ours again from scratch,
|
|
# which resets the palette to the package
|
|
# default. That is right for a machine arriving
|
|
# from another desktop and wrong for a room that
|
|
# chose the light theme, so whose it is decides.
|
|
#
|
|
# ~/.config/kdeglobals the user's own file, which beats every system
|
|
# directory. plasma-apply-colorscheme expands a
|
|
# whole palette into it, so a light Breeze scheme
|
|
# from a previous desktop would keep the room
|
|
# light no matter what the package said.
|
|
#
|
|
# The colour scheme is the one piece of appearance a room is allowed to
|
|
# choose, through classroom-theme, so an Ekklesia scheme is kept and
|
|
# anything else is removed. This is not a wipe of the home directory: two
|
|
# named pieces of appearance state, neither of them a teacher's work.
|
|
|
|
# The [General] ColorScheme of a KConfig file, or nothing.
|
|
scheme_of() {
|
|
awk -F= '
|
|
/^\[/ { general = ($0 == "[General]"); next }
|
|
general && $1 == "ColorScheme" { print $2; exit }
|
|
' "$1" 2>/dev/null || true
|
|
}
|
|
|
|
defaults_are_ours=no
|
|
|
|
if [[ -d "$CONFIG_HOME/kdedefaults" ]]; then
|
|
case "$(scheme_of "$CONFIG_HOME/kdedefaults/kdeglobals")" in
|
|
Ekklesia*)
|
|
defaults_are_ours=yes
|
|
;;
|
|
*)
|
|
rm -rf "$CONFIG_HOME/kdedefaults"
|
|
say "removed another desktop's look and feel defaults"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
if [[ -f "$CONFIG_HOME/kdeglobals" ]]; then
|
|
scheme=$(scheme_of "$CONFIG_HOME/kdeglobals")
|
|
|
|
case "$scheme" in
|
|
Ekklesia*)
|
|
say "keeping the room's colour scheme ($scheme)"
|
|
;;
|
|
"")
|
|
# No scheme named. That is what Plasma leaves behind after it
|
|
# applies a look and feel — it writes a hash instead — so the
|
|
# palette belongs to whichever look and feel last ran, and the
|
|
# directory above has just said whose that was.
|
|
if [[ "$defaults_are_ours" == yes ]]; then
|
|
say "keeping the palette the classroom's look and feel left"
|
|
else
|
|
rm -f "$CONFIG_HOME/kdeglobals"
|
|
say "removed an unnamed palette from another desktop"
|
|
fi
|
|
;;
|
|
*)
|
|
rm -f "$CONFIG_HOME/kdeglobals"
|
|
say "removed a foreign palette ($scheme); the package supplies one"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# THE KEYS THAT END A SESSION.
|
|
#
|
|
# Ctrl+Alt+Delete opened Plasma's session screen in a real classroom and a
|
|
# teacher hibernated the machine from it. The chord is a KGlobalAccel
|
|
# shortcut, and KGlobalAccel is the one part of Plasma the classroom
|
|
# cannot reach through KConfig's cascade: kglobalacceld opens
|
|
# kglobalshortcutsrc as a SimpleConfig, so it reads this one file in the
|
|
# teacher's home and no system directory at all. Measured — with the
|
|
# settings in /etc/ekklesia-classroom/xdg, kreadconfig6 reported the chord
|
|
# unbound while the shortcut server went on answering Ctrl+Alt+Del.
|
|
#
|
|
# So the package's copy is put there directly, before the shortcut server
|
|
# starts, exactly as the panel is. Nothing of a teacher's is lost: there
|
|
# is no settings application in this room, and KGlobalAccel puts every
|
|
# shortcut that is not named in the file back from its own defaults when
|
|
# each component registers. The entries the package does name are marked
|
|
# immutable, which is what stops it putting those back too.
|
|
SHORTCUTS=/etc/ekklesia-classroom/kglobalshortcutsrc
|
|
|
|
if [[ -r "$SHORTCUTS" ]]; then
|
|
if cmp -s "$SHORTCUTS" "$CONFIG_HOME/kglobalshortcutsrc"; then
|
|
say "session shortcuts already the package's"
|
|
else
|
|
install -m 600 "$SHORTCUTS" "$CONFIG_HOME/kglobalshortcutsrc"
|
|
say "session shortcuts replaced with the package's"
|
|
fi
|
|
else
|
|
say "WARNING: $SHORTCUTS is missing — Ctrl+Alt+Delete may open the"
|
|
say " session screen this room is not supposed to have"
|
|
fi
|
|
|
|
# The room's power behaviour is policy, not preference: never sleep, never
|
|
# dim, never lock. PowerDevil loads an idle action when it finds a group
|
|
# for it, and the merged view of this file is where such a group could
|
|
# come from, so the user's copy goes at every login. Nothing of a
|
|
# teacher's is in it — there is no settings application in this room.
|
|
if [[ -f "$CONFIG_HOME/powermanagementprofilesrc" ]]; then
|
|
rm -f "$CONFIG_HOME/powermanagementprofilesrc"
|
|
say "cleared the user's power settings; the package supplies them"
|
|
fi
|
|
|
|
# THE DISC PLAYER, AND WHETHER THIS MACHINE HAS ONE.
|
|
#
|
|
# The panel about to be built comes from the packaged layout, and the one
|
|
# line in it that depends on hardware is written by
|
|
# ekklesia-classroom-layout.service before the display manager starts.
|
|
# This cannot write /usr and does not try. It checks, because a room
|
|
# showing a disc player it has no drive for is a teacher clicking a button
|
|
# that can only disappoint, and the journal should say so at the moment
|
|
# the panel is built rather than after somebody notices.
|
|
LAYOUT=/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/layouts/org.kde.plasma.desktop-layout.js
|
|
|
|
if [[ -r "$LAYOUT" ]]; then
|
|
if compgen -G "/dev/sr[0-9]*" >/dev/null; then
|
|
drive=yes
|
|
else
|
|
drive=no
|
|
fi
|
|
|
|
if grep -q '^\s*/\* CLASSROOM-DVD-LAUNCHER \*/' "$LAYOUT"; then
|
|
offered=yes
|
|
else
|
|
offered=no
|
|
fi
|
|
|
|
if [[ "$drive" != "$offered" ]]; then
|
|
say "WARNING: this machine has drive=$drive but the layout offers"
|
|
say " the disc player=$offered — run classroom-desktop-layout"
|
|
fi
|
|
fi
|
|
|
|
CONFIG="$CONFIG_HOME/plasma-org.kde.plasma.desktop-appletsrc"
|
|
|
|
if [[ -e "$CONFIG" ]]; then
|
|
rm -f "$CONFIG"
|
|
say "panel will be rebuilt from the layout"
|
|
else
|
|
say "no panel configuration to clear"
|
|
fi
|
|
|
|
# A shell package the classroom used to select.
|
|
#
|
|
# One build chose a shell package of its own so that it could replace the
|
|
# volume display. With one selected, plasmashell builds no panel at all —
|
|
# and this script deletes the panel at every login, so the room came up
|
|
# with an empty screen. The package no longer ships it; this removes the
|
|
# choice from a profile that was written while it did.
|
|
if [[ -f "$CONFIG_HOME/plasmashellrc" ]] \
|
|
&& grep -q "^ShellPackage=" "$CONFIG_HOME/plasmashellrc" 2>/dev/null; then
|
|
sed -i "/^ShellPackage=/d" "$CONFIG_HOME/plasmashellrc"
|
|
say "removed a shell package choice that leaves the room with no panel"
|
|
fi
|
|
|
|
# The display, like the panel, comes from the package rather than from
|
|
# whatever the room last negotiated. KWin remembers a resolution and a
|
|
# scale per output in this file, and a television that once came up in the
|
|
# wrong mode keeps it for good — the scale service applies UI_SCALE again
|
|
# a moment after this, so there is nothing here to preserve. Seen in the
|
|
# lab: an output that had been through two scale changes stayed at a
|
|
# resolution nothing on the machine had asked for.
|
|
rm -f "$CONFIG_HOME/kwinoutputconfig.json" 2>/dev/null || true
|
|
|
|
# The icon cache remembers a name it once failed to find, which outlives
|
|
# the package that later supplies it.
|
|
rm -f "$CACHE_HOME/icon-cache.kcache" 2>/dev/null || true
|