Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af0ba13573 |
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
readonly PANAMA_OSD_SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
|
|
||||||
strict_delivery() {
|
strict_delivery() {
|
||||||
[[ ${PANAMA_OSD_STRICT:-false} == true || ${PANAMA_OSD_STRICT:-false} == 1 ]]
|
[[ ${PANAMA_OSD_STRICT:-false} == true || ${PANAMA_OSD_STRICT:-false} == 1 ]]
|
||||||
}
|
}
|
||||||
@@ -69,151 +67,18 @@ adjust_microphone() {
|
|||||||
show_volume "$target" microphone
|
show_volume "$target" microphone
|
||||||
}
|
}
|
||||||
|
|
||||||
brightness_percent() {
|
|
||||||
local output="$1" percent
|
|
||||||
percent="$(awk -F, 'NR == 1 { value=$5; gsub(/%/, "", value); print value }' <<<"$output")"
|
|
||||||
[[ $percent =~ ^[0-9]+$ ]] || return 1
|
|
||||||
printf '%s\n' "$percent"
|
|
||||||
}
|
|
||||||
|
|
||||||
brightness_error() {
|
|
||||||
local detail="$1" label="External brightness unavailable"
|
|
||||||
if [[ $detail == *udev* || $detail == *accessible* || $detail == *permission* ]]; then
|
|
||||||
label="Brightness needs permission"
|
|
||||||
fi
|
|
||||||
|
|
||||||
show_message dialog-warning-symbolic "$label" || true
|
|
||||||
if command -v notify-send >/dev/null 2>&1; then
|
|
||||||
notify-send --app-name=Panama --icon=display-brightness-symbolic \
|
|
||||||
"Brightness unavailable" "$detail" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
discover_ddc_bus() {
|
|
||||||
local helper="$1" cache_file="$2" list_json focused selected error bus connector
|
|
||||||
|
|
||||||
command -v jq >/dev/null 2>&1 || {
|
|
||||||
brightness_error "jq is required to discover DDC/CI displays."
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
list_json="$("$helper" list 2>/dev/null)" || {
|
|
||||||
brightness_error "The external brightness helper could not inspect connected displays."
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if ! jq -e 'type == "object" and (.displays | type == "array")' >/dev/null 2>&1 <<<"$list_json"; then
|
|
||||||
brightness_error "The external brightness helper returned invalid display information."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
error="$(jq -r '.error // empty' <<<"$list_json")"
|
|
||||||
if [[ -n $error ]]; then
|
|
||||||
brightness_error "$error"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
focused="$(hyprctl -j monitors 2>/dev/null \
|
|
||||||
| jq -r '.[] | select(.focused == true) | .name' 2>/dev/null \
|
|
||||||
| head -n1)"
|
|
||||||
selected="$(jq -r --arg connector "$focused" '
|
|
||||||
([.displays[] | select(.connector == $connector)][0] // .displays[0] // empty)
|
|
||||||
| [.bus, .connector]
|
|
||||||
| @tsv
|
|
||||||
' <<<"$list_json")"
|
|
||||||
IFS=$'\t' read -r bus connector <<<"$selected"
|
|
||||||
if [[ ! $bus =~ ^[0-9]+$ ]]; then
|
|
||||||
brightness_error "No connected monitor exposes DDC/CI brightness control."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
umask 077
|
|
||||||
printf '%s\t%s\n' "$bus" "$connector" >"$cache_file"
|
|
||||||
printf '%s\n' "$bus"
|
|
||||||
}
|
|
||||||
|
|
||||||
adjust_ddc_brightness() {
|
|
||||||
local action="$1" step="$2"
|
|
||||||
local helper="${PANAMA_OSD_BRIGHTNESS_HELPER:-$PANAMA_OSD_SCRIPT_DIR/panama-brightness}"
|
|
||||||
local runtime_dir="${PANAMA_OSD_RUNTIME_DIR:-${XDG_RUNTIME_DIR:-/tmp}/panama-osd-${UID}}"
|
|
||||||
local cache_file="$runtime_dir/brightness-bus" lock_file="$runtime_dir/brightness.lock"
|
|
||||||
local bus="" connector="" current target lock_fd
|
|
||||||
|
|
||||||
[[ -x $helper ]] || {
|
|
||||||
brightness_error "The external brightness helper is not installed."
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
mkdir -p "$runtime_dir" || return 0
|
|
||||||
chmod 700 "$runtime_dir" 2>/dev/null || true
|
|
||||||
|
|
||||||
exec {lock_fd}>"$lock_file" || return 0
|
|
||||||
# DDC transactions on one I2C bus cannot safely overlap. A short wait also
|
|
||||||
# sheds an excessive key-repeat backlog instead of replaying it seconds later.
|
|
||||||
flock -w 2 "$lock_fd" || return 0
|
|
||||||
|
|
||||||
if [[ -r $cache_file ]]; then
|
|
||||||
IFS=$'\t' read -r bus connector <"$cache_file" || true
|
|
||||||
[[ $bus =~ ^[0-9]+$ ]] || bus=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n $bus ]]; then
|
|
||||||
current="$("$helper" get "$bus" 2>/dev/null)" || current=""
|
|
||||||
if [[ ! $current =~ ^[0-9]+$ ]]; then
|
|
||||||
: >"$cache_file"
|
|
||||||
bus=""
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z $bus ]]; then
|
|
||||||
bus="$(discover_ddc_bus "$helper" "$cache_file")" || return 0
|
|
||||||
current="$("$helper" get "$bus" 2>/dev/null)" || current=""
|
|
||||||
fi
|
|
||||||
if [[ ! $current =~ ^[0-9]+$ ]]; then
|
|
||||||
brightness_error "The selected monitor stopped responding over DDC/CI."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $action == up ]]; then
|
|
||||||
target=$(( current + step ))
|
|
||||||
else
|
|
||||||
target=$(( current - step ))
|
|
||||||
fi
|
|
||||||
(( target > 100 )) && target=100
|
|
||||||
(( target < 0 )) && target=0
|
|
||||||
|
|
||||||
if ! "$helper" set "$bus" "$target" >/dev/null 2>&1; then
|
|
||||||
brightness_error "The selected monitor did not accept the brightness change."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
show_progress brightness "$target" "${target}%"
|
|
||||||
}
|
|
||||||
|
|
||||||
adjust_brightness() {
|
adjust_brightness() {
|
||||||
local action="${1:-}" step="${2:-5}" output percent
|
local action="${1:-}" step="${2:-5}" output percent
|
||||||
[[ $step =~ ^[0-9]+$ ]] || {
|
|
||||||
printf 'Usage: panama-osd brightness up|down [step]\n' >&2
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
case "$action" in
|
case "$action" in
|
||||||
up|down) ;;
|
up) brightnessctl -e4 -n2 set "${step}%+" >/dev/null || return ;;
|
||||||
|
down) brightnessctl -e4 -n2 set "${step}%-" >/dev/null || return ;;
|
||||||
*) printf 'Usage: panama-osd brightness up|down [step]\n' >&2; return 2 ;;
|
*) printf 'Usage: panama-osd brightness up|down [step]\n' >&2; return 2 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Laptop panels expose a kernel backlight class and remain the fastest,
|
|
||||||
# most reliable path. Desktops fall through to DDC/CI monitor control.
|
|
||||||
output="$(brightnessctl -m -c backlight 2>/dev/null)" || output=""
|
|
||||||
if percent="$(brightness_percent "$output")"; then
|
|
||||||
if [[ $action == up ]]; then
|
|
||||||
brightnessctl -e4 -n2 -c backlight set "${step}%+" >/dev/null || return 0
|
|
||||||
else
|
|
||||||
brightnessctl -e4 -n2 -c backlight set "${step}%-" >/dev/null || return 0
|
|
||||||
fi
|
|
||||||
output="$(brightnessctl -m -c backlight 2>/dev/null)" || return 0
|
output="$(brightnessctl -m -c backlight 2>/dev/null)" || return 0
|
||||||
percent="$(brightness_percent "$output")" || return 0
|
percent="$(awk -F, 'NR == 1 { value=$5; gsub(/%/, "", value); print value }' <<<"$output")"
|
||||||
|
[[ $percent =~ ^[0-9]+$ ]] || return 0
|
||||||
show_progress brightness "$percent" "${percent}%"
|
show_progress brightness "$percent" "${percent}%"
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
adjust_ddc_brightness "$action" "$step"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
media_action() {
|
media_action() {
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
# Panama Health & Recovery Design
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Panama Health & Recovery makes the desktop explain itself. It verifies the
|
||||||
|
local services, dependencies, links, and integrations that Panama relies on,
|
||||||
|
then presents useful recovery actions without asking the user to read logs or
|
||||||
|
diagnose a collection of unrelated Linux processes.
|
||||||
|
|
||||||
|
The feature is intentionally quiet. A healthy desktop produces no notification,
|
||||||
|
banner, or permanent bar ornament. Problems appear in Panama Settings and, when
|
||||||
|
actionable, as one restrained bar indicator. User-initiated repairs receive
|
||||||
|
immediate Prism OSD or inline feedback.
|
||||||
|
|
||||||
|
## Product boundaries
|
||||||
|
|
||||||
|
The first release covers Panama-owned or Panama-integrated functionality:
|
||||||
|
|
||||||
|
- Hyprland, Quickshell, the notification server, XDG desktop portals, PipeWire,
|
||||||
|
Vicinae, the clipboard watcher, wallpaper, idle policy, and Panama's runtime
|
||||||
|
configuration links.
|
||||||
|
- The Panama command collection, screenshot and OCR dependencies, DDC
|
||||||
|
brightness support, and the currently selected terminal and launcher.
|
||||||
|
- Nextcloud, RustDesk, KDE Connect, BlueBubbles, Home Assistant, calendar
|
||||||
|
aggregation, and the configured autostart entries.
|
||||||
|
- Orphaned Panama processes and inhibitors, including duplicate Caffeine locks.
|
||||||
|
- Versions and non-sensitive diagnostic context needed for a useful copied
|
||||||
|
report.
|
||||||
|
|
||||||
|
It does not become a package manager, a generic system monitor, or a replacement
|
||||||
|
for Fedora's troubleshooting tools. It never installs packages, invokes `sudo`,
|
||||||
|
deletes user data, rewrites arbitrary configuration, or repairs services Panama
|
||||||
|
does not own.
|
||||||
|
|
||||||
|
An optional integration that has never been configured is neutral **Not set
|
||||||
|
up**, not a warning. A configured integration that cannot operate is degraded.
|
||||||
|
This distinction prevents the health UI from pressuring the user to enable
|
||||||
|
features they do not want.
|
||||||
|
|
||||||
|
## Information architecture
|
||||||
|
|
||||||
|
The existing **Startup & Services** destination becomes **System Health**. This
|
||||||
|
avoids two pages reporting the same background services. Its existing Open and
|
||||||
|
Refresh actions remain available through the richer health rows.
|
||||||
|
|
||||||
|
The page has four levels:
|
||||||
|
|
||||||
|
1. A compact summary hero: **Healthy**, **Needs attention**, or **Action
|
||||||
|
required**, the last completed scan time, Refresh, and Copy Report.
|
||||||
|
2. An issues-first section shown only when one or more checks are degraded.
|
||||||
|
3. Grouped cards for Desktop Foundation, Input & Media, Integrations, and Panama
|
||||||
|
Tools. Healthy rows remain visible but visually quiet.
|
||||||
|
4. A short boundary note linking to GNOME or Fedora tools for system areas Panama
|
||||||
|
does not own.
|
||||||
|
|
||||||
|
Each row contains a stable title, one-sentence observation, status label, and at
|
||||||
|
most one primary action. Actions use concrete language such as **Restart
|
||||||
|
Vicinae**, **Repair command link**, **Open Home settings**, or **View setup
|
||||||
|
instructions**. There is no generic Fix Everything button.
|
||||||
|
|
||||||
|
The Settings sidebar's existing health footer becomes real and clickable. It
|
||||||
|
shows the aggregate state and opens System Health. The top bar gains a small
|
||||||
|
`HealthIndicator` only while an actionable warning or error exists; clicking it
|
||||||
|
opens the same page. Background scans never publish Signal Glass events or
|
||||||
|
desktop notifications.
|
||||||
|
|
||||||
|
## Diagnostic engine
|
||||||
|
|
||||||
|
`config/dot/quickshell/scripts/panama-doctor` is the single operating-system
|
||||||
|
boundary. It supports:
|
||||||
|
|
||||||
|
- `panama-doctor --json` for a complete versioned snapshot.
|
||||||
|
- `panama-doctor --summary` for a concise human-readable installer or terminal
|
||||||
|
result.
|
||||||
|
- `panama-doctor --repair CHECK_ID --json` for an explicitly allow-listed repair.
|
||||||
|
|
||||||
|
The helper emits one schema:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"generatedAt": "2026-08-18T12:00:00Z",
|
||||||
|
"summary": {
|
||||||
|
"status": "warning",
|
||||||
|
"healthy": 18,
|
||||||
|
"warnings": 1,
|
||||||
|
"errors": 0,
|
||||||
|
"unconfigured": 2
|
||||||
|
},
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"id": "launcher.panama-commands",
|
||||||
|
"group": "panama-tools",
|
||||||
|
"title": "Panama Commands",
|
||||||
|
"status": "warning",
|
||||||
|
"detail": "16 of 17 commands are loaded",
|
||||||
|
"action": {
|
||||||
|
"kind": "repair",
|
||||||
|
"label": "Repair command link"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Allowed statuses are `ok`, `warning`, `error`, and `unconfigured`. Check IDs,
|
||||||
|
group IDs, titles, and repair mappings are authored constants. Probe output may
|
||||||
|
populate observations but can never become a command or executable argument.
|
||||||
|
|
||||||
|
Checks run concurrently where doing so is safe, with short per-probe timeouts.
|
||||||
|
A failed or timed-out probe yields a check result rather than aborting the whole
|
||||||
|
snapshot. Output order is deterministic so tests, copied reports, and visual
|
||||||
|
rows do not jump between scans.
|
||||||
|
|
||||||
|
No secrets are read. The report may state whether a Home Assistant URL or token
|
||||||
|
is configured, but never includes either value. It excludes clipboard contents,
|
||||||
|
notification bodies, calendar event data, SSIDs, device addresses, environment
|
||||||
|
values, file contents, and command output that has not been explicitly parsed.
|
||||||
|
|
||||||
|
## Quickshell state and refresh model
|
||||||
|
|
||||||
|
`services/Health.qml` owns the latest accepted snapshot, aggregate severity,
|
||||||
|
busy state, last scan time, and the result of the most recent repair. It invokes
|
||||||
|
`panama-doctor` with argument arrays through `Process`; UI components never
|
||||||
|
construct shell commands.
|
||||||
|
|
||||||
|
Health performs one delayed scan after the shell reaches a stable startup state.
|
||||||
|
It scans again when the System Health page is opened, when the user presses
|
||||||
|
Refresh, and after a repair settles. There is no periodic polling loop while the
|
||||||
|
desktop is idle. Services that already expose event-driven state remain the
|
||||||
|
authoritative source for their own interactive controls; Health is a diagnostic
|
||||||
|
snapshot, not a competing live service model.
|
||||||
|
|
||||||
|
Every scan receives a monotonically increasing generation. Late output from an
|
||||||
|
older scan is discarded. A malformed snapshot leaves the last valid result in
|
||||||
|
place, marks the diagnostic engine unavailable, and offers a bounded Retry.
|
||||||
|
|
||||||
|
The shell exposes a typed `health` IPC target with `refresh`, `status`, `open`,
|
||||||
|
and `repair(id)` operations. Vicinae gains **Panama: Check System Health**, which
|
||||||
|
opens the page and requests a fresh scan through the existing `panama-action`
|
||||||
|
dispatcher.
|
||||||
|
|
||||||
|
## Repair policy
|
||||||
|
|
||||||
|
Repairs are narrow, reversible, and attached to one check. The first release may:
|
||||||
|
|
||||||
|
- Restart Panama's user services such as Vicinae, Hyprpaper, or Hypridle.
|
||||||
|
- Recreate Panama-owned symlinks when their destination is known and tracked.
|
||||||
|
- Reload Vicinae's Panama command collection.
|
||||||
|
- Release duplicate user-owned inhibitors whose metadata identifies Panama and
|
||||||
|
Caffeine.
|
||||||
|
- Restart Quickshell through the verified `panama-action restart-shell` path.
|
||||||
|
- Open the exact Panama Settings page required to finish credentials or entity
|
||||||
|
selection.
|
||||||
|
|
||||||
|
Restarting a working service is not presented as a repair. Repairs that interrupt
|
||||||
|
visible desktop chrome require a confirmation sheet in Settings. Navigation and
|
||||||
|
setup actions do not. Package installation, privileged service changes, display
|
||||||
|
mode writes, and destructive cleanup are never automatic; the UI shows concise
|
||||||
|
instructions instead.
|
||||||
|
|
||||||
|
After a repair, Health rescans and judges success from the observed result. A
|
||||||
|
zero exit status alone never turns a row green. Failure remains inline on the
|
||||||
|
affected row and also produces the existing Panama action-failure notification
|
||||||
|
when the action originated outside Settings.
|
||||||
|
|
||||||
|
## Visual language and interaction
|
||||||
|
|
||||||
|
System Health uses the established Settings cards and Prism tokens. Healthy
|
||||||
|
states use a small muted green dot and subdued **Healthy** copy. Warnings use
|
||||||
|
amber; red is reserved for functionality that is configured, required, and
|
||||||
|
currently broken. `unconfigured` rows use neutral gray.
|
||||||
|
|
||||||
|
The summary hero does not use a decorative gauge, percentage score, pulse,
|
||||||
|
shimmer, or animated gradient. A desktop is not “82% healthy.” The headline and
|
||||||
|
issue count are more understandable and do not create false precision.
|
||||||
|
|
||||||
|
Rows keep their height stable while refreshing. The previous snapshot remains
|
||||||
|
visible with a quiet **Checking…** label rather than replacing the page with a
|
||||||
|
spinner. Keyboard focus order reaches Refresh, Copy Report, issue rows, repair
|
||||||
|
actions, and external handoffs. Status is always expressed in text as well as
|
||||||
|
color.
|
||||||
|
|
||||||
|
Before production components are edited, the page and degraded bar indicator
|
||||||
|
will be shown in several static mocks using the existing Settings geometry. The
|
||||||
|
chosen mock must preserve this information architecture and Panama's current
|
||||||
|
Prism language rather than introduce a new visual system.
|
||||||
|
|
||||||
|
## Failure handling
|
||||||
|
|
||||||
|
- Missing required executables become actionable check results.
|
||||||
|
- Missing optional applications remain neutral until configured.
|
||||||
|
- A doctor crash, timeout, or malformed JSON does not clear the last good
|
||||||
|
snapshot or crash Quickshell.
|
||||||
|
- Concurrent refresh requests coalesce into one follow-up scan.
|
||||||
|
- A repair request for an unknown or non-repairable ID is rejected before any
|
||||||
|
process starts.
|
||||||
|
- Copy Report uses only the already-redacted snapshot and reports clipboard
|
||||||
|
failure inline.
|
||||||
|
- If the Settings window is closed during a scan or repair, the process may
|
||||||
|
finish; reopening the page shows the settled result.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Run the real helper against isolated fake command, config, state, and runtime
|
||||||
|
directories and prove every status transition deterministically.
|
||||||
|
- Validate the JSON schema, stable check IDs, deterministic ordering, and
|
||||||
|
uniqueness of each ID.
|
||||||
|
- Prove unconfigured integrations remain neutral while configured failures are
|
||||||
|
degraded.
|
||||||
|
- Prove reports contain no fixture secrets, clipboard text, calendar data,
|
||||||
|
addresses, or unparsed environment values.
|
||||||
|
- Exercise every repair through the allow-list, assert its exact command, and
|
||||||
|
prove unknown IDs cannot execute anything.
|
||||||
|
- Test scan generations, malformed snapshots, refresh coalescing, repair
|
||||||
|
rescans, and preservation of the last valid state in a Quickshell harness.
|
||||||
|
- Verify Settings routing, search entries, the live sidebar footer, and the
|
||||||
|
degraded-only bar indicator without QML warnings.
|
||||||
|
- Validate the Vicinae command and typed IPC surface.
|
||||||
|
- Run a read-only doctor scan on the real workstation and compare key results to
|
||||||
|
direct service checks. State-changing live repair tests require an actually
|
||||||
|
degraded disposable target or explicit user approval.
|
||||||
|
- Restart the live shell, inspect the fresh log, and visually review healthy,
|
||||||
|
warning, error, unconfigured, refreshing, and repair-result states.
|
||||||
|
|
||||||
|
## Delivery slices
|
||||||
|
|
||||||
|
1. Diagnostic schema, read-only probes, redaction, and contract tests.
|
||||||
|
2. `Health.qml`, typed IPC, startup/manual refresh, and fixture harness.
|
||||||
|
3. System Health Settings page, live sidebar footer, search, and report copy.
|
||||||
|
4. Degraded-only bar indicator and Vicinae command.
|
||||||
|
5. Allow-listed repairs, confirmations, post-repair verification, and live audit.
|
||||||
|
|
||||||
|
The slices are one feature and land together. Their order keeps the UI backed by
|
||||||
|
real diagnostics from its first production render.
|
||||||
@@ -26,62 +26,10 @@ printf 'brightnessctl' >>"$OSD_TEST_LOG"
|
|||||||
printf ' <%s>' "$@" >>"$OSD_TEST_LOG"
|
printf ' <%s>' "$@" >>"$OSD_TEST_LOG"
|
||||||
printf '\n' >>"$OSD_TEST_LOG"
|
printf '\n' >>"$OSD_TEST_LOG"
|
||||||
if [[ " $* " == *" -m "* && " $* " != *" set "* ]]; then
|
if [[ " $* " == *" -m "* && " $* " != *" set "* ]]; then
|
||||||
[[ ${BACKLIGHT_AVAILABLE:-true} == true ]] || exit 1
|
|
||||||
printf '%s\n' "${BRIGHTNESS_OUTPUT:-intel_backlight,backlight,500,1000,50%}"
|
printf '%s\n' "${BRIGHTNESS_OUTPUT:-intel_backlight,backlight,500,1000,50%}"
|
||||||
fi
|
fi
|
||||||
SH
|
SH
|
||||||
|
|
||||||
cat >"$scratch/bin/panama-brightness" <<'SH'
|
|
||||||
#!/bin/bash
|
|
||||||
printf 'panama-brightness' >>"$OSD_TEST_LOG"
|
|
||||||
printf ' <%s>' "$@" >>"$OSD_TEST_LOG"
|
|
||||||
printf '\n' >>"$OSD_TEST_LOG"
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
list)
|
|
||||||
if [[ -n ${DDC_LIST_JSON:-} ]]; then
|
|
||||||
printf '%s\n' "$DDC_LIST_JSON"
|
|
||||||
else
|
|
||||||
printf '%s\n' '{"displays":[],"error":"No displays"}'
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
get)
|
|
||||||
[[ ${DDC_FAIL_GET_BUS:-} != "${2:-}" ]] || exit 1
|
|
||||||
if [[ -s $OSD_DDC_STATE ]]; then
|
|
||||||
cat "$OSD_DDC_STATE"
|
|
||||||
else
|
|
||||||
printf '%s\n' "${DDC_GET_VALUE:-40}"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
set)
|
|
||||||
if [[ -n ${DDC_SET_DELAY:-} ]]; then
|
|
||||||
if ! mkdir "$OSD_DDC_PROBE" 2>/dev/null; then
|
|
||||||
printf 'ddc-overlap\n' >>"$OSD_TEST_LOG"
|
|
||||||
fi
|
|
||||||
sleep "$DDC_SET_DELAY"
|
|
||||||
rmdir "$OSD_DDC_PROBE" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
printf '%s\n' "${3:-0}" >"$OSD_DDC_STATE"
|
|
||||||
;;
|
|
||||||
*) exit 2 ;;
|
|
||||||
esac
|
|
||||||
SH
|
|
||||||
|
|
||||||
cat >"$scratch/bin/hyprctl" <<'SH'
|
|
||||||
#!/bin/bash
|
|
||||||
printf 'hyprctl' >>"$OSD_TEST_LOG"
|
|
||||||
printf ' <%s>' "$@" >>"$OSD_TEST_LOG"
|
|
||||||
printf '\n' >>"$OSD_TEST_LOG"
|
|
||||||
printf '[{"name":"%s","focused":true}]\n' "${FOCUSED_MONITOR:-DP-2}"
|
|
||||||
SH
|
|
||||||
|
|
||||||
cat >"$scratch/bin/notify-send" <<'SH'
|
|
||||||
#!/bin/bash
|
|
||||||
printf 'notify-send' >>"$OSD_TEST_LOG"
|
|
||||||
printf ' <%s>' "$@" >>"$OSD_TEST_LOG"
|
|
||||||
printf '\n' >>"$OSD_TEST_LOG"
|
|
||||||
SH
|
|
||||||
|
|
||||||
cat >"$scratch/bin/playerctl" <<'SH'
|
cat >"$scratch/bin/playerctl" <<'SH'
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
printf 'playerctl' >>"$OSD_TEST_LOG"
|
printf 'playerctl' >>"$OSD_TEST_LOG"
|
||||||
@@ -105,21 +53,9 @@ SH
|
|||||||
chmod +x "$scratch/bin/"*
|
chmod +x "$scratch/bin/"*
|
||||||
|
|
||||||
run_helper() {
|
run_helper() {
|
||||||
local runtime="${OSD_RUNTIME_DIR:-$scratch/runtime-default}"
|
|
||||||
mkdir -p "$runtime"
|
|
||||||
PATH="$scratch/bin:$PATH" OSD_TEST_LOG="$log" \
|
PATH="$scratch/bin:$PATH" OSD_TEST_LOG="$log" \
|
||||||
OSD_TEST_FAIL_QS="${OSD_TEST_FAIL_QS:-false}" \
|
OSD_TEST_FAIL_QS="${OSD_TEST_FAIL_QS:-false}" \
|
||||||
PANAMA_OSD_STRICT="${PANAMA_OSD_STRICT:-false}" \
|
PANAMA_OSD_STRICT="${PANAMA_OSD_STRICT:-false}" \
|
||||||
PANAMA_OSD_BRIGHTNESS_HELPER="$scratch/bin/panama-brightness" \
|
|
||||||
PANAMA_OSD_RUNTIME_DIR="$runtime" \
|
|
||||||
OSD_DDC_STATE="$runtime/ddc-state" \
|
|
||||||
OSD_DDC_PROBE="$runtime/ddc-probe" \
|
|
||||||
BACKLIGHT_AVAILABLE="${BACKLIGHT_AVAILABLE:-true}" \
|
|
||||||
DDC_LIST_JSON="${DDC_LIST_JSON:-}" \
|
|
||||||
DDC_GET_VALUE="${DDC_GET_VALUE:-40}" \
|
|
||||||
DDC_FAIL_GET_BUS="${DDC_FAIL_GET_BUS:-}" \
|
|
||||||
DDC_SET_DELAY="${DDC_SET_DELAY:-}" \
|
|
||||||
FOCUSED_MONITOR="${FOCUSED_MONITOR:-DP-2}" \
|
|
||||||
"$helper" "$@"
|
"$helper" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,102 +86,9 @@ assert_line 'qs <ipc> <call> <osd> <progress> <microphone-muted> <72> <100> <Mut
|
|||||||
|
|
||||||
: >"$log"
|
: >"$log"
|
||||||
run_helper brightness up 5
|
run_helper brightness up 5
|
||||||
|
assert_line 'brightnessctl <-e4> <-n2> <set> <5%+>'
|
||||||
assert_line 'brightnessctl <-m> <-c> <backlight>'
|
assert_line 'brightnessctl <-m> <-c> <backlight>'
|
||||||
assert_line 'brightnessctl <-e4> <-n2> <-c> <backlight> <set> <5%+>'
|
|
||||||
assert_line 'qs <ipc> <call> <osd> <progress> <brightness> <50> <100> <50%>'
|
assert_line 'qs <ipc> <call> <osd> <progress> <brightness> <50> <100> <50%>'
|
||||||
if grep -Fq 'panama-brightness' "$log"; then
|
|
||||||
printf 'osd helper contract: DDC fallback ran despite a native backlight\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
: >"$log"
|
|
||||||
OSD_RUNTIME_DIR="$scratch/runtime-ddc" \
|
|
||||||
BACKLIGHT_AVAILABLE=false \
|
|
||||||
DDC_LIST_JSON='{"displays":[{"bus":3,"connector":"HDMI-A-1","value":35},{"bus":5,"connector":"DP-2","value":40}],"error":""}' \
|
|
||||||
run_helper brightness up 5
|
|
||||||
assert_line 'hyprctl <-j> <monitors>'
|
|
||||||
assert_line 'panama-brightness <list>'
|
|
||||||
assert_line 'panama-brightness <get> <5>'
|
|
||||||
assert_line 'panama-brightness <set> <5> <45>'
|
|
||||||
assert_line 'qs <ipc> <call> <osd> <progress> <brightness> <45> <100> <45%>'
|
|
||||||
|
|
||||||
# A cached bus avoids the expensive display scan on subsequent key presses.
|
|
||||||
: >"$log"
|
|
||||||
OSD_RUNTIME_DIR="$scratch/runtime-ddc" \
|
|
||||||
BACKLIGHT_AVAILABLE=false \
|
|
||||||
DDC_LIST_JSON='{"displays":[{"bus":3,"connector":"HDMI-A-1","value":35},{"bus":5,"connector":"DP-2","value":45}],"error":""}' \
|
|
||||||
run_helper brightness down 5
|
|
||||||
assert_line 'panama-brightness <get> <5>'
|
|
||||||
assert_line 'panama-brightness <set> <5> <40>'
|
|
||||||
assert_line 'qs <ipc> <call> <osd> <progress> <brightness> <40> <100> <40%>'
|
|
||||||
if grep -Fq 'panama-brightness <list>' "$log"; then
|
|
||||||
printf 'osd helper contract: cached DDC bus triggered another display scan\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# A disconnected cached monitor is discarded and rediscovered once.
|
|
||||||
mkdir -p "$scratch/runtime-ddc-stale"
|
|
||||||
printf '9\tDP-9\n' >"$scratch/runtime-ddc-stale/brightness-bus"
|
|
||||||
: >"$log"
|
|
||||||
OSD_RUNTIME_DIR="$scratch/runtime-ddc-stale" \
|
|
||||||
BACKLIGHT_AVAILABLE=false \
|
|
||||||
DDC_FAIL_GET_BUS=9 \
|
|
||||||
DDC_LIST_JSON='{"displays":[{"bus":5,"connector":"DP-2","value":40}],"error":""}' \
|
|
||||||
run_helper brightness up 5
|
|
||||||
assert_line 'panama-brightness <get> <9>'
|
|
||||||
assert_line 'panama-brightness <list>'
|
|
||||||
assert_line 'panama-brightness <get> <5>'
|
|
||||||
assert_line 'panama-brightness <set> <5> <45>'
|
|
||||||
|
|
||||||
# If the focused output is not DDC-capable, use the first discovered display.
|
|
||||||
: >"$log"
|
|
||||||
OSD_RUNTIME_DIR="$scratch/runtime-ddc-first" \
|
|
||||||
BACKLIGHT_AVAILABLE=false \
|
|
||||||
FOCUSED_MONITOR='eDP-1' \
|
|
||||||
DDC_GET_VALUE=35 \
|
|
||||||
DDC_LIST_JSON='{"displays":[{"bus":3,"connector":"HDMI-A-1","value":35},{"bus":5,"connector":"DP-2","value":40}],"error":""}' \
|
|
||||||
run_helper brightness down 10
|
|
||||||
assert_line 'panama-brightness <get> <3>'
|
|
||||||
assert_line 'panama-brightness <set> <3> <25>'
|
|
||||||
assert_line 'qs <ipc> <call> <osd> <progress> <brightness> <25> <100> <25%>'
|
|
||||||
|
|
||||||
# Permission and discovery errors must be visible, never masquerade as 0%.
|
|
||||||
: >"$log"
|
|
||||||
OSD_RUNTIME_DIR="$scratch/runtime-ddc-error" \
|
|
||||||
BACKLIGHT_AVAILABLE=false \
|
|
||||||
DDC_LIST_JSON='{"displays":[],"error":"Run sudo udevadm control --reload-rules && sudo udevadm trigger --subsystem-match=i2c-dev --subsystem-match=drm"}' \
|
|
||||||
run_helper brightness up 5
|
|
||||||
assert_line 'qs <ipc> <call> <osd> <message> <dialog-warning-symbolic> <Brightness needs permission>'
|
|
||||||
assert_line 'notify-send <--app-name=Panama> <--icon=display-brightness-symbolic> <Brightness unavailable> <Run sudo udevadm control --reload-rules && sudo udevadm trigger --subsystem-match=i2c-dev --subsystem-match=drm>'
|
|
||||||
if grep -Fq 'osd> <progress> <brightness>' "$log"; then
|
|
||||||
printf 'osd helper contract: unavailable brightness rendered a false percentage\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Separate key-repeat processes must not overlap their DDC transactions.
|
|
||||||
: >"$log"
|
|
||||||
OSD_RUNTIME_DIR="$scratch/runtime-ddc-lock" \
|
|
||||||
BACKLIGHT_AVAILABLE=false \
|
|
||||||
DDC_SET_DELAY=0.15 \
|
|
||||||
DDC_LIST_JSON='{"displays":[{"bus":5,"connector":"DP-2","value":40}],"error":""}' \
|
|
||||||
run_helper brightness up 5 &
|
|
||||||
first_pid=$!
|
|
||||||
OSD_RUNTIME_DIR="$scratch/runtime-ddc-lock" \
|
|
||||||
BACKLIGHT_AVAILABLE=false \
|
|
||||||
DDC_SET_DELAY=0.15 \
|
|
||||||
DDC_LIST_JSON='{"displays":[{"bus":5,"connector":"DP-2","value":40}],"error":""}' \
|
|
||||||
run_helper brightness up 5 &
|
|
||||||
second_pid=$!
|
|
||||||
wait "$first_pid"
|
|
||||||
wait "$second_pid"
|
|
||||||
if grep -Fqx 'ddc-overlap' "$log"; then
|
|
||||||
printf 'osd helper contract: concurrent DDC transactions overlapped\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [[ $(<"$scratch/runtime-ddc-lock/ddc-state") != 50 ]]; then
|
|
||||||
printf 'osd helper contract: serialized key repeats did not both apply\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
: >"$log"
|
: >"$log"
|
||||||
run_helper media next
|
run_helper media next
|
||||||
|
|||||||
Reference in New Issue
Block a user