On a 4 GB VM the kernel killed the import service mid-conversion. Measured: LibreOffice needs 0.2 GB for a text deck and 1.4-2.1 GB for decks of many different photographs, for about twenty seconds — a spike, not a leak. What made it a problem was the rest: OOMPolicy=stop took the watcher down with LibreOffice, it restarted and resumed the same conversion with no limit, and the library said "Getting this ready" throughout. The service is capped at 40% of memory, is first in line when memory runs short everywhere, and survives a killed LibreOffice (OOMPolicy=continue). A killed conversion is a failed import of kind "memory"; an import interrupted twice is not tried a third time; the card says the computer needed more memory, to close other programs and choose Try again; an import nobody has worked on for 20 minutes shows the same card; and an exclusive lock keeps it to one import at a time. Lab, the real service and LibreOffice: at a 4 GB-machine ceiling the 2.1 GB deck still imported; with no swap and a 300 MB ceiling LibreOffice was killed, the watcher kept its PID and the card offered Try again, which worked; the whole service killed during two attempts gave up after the second; three decks at once never ran two LibreOffices together. Co-Authored-By: Claude Opus 5.5 <[email protected]>
283 lines
13 KiB
Markdown
283 lines
13 KiB
Markdown
# Presentations
|
|
|
|
Optional per device, off by default. `PRESENTATIONS_ENABLED=true` in
|
|
`/etc/ekklesia-classroom/device.conf` puts a **Presentations** button on
|
|
the bar and starts the importer; nothing else in the room changes.
|
|
|
|
It depends on nothing else. A machine may have Presentations with no
|
|
Wonder Ink, no drive, no Parent Call, on Wi-Fi or a cable, as a laptop or
|
|
a tower. The Bible study laptop is the first installation.
|
|
|
|
## What was measured before any of this was designed
|
|
|
|
The brief was to use existing presentation software. So the existing
|
|
presentation software was tried first, on Debian 13 in the Plasma lab,
|
|
against a ten-slide deck in both PPTX and PDF. **The critical
|
|
requirement is that a teacher who ends or cancels a slideshow lands back
|
|
in the library and never in an editor.**
|
|
|
|
| Tried | What it actually did |
|
|
| --- | --- |
|
|
| `soffice --show deck.pptx` | Starts a real fullscreen slideshow — `app_id=libreoffice-impress`, `"Presenting: session7"`, `fullscreen=1`. But it needs `XDG_SESSION_TYPE=wayland` **and** `libreoffice-gtk3`/`-qt6`, or it dies with *"Failed to open display"*; it shows the LibreOffice splash on the way up; and a **"Tip of the Day: 1/225"** dialog opened on top of the slideshow on first run. Ending the show leaves the Impress editor. |
|
|
| `okular --presentation deck.pdf` | Opens **three** windows: the presentation, the ordinary Okular document window behind it, and an **"Information — Okular"** dialog. The full viewer UI — File, Open, Settings — sits behind the slides. |
|
|
| `zathura deck.pdf` | Opens windowed, not fullscreen. A document reader, not a presenter. |
|
|
| `pdfpc deck.pdf` | Single fullscreen window, the closest of the four. With `--single-screen` it came up windowed, and in the lab it did not respond to Right, Escape or `q`. A presenter-console tool with notes and a timer, and another dependency. |
|
|
|
|
None of them returns a teacher to a library, because none of them knows
|
|
there is one. Every one of them leaves an application on the screen that
|
|
can open any file on the machine.
|
|
|
|
### What was chosen instead
|
|
|
|
**LibreOffice converts once, headless, at import. Poppler renders the
|
|
pages. The classroom shows the pictures.**
|
|
|
|
```
|
|
Session 7.pptx --soffice --headless --convert-to pdf--> slides.pdf
|
|
slides.pdf --pdftoppm -png -scale-to-x 1920--------> page-001.png ...
|
|
```
|
|
|
|
Measured on the same deck:
|
|
|
|
| Step | Time |
|
|
| --- | --- |
|
|
| PPTX → PDF, ten slides | 0.34 s |
|
|
| All ten pages → PNG | 0.03 s |
|
|
| One page at 1920px | 0.10 s |
|
|
|
|
Fonts embed correctly (`BAAAAA+LiberationSans`, subset, embedded) and the
|
|
text survives, which is where a title comes from when the filename is a
|
|
hash.
|
|
|
|
This is not a new renderer. LibreOffice does exactly the rendering
|
|
Impress would have done live — it is the same engine — and poppler does
|
|
what every PDF viewer does. The classroom's own code displays a PNG.
|
|
Because playback is made from static page images, PowerPoint animations,
|
|
video, audio and other interactive content are not supported.
|
|
What it buys:
|
|
|
|
* **A teacher can never reach an editor**, because no editor ever runs
|
|
interactively. `soffice` runs once, with no window, at import.
|
|
* **Escape is ours.** The presenter is a window of the library's own
|
|
process, so ending a slideshow is the library raising itself, not a
|
|
second program being asked to exit.
|
|
* **Presenting is instant.** The pictures already exist; there is no
|
|
LibreOffice to start in front of a room.
|
|
* **PDF is first class.** A session exported from Google Slides as PDF
|
|
skips the conversion entirely and keeps exactly the appearance Google
|
|
gave it. PPT and PPTX join that path rather than having one of their
|
|
own.
|
|
* **Offline by construction.** Nothing after the download touches the
|
|
network.
|
|
|
|
`libreoffice-gtk3` is deliberately *not* a dependency: nothing
|
|
interactive runs, so no UI integration package is needed.
|
|
|
|
## Where things are
|
|
|
|
```
|
|
~/Downloads/ completed presentations move from here
|
|
~/Downloads/Wonder Ink/ Wonder Ink's worksheets. NEVER touched.
|
|
|
|
~/.local/share/ekklesia-classroom/presentations/
|
|
imported.json which downloads have been dealt with
|
|
<id>/
|
|
presentation.json title, pages, thumbnail slide, timers
|
|
original/
|
|
Session 7 - Romans.pptx the one original, name kept
|
|
slides.pdf what everything is rendered from
|
|
pages/page-001.png ... one per slide, 1920px wide
|
|
thumbnail.png the card picture, from the chosen slide
|
|
```
|
|
|
|
## Importing
|
|
|
|
`ekklesia-classroom-presentations.service` (user) runs
|
|
`classroom-presentation-import`, which looks at `~/Downloads` every four
|
|
seconds. Polling, not inotify: a teacher waits the same few seconds
|
|
either way, and this cannot miss an event across a restart.
|
|
|
|
A file is imported when **all** of these hold:
|
|
|
|
* its extension is `.pdf`, `.pptx` or `.ppt`;
|
|
* it is not inside the `Wonder Ink` directory;
|
|
* there is no `.crdownload`, `.part`, `.partial`, `.download` or `.tmp`
|
|
sibling — Chrome writes `Session 7.pptx.crdownload` and renames it when
|
|
the transfer finishes, so that sibling is the authoritative "still
|
|
arriving";
|
|
* it has not changed for six seconds, which catches a copy from a USB
|
|
stick that has no `.crdownload` to go by;
|
|
* it is not empty; and
|
|
* its fingerprint — path, size, modification time — is not already in
|
|
`imported.json`.
|
|
|
|
Two downloads of the same name are two presentations in separate id
|
|
directories. The second gets `(2)` after its title; neither overwrites the
|
|
other.
|
|
|
|
**Move and crash safety.** On one filesystem the completed download is
|
|
renamed atomically. Across filesystems it is copied to a hidden temporary,
|
|
flushed, published atomically, and only then removed from Downloads. The
|
|
metadata and transaction fingerprint are written first. On startup, an
|
|
interruption before the source was removed discards the partial destination
|
|
and retries; an interruption after the move resumes conversion from the
|
|
managed original. A presentation becomes ready only once every derived file
|
|
it needs is on disk.
|
|
|
|
A file that cannot be converted gets a card saying so, offering only
|
|
**Remove**. Its managed original remains recoverable even when PDF or page
|
|
generation fails.
|
|
|
|
## Memory (0.2.6.2)
|
|
|
|
On a 4 GB Plasma VM the kernel killed `ekklesia-classroom-presentations.service`
|
|
mid-import and Plasma said *Memory Shortage Avoided*. Measured afterwards,
|
|
LibreOffice converting one deck with the importer's own code:
|
|
|
|
| Deck | PPTX | LibreOffice peak | Rendering peak |
|
|
| --- | --- | --- | --- |
|
|
| 10 slides of text | 20 KB | 203 MB, 0.8 s | 38 MB |
|
|
| 30 slides, 12 photos shared | 23.5 MB | 738 MB, 16 s | 70 MB |
|
|
| 60 slides, the same 12 photos | 23.6 MB | 754 MB, 28 s | 70 MB |
|
|
| 30 slides, 30 different 12 MP photos | 58.7 MB | **1.43 GB**, 22 s | 69 MB |
|
|
| 20 slides, 20 different 27 MP photos | 143 MB | **2.08 GB**, 24 s | 70 MB |
|
|
|
|
It grows with the photographs, not the slides, and it is a spike rather
|
|
than a leak. PDF export with images reduced to 200 dpi saved only 5%,
|
|
because LibreOffice holds the full images regardless. So the failure was a
|
|
photo-heavy deck meeting a 4 GB machine with the room's other programs
|
|
open — and what made it a problem was what happened next. systemd's
|
|
default `OOMPolicy=stop` took the whole service down with the killed
|
|
LibreOffice; it restarted, `sweep()` resumed the same conversion with no
|
|
limit, and the library said *Getting this ready…* throughout.
|
|
|
|
Now:
|
|
|
|
* **`MemoryMax=40%`** — 1.6 GB on a 4 GB machine, 3.2 GB on 8 GB. Measured
|
|
in the lab at 1.6 GB, the 2.1 GB deck still imported in 33 s: much of
|
|
that peak is file-backed and reclaimable, and on a machine with swap the
|
|
rest is swapped rather than killed (1.4 GB of it at a 200 MB ceiling).
|
|
* **`OOMScoreAdjust=800`** — under pressure everywhere, LibreOffice goes
|
|
before the lesson on the screen, the browser or the panel.
|
|
* **`OOMPolicy=continue`** — a killed LibreOffice is a failed import, not a
|
|
dead service. Measured with no swap and a 300 MB ceiling: the kernel
|
|
killed LibreOffice, the watcher kept its PID with no restart, and the
|
|
import was recorded as `failed`, kind `memory`.
|
|
* **Two attempts, then stop.** Each attempt is counted before it starts.
|
|
Measured by killing the whole service during attempt 1 and again during
|
|
attempt 2: it resumed once, then said "giving up … the teacher can try
|
|
again" and did not start a third.
|
|
* **The card says so**, and offers **Try again**: *This needed more memory
|
|
than the computer had free. Close other programs, then choose Try
|
|
again.* Try again was pressed in the lab and the deck became ready.
|
|
A file that simply will not convert still says *This file could not be
|
|
opened* and offers no Try again, because trying again will not help.
|
|
* **Never stuck.** An import still "getting ready" 20 minutes after it
|
|
began — longer than any attempt can take — is shown as the same Try
|
|
again card, because nothing is working on it.
|
|
* **One import at a time**, by an exclusive lock as well as the one loop;
|
|
measured, three decks downloaded together converted one after another
|
|
with never more than one LibreOffice running.
|
|
|
|
What the lab cannot give is a real 4 GB machine's mix of programs; the
|
|
ceilings were emulated per service.
|
|
|
|
## The three things kept apart
|
|
|
|
This is the part most worth not breaking.
|
|
|
|
| | Changed by | Never changed by |
|
|
| --- | --- | --- |
|
|
| **Library cover** | *Use as cover* in Quick Look | presenting, importing |
|
|
| **Starting slide** | nothing — always slide 1 | choosing a thumbnail |
|
|
| **Retention** | a slide actually reaching the screen | Quick Look, opening the library, choosing a thumbnail, preview generation, a viewer that failed to start |
|
|
|
|
The first slide of a Bible study session is a generic opening shared by
|
|
every session; the one that names the session is usually the second. So
|
|
the thumbnail defaults to slide 1 and Quick Look exists to change it, and
|
|
changing it must not be mistaken for having presented anything.
|
|
|
|
`Presenter.show_page` calls `mark_presented()` at the moment the first
|
|
slide is drawn, and not before. Pressing **Present** is an intention; a
|
|
picture on the screen is a presentation.
|
|
|
|
## Retention
|
|
|
|
| State | Kept |
|
|
| --- | --- |
|
|
| Never presented | 180 days from import |
|
|
| Presented | 30 days from the **last** presentation |
|
|
|
|
Every presentation restarts the thirty days. No teacher-facing setting.
|
|
|
|
The cleanup runs hourly in the importer and:
|
|
|
|
* never removes a presentation whose id is in
|
|
`$XDG_RUNTIME_DIR/ekklesia-classroom-presenting` — the lock the
|
|
presenter holds while a slide is on the screen. In the runtime
|
|
directory so a crash cannot make a presentation permanently
|
|
un-removable;
|
|
* only ever removes a directory under the library root;
|
|
* removes the managed original and all derived assets together;
|
|
* never touches unrelated files in `~/Downloads`, and therefore never a
|
|
Wonder Ink worksheet;
|
|
* tolerates a directory that is already half gone.
|
|
|
|
## The presenter
|
|
|
|
A fullscreen `Gtk.Window` in the library's own process showing one
|
|
pre-rendered page at a time, letterboxed — never cropped, never
|
|
stretched.
|
|
|
|
| Key | Does |
|
|
| --- | --- |
|
|
| Space, →, ↓, Page Down, Return, `n` | Next slide |
|
|
| ←, ↑, Page Up, Backspace, `p` | Previous slide |
|
|
| Home / End | First / last slide |
|
|
| Escape, `q`, F5 | Finish, back to the library |
|
|
|
|
Page Up and Page Down are what the clicker in the cupboard sends.
|
|
|
|
Going past the last slide finishes, rather than showing a black "click to
|
|
exit" slide.
|
|
|
|
**Fullscreen is re-asserted, not assumed.** The presenter requests
|
|
fullscreen after its Wayland surface is mapped, and its stable `Presenting`
|
|
title lets a presenter-only KWin rule apply fullscreen and remove the border
|
|
without affecting the library or Quick Look. `window-state-event` reasserts
|
|
the state for as long as the presentation is running and stops the moment
|
|
Escape is pressed. The library is hidden during playback and restored on
|
|
exit; the Plasma panel itself is never hidden or reconfigured.
|
|
|
|
## Fonts
|
|
|
|
The pastor's material comes from Google Slides. These are all in Debian
|
|
main and install without a question:
|
|
|
|
| Package | Covers |
|
|
| --- | --- |
|
|
| `fonts-liberation2` | Arial, Times New Roman, Courier New — metric compatible |
|
|
| `fonts-crosextra-carlito` | Calibri |
|
|
| `fonts-crosextra-caladea` | Cambria |
|
|
| `fonts-croscore` | the same three under their Chrome OS names |
|
|
| `fonts-liberation-sans-narrow` | Arial Narrow |
|
|
| `fonts-roboto`, `fonts-open-sans`, `fonts-lato`, `fonts-montserrat` | the web fonts Google Slides offers |
|
|
| `fonts-urw-base35` | the PostScript standard 35 |
|
|
|
|
`ttf-mscorefonts-installer` is deliberately **absent**. It is in contrib,
|
|
it downloads from a third party at install time, and it shows an EULA
|
|
prompt — which an unattended classroom upgrade cannot answer.
|
|
|
|
## Checking a machine
|
|
|
|
```
|
|
classroom-presentation-import --status what the library holds
|
|
classroom-presentation-import --once one pass, then stop
|
|
classroom-presentation-import --cleanup apply retention now
|
|
classroom-manual --features which manual pages this device shows
|
|
```
|
|
|
|
`--status` prints titles, slide counts, the chosen thumbnail slide, both
|
|
timers and the expiry date. It is the fastest way to answer "why has this
|
|
gone" and "why has this not appeared".
|