Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ef86da3
Keep rotation layers mapped and touch on the panel
brkosebay Nov 8, 2026
d07e4bf
Keep rotation layers mapped and touch on the panel
brkosebay Nov 8, 2026
e83a628
Power, idle dim and native logout
brkosebay Nov 8, 2026
1bdc70d
Optional smooth automatic brightness
brkosebay Nov 8, 2026
84bf7a1
shell: persist display scale and gather off-screen windows
brkosebay Nov 8, 2026
0c43b09
Merge main into j71t/rotation
brkosebay Nov 12, 2026
e85b9c3
Merge the j71t/rotation branch into j71t/power
brkosebay Nov 12, 2026
f38f08f
Merge the j71t/power branch into j71t/brightness
brkosebay Nov 12, 2026
237ad89
Merge the j71t/brightness branch into j71t/display-scale
brkosebay Nov 12, 2026
4724b81
Rotation: manual rotation and rotation lock next to automatic rotation
brkosebay Nov 12, 2026
cc58e93
Power: cover lock, idle dim and suspend readiness
brkosebay Nov 12, 2026
674577a
Brightness: optional smooth automatic brightness
brkosebay Nov 12, 2026
0be0126
Shell: persist display scale and gather off-screen windows
brkosebay Nov 12, 2026
48e6f72
Merge upstream main into j71t/rotation
brkosebay Nov 13, 2026
56e9cb8
Merge the j71t/rotation branch into j71t/power
brkosebay Nov 13, 2026
418af7a
Merge the j71t/power branch into j71t/brightness
brkosebay Nov 13, 2026
f3404de
Merge the j71t/brightness branch into j71t/display-scale
brkosebay Nov 13, 2026
f689e09
Power: keep upstream's wording for powerd's role
brkosebay Nov 13, 2026
93a0723
Merge the j71t/power branch into j71t/brightness
brkosebay Nov 13, 2026
331f036
Merge the j71t/brightness branch into j71t/display-scale
brkosebay Nov 13, 2026
3bf8d88
Brightness: neutral wording in a test note and the sensor doc
brkosebay Nov 13, 2026
b09be90
Merge the j71t/brightness branch into j71t/display-scale
brkosebay Nov 13, 2026
d5bb8b8
Power: drop the J71t Mac USB current profile notes
brkosebay Nov 13, 2026
1412b2e
Power: take suspend readiness from the kernel alone
brkosebay Nov 13, 2026
550a86c
Merge the j71t/power branch into j71t/brightness
brkosebay Nov 13, 2026
ad45ad1
Merge the j71t/brightness branch into j71t/display-scale
brkosebay Nov 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
862 changes: 862 additions & 0 deletions desktop/configuration/auto-brightness

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions desktop/configuration/labwc-autostart
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ rm -f "$XDG_RUNTIME_DIR/omarchy-overview-open"
/usr/local/libexec/arch-ipad-rotate-output > "$XDG_RUNTIME_DIR/arch-ipad-rotation.log" 2>&1 &
# Let user services launched after the compositor see its actual Wayland socket.
systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE > "$XDG_RUNTIME_DIR/arch-ipad-user-env.log" 2>&1
# Sensor policy follows the compositor lifetime; it stays off until enabled.
/usr/local/libexec/arch-ipad-auto-brightness > "$XDG_RUNTIME_DIR/arch-ipad-auto-brightness.log" 2>&1 &
# Keep the theme-coloured keyboard resident but hidden until the user taps Show.
export OMARCHY_PATH="$HOME/.local/share/omarchy-ipad"
printf '0\n' > "$XDG_RUNTIME_DIR/omarchy-keyboard-visible"
"$OMARCHY_PATH/bin/omarchy-keyboard" run > "$XDG_RUNTIME_DIR/omarchy-keyboard.log" 2>&1 &
qs -p "$OMARCHY_PATH/shell/shell.qml" > "$XDG_RUNTIME_DIR/omarchy-shell.log" 2>&1 &

# Observe touch without grabbing; actions are native labwc and honor lock/power.
9 changes: 8 additions & 1 deletion desktop/configuration/scale-output
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,11 @@ if (( ${#outputs[@]} != 1 )) || ! grep -q ' Enabled: yes' <<< "$state" ||
echo 'Native single-panel mode not established; left scale unchanged' >&2
exit 0
fi
wlr-randr --output "${outputs[0]}" --scale 2
# The user's choice from the Display panel (omarchy-display-scale), else 2.
scale=2
saved=${XDG_STATE_HOME:-$HOME/.local/state}/omarchy-ipad/display-scale
if [[ -r $saved ]]; then
value=$(<"$saved")
[[ $value =~ ^(1|1\.25|1\.5|1\.75|2|2\.25|2\.5|3)$ ]] && scale=$value
fi
wlr-randr --output "${outputs[0]}" --scale "$scale"
2 changes: 2 additions & 0 deletions desktop/shell/bin/omarchy-auto-brightness
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
exec /usr/local/libexec/arch-ipad-auto-brightness "$@"
21 changes: 21 additions & 0 deletions desktop/shell/bin/omarchy-display-scale
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/sh
# Show or set the panel's display scale. The choice persists: the session's
# arch-ipad-scale-output applies it again at the next login.
# omarchy-display-scale prints the current scale, e.g. 2
# omarchy-display-scale 1.75 applies and saves 1.75
set -eu
allowed='1 1.25 1.5 1.75 2 2.25 2.5 3'
state_dir=${XDG_STATE_HOME:-$HOME/.local/state}/omarchy-ipad
output=$(wlr-randr | awk '/^[^[:space:]]/ {print $1; exit}')
[ -n "$output" ] || { echo 'omarchy-display-scale: no output' >&2; exit 1; }
if [ $# -eq 0 ]; then
wlr-randr | awk '/^[^[:space:]]/ {seen = 1} seen && $1 == "Scale:" {print $2 + 0; exit}'
exit 0
fi
case " $allowed " in
*" $1 "*) ;;
*) echo "omarchy-display-scale: unsupported scale $1 (one of: $allowed)" >&2; exit 2 ;;
esac
wlr-randr --output "$output" --scale "$1"
mkdir -p "$state_dir"
printf '%s\n' "$1" > "$state_dir/display-scale"
24 changes: 24 additions & 0 deletions desktop/shell/bin/omarchy-rotate
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
"""Manual rotation uses the same output protocol as auto-rotation; holds auto until unlock."""
import json, os, subprocess, sys
from pathlib import Path
mode = sys.argv[1] if len(sys.argv)>1 else 'status'
outputs = json.loads(subprocess.check_output(['wlr-randr', '--json'], text=True))
panel = next((o for o in outputs if o['name'] == 'eDP-1'), None)
if panel is None:
panel = next(o for o in outputs if o.get('enabled'))
current = panel['transform']
turns = ['normal', '90', '180', '270']
if mode == 'status':
print(current); raise SystemExit(0)
if mode == 'next': mode = turns[(turns.index(current)+1)%4]
mode = {'portrait':'normal','landscape':'90','portrait-flipped':'180','landscape-flipped':'270'}.get(mode,mode)
if mode not in turns: raise SystemExit('Usage: omarchy-rotate status|next|normal|90|180|270')
lock = Path(os.environ.get('XDG_CONFIG_HOME', str(Path.home()/'.config')))/'omarchy/rotation-lock'
was_locked = lock.exists()
lock.parent.mkdir(parents=True,exist_ok=True); lock.touch()
try:
subprocess.run(['wlr-randr','--output',panel['name'],'--transform',mode],check=True,timeout=5)
except (OSError, subprocess.SubprocessError):
if not was_locked: lock.unlink(missing_ok=True)
raise
2 changes: 2 additions & 0 deletions desktop/shell/default/i18n/en/shell.json
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@
"other": "%1 selected"
},
"monitor.brightness": "BRIGHTNESS",
"monitor.autoBrightness": "Auto-brightness",
"monitor.autoBrightnessHint": "Follow the light sensor. Moving the slider pauses it for 30 s.",
"monitor.display": "Display",
"monitor.displayFocused": "%1 · focused",
"monitor.displays": "DISPLAYS",
Expand Down
5 changes: 4 additions & 1 deletion desktop/shell/default/omarchy/omarchy-menu.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
// System
"system.screensaver": {"icon":"󱄄","label":"Screensaver","action":"omarchy-launch-screensaver force"},
"system.lock": {"icon":"","label":"Lock","action":"omarchy-system-lock"},
"system.suspend": {"icon":"󰒲","label":"Suspend","when":"! omarchy-toggle-enabled suspend-off && ! omarchy-sleep-disabled","action":"systemctl suspend"},
"system.suspend": {"icon":"󰒲","label":"Suspend","when":"! omarchy-toggle-enabled suspend-off && /usr/local/bin/ipadlinux-suspend-ready","action":"/usr/local/bin/ipadlinux-suspend-ready && systemctl suspend"},
"system.hibernate": {"icon":"󰤁","label":"Hibernate","when":"omarchy-hibernation-available && ! omarchy-sleep-disabled","action":"systemctl hibernate"},
"system.logout": {"icon":"󰍃","label":"Logout","action":"omarchy-system logout"},
// Orchard: a reboot does not come back into Linux. The device's own boot chain starts its own system; Linux returns through DFU.
Expand Down Expand Up @@ -184,6 +184,9 @@
"trigger.tests.disk-speedtest": {"icon":"󰋊","label":"Disk Speed Test","action":"omarchy-shell shell summon omarchy.disk-speedtest"},

// Setup
"setup.tablet": {"icon":"󰦧","label":"Tablet"},
"setup.tablet.rotate": {"icon":"󰑢","label":"Rotate Once and Lock","action":"omarchy-rotate next"},
"setup.tablet.auto-brightness": {"icon":"󰃠","label":"Auto Brightness","when":"omarchy-auto-brightness available","checked":"omarchy-auto-brightness enabled","action":"omarchy-auto-brightness toggle"},
"setup.monitors": {"icon":"󰍹","label":"Monitors","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/monitors.lua\""},
"setup.keybindings": {"icon":"","label":"Keybindings","when":"[[ -f ~/.config/hypr/bindings.lua ]]","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/bindings.lua\""},
"setup.input": {"icon":"","label":"Input","when":"[[ -f ~/.config/hypr/input.lua ]]","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/input.lua\""},
Expand Down
12 changes: 12 additions & 0 deletions desktop/shell/labwc-rc.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@
<keybind key="W-Tab">
<action name="ToggleOverview" />
</keybind>
<!-- Gather: every window to this workspace, placed on screen and raised.
Rescues windows dragged off the panel. -->
<keybind key="W-S-g">
<action name="ForEach">
<query />
<then>
<action name="SendToDesktop" to="current" follow="no" />
<action name="AutoPlace" policy="automatic" />
<action name="Raise" />
</then>
</action>
</keybind>
<keybind key="A-Tab">
<action name="NextWindow" />
</keybind>
Expand Down
86 changes: 86 additions & 0 deletions desktop/shell/shell/plugins/panels/monitor/Panel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ Panel {
property bool brightnessAvailable: false
property int brightnessPercent: 0
property int queuedPercent: -1
// Ambient-light auto-brightness (omarchy-auto-brightness); hidden when the tool is absent.
property bool autoAvailable: false
property bool autoOn: false
// Display scale (omarchy-display-scale); saved and reapplied at login.
readonly property var scaleOptions: ["1.5", "1.75", "2", "2.25", "2.5"]
property string scaleValue: ""

// The shell's idle service owns Stay Awake (state file, bar indicator, IPC).
readonly property var idleService: bar?.shell?.firstPartyServiceFor("omarchy.idle")
readonly property bool stayAwake: idleService ? idleService.stayAwake === true : false
Expand All @@ -23,8 +30,24 @@ Panel {
readonly property string rotationHelper: (Quickshell.env("OMARCHY_PATH") ||
Quickshell.env("HOME") + "/.local/share/omarchy-ipad") + "/bin/omarchy-rotation-lock"

function setAuto(on) {
if (!autoAvailable || autoWriteProc.running) return
root.autoOn = on // optimistic: the knob moves at once
autoWriteProc.command = ["omarchy-auto-brightness", on ? "on" : "off"]
autoWriteProc.running = true
}

function setScale(value) {
if (scaleWriteProc.running || value === root.scaleValue) return
root.scaleValue = value
scaleWriteProc.command = ["omarchy-display-scale", value]
scaleWriteProc.running = true
}

function refresh() {
if (!readProc.running && !writeProc.running) readProc.running = true
if (!autoReadProc.running && !autoWriteProc.running) autoReadProc.running = true
if (!scaleReadProc.running && !scaleWriteProc.running) scaleReadProc.running = true
if (!rotationRead.running && !rotationWrite.running) rotationRead.running = true
}
function setBrightness(value) {
Expand Down Expand Up @@ -85,11 +108,47 @@ Panel {
}
}
}

Process {
id: autoReadProc
command: ["omarchy-auto-brightness", "status"]
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: {
var m = /^auto-brightness:\s*(on|off)/m.exec(String(text || ""))
root.autoAvailable = m !== null
if (m) root.autoOn = m[1] === "on"
}
}
}
Process {
id: rotationWrite
onExited: function(exitCode, exitStatus) { root.refresh() }
}

Process {
id: autoWriteProc
stdout: StdioCollector { waitForEnd: true }
onRunningChanged: if (!running) root.refresh()
}

Process {
id: scaleReadProc
command: ["omarchy-display-scale"]
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: {
var v = parseFloat(String(text || "").trim())
root.scaleValue = isNaN(v) ? "" : String(v)
}
}
}
Process {
id: scaleWriteProc
stdout: StdioCollector { waitForEnd: true }
onRunningChanged: if (!running) root.refresh()
}

BarIconButton {
id: button
anchors.fill: parent
Expand Down Expand Up @@ -182,6 +241,33 @@ Panel {
onMoved: function(v) { root.setBrightness(v) }
onReleased: function(v) { root.setBrightness(v) }
}
Toggle {
width: parent.width
visible: root.autoAvailable
label: Commons.I18n.t("monitor.autoBrightness")
description: Commons.I18n.t("monitor.autoBrightnessHint")
checked: root.autoOn
foreground: root.bar.foreground
fontFamily: root.bar.fontFamily
onClicked: root.setAuto(!root.autoOn)
}
Text {
width: parent.width
visible: root.scaleValue !== ""
text: Commons.I18n.t("monitor.scale")
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: Style.font.caption
textFormat: Text.PlainText
}
ButtonGroup {
visible: root.scaleValue !== ""
options: root.scaleOptions
value: root.scaleValue
foreground: root.bar.foreground
fontFamily: root.bar.fontFamily
onChanged: function(v) { root.setScale(v) }
}
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions desktop/shell/shell/plugins/services/idle/Service.qml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,19 @@ Item {
onTriggered: if (root.screensaverEnabled) root.launchScreensaver()
}

// Dim before the 5-minute lock. The power daemon serializes dim/ALS/off;
// input restores only if a manual brightness change has not superseded dim.
IdleMonitor {
id: dimMonitor
timeout: 180
enabled: Quickshell.env("OMARCHY_TOUCH_MODE") === "1" && root.idleEnabled && root.lockEnabled
onIsIdleChanged: {
dimProcess.command = ["/usr/libexec/omarchy-powerctl", isIdle ? "dim" : "undim"]
dimProcess.running = true
}
}
Process { id: dimProcess }

Timer {
id: lockTimer
repeat: false
Expand Down
8 changes: 2 additions & 6 deletions desktop/stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def main():
PORT / "fastfetch/about-compact.txt": config / "omarchy/branding/about-compact.txt",
CONFIG / "first-theme": target / "usr/local/libexec/arch-ipad-first-theme",
CONFIG / "scale-output": target / "usr/local/libexec/arch-ipad-scale-output",
CONFIG / "auto-brightness": target / "usr/local/libexec/arch-ipad-auto-brightness",
CONFIG / "rotate-output": target / "usr/local/libexec/arch-ipad-rotate-output",
CONFIG / "touch-output": target / "usr/local/libexec/arch-ipad-touch-output",
CONFIG / "login-session": target / "usr/local/libexec/arch-ipad-login-session",
Expand All @@ -123,16 +124,11 @@ def main():
copy(src, dst)
# Branding is static; kernel, memory and CPU information use live probes.
assert "VM prototype" not in (config / "fastfetch/config.jsonc").read_text()
# Offer Suspend only when the kernel-side charger PM contract is ready.
menu = source / "default/omarchy/omarchy-menu.jsonc"
original_menu = menu.read_text()
stock_guard = '"when":"! omarchy-toggle-enabled suspend-off && ! omarchy-sleep-disabled"'
assert original_menu.count(stock_guard) == 1
menu.write_text(original_menu.replace(stock_guard, '"when":"command -v ipadlinux-suspend-ready >/dev/null && ipadlinux-suspend-ready"'))
(config / "labwc/autostart").chmod(0o755)
(target / "usr/local/libexec/arch-ipad-first-theme").chmod(0o755)
(target / "usr/local/libexec/arch-ipad-scale-output").chmod(0o755)
(target / "usr/local/libexec/arch-ipad-rotate-output").chmod(0o755)
(target / "usr/local/libexec/arch-ipad-auto-brightness").chmod(0o755)
(target / "usr/local/libexec/arch-ipad-touch-output").chmod(0o755)
(target / "usr/local/libexec/arch-ipad-login-session").chmod(0o755)
# The base overlay preserves directory modes. Set the target layout
Expand Down
48 changes: 46 additions & 2 deletions docs/power-and-lock.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
of the screen, the lock request and sleep. Its state machine, the `Machine`
class, has no I/O and no clocks; `tests/test_powerd.py` walks every reachable
state with every input and checks the invariants below. Whether a board may
suspend at all is not decided here or by device data: every board may (see
orchard `docs/bring-up/suspend.md`); this model decides when.
suspend at all is decided by the kernel's suspend admission; this model decides
when. Where the kernel refuses, boards retain dim, blank and wake.

## Requirements

Expand Down Expand Up @@ -222,3 +222,47 @@ when `user.slice` thaws.
`omarchy-tablet-power` (one process per press, state in brightness and files, a
fixed window after resume), `omarchy-screen-toggle` and `omarchy-greeter-wake`
(a second and third writer of the backlight).

## Suspend readiness

The root overlay installs `/usr/local/bin/ipadlinux-suspend-ready` for both
fresh roots and public-root refreshes. It reads the kernel's admission query,
`/sys/power/suspend_ready` (Orchard's suspend topic), on every board: `ready`
exits 0; `not-ready <who> <errno>` exits 1 naming the refusing provider device
or precheck. The kernel runs every admission provider's non-reserving check
(GPU, video decoder) and every early precheck (PCI/NVMe, AOP, USB) for the
current `mem_sleep` state, takes no reservation, and answers
`not-ready transition` while a transition runs. A missing, unreadable or
malformed answer refuses: a kernel without the query cannot show that a suspend
would be admitted. There is no board list and no feature-state lookup. Refusal
returns 1 with a reason on stderr. Arguments are rejected with status 2. It
never writes sysfs, unbinds drivers, runs a PM test or requests suspend. The
power daemon logs refusal reasons. The answer is advisory; the real suspend
reserves and checks again.

The source menu used by both desktop staging and `release/public_root.py` uses
this helper for visibility and again before its suspend action. Powerd uses it
for every sleep request (Power, idle and closing the Smart Cover); a refusal
leaves the screen dark until touch, Home, Power or opening the cover wakes it,
without retrying sleep. Logind ignores lid, idle, suspend and power keys;
keyboard or direct logind/systemctl requests still pass the same helper in
`systemd-suspend.service`'s `ExecStartPre`. Hibernation, hybrid sleep and
suspend-then-hibernate remain disabled and their targets masked. No static
suspend mask or board-specific override is installed: the kernel's admission is
the only gate.

Device verification on a rebuilt image:

```sh
cat /sys/power/suspend_ready
timeout -k 5 10 /usr/local/bin/ipadlinux-suspend-ready
```

The helper's status must match the kernel's answer: 0 for `ready`, 1 with the
same refusal named for `not-ready`. While it refuses, `systemctl suspend` must
refuse too, the journal must name the kernel's refusal rather than a suspend
attempt, suspend success counters must not advance, and the final session menu
must omit Suspend. Use `timeout -k 5 5 omarchy-powerctl idle`, closing the
Smart Cover, and a short Power press separately; while suspend is refused each
must blank without sleep, and touch, Power or opening the cover must wake.
Dim/undim must still restore the previous brightness. These are pending device checks, not host-test results.
25 changes: 25 additions & 0 deletions docs/tablet-sensors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Tablet light and orientation

Orientation already comes from iio-sensor-proxy and the kernel's accelerometer mount matrix. Labwc maps touch to the panel, so display and touch rotate together. This remains the native orientation path; no extra board matrix or second sensor reader is installed.

The Tablet submenu under Setup adds Rotation Lock. `omarchy-rotation-lock on|off|toggle|status` uses the same lock as the orientation helper. While locked the helper keeps watching; after unlock it applies the last settled pose even without a new sensor event. Undefined/flat poses stay ignored and the existing half-second settling remains. The helper exits with labwc.

Auto Brightness uses the existing tablet ALS curve, smoothing, hysteresis, ramp and manual-change pause policy. It is **off by default** because panel/light calibration still needs device validation. Enable it in Setup → Tablet → Auto Brightness, or:

```sh
omarchy-auto-brightness on
omarchy-auto-brightness status
omarchy-auto-brightness off
```

It reads generic IIO lux or iio-sensor-proxy. Missing sensors are harmless; the menu action appears when an IIO light sensor and panel exist. Its process follows the compositor lifetime. Logs are in `$XDG_RUNTIME_DIR/arch-ipad-auto-brightness.log`; `--list` shows discovery and `--probe 20` computes a dry run without backlight writes.

User preferences live in `~/.config/omarchy/als-brightness.conf` and the switch is saved in `~/.local/state/omarchy/display`. Optional configuration keys are `enabled`, `backlight`, `curve`, `min_pct`, `max_pct`, `lux_scale`, `smooth_secs`, `hysteresis`, `min_pct_step`, `min_interval`, `ramp_pct_per_s`, `override_secs`, and `poll_hz`. The default curve is `0:10, 5:18, 20:30, 80:50, 250:70, 700:90, 1500:100` (lux:percent). Readings pass through a five-sample median and log-lux smoothing. Manual changes pause automatic adjustment for 30 seconds.

Automatic writes use omarchy-powerd's control socket, serialized with its existing screen/sleep/wake state machine. The request names the panel and supplies the expected old raw value. Powerd accepts only positive, in-range levels for that panel while lit and rejects stale values. A dark panel, unavailable power daemon or manual edit therefore stops a ramp. Wake still restores the level through powerd before automatic adjustment resumes. No brightnessctl helper, root user daemon or passwordless brightness sudo rule is added.

Both fresh desktop staging and the public-root builder include the helper, CLI commands, native rotation helper and updated menu. A running install needs those files plus the updated power daemon; restart omarchy-powerd and log into a fresh labwc session. Preserve local autostart/menu edits when applying by hand. The power daemon remains the sole owner of screen-off and sleep.

Host checks cover the retained light policy, switch state, missing power daemon, stale-value/dark-panel refusal, manual pause, actual socket writes, rotation debounce/lock/unlock and compositor exit, plus staging and public-root inclusion. Physical lux response, all four orientation directions, touch mapping and power-button/idle/sleep coexistence still need a device run. An AOP-disabled warm kernel cannot supply rotation data; cold boot through the matching kernel/m1n1 restores it.

Proposed changelog entry (not added): “Tablet: optional ambient-light brightness and rotation lock, with screen-off and sleep still owned by the power daemon.”
Loading
Loading