Files
Classroom_Desktop/config/plasma/lookandfeel/contents/layouts/org.kde.plasma.desktop-layout.js
T
ekklesiaandClaude Opus 5 a31fa75d55 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]>
2026-09-21 19:34:15 -05:00

229 lines
10 KiB
JavaScript

// The Ekklesia Classroom desktop.
//
// Plasma runs this once, when a session has no panel yet, and again on
// demand — `plasma-apply-lookandfeel -a org.ekklesia.classroom.desktop
// --resetLayout` rebuilds the whole desktop from this file. That is the
// classroom's recovery path for a damaged shell, and it is Plasma's own
// mechanism rather than something written here.
//
// One full-width bar along the bottom. Left is auxiliary, the middle is
// applications, the right is the small amount of status a teacher needs.
// Nothing else is on the screen.
// WHICH APPLICATIONS THIS DEVICE OFFERS.
//
// classroom-desktop-layout rewrites the three lines between the markers
// to match device.conf and this machine's hardware, and nothing else in
// this file. Each application is its own line because each is its own
// setting: a laptop taking children's church needs Wonder Ink and a tower
// used for Bible study does not, so the shape of the machine is never
// allowed to decide what it is for.
//
// All true here is the shipped default, which is what an existing
// classroom must come up as.
/* CLASSROOM-FEATURES-BEGIN */
var WONDERINK = true;
var DVD = true;
var PARENT_CALL = true;
/* CLASSROOM-FEATURES-END */
var WALLPAPER = "/usr/share/backgrounds/ekklesia/classroom-calm.png";
// Matches the 76px strip the classroom has always used, so muscle memory
// and the room's viewing distance both carry over.
var PANEL_HEIGHT = 76;
// The idle desktop: the wallpaper, and nothing on it.
var screens = desktops();
for (var i = 0; i < screens.length; i++) {
var desktop = screens[i];
desktop.wallpaperPlugin = "org.kde.image";
desktop.currentConfigGroup = ["Wallpaper", "org.kde.image", "General"];
desktop.writeConfig("Image", "file://" + WALLPAPER);
desktop.writeConfig("SlidePaths", "");
// 2 is "scaled and cropped": the landscape fills the display at any
// aspect ratio without being stretched out of shape.
desktop.writeConfig("FillMode", 2);
desktop.reloadConfig();
}
var panel = new Panel();
panel.location = "bottom";
panel.height = PANEL_HEIGHT;
panel.hiding = "none";
panel.floating = false;
panel.lengthMode = "fill";
panel.alignment = "center";
panel.opacity = "adaptive";
// LEFT — auxiliary. Parent Call is not one of the room's applications and
// is deliberately not in the row with them.
// Quick Launch: a launcher, and only a launcher.
//
// Two other widgets were tried on a real panel. The Icon widget copies
// the desktop entry into the user's home and uses the copy, so an update
// could never change the icon or the command — it kept showing a bell out
// of a package that no longer contained one. A task manager reads
// /usr/share/applications live, which fixes that, but a task manager also
// shows every running window, so the auxiliary corner filled up with the
// lesson's tasks.
//
// Quick Launch reads the entry live and shows nothing but the button.
if (PARENT_CALL) {
var parentCall = panel.addWidget("org.kde.plasma.quicklaunch");
parentCall.currentConfigGroup = ["General"];
// A file URL, not applications:. Quick Launch resolves the entry
// itself and does not copy it, so the path is a live reference to
// what the package installs.
parentCall.writeConfig(
"launcherUrls",
"file:///usr/share/applications/ekklesia-parent-call.desktop"
);
parentCall.writeConfig("showLauncherNames", false);
parentCall.writeConfig("enablePopup", false);
parentCall.writeConfig("maxSectionCount", 1);
}
var leftGap = panel.addWidget("org.kde.plasma.panelspacer");
leftGap.currentConfigGroup = ["General"];
leftGap.writeConfig("expanding", true);
// CENTRE — the applications, and the whole of the running/minimised/active
// story. This applet is the reason the classroom stops writing a dock.
var tasks = panel.addWidget("org.kde.plasma.icontasks");
tasks.currentConfigGroup = ["General"];
// THE LAUNCHERS, IN ONE FIXED ORDER.
//
// The order is written here and nowhere else, so the same set of enabled
// applications produces the same bar on every machine in the church.
// Anything switched off is left out of the list rather than blanked, so
// the remaining launchers close up and there is never a gap where a
// disabled application used to be.
//
// The browser is always present. Wonder Ink and the disc player are
// answered above.
var launchers = [];
if (WONDERINK) {
launchers.push("applications:ekklesia-wonderink.desktop");
}
launchers.push("applications:google-chrome.desktop");
if (DVD) {
launchers.push("applications:ekklesia-dvd.desktop");
}
tasks.writeConfig("launchers", launchers.join(","));
// THE ORDER STAYS PUT WHEN AN APPLICATION OPENS.
//
// 1 is "manually", and it has to be 1. This is not a preference; it is
// the only value at which the Icons-Only Task Manager keeps a running
// application in its pinned position.
//
// 0.2.4 set this to 0 to stop teachers dragging the launchers about, and
// that worked — and broke the order instead, which is the bug this
// replaces. Two lines of the stock applet explain the whole thing:
//
// main.qml:153 hideActivatedLaunchers: tasks.iconsOnly || ...
// main.qml:155 launchInPlace: tasks.iconsOnly && sortingStrategy === 1
//
// For an icons-only bar the first is ALWAYS true: the moment an
// application starts, its launcher is hidden. The second is what puts the
// running window back in the launcher's place — and only at 1. At 0 the
// launcher vanished and the window was appended after the others, so
// opening the browser moved it behind the disc player. Reproduced in the
// lab with a real Chrome window, and fixed by this line; both measured.
//
// The cost is that manual sorting is also the only mode in which
// MouseHandler.qml accepts a reordering drop, so a teacher can drag the
// launchers again within a session. There is no value that gives both,
// and no Kiosk key that separates them. classroom-desktop-reset rebuilds
// this bar from the package at every login, so a drag never outlives the
// session, and docs/testing.md records the trade rather than hiding it.
tasks.writeConfig("sortingStrategy", 1);
// Launchers stay together at the start of the row and running windows
// appear after them, so opening or closing an application cannot move a
// pinned icon. This is Plasma's default; it is written down because the
// classroom depends on it.
tasks.writeConfig("separateLaunchers", true);
// Every window gets its own button. Teachers should never have to open a
// group to find out that a second window exists.
tasks.writeConfig("groupingStrategy", 0);
tasks.writeConfig("showOnlyCurrentDesktop", false);
tasks.writeConfig("showOnlyCurrentActivity", false);
tasks.writeConfig("showOnlyCurrentScreen", false);
// Clicking a button always means "show me this", and never "hide it".
//
// This was measured before it was decided. Plasma 6.3 draws a minimised
// task and a task that is merely behind another window identically: no
// indicator for closed, a grey indicator for running, a teal one for
// active — three marks for four states. Rather than write a task manager
// that draws a fourth, the classroom removes the state. There is nothing
// on the idle desktop for a teacher to want to see, so minimising an
// application in this room has no purpose and only ever loses it.
//
// A window minimised some other way is still shown as running and comes
// back with one click, so it can never be lost even then.
tasks.writeConfig("minimizeActiveTaskOnClick", false);
tasks.writeConfig("fill", false);
tasks.writeConfig("maxStripes", 1);
tasks.writeConfig("indicateAudioStreams", false);
tasks.writeConfig("wheelEnabled", false);
tasks.writeConfig("middleClickAction", "NewInstance");
tasks.writeConfig("showToolTips", true);
tasks.writeConfig("iconSpacing", 2);
var rightGap = panel.addWidget("org.kde.plasma.panelspacer");
rightGap.currentConfigGroup = ["General"];
rightGap.writeConfig("expanding", true);
// RIGHT — status.
//
// Three ordinary applets straight on the bar, and no system tray around
// them. A tray is a container that discovers whatever is installed and
// then hides most of it behind an arrow; measured in the lab it collected
// battery, brightness, clipboard, keyboard layout, camera, vaults and
// screen management on a machine that has none of those to say anything
// about. Naming the four the classroom actually uses is both smaller and
// deterministic, and it leaves nothing for a teacher to expand.
panel.addWidget("org.kde.plasma.networkmanagement");
var volume = panel.addWidget("org.kde.plasma.volume");
volume.currentConfigGroup = ["General"];
// No chime when the volume changes. A classroom is often listening to
// something when somebody adjusts it, and a beep over a lesson is worse
// than no feedback at all — the on-screen display is the feedback, and it
// is in the look and feel package where a television can read it.
//
// Plasma 6 moved this setting out of the applet and into a global one,
// and the applet migrates a false written here into it on first run. So
// this is both the setting and the instruction to carry it across.
volume.writeConfig("volumeFeedback", false);
volume.writeConfig("volumeOsd", true);
volume.writeConfig("muteOsd", true);
volume.writeConfig("outputChangeOsd", true);
// A teacher pressing the volume key expects a step they can hear.
volume.writeConfig("volumeStep", 5);
panel.addWidget("org.kde.plasma.notifications");
var clock = panel.addWidget("org.kde.plasma.digitalclock");
clock.currentConfigGroup = ["Appearance"];
clock.writeConfig("showDate", true);
clock.writeConfig("dateFormat", "custom");
clock.writeConfig("customDateFormat", "ddd d MMM");
clock.writeConfig("showSeconds", 0);
clock.writeConfig("dateDisplayFormat", "BelowTime");
// Sized by the bar, not by a number written here.
//
// It used to be pinned at 15 points to stop it becoming the largest text
// in the room. On a 4K television that was a clock a teacher could not
// read from the back of the room, and it was the one thing in the
// classroom that did not follow UI_SCALE, because a point size written
// into a layout is the same point size at every scale. Plasma's own
// sizing follows the bar, and the bar follows the scale.
clock.writeConfig("autoFontAndSize", true);
clock.writeConfig("fontWeight", 500);