15d54b16f6780b719d28a325cc536b81ff795d01
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3b01f1e020 |
Let the Dock choose an edge, choose its screens, and be dragged into order
Three things that were parked, and the reasons they were parked turned out to be the useful part of doing them. The Dock can sit on the left or the right as well as the bottom. Everything that assumed the bottom edge is now asked which edge it is on: the anchors, the axis that gets an implicit size, the sliver of input region that survives hiding, the direction the body slides away in, and which side a tooltip opens towards. The body was a Row and is a Grid, because one declaration then serves both orientations -- Row and Column would each need their own children, and the cross-axis anchors that centre items in a Row are the wrong axis in a Column. Bottom is unchanged in every particular, and the settings default to it, so a hot reload in the middle of this work left the running dock exactly where it was. One bug worth recording because static review would never have found it: a dock spans the edge it lives on, which means anchoring BOTH ends of that edge. The first side dock anchored top and left only, was free to collapse to its implicit height, and came out one pixel tall. It parsed, it loaded, and it rendered nothing. The contract measures the geometry rather than reading the source for that reason, and was verified by putting the single-ended anchor back. Per-screen is a list of names where empty means every screen, because a list is what goes stale when a display is unplugged and "all" should not be spelled as one. Turning off the last screen collapses to "all" rather than leaving no dock anywhere and no obvious way back. Pins can be dragged by a grip. The objection this file recorded for a long time was real -- dragging inside a Flickable inside a scrolling page fails in a way that reads as breakage -- and the answer is preventStealing on the grip, so the page cannot claim a gesture that started there. The arrow buttons stay: they are the keyboard-reachable path and a grip is not. The order is held locally during the drag and written once on release, rather than rewriting settings.json for every slot crossed. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
6dc606b872 |
Give focus modes conditions rather than alarms, and let Gaming hand over
A mode is on because something is true right now: a game is running, a window is fullscreen on a given display, a workspace is focused, the clock is inside a window. That is asked again rather than fired once, and it is the whole reason schedules could be included here without the usual failure modes. A machine asleep at 23:30, rebooted at 02:00, or opened at 08:00 into a window that has already passed all reach the right answer by being asked again; an alarm gets all three wrong. The midnight-crossing rule is the part worth being careful about: a window belongs to the day it STARTS on, so a Friday-only 23:30-07:00 covers Saturday morning and must not cover Saturday night. That arithmetic was tested as pure logic before anything was built on it, including every malformed input failing closed -- silencing someone because a time string was wrong is the worst way this could fail. This does not take over the manual timed session. FocusSession already owns that, with its capsule, shortcut, Quick Settings entry and contracts, so modes defer entirely while one runs. Two writers of Do Not Disturb would each restore whatever the other happened to leave behind. Gaming hands over rather than being duplicated. The hook was silencing notifications itself, which would have made exactly those two owners -- and Gaming.active only polls while its settings page is open, so a mode could not have seen a game reliably in any case. The hook reports the game over IPC now and the mode decides what that means, the Gaming page points at it, and gamingSilenceNotifications is retired from the schema, since a setting nothing reads is the dead row this work keeps removing. Sleep ships disabled. A desktop that starts silencing someone on first boot has overstepped, whatever the default hour. Three contracts moved with it. gaming-contract asserted the hook uses setDnd, which was right before and wrong now; the shell-side assertions that setDnd and dndState exist stay, because a toggle would flip an already-silent machine back on. The new contract is proven to fail by breaking the midnight rule and by letting modes run alongside a manual session. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
32fab59d24 |
Let a sound device be heard, and the Dock's icons be sized
Nine outputs named after their chipsets cannot be told apart by reading, so each one gets a Test button that plays a short sample out of that device. Targeted by node name rather than by making it the default first, because finding out which is which should not move where everything else is playing. That belongs in its own service rather than in AudioDevices. sound-page-contract forbids Process, pactl and wpctl in the files that own device state, and it is right to: shelling out there races the PipeWire service that owns those same objects. Playback is a different thing -- pw-play opens its own stream and mutates no device, so there is nothing to race -- but the rule's letter covered it, and weakening a guard to fit a new case is how guards stop meaning anything. SoundTest exists so AudioDevices stays native bindings only. Worth recording next to the call: pw-play falls back to the default output for a target it cannot find, rather than failing. A stale node name would play from the wrong device and look exactly like a successful test, which is why the name is taken straight from the live node. The Dock's icon size was a constant in Theme. It goes through the preference schema like everything else, so validation, search, the generated docs and the write sweep all pick it up without being told about it separately -- and two contracts duly failed until docs/settings.md and the per-page commands were regenerated. Dock position is deliberately not here. It is not a setting but a rework: the dock is anchored bottom, and the reveal strip, tooltip placement, intellihide and the qs-dock rule in hypr/rules.lua all assume that. Doing it properly means changing compositor rules on a machine somebody uses daily, which is not something to start as a side effect of adding a slider. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
4cbe3b882a |
Add a Gaming page, and let the desktop react to games
Live first, because unlike every other page here this one has a live dimension: card temperature, power draw, whether Game Mode actually engaged. It polls only while it is open, since a settings page nobody is looking at has no business waking the CPU. The part that makes it Panama's page rather than a gamemode config editor is the hook. gamemode runs a script when a game asks for it and another when the game exits, so the power profile switches to performance and notifications go quiet for exactly the duration of a game -- and afterwards both go back to what they WERE, not to a default. A Do Not Disturb someone set by hand survives a game; a power profile someone chose is restored rather than replaced. Verified against real gamemode activation, not merely by calling the hook. Two things the page reports rather than hides. Game Mode's headline trick is switching the CPU governor to performance, and this machine already runs performance, so it says so instead of implying it helps. And Proton builds are listed but never chosen: Steam picks the runtime per game, and a control here would claim an authority this page does not have. The hook first called a notifications function that did not exist, and the one that did was a TOGGLE -- the wrong primitive entirely, since toggling at game start would unsilence notifications that were already silent. The shell gained an explicit setter and reader. search-routing-contract kept its own hand-written list of every page, which made adding one fail as "not a known page" -- a sixth place to register a page and a sixth chance to forget. It now derives the mapping from the shell, which already knows it. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
6997dd535f |
Add high contrast, and make remote desktop configurable
Two of the three panels still handed to GNOME, having actually checked each rather than repeating that they were not worth owning. Universal Access turned out to be mostly ours already: the magnifier, pointer size, text scale, motion and dimming were all present. High contrast was the real gap. It reaches GTK4 applications through the desktop portal, which republishes GNOME's accessibility setting as org.freedesktop.appearance contrast -- so no high-contrast theme is involved, and none is installed here. Verified end to end: committing the preference drove gsettings and the portal reported contrast 1. Sticky, slow and bounce keys stay absent. There is no Wayland or Hyprland implementation, and the compositor would store the XKB option while nothing ever acted on it. Remote desktop gained port, view-only, and clearing stored credentials. SETTING credentials opens a terminal running grdctl, which prompts for the password itself. That is not a hand-off for lack of effort: grdctl takes the password on a terminal and core-dumps without one, and the only alternative -- passing it as an argument -- would publish it through /proc to every process on this machine. Typed into grdctl directly it never passes through Panama, and a contract now fails if it ever appears on a command line. Color stays with GNOME, and not for lack of effort either. colord runs here with seven profiles and zero devices registered, because the daemons that register displays do not run under this session, and Hyprland exposes no ICC, gamma, or color-management option at all. A Color page could import a profile, attach it to nothing, and change nothing -- the same failure refused for rollback and printer drivers. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |
||
|
|
588dec4adc |
Generate the settings reference from the schema
Every other form of documentation here has drifted at least once today: search routing that pointed at a page not containing the setting, a contract that pinned the bug the same commit fixed, and a comment in shell.qml that failed to stop me making the exact mistake it described. Prose describing 127 settings would drift the day after it was written. So docs/settings.md is generated, and a contract fails the moment the committed copy stops matching the schema. The document cannot be wrong for longer than it takes to run the suite. It reads the schema by parsing rather than importing, since there is no QML interpreter here and requiring a compositor to build documentation would be worse. That parser is the risk, so it FAILS LOUDLY: if it stops recognising the file it exits non-zero with the reason and writes nothing, because a partial reference is worse than a stale one -- stale is caught by --check, partial reads as complete. Verified: with the entry pattern broken it reports "only 0 entries parsed" and leaves the committed file untouched. The contract also proves --check actually compares content, by appending a line and confirming it fails, rather than trusting a command that returns success to mean anything. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L |