Repository navigation
[ryoku] wm: emit enable/disable DPMS actions instead of state toggle - #278
Closed
Sipper1236 wants to merge 1 commit into
Closed
Sipper1236 wants to merge 1 commit into
Sipper1236 wants to merge 1 commit into
Conversation
Hyprland's Lua dispatcher reads the toggle field named action (enable, disable, toggle); a state key is silently ignored, so an explicit on request could leave the display off. Map output.power on/off to enable/disable, preserving the optional monitor selector. Add TestActOutputPowerIsIdempotent, which pins the new literal and repeats each request to prove an explicit enable cannot re-disable the display during resume. Add a wm job to go-unit-tests so the provider contract is gated in CI; it was previously untested, which is how the defect shipped. Note: Fixed: display power requests now use the Hyprland action field, so an explicit on no longer leaves the screen off
neur0map
added a commit
that referenced
this pull request
Sep 26, 2026
The DPMS dispatcher's field is `action`, and an explicit on or off must emit the same literal every time it is called, never a toggle: the resume path can re-issue an enable while the lock surface is still appearing, and a toggling field would flip the panel back off and flash the screen after wake (#264, #258, #277). The provider already maps on/off to the action field; this adds the regression test that holds that invariant, adapted from @Sipper1236's PR #278, whose code fix the seam already carried. Note: Fixed: a test now pins that re-asserting display power cannot toggle it off
Collaborator
|
Thanks for this, and for the idempotency test especially. The seam already maps output.power onto the DPMS |
itsKontra
added a commit
to itsKontra/ryoku-fedora
that referenced
this pull request
Sep 27, 2026
* [system] bound the qylock guard waits and cap the cutover retry loop The launch guard and the generation guard take the same exclusive launch lock, and the generation guard holds it for its whole life on purpose. Requesting a launch guard while a generation guard was up therefore waited for a lock that could never be granted, and ensure_qylock_launch_guard polled it with no timeout: ryoku update sat at shell-quiesce forever with the shell unit deactivating (Ryoku-dev#273). The generation guard now satisfies the launch boundary itself (while it is up, launches are already excluded), and both ensure loops fail loud after 30 s naming the stale holder instead of polling forever. The launch guard's keep-alive also closes the lock fds now, the same leak the generation guard already fixed. wait_qylock_cutover retried the whole cutover in an unbounded loop and every attempt stops and re-reaches ryoku-session.target, so one persistent failure restarted the desktop every few seconds until units hit start-limit (Ryoku-dev#275). It now makes three attempts with backoff, leaves the running session untouched when it gives up, and says which command retries by hand; the transient unit keeps systemd's own start limit as a backstop instead of StartLimitIntervalSec=0. Note: Fixed: ryoku update no longer hangs at shell-quiesce on the qylock guards, and a failing lockscreen cutover no longer restart-loop the desktop * [system] clamshell reclaims a leaked lock, fails loud A daemon from before the fd-leak fix orphaned its udevadm monitor with the lock fd open; that orphan pins the flock across an upgrade, and the new daemon answered the contention with a silent exit 0, indistinguishable from a healthy second instance, so Restart=on-failure never retried and nothing reached the journal (Ryoku-dev#274). The daemon now reclaims non-daemon holders in rounds (killing a leaked holder does not close the fd in the children that inherited it), treats only the recorded daemon or a ryoku-clamshell process as a legitimate "already running", and exits 1 with the fuser command when the lock is held by something it cannot classify. session-start at login ran from the compositor's exec where its stderr vanishes; a silent early exit left a blank desktop and a held sleep inhibitor with no evidence anywhere. The autostart lines on both desktops now call session-start-logged, which appends stderr and the exit code to ~/.local/state/ryoku/session-start.log. Note: Fixed: the clamshell daemon recovers a lock leaked by an older version instead of staying silently dead, and login session-start failures now leave a log * [system] the power-cutover post hook survives its own first install The hook pair stages the pre-upgrade helper at /run/ryoku-power-cutover in PreTransaction and execs it in PostTransaction. On the transaction that first installs the pair the staging hook is not on disk yet, so /run never appears and the PostTransaction execv fails with "No such file or directory", leaving pacman reporting a failed hook on exactly the upgrade that introduces the guarded cutover (Ryoku-dev#272). The post hook now execs the installed binary, which prefers the staged copy when it exists (same semantics as before, and a staged copy of this code cannot exec itself) and falls back to itself when nothing was staged. Note: Fixed: the upgrade that first installs the guarded power cutover no longer reports a failed pacman hook * [ryoku] cli: rollback across the packaging split, keep the db on failure A release that predates the compositor split serves no ryoku-desktop-hyprland or ryoku-desktop-niri, and the installed meta's exact-version pins on six packages make the whole downgrade transaction unsatisfiable, so every rollback across the split failed and burned a snapper snapshot (Ryoku-dev#271). A channel move or rollback now drops installed split metas the target repo does not serve before the transaction; the downgrade of ryoku-desktop re-ties the graph. The failure cleanup also deleted the [ryoku] sync database after ANY failed transaction, which turned one clear dependency error into a "target not found" storm on the next run and destroyed the evidence. The db is dropped only when the failure actually names the database or its signature. Note: Fixed: ryoku rollback to a release from before the compositor split works again, and a failed update no longer deletes the package database that explains it * [installation] script installer survives an oh-my-zsh-git box ryoku-oh-my-zsh provides and replaces both upstream zsh frameworks, but the desktop transaction still aborted on a box carrying oh-my-zsh-git: a plain removal under --noconfirm refuses while an installed plugin package depends on the framework, and pacman then reports the dependency conflict as a failed transaction preparation. The conflict step drops the upstream framework with -Rdd before the desktop install; its plugins re-resolve against ryoku-oh-my-zsh's provides in the same step, and the recorded undo restores the framework. Note: Fixed: converting an Arch box that runs oh-my-zsh or oh-my-zsh-git no longer fails at the desktop package step * release: bump unstable to 0.74.7-beta.20 * [ryoku] i18n: regenerate translations * [ryoku] a palette change reaches the cursor without a re-pick Recoloring the Material cursor for a new palette rewrote the image files, but a running compositor keeps the loaded cursor theme cached in memory, so the new colours only appeared after re-picking the theme or logging back in. The seam gains cursor.reassert: Hyprland re-runs setcursor with the store's resolved theme (DYNAMIC included), niri regenerates its config tree, which it watches. The daemon calls it right after a successful recolor, best-effort, so a provider without the action simply answers with an error nobody sees. Note: Fixed: the pointer recolours with the wallpaper again, without re-picking the cursor theme * [ryoku] cli: name the backlight Ryoku writes to when a box has several A laptop can expose more than one /sys/class/backlight node, and exactly one of them is a phantom on that hardware; a brightness control that "only has off and full" is what writing percentages to the wrong node looks like from the user's seat. With more than one device present the doctor finding now names the node every Ryoku brightness writer targets (media keys, OSD daemon and bar slider all call the same helper) plus each candidate's current and maximum level, so the next report from such a box pinpoints the phantom in one line instead of a guess. Note: New: ryoku doctor reports which backlight device Ryoku drives, and every candidate's level, on multi-backlight laptops * [ryoku] ryogami names why a wallhaven download was refused Note: Fixed: a refused wallhaven download now says why in the log instead of doing nothing * [ryoku] sync the shell daemon's vendored seam copy The cursor.reassert action landed in the seam; the daemon builds against its vendored copy, which must match or the pre-push vendor gate fails. * release: bump unstable to 0.74.8-beta.20 * [ryoku] cli: name the reverse-PRIME first-commit hazard in doctor On a hybrid laptop whose connected panel is driven by the iGPU while the render pin puts NVIDIA leftmost in AQ_DRM_DEVICES, some kernel/driver combinations fail the session's very first cross-GPU commit once ("amdgpu: Not enough memory for command submission"), leaving a live compositor with a black panel until reboot (Ryoku-dev#270). Clearing the pin makes it disappear, but the pin is deliberate Ryoku policy on every multi-GPU machine, so doctor reports the combination instead of touching it: panel driver from sysfs, pin's first driver resolved through ryoku-gpu order, and the two commands that move the pin either way. Detection is pure over its inputs and fixture-tested. Note: New: ryoku doctor flags a NVIDIA-first render pin over an iGPU-driven panel, the combination behind a black panel on first commit * release: bump unstable to 0.74.9-beta.20 * [ryoku] i18n: regenerate translations * [tooling] the install gate asserts the hook contract that actually ships The container-install gate pinned the post-transaction hook's Exec to the staged /run path, which is exactly the design that fails on the transaction first installing the hook pair (Ryoku-dev#272): the staging hook is not on disk yet, so nothing ever writes /run/ryoku-power-cutover and the execv fails. The gate now asserts the shipped contract instead: the hook execs the installed binary, and that binary prefers the staged copy (RYOKU_CUTOVER_STAGED guards the exec), so both the first-install fallback and the preserved-executor semantics stay proven on every publish. Note: Fixed: the publish gate no longer rejects the first-install-safe power-cutover hook * release: bump unstable to 0.75.0-beta.20 * [ryoku] ryogami: matugen app-template toggles write via the live verb The toggle row called `ryoku-hub hypr matugen set`, a command path that does not exist: the hub printed usage and exited 0, and the write was detached, so the failure was silent and every App Templates toggle looked dead while matugen.json never changed. The row now uses the same merge writer as the rest of the picker and the Hub (`ryoku-hub desktop matugen set`), so the watched FileView reloads and the switch follows the click (issue Ryoku-dev#276). Note: Fixed: the App Templates toggles in Wallpaper Picker > Settings > Matugen respond to clicks again * release: bump unstable to 0.75.1-beta.20 * [ryoku] rashin: pass the hermes installer only flags that build knows Setup hardcoded --non-interactive --skip-browser --skip-computer-use, but the official installer exits 1 on any option it does not recognize, so once upstream dropped --skip-computer-use every "Set up Hermes agent" died with "unknown option: --skip-computer-use" (issue Ryoku-dev#279). Setup now downloads the script and passes --non-interactive (the guard against the hidden tty prompt) plus each optional flag only when the downloaded script names it, so upstream growing or dropping an option can no longer break the one-click flow. Note: Fixed: setting up the Hermes agent from the Hub works again on current installer builds * [tooling] publish: populate the base keyring before the container -Sy The container-install gate does `pacman -Sy --noconfirm --needed git` as its first step in a fresh base image. A fresh container ships an unpopulated pacman keyring, so that very first sync can fail on the image's OWN repository signature ("error: cachyos: signature from ... is invalid") before any Ryoku package is in the transaction, wedging the whole release on a mutable base image the build did not change. The step now initializes and populates the base keyring (archlinux, plus cachyos on the CachyOS matrix leg) before syncing, and refreshes the CachyOS signing key from the upstream keyserver once on a retry, the same recovery the installer documents for a rotated key. SigLevel is never lowered, so a genuinely unsigned Ryoku package still fails the gate. Note: Fixed: the release publish no longer aborts when a base container's pacman keyring is unpopulated * release: bump unstable to 0.75.2-beta.20 * [ryoku] wm: pin that output.power on/off is idempotent The DPMS dispatcher's field is `action`, and an explicit on or off must emit the same literal every time it is called, never a toggle: the resume path can re-issue an enable while the lock surface is still appearing, and a toggling field would flip the panel back off and flash the screen after wake (Ryoku-dev#264, Ryoku-dev#258, Ryoku-dev#277). The provider already maps on/off to the action field; this adds the regression test that holds that invariant, adapted from @Sipper1236's PR Ryoku-dev#278, whose code fix the seam already carried. Note: Fixed: a test now pins that re-asserting display power cannot toggle it off * release: bump unstable to 0.75.3-beta.20 * [test] container-install: name the lid drop-in's real owner on failure The gate died with "ryoku-desktop does not claim /etc/systemd/logind.conf.d/10-ryoku-lid.conf" while pacman -Qo found an owner, and the message could not say who: a sibling package claiming the path, a scriptlet replacing the file outside the database, and a missing install line all looked identical. The failure now carries the owner pacman reports, so the next run is decisive instead of a mystery. * release: bump unstable to 0.75.4-beta.20 * [test] container-install: drain the package list before matching The claim check piped `pacman -Ql ryoku-desktop` into `grep -q` under pipefail. grep exits at the first match, which SIGPIPEs pacman mid-write on a package this large, the pipeline reports failure, and the gate dies with "does not claim" on a package that does: the same run's `pacman -Qo` named ryoku-desktop as the owner, and a packaged-box probe against the published channel showed the file on disk, owned, and listed. The list is now captured whole before matching, so the assertion cannot lose a race with its own producer. * release: bump unstable to 0.75.5-beta.20 * [system] Harden the merged session start, clamshell and rollback Review fixes on top of the upstream merge. session-start-logged ran start_login_session on the left of ||, which turns errexit off inside it, so a failed step (a guard timeout) carried on and the log recorded exit 0. It now runs the step as its own process. The qylock cutover waiter retries three times itself, but its unit also restarted on failure and each pass outlasts the default start limit, so a persistent failure looped. The unit no longer restarts. The clamshell lock reclaim listed its own forks, which hold fd 9 under the ryoku-clamshell comm, as a live daemon, so a leaked holder was never reclaimed. The holder scan now runs with fd 9 closed, and "already running" needs a recorded PID that is still ryoku-clamshell. A rollback across the compositor split treated a failed or empty repo listing as "serves nothing" and removed the compositor metapackages. It now keeps them when the listing cannot be read. The reverse-PRIME doctor note points at Hybrid graphics mode, which keeps the pin cleared, instead of ryoku-gpu disable, which the next doctor run undid. * [ryoku] shell: read a sun window that spans midnight as daytime isDaytime projects sunrise and sunset onto today's clock. When the sunset clock is earlier than the sunrise clock, as with a forecast in another timezone than the box, nothing read as daytime and the sun theme stayed dark all day. TestResolveModeSunFollowsTheWindow hit the same case whenever CI ran within an hour of midnight. Note: Fixed: The follow-the-sun theme no longer stays dark when the forecast's day crosses midnight on the box's clock --------- Co-authored-by: carlos <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: ryoku-i18n <noreply@ryoku> Co-authored-by: itsKontra <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Fixes
output.powerin the Hyprland provider to emit the correct Lua dispatcher field.Root cause: Hyprland's
hl.dsp.dpms()reads the toggle field namedaction(values:enable,disable,toggle). The previous code emittedstate, which is silently ignored by the dispatcher — so an explicitonrequest could leave the display off.Fix: Map
output.power on→action = "enable",output.power off→action = "disable", preserving the optional monitor selector.Tests
TestActEmitsLuaDialectwith the corrected literal.TestActOutputPowerIsIdempotent— pins the new literal and repeats each request to prove an explicit enable cannot re-disable the display during resume (the bug that caused repeated flashing).CI
Adds a
wmjob to.github/workflows/go-unit-tests.ymlso the provider contract is now gated in CI. This module was previously untested, which is how the defect shipped.Upstream status
Checked September 26, 2026 against the local
ryoku-archcheckout and the currentryoku-dev/ryoku-archGitHub sources. The display-power bug is present upstream. The fix should change the provider and its test to emit the correct dispatcher form.Sources confirmed:
tableToggleActionreads field"action", parses"enable"|"on"→TOGGLE_ACTION_ENABLE,"disable"|"off"→TOGGLE_ACTION_DISABLENote
Note: Fixed: display power requests now use the Hyprland action field, so an explicit on no longer leaves the screen off