fix: the bar keeps its order when an application opens
On the laptop the browser jumped behind the disc player the moment it was
opened. 0.2.4 caused that, and the cause is two lines of the stock applet:
main.qml:153 hideActivatedLaunchers: tasks.iconsOnly || ...
main.qml:155 launchInPlace: tasks.iconsOnly && sortingStrategy === 1
For an icons-only bar the first is always true — a launcher is hidden the
instant its application starts — and the second is what puts the running
window back where the launcher was. It only does that at sortingStrategy
1. 0.2.4 set it to 0 to stop teachers dragging the icons about, so the
launcher vanished and the window was appended after the others.
Reproduced in the lab with a real Chrome window before changing anything,
and the bar read off the screen: Wonder Ink, the player, then the
browser. With 1 it is Wonder Ink, the browser, the player, through
opening, a second window, another application opening beside it, and
every close.
The launcher identity was suspected and cleared: only one browser button
appears, because google-chrome.desktop already carries Chrome's own file
name and StartupWMClass for exactly this reason.
THE COST, AND IT IS REAL. Manual sorting is also the only mode in which
MouseHandler.qml accepts a reordering drop, so a teacher who knows to drag
can rearrange the bar again. There is no value that gives both and no
Kiosk key that separates them. classroom-desktop-reset already rebuilds
the bar from the package at every login, so a drag never outlives the
session. docs/testing.md states the trade rather than burying it, and the
configuration escape is written down there too as still open.
tools/lab/order-check is the test this needed and did not have. 0.2.4
passed a full set of configuration assertions while the bar did something
else, so this one opens real application windows and reads the panel
strip as pixels — there is no image library in the lab, so it captures
PPM and names each icon by colour. Six checks, all of them behaviour.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0fa26f398d
commit
a31fa75d55
@@ -201,10 +201,20 @@ else:
|
||||
ink_laptop and ink_laptop["config"].get(LAUNCH) == f"{INK},{WEB},{DISC}",
|
||||
(ink_laptop or {}).get("config", {}).get(LAUNCH, why))
|
||||
|
||||
print("6. the bar cannot be rearranged by dragging")
|
||||
check("sorting is off, which is what refuses a reordering drop",
|
||||
print("6. the order survives an application opening")
|
||||
# 1, "manually", is the ONLY value at which the Icons-Only Task
|
||||
# Manager keeps a running application in its pinned position:
|
||||
# main.qml gates launchInPlace on sortingStrategy === 1, and for an
|
||||
# icons-only bar the launcher is always hidden once its application
|
||||
# starts. 0.2.4 set this to 0 and the browser jumped behind the disc
|
||||
# player the moment it opened.
|
||||
#
|
||||
# This assertion is the mechanism. The behaviour itself is checked
|
||||
# against real windows by tools/lab/order-check, because a
|
||||
# configuration test is exactly what 0.2.4 passed.
|
||||
check("sorting is manual, which is what keeps a launcher in place",
|
||||
every and every["config"].get(
|
||||
"org.kde.plasma.icontasks.sortingStrategy") == "0",
|
||||
"org.kde.plasma.icontasks.sortingStrategy") == "1",
|
||||
str((every or {}).get("config", {}).get(
|
||||
"org.kde.plasma.icontasks.sortingStrategy")))
|
||||
check("and launchers stay ahead of running windows",
|
||||
|
||||
Executable
+153
@@ -0,0 +1,153 @@
|
||||
#!/bin/bash
|
||||
# Moving an installer-configured Wi-Fi out of ifupdown's hands.
|
||||
#
|
||||
# The Bible study laptop had working internet and an applet that said
|
||||
# there was no connection, because Debian's installer had written the
|
||||
# Wi-Fi into /etc/network/interfaces and NetworkManager marks an
|
||||
# interface named there UNMANAGED. This is the parsing half of the fix:
|
||||
# find that stanza, get the network out of it, and comment out exactly
|
||||
# it and nothing else.
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
PYTHONPATH="$ROOT/lib" python3 - "$ROOT" <<'PY'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(sys.argv[1])
|
||||
sys.path.insert(0, str(root / "lib"))
|
||||
|
||||
from classroom_network import comment_out, credentials, parse, wireless
|
||||
|
||||
passed = failed = 0
|
||||
|
||||
|
||||
def check(label, condition, detail=""):
|
||||
global passed, failed
|
||||
if condition:
|
||||
print(f" ok {label}")
|
||||
passed += 1
|
||||
else:
|
||||
print(f" FAIL {label}")
|
||||
if detail:
|
||||
print(f" {detail}")
|
||||
failed += 1
|
||||
|
||||
|
||||
# The shape Debian's installer writes when it is given a wireless network
|
||||
# during installation. Measured against the laptop: /etc/network/interfaces
|
||||
# is 0600 root:root, wpa-ssid and wpa-psk are inline, and the
|
||||
# wpa_supplicant it starts has no -c config file, which is what tells you
|
||||
# ifupdown fed it these values rather than a wpa_supplicant.conf.
|
||||
LAPTOP = """# This file describes the network interfaces available on your system
|
||||
source /etc/network/interfaces.d/*
|
||||
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
|
||||
allow-hotplug wlp2s0
|
||||
iface wlp2s0 inet dhcp
|
||||
\twpa-ssid Sanctuary
|
||||
\twpa-psk a-real-secret
|
||||
"""
|
||||
|
||||
# A classroom tower: static Ethernet an administrator put under
|
||||
# ifupdown's control on purpose. Nothing here may touch it.
|
||||
TOWER = """source /etc/network/interfaces.d/*
|
||||
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
|
||||
auto enp1s0
|
||||
iface enp1s0 inet static
|
||||
\taddress 10.40.0.55/24
|
||||
\tgateway 10.40.0.1
|
||||
"""
|
||||
|
||||
|
||||
def wireless_names(*names):
|
||||
return lambda device: device in names
|
||||
|
||||
|
||||
print("1. the laptop's own file")
|
||||
stanzas = parse(LAPTOP)
|
||||
check("every iface stanza is found",
|
||||
[s["name"] for s in stanzas] == ["lo", "wlp2s0"],
|
||||
str([s["name"] for s in stanzas]))
|
||||
found = wireless(stanzas, wireless_names("wlp2s0"))
|
||||
check("the wireless one is picked out", len(found) == 1)
|
||||
check("and it is not the loopback", found and found[0]["name"] == "wlp2s0")
|
||||
|
||||
ssid, psk = credentials(found[0])
|
||||
check("the network name is read", ssid == "Sanctuary", repr(ssid))
|
||||
check("and the password", psk == "a-real-secret", repr(psk))
|
||||
|
||||
print("2. a tower is left completely alone")
|
||||
tower = parse(TOWER)
|
||||
check("its Ethernet is not mistaken for wireless",
|
||||
wireless(tower, wireless_names("wlp2s0")) == [])
|
||||
check("even though it has a static stanza ifupdown owns",
|
||||
[s["name"] for s in tower] == ["lo", "enp1s0"])
|
||||
# managed=false stays because of this machine, so nothing may change here.
|
||||
check("commenting out is never reached for it",
|
||||
not wireless(tower, lambda d: False))
|
||||
|
||||
print("3. only the wireless stanza is commented out")
|
||||
after = comment_out(LAPTOP, found[0])
|
||||
check("the iface line is commented",
|
||||
"# ekklesia-classroom: iface wlp2s0" in after, after)
|
||||
check("its options are commented with it",
|
||||
"# ekklesia-classroom: \twpa-ssid" in after)
|
||||
check("the allow-hotplug above it goes too, or ifupdown asks for an "
|
||||
"interface with no stanza",
|
||||
"# ekklesia-classroom: allow-hotplug wlp2s0" in after)
|
||||
check("the loopback is untouched",
|
||||
"\niface lo inet loopback\n" in after)
|
||||
check("and so is the source line",
|
||||
"\nsource /etc/network/interfaces.d/*\n" in after)
|
||||
check("the password is still in the file, not deleted",
|
||||
"a-real-secret" in after,
|
||||
"the original must remain recoverable until rollback is impossible")
|
||||
|
||||
print("4. doing it twice changes nothing the second time")
|
||||
twice = comment_out(after, parse(after)[0] if parse(after) else found[0])
|
||||
check("already-commented lines are not commented again",
|
||||
"# ekklesia-classroom: # ekklesia-classroom:" not in twice)
|
||||
|
||||
print("5. a stanza this must refuse to move")
|
||||
# Pointing at a wpa_supplicant.conf means the credentials are not in this
|
||||
# file, so there is nothing here to recreate and guessing would be wrong.
|
||||
CONF = """allow-hotplug wlan0
|
||||
iface wlan0 inet dhcp
|
||||
\twpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
|
||||
"""
|
||||
stanza = wireless(parse(CONF), wireless_names("wlan0"))[0]
|
||||
check("a wpa-conf stanza yields no SSID, so the caller stops",
|
||||
credentials(stanza) == (None, None), str(credentials(stanza)))
|
||||
|
||||
print("6. a renamed or absent device is still recognised")
|
||||
# The machine is asked whether a device is wireless, but a wpa-ssid is
|
||||
# evidence on its own — a device that has been renamed between kernels
|
||||
# would otherwise be invisible.
|
||||
check("a wpa-ssid alone is enough",
|
||||
len(wireless(parse(LAPTOP), lambda d: False)) == 1)
|
||||
|
||||
print("7. the command itself")
|
||||
wifi = (root / "scripts/classroom-wifi").read_text()
|
||||
check("--adopt exists and has a dry run", "--adopt" in wifi and "--dry-run" in wifi)
|
||||
check("it keeps the original file before changing it",
|
||||
"before-ekklesia-classroom" in wifi)
|
||||
check("it verifies the room is really online, not just that NM says so",
|
||||
"curl" in wifi and "connectivity" in wifi)
|
||||
check("it rolls back to ifupdown if the network does not come back",
|
||||
"rolling back" in wifi and "ifup" in wifi)
|
||||
check("it never prints the password",
|
||||
"psk}" not in wifi and "{psk" not in wifi)
|
||||
check("the connection it creates is still a system one",
|
||||
'"connection.permissions"' not in wifi)
|
||||
|
||||
print()
|
||||
print(f"{passed} passed, {failed} failed")
|
||||
raise SystemExit(1 if failed else 0)
|
||||
PY
|
||||
Reference in New Issue
Block a user