Files
Classroom_Desktop/config/plasma/lookandfeel/contents/layouts/org.kde.plasma.desktop-layout.js
T
ekklesiaandClaude Opus 5 55f4c183c7 feat: Presentations, for a teacher who downloaded tonight's session
PRESENTATIONS_ENABLED, off by default, independent of everything else. A
teacher downloads a session in ordinary Chrome and it is on the
Presentations screen a few seconds later, ready for the television. No
file manager, no import button, no Impress.

WHY NOTHING LAUNCHES A DOCUMENT APPLICATION. The brief was to use
existing presentation software, so the existing software was tried
first, on Debian 13, before any of this was designed. `soffice --show`
does start a real fullscreen slideshow — and needs XDG_SESSION_TYPE plus
a VCL plugin or it dies with "Failed to open display", shows a splash,
opened a "Tip of the Day: 1/225" dialog over the slides, and leaves the
Impress editor when the show ends. `okular --presentation` opens THREE
windows: the presentation, the ordinary document window behind it, and
an information dialog. zathura is not a presenter. pdfpc is the closest
and is another dependency with a presenter-console model. Every one of
them leaves an application on screen that can open any file on the
machine, and none of them knows there is a library to go back to.

So LibreOffice converts once, headless, at import, with no window on any
screen; poppler renders the pages; the classroom shows the pictures.
Measured: 0.34s for a ten-slide PPTX, 0.03s for its ten thumbnails,
0.10s for one slide at 1920px. That is not a new renderer — it is the
same engine Impress would have used live — and it means Escape is ours,
presenting is instant, and a PDF exported from Google Slides skips the
conversion entirely and keeps the appearance Google gave it.

THE THREE THINGS KEPT APART. The library thumbnail, the starting slide
and the retention clock are independent, because the first slide of a
Bible study session is a generic opening shared by every session and the
one that names it is usually the second. Quick Look chooses the picture;
it does not change where the slideshow starts and it does not count as
having presented anything. mark_presented() is called when a slide is
actually drawn, not when Present is pressed: an intention is not a
presentation, and a viewer that failed to start must not buy another
thirty days.

IMPORTING. The teacher's download is copied, never moved, renamed or
deleted. A file with a .crdownload beside it is still arriving. Wonder
Ink's own folder is not looked at. Two downloads of the same name are
two presentations, the second titled (2) — found in the lab, where a PDF
and a PPTX of one session both came out with the same name because the
import being named was excluded from the taken-titles set BY TITLE, and
a set holds one copy of a repeated string.

Metadata is written saying "importing" before any work, so a reboot
mid-conversion leaves a directory the next startup sweeps and imports
again. A file that cannot be converted gets a card saying so, offering
only Remove, with the teacher's download untouched.

Retention: 180 days never presented, 30 days from the last presentation,
every presentation starting the thirty over. The cleanup never touches a
presentation being shown, never leaves the library directory, and never
looks at Downloads.

The dock order is unchanged: Presentations is appended after the disc
player, so Wonder Ink, the browser and the player keep the places
teachers have been reaching for. There is a test for exactly that.

classroom-wonderink now asks feature() rather than carrying its own copy
of the default, so the panel and the command cannot disagree.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-22 20:21:19 -05:00

264 lines
12 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 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.
//
// The first three are true here, which is what an existing classroom must
// come up as. PRESENTATIONS is false, which is what an existing classroom
// must come up as too: no room has ever had it, and an upgrade may not
// put a button on a bar nobody asked for.
/* CLASSROOM-FEATURES-BEGIN */
var WONDERINK = true;
var DVD = true;
var PARENT_CALL = true;
var PRESENTATIONS = false;
/* 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);
}
// HELP, beside Parent Call and not in the row with the applications.
//
// Every room gets it: the manual is the one thing that is never optional,
// because a teacher who needs it is by definition a teacher who does not
// know where to look. It shows only the pages for the features THIS
// device has — see classroom-manual — so the button means the same thing
// in every room while what it opens does not.
//
// On the left, with Parent Call, for the same reason Parent Call is
// there: it is not one of the room's applications, and putting it in the
// middle row would move buttons teachers reach for without looking.
var help = panel.addWidget("org.kde.plasma.quicklaunch");
help.currentConfigGroup = ["General"];
help.writeConfig(
"launcherUrls",
"file:///usr/share/applications/ekklesia-manual.desktop"
);
help.writeConfig("showLauncherNames", false);
help.writeConfig("enablePopup", false);
help.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");
}
// LAST, AND THAT IS THE WHOLE REASON IT IS HERE.
//
// Every existing room has Wonder Ink, the browser and the disc player in
// that order, and teachers have been pressing them in that order for
// months. Appending keeps all three exactly where they are on every
// machine that upgrades; putting Presentations anywhere else in the row
// would move a button somebody reaches for without looking.
if (PRESENTATIONS) {
launchers.push("applications:ekklesia-presentations.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);