Repository navigation
omamobile-shell 0.2.120: recent apps start a failed window capture again, and keep each window's last picture - #658
Conversation
… and copy live only the middle card
A recent-apps card copied its window live through Hyprland's toplevel
capture, and showed nothing until the window gave a frame. Every app sits
on a screen of its own, and Hyprland hands a frame over only when the
window draws: a still page (the Settings app's About list) gave none, so
its card stayed empty until something on the page changed. A capture
Hyprland fails ends for good ("Ending recording due to screencopy
failure", eight times in one morning on the 9a), and the card stays empty
until recent apps are opened again. And every card was rebuilt, empty,
when another window closed.
- Each card keeps its window's last frame as a still, at the card's size
(grabToImage, so a few hundred kilobytes each), and shows it until its
capture has a frame. The stills live on the switcher, not on the cards,
so a rebuilt card has its picture at once; a window that closes takes
its still with it (SwitcherModel.js: stillOf, withStill, prunedStills).
- Only the card in the middle copies live; the others copy one frame
each. Before, every open window was captured live at the screen's size
while recent apps were open.
- A capture that stops is started again a second later, three times at
most while recent apps are open.
Tests: test/switcher-model.js (the stills), and in test/switcher-touch the
middle card live and its neighbours not, the still shown while the capture
has no frame and gone with its window, and the retries. The ScreencopyView
stub gains hasContent, a stopped signal and a colour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first commit copied only the middle card live and its neighbours one frame each, to save work. Measured on the 9a with five windows (recent apps open, swiped through every card, 8 s of CPU time and the dma-buf total from debugfs): the shell's CPU was 31.8 % before and 32.9 % after, and the dma-bufs 239 MB before and 235 MB after. A neighbour that copies one frame still holds its buffer, and an idle window is copied only when it draws, so nothing measurable was saved, and the neighbours lost their live picture. Back to every card live. What stays: a stopped capture starts again (Quickshell ends a recording for good when Hyprland fails one, "Ending recording due to screencopy failure", three times in the second of the owner's empty card), and each card keeps its window's last frame as a still for when its capture has none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
One more measurement on keeping only the middle card live, this time with windows that keep drawing. The setup:
Every card live costs no more than the middle card alone, even with neighbours that redraw, so the head keeps every card live. An earlier round I discarded had the screen off: with the panel dark, Hyprland renders nothing, and no capture runs at all. |
…ard has no frame The restart in the first commit waited for ScreencopyView's stopped signal, but Quickshell 0.3.2 declares that signal and never emits it: when Hyprland fails a capture, the view only drops its context. So on a phone the restart never ran; test/switcher-touch passed only because its stub emitted the signal. Now a card that has no frame a second after recent apps open, or that loses its frame, drops its capture and starts a new one, three times at most while recent apps are open. The stub no longer has the signal, and the test leaves the capture without a frame, as Hyprland's failure does. Reproduced on the 9a: with the on-screen keyboard up, opening recent apps closes the keyboard, the window grows from 579 to 821 points, and Hyprland fails the capture that was sized for the old window. Before: the card stayed empty. After: the first frame, the failure, and a second later the window at its new size. The measurement in the previous commit is void: the shell's local-plugin reload keeps the component it compiled first, so both runs ran the same switcher. Measured again with a shell restart before each run (two foot terminals redrawing ten times a second, recent apps open, 12 s, three rounds): every card live, shell 26.6-27.3 % and Hyprland 46.4-49.2 %; only the middle card live, shell 30.3-31.0 % and Hyprland 39.9-42.0 %. Hyprland saves about 7 points, the shell spends about 4 more, and the neighbours would lose their live picture, so every card stays live. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A correction, and a fix in db2d0d1. My earlier measurements here were void. I swapped the plugin copy and relied on the shell's local-plugin reload, but that reload keeps the component it compiled first, so both sides of each A/B ran the same switcher. That includes my last comment ("only the middle card live saves nothing") and the main-against-branch load in the first version of the description. I've redone everything with The restart never ran on a phone. Quickshell 0.3.2's It reproduces now: with the on-screen keyboard up, open recent apps. The window grows as the keyboard closes, and Hyprland fails the capture. With three windows that was three failures on both main and this branch. Main's cards stayed empty, and this branch's had their pictures within 3 s. Load with valid runs (two terminals redrawing ten times a second, 12 s, three runs each):
With working captures, this branch costs what main costs. When main looks cheaper, that's because its failed captures stay dead. Middle-card-only takes about 7 points off Hyprland and adds about 4 to the shell, so I kept every card live. |
|
Thank you for this, and especially for the two corrections you made on your own: finding that the local-plugin reload kept the first compiled component, so your A/B runs compared a switcher with itself, and finding that the restart never ran on a phone because the test stub emitted a signal the real view doesn't. I read Quickshell at the commit the phones run ( I ran One change before I merge: The stills' size. One question: Did you see the still itself on the phone? Your results describe the restart, and they're convincing. But I couldn't find what the card showed in the second between the failure and the new frame, which is what the stills are for. The offscreen test grabs the stub's rectangle, so it can't tell us whether Small things, as you see fit: the still is taken at a capture's first frame, so "last picture" really means "from the last time recent apps were open", and the README line could say that. And the "screencopy failure" with no keyboard involved deserves its own issue with the journal line, so we can chase the cause separately. After that I'll try it on my 7 Pro (recent apps with the keyboard up, and with several apps from the home screen) and merge. It goes in after #553, and I'll set the version number at merge. |
What and why
On the owner's Pixel 9a, recent apps sometimes showed a card with no picture. In their screen recording at 09:33 that morning it was the Settings card, while WhatsApp and Home Assistant had theirs. In that same second, the shell's journal has three lines of "Ending recording due to screencopy failure".
That message is the whole story. When Hyprland fails a toplevel capture, Quickshell ends the recording for good: the context's
stoppedmakesScreencopyViewdrop it, the view's ownstoppedsignal is never emitted (0.3.2 declares it, nothing emits it), and nothing starts the capture again. So the card stays empty until recent apps are opened again. Otherwise a card's first frame always comes at once: Quickshell asks for it withignore_damage, and Hyprland copies such a frame immediately. So an empty card means a failed capture.It happens on demand like this: with the on-screen keyboard up, open recent apps. The keyboard closes, the window grows (579 to 821 points here), and Hyprland fails the capture that was sized for the old window. That's how the owner usually opens recent apps (
swipe … recents (keyboard)in the journal). Captures also fail now and then without a keyboard, sometimes all three cards' at once.What this changes:
hasContent, since the view never saysstopped.grabToImage, a few hundred kilobytes each), and shows it until its capture has a frame. The stills live on the switcher, not on the cards, so a card rebuilt after another window closed has its picture at once, and a window that closes takes its still with it (SwitcherModel.js:stillOf,withStill,prunedStills).The first commit also copied only the middle card live; the second takes that back out (measured below). The first commit's restart waited for
stopped, so it never ran on a phone; the third commit fixes that.Device and build
uname -r): 7.3.0-rc6-omamobile-push1-gcbb1a7eb0a78Checks (no phone needed)
tools/pr-check upstream/mainpassestests/allpasses. On the phone one check fails, "display-msm should log not stamping on empty tree", and it fails the same way on unchanged main (c3cfd86) there; everything else passes.tests/packages: omamobile-shell'stest/all, 317 okversionbumped, and the new version is in the commit subjectCo-Authored-By:orAssisted-by:trailer, never aSigned-off-by:New tests:
test/switcher-model.js: the stills.test/switcher-touch:ScreencopyViewstub gainshasContentand a colour, and drops its frame when it loses its source, as Quickshell's does. A failed capture is one that never gets a frame; like Quickshell's, the stub has nostopped.On the phone
omamobile.switcheras the local plugin copy, on omamobile-shell 0.2.119 (omamobile-shell 0.2.119: a swipe up closes the shade card and the Wi-Fi and Bluetooth panels #553), whose switcher is the same as main's. Touches came from a uinput touchscreen, and recent apps were closed through the plugin between openings.omarchy-restart-shell). Without the restart the shell's local-plugin reload keeps the component it compiled first, so my earlier A/B runs in this PR compared a switcher with itself; I've withdrawn them.Keyboard up, then recent apps:
Three windows, recent apps opened from home, screenshot after 3 s, twice each: main's first run had three failures and an empty card; this branch's runs and main's second had none and showed the window.
Load, two foot terminals redrawing a clock ten times a second, recent apps open, 12 s of CPU time, three runs each:
So with working captures this branch costs what main costs, and it got its cards back in the run with three failures. In an earlier series every run started with three failures. There, main's cards stayed dead and it read 2.5–10.7 % shell and 0.2–18.8 % Hyprland, which is the price of an empty card, not a saving. Live on the middle card only takes about 7 points off Hyprland and adds about 4 to the shell, and the neighbours would lose their live picture, so every card stays live.
userdata, weakens the storage guard, or reads registers from userspace (AGENTS.md §1)tools/phone-smokereportphone-smoke: unknown
Kernel
7.3.0-rc6-omamobile-push1-gcbb1a7eb0a78, tools atunknown, 2026-11-11T09:00Z.Packages: hyprland 0.57.0-3.1, libcamera 0.7.2-4.22, mesa 1:26.2.4-1.4, omacam 0.3.3-1, omamessages 0.2.2-1, omamobile-apps 0.2.2-1, omamobile-audio 0.5.18-1, omamobile-device-tegu 0.2.14-1, omamobile-firstboot 0.1.3-1, omamobile-gnss 0.2.9-1, omamobile-keyring 20261026-1, omamobile-mirror 0.1.1-1, omamobile-mmd 0.1.4-1, omamobile-modem 0.1.20-1, omamobile-modem-sit 0.3.34-1, omamobile-omarchy 0.1.41-1, omamobile-owl-widgets 0.1.2-1, omamobile-sensors 0.2.1-1, omamobile-settings 0.1.1-1, omamobile-shell 0.2.119-1, omamobile-voice 0.1.0-1, omaphone 0.2.3-1, omarchy 4.0.6-1, omatel-agent 0.2.4-2, quickshell 0.3.2.r7.g2b4501a-1
🤖 Generated with Claude Code