377 lines
16 KiB
Bash
377 lines
16 KiB
Bash
# tools/linux/display-layout-env.sh
|
|
#
|
|
# Sourced by a window manager session script (e.g.
|
|
# tools/arch_usb/wm-session.sh) before `target/release/makepad-app-wm` is
|
|
# exec'd, to turn the saved
|
|
# ${XDG_CONFIG_HOME:-$HOME/.config}/makepad/wm/display-layout
|
|
# file into the env vars the platform reads before Event::Startup
|
|
# (platform/src/os/linux/vulkan_linux.rs:new_direct, called before the WM's
|
|
# own `Event::Startup`, so the WM cannot set these for its own process):
|
|
#
|
|
# MAKEPAD_DISPLAY_ORDER this-boot output names, left to right
|
|
# MAKEPAD_DRM_MODES per-screen modes
|
|
# MAKEPAD_VULKAN_COMPOSITOR_PCI the render-on GPU's PCI address
|
|
# MAKEPAD_VIRTUAL_DISPLAY "WxH@Hz panels" from the `virtual` line
|
|
# (the virtual desktop's fallback size and
|
|
# what the panels show: mirror or off)
|
|
#
|
|
# File format and the screen-key resolution rule are in
|
|
# apps/wm/src/shell/display_layout.rs (the pure model). A screen's
|
|
# key is (card PCI address, connector name without its `cardN-` prefix);
|
|
# resolving it to this boot's name means finding the card whose PCI
|
|
# address (the basename of `readlink -f /sys/class/drm/cardN/device`)
|
|
# matches, then using `cardN-<connector>` as the name. A screen key that
|
|
# does not resolve this boot is dropped, never matched by connector alone.
|
|
#
|
|
# Safe to source with `set -e`/`set -u` active in the caller: this file
|
|
# never calls `exit`, copes with a missing or unreadable `display-layout`
|
|
# (an existing-but-unreadable file is treated as empty, exactly as
|
|
# `read_display_layout` in system_linux.rs does: the file's *presence*
|
|
# decides whether the legacy fallback below applies, not its readability),
|
|
# and leaves no function or variable behind once it returns -- everything
|
|
# lives in `local`s inside the functions below, and every function this
|
|
# file defines is `unset -f` at the end.
|
|
#
|
|
# `MAKEPAD_SYSFS_DRM` (default `/sys/class/drm`) is the sysfs root used to
|
|
# build the `pci -> cardN` map, overridable so this can be tested against
|
|
# a fake tree (see tools/linux/display-layout-env.test.sh).
|
|
#
|
|
# Each of the three env vars above is exported only when it is currently
|
|
# unset or empty -- an externally set, non-empty value always wins over
|
|
# the file, matching the rule the WM's own runtime restore applies
|
|
# (apps/wm/src/shell/display_layout.rs:env_restore_plan).
|
|
#
|
|
# `render-on` is read from `display-layout` when that file exists (even
|
|
# if it has no `render-on` line, meaning "Auto": no fallback then). When
|
|
# `display-layout` does not exist at all, this falls back to the legacy
|
|
# `display-gpu` file for one release, exactly as the GPU block in
|
|
# tools/arch_usb/wm-session.sh (which this helper replaces) did. When the
|
|
# helper itself sets `MAKEPAD_VULKAN_COMPOSITOR_PCI` from either source,
|
|
# it also sets `MAKEPAD_WM_GPU_FROM_SAVED=1` so the worker's `gpu_env()`
|
|
# (system_linux.rs) does not mistake the saved choice for a true external
|
|
# override; that flag is always unset first so a stale one from the
|
|
# caller's environment cannot persist by accident.
|
|
#
|
|
# `MAKEPAD_DISPLAY_ORDER` and `MAKEPAD_DRM_MODES` get the same treatment,
|
|
# but with one marker each -- `MAKEPAD_WM_ORDER_FROM_SAVED=1` /
|
|
# `MAKEPAD_WM_MODES_FROM_SAVED=1` -- rather than a single combined flag,
|
|
# because the two variables are independent exports that can disagree
|
|
# about which one this helper set from the file and which one was
|
|
# already pinned from outside (e.g. a caller that only pins the order).
|
|
# A single marker could not tell the two cases apart; the worker reads
|
|
# both into `SystemSnapshot::order_env`/`modes_env` (`system_linux.rs`),
|
|
# and its runtime restore of the saved layout
|
|
# (`display_layout::env_restore_plan`, `linux_controls.rs`) skips the
|
|
# Order op entirely while `order_env` is pinned, and a screen's Mode op
|
|
# while that screen's name appears in a pinned `modes_env` -- the same
|
|
# "an externally set env var always wins" rule this helper already
|
|
# applies at session start, also honoured by the WM's own safety-net
|
|
# restore. Both markers are always unset first, same reason as the GPU
|
|
# one.
|
|
#
|
|
# `MAKEPAD_VIRTUAL_DISPLAY` follows the same rules, with the marker
|
|
# `MAKEPAD_WM_VIRTUAL_FROM_SAVED=1`: `virtual WxH@Hz panels=mirror|off ...`
|
|
# (the first valid line wins; the scale= and follow= tokens are the WM's
|
|
# own, not the renderer's).
|
|
#
|
|
# One line per export/skip decision is logged to stderr, prefixed
|
|
# `display-layout:`.
|
|
|
|
# `pci-address` as sysfs writes and `validate_gpu_choice` requires it:
|
|
# `dddd:bb:ss.f`, 4-8 hex digits for the domain, lowercase.
|
|
_mdl_is_pci_address() {
|
|
[[ "$1" =~ ^[0-9a-f]{4,8}:[0-9a-f]{2}:[0-9a-f]{2}\.[0-9a-f]$ ]]
|
|
}
|
|
|
|
# `WxH` or `WxH@Hz` (Hz may be fractional), digits only.
|
|
_mdl_virtual_mode_is_valid() {
|
|
[[ "$1" =~ ^[0-9]+x[0-9]+(@[0-9]+(\.[0-9]+)?)?$ ]]
|
|
}
|
|
|
|
# `vvvv:dddd`, lowercase hex, as `validate_gpu_choice` requires.
|
|
_mdl_is_pci_ids() {
|
|
[[ "$1" =~ ^[0-9a-f]{4}:[0-9a-f]{4}$ ]]
|
|
}
|
|
|
|
# Mirrors `mode_string_is_valid` (vulkan_linux.rs): `WxH`, `WxH@Hz` or
|
|
# `WxH-Hz`, digits only.
|
|
_mdl_mode_is_valid() {
|
|
[[ "$1" =~ ^[0-9]+x[0-9]+([@-][0-9]+)?$ ]]
|
|
}
|
|
|
|
# Prints one `<pci> <vendor:device> <cardN>` line per usable card under
|
|
# $1 (the sysfs DRM root): a `cardN` entry (not a `cardN-<connector>`
|
|
# status directory) whose `device` symlink resolves. Glob-dependent, so
|
|
# the caller must not call this while `set -f` (noglob) is in effect.
|
|
_mdl_build_card_table() {
|
|
local sysfs_root="$1"
|
|
local card_dir card pci ven dev
|
|
for card_dir in "$sysfs_root"/card[0-9]*; do
|
|
[ -d "$card_dir" ] || continue
|
|
card=$(basename "$card_dir")
|
|
case "$card" in *-*) continue ;; esac
|
|
[ -e "$card_dir/device" ] || continue
|
|
pci=$(readlink -f "$card_dir/device" 2>/dev/null) || continue
|
|
pci=$(basename "$pci" 2>/dev/null) || continue
|
|
[ -n "$pci" ] || continue
|
|
ven=$(cat "$card_dir/device/vendor" 2>/dev/null); ven=${ven#0x}
|
|
dev=$(cat "$card_dir/device/device" 2>/dev/null); dev=${dev#0x}
|
|
printf '%s %s:%s %s\n' "$pci" "$ven" "$dev" "$card"
|
|
done
|
|
}
|
|
|
|
# Prints the `cardN` whose row in $1 (the card table) has pci $2; empty
|
|
# and a non-zero return when there is none.
|
|
_mdl_card_for_pci() {
|
|
local table="$1" want_pci="$2" row_pci row_ids row_card
|
|
while IFS=' ' read -r row_pci row_ids row_card; do
|
|
[ -n "$row_pci" ] || continue
|
|
if [ "$row_pci" = "$want_pci" ]; then
|
|
printf '%s\n' "$row_card"
|
|
return 0
|
|
fi
|
|
done <<<"$table"
|
|
return 1
|
|
}
|
|
|
|
# True when $1 (the card table) has a row matching both pci $2 and
|
|
# vendor:device $3.
|
|
_mdl_table_has_match() {
|
|
local table="$1" want_pci="$2" want_ids="$3" row_pci row_ids row_card
|
|
while IFS=' ' read -r row_pci row_ids row_card; do
|
|
[ -n "$row_pci" ] || continue
|
|
if [ "$row_pci" = "$want_pci" ] && [ "$row_ids" = "$want_ids" ]; then
|
|
return 0
|
|
fi
|
|
done <<<"$table"
|
|
return 1
|
|
}
|
|
|
|
# Exports $1=$2 (logged as "$3") unless $1 is already set and non-empty
|
|
# (logged instead). Returns non-zero when it left the existing value
|
|
# alone, so callers can tell which branch ran.
|
|
_mdl_set_env() {
|
|
local name="$1" value="$2" desc="$3" current
|
|
current=${!name:-}
|
|
if [ -n "$current" ]; then
|
|
echo "display-layout: $name already set; leaving it alone" >&2
|
|
return 1
|
|
fi
|
|
export "$name=$value"
|
|
echo "display-layout: $name=$value ($desc)" >&2
|
|
return 0
|
|
}
|
|
|
|
# The legacy `display-gpu` fallback this replaces
|
|
# (tools/arch_usb/wm-session.sh's former inline block): exactly one line
|
|
# "<pci-address> <vendor>:<device>", with or without a final newline,
|
|
# nothing else in the file. Prints "<pci> <ids>" on a match; nothing and
|
|
# a non-zero return otherwise.
|
|
_mdl_legacy_gpu_choice() {
|
|
local gpu_file="$1" line="" terminated=0 bytes want_pci want_ids
|
|
[ -r "$gpu_file" ] || return 1
|
|
if IFS= read -r line <"$gpu_file"; then
|
|
terminated=1
|
|
fi
|
|
bytes=$(LC_ALL=C wc -c <"$gpu_file" 2>/dev/null | tr -d '[:space:]')
|
|
[ "$bytes" = "$((${#line} + terminated))" ] || return 1
|
|
want_pci=${line% *}
|
|
want_ids=${line#* }
|
|
[ "$want_pci $want_ids" = "$line" ] || return 1
|
|
_mdl_is_pci_address "$want_pci" || return 1
|
|
_mdl_is_pci_ids "$want_ids" || return 1
|
|
printf '%s %s\n' "$want_pci" "$want_ids"
|
|
}
|
|
|
|
# The whole helper, run as the condition of the `if` below so that
|
|
# whatever fails inside it (a missing file, a bad sysfs read, ...) never
|
|
# trips the caller's `set -e`.
|
|
_mdl_main() {
|
|
local cfg_root layout_file gpu_file sysfs_root
|
|
local layout_exists=0 layout_readable=0
|
|
local render_on_pci="" render_on_ids=""
|
|
local order_list="" modes_list=""
|
|
local virtual_mode="" virtual_panels="mirror"
|
|
local card_table=""
|
|
local had_noglob=0
|
|
local line keyword pci connector token mode value name card
|
|
local seen_keys=$'\x01'
|
|
|
|
cfg_root=${XDG_CONFIG_HOME:-}
|
|
case "$cfg_root" in
|
|
/*) ;;
|
|
*) cfg_root=${HOME:-}/.config ;;
|
|
esac
|
|
layout_file="$cfg_root/makepad/wm/display-layout"
|
|
gpu_file="$cfg_root/makepad/wm/display-gpu"
|
|
sysfs_root=${MAKEPAD_SYSFS_DRM:-/sys/class/drm}
|
|
|
|
# Glob-enabled: build the pci -> cardN(+ids) map once, before any file
|
|
# content is word-split.
|
|
card_table=$(_mdl_build_card_table "$sysfs_root")
|
|
|
|
[ -e "$layout_file" ] && layout_exists=1
|
|
[ -r "$layout_file" ] && layout_readable=1
|
|
|
|
if [ "$layout_exists" = 1 ] && [ "$layout_readable" = 0 ]; then
|
|
echo "display-layout: $layout_file exists but is not readable; treating it as empty" >&2
|
|
fi
|
|
|
|
if [ "$layout_readable" = 1 ]; then
|
|
case "$-" in *f*) had_noglob=1 ;; esac
|
|
set -f
|
|
while IFS= read -r line || [ -n "$line" ]; do
|
|
line=${line%$'\r'}
|
|
set -- $line
|
|
[ "$#" -gt 0 ] || continue
|
|
keyword=$1
|
|
case "$keyword" in
|
|
'#'*) continue ;;
|
|
makepad-display-layout) continue ;;
|
|
render-on)
|
|
if [ -n "$render_on_pci" ]; then
|
|
continue
|
|
fi
|
|
if [ "$#" -ne 3 ]; then
|
|
echo "display-layout: malformed render-on line; skipped" >&2
|
|
continue
|
|
fi
|
|
if _mdl_is_pci_address "$2" && _mdl_is_pci_ids "$3"; then
|
|
render_on_pci=$2
|
|
render_on_ids=$3
|
|
else
|
|
echo "display-layout: malformed render-on line; skipped" >&2
|
|
fi
|
|
;;
|
|
virtual)
|
|
if [ -n "$virtual_mode" ]; then
|
|
continue
|
|
fi
|
|
if [ "$#" -lt 2 ] || ! _mdl_virtual_mode_is_valid "$2"; then
|
|
echo "display-layout: malformed virtual line; skipped" >&2
|
|
continue
|
|
fi
|
|
virtual_mode=$2
|
|
shift 2
|
|
for token in "$@"; do
|
|
case "$token" in
|
|
panels=mirror) virtual_panels=mirror ;;
|
|
panels=off) virtual_panels=off ;;
|
|
esac
|
|
done
|
|
;;
|
|
screen)
|
|
if [ "$#" -lt 3 ]; then
|
|
echo "display-layout: malformed screen line; skipped" >&2
|
|
continue
|
|
fi
|
|
pci=$2
|
|
connector=$3
|
|
case "$seen_keys" in
|
|
*"${pci}|${connector}"$'\x01'*)
|
|
continue
|
|
;;
|
|
esac
|
|
seen_keys="${seen_keys}${pci}|${connector}"$'\x01'
|
|
shift 3
|
|
mode=""
|
|
for token in "$@"; do
|
|
case "$token" in
|
|
mode=*)
|
|
value=${token#mode=}
|
|
if _mdl_mode_is_valid "$value"; then
|
|
mode="$value"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
card=$(_mdl_card_for_pci "$card_table" "$pci") || card=""
|
|
if [ -z "$card" ]; then
|
|
echo "display-layout: screen $pci $connector not present this boot; dropped" >&2
|
|
continue
|
|
fi
|
|
name="${card}-${connector}"
|
|
if [ -z "$order_list" ]; then
|
|
order_list="$name"
|
|
else
|
|
order_list="$order_list,$name"
|
|
fi
|
|
if [ -n "$mode" ]; then
|
|
if [ -z "$modes_list" ]; then
|
|
modes_list="$name=$mode"
|
|
else
|
|
modes_list="$modes_list,$name=$mode"
|
|
fi
|
|
fi
|
|
;;
|
|
*) continue ;;
|
|
esac
|
|
done <"$layout_file"
|
|
[ "$had_noglob" = 1 ] || set +f
|
|
fi
|
|
|
|
unset MAKEPAD_WM_ORDER_FROM_SAVED
|
|
if [ -n "$order_list" ]; then
|
|
if _mdl_set_env MAKEPAD_DISPLAY_ORDER "$order_list" "from display-layout"; then
|
|
export MAKEPAD_WM_ORDER_FROM_SAVED=1
|
|
fi
|
|
else
|
|
echo "display-layout: no resolvable screen order; MAKEPAD_DISPLAY_ORDER left to the platform" >&2
|
|
fi
|
|
|
|
unset MAKEPAD_WM_MODES_FROM_SAVED
|
|
if [ -n "$modes_list" ]; then
|
|
if _mdl_set_env MAKEPAD_DRM_MODES "$modes_list" "from display-layout"; then
|
|
export MAKEPAD_WM_MODES_FROM_SAVED=1
|
|
fi
|
|
else
|
|
echo "display-layout: no saved modes; MAKEPAD_DRM_MODES left to the platform" >&2
|
|
fi
|
|
|
|
unset MAKEPAD_WM_VIRTUAL_FROM_SAVED
|
|
if [ -n "$virtual_mode" ]; then
|
|
if _mdl_set_env MAKEPAD_VIRTUAL_DISPLAY "$virtual_mode $virtual_panels" "from display-layout"; then
|
|
export MAKEPAD_WM_VIRTUAL_FROM_SAVED=1
|
|
fi
|
|
else
|
|
echo "display-layout: no saved virtual display; MAKEPAD_VIRTUAL_DISPLAY left to the platform" >&2
|
|
fi
|
|
|
|
# render-on: display-layout (even with no render-on line, meaning
|
|
# Auto) always decides once it exists; the legacy display-gpu file is
|
|
# only consulted when display-layout does not exist at all -- the
|
|
# same rule system_linux.rs's read_display_layout applies.
|
|
if [ "$layout_exists" = 0 ]; then
|
|
local legacy
|
|
legacy=$(_mdl_legacy_gpu_choice "$gpu_file") || legacy=""
|
|
if [ -n "$legacy" ]; then
|
|
render_on_pci=${legacy% *}
|
|
render_on_ids=${legacy#* }
|
|
fi
|
|
fi
|
|
|
|
unset MAKEPAD_WM_GPU_FROM_SAVED
|
|
if [ -n "${MAKEPAD_VULKAN_COMPOSITOR_PCI:-}" ] || [ -n "${MAKEPAD_VULKAN_COMPOSITOR_UUID:-}" ]; then
|
|
echo "display-layout: MAKEPAD_VULKAN_COMPOSITOR_PCI/_UUID already set; leaving the render-on GPU alone" >&2
|
|
elif [ -n "$render_on_pci" ]; then
|
|
if _mdl_table_has_match "$card_table" "$render_on_pci" "$render_on_ids"; then
|
|
export MAKEPAD_WM_GPU_FROM_SAVED=1
|
|
export MAKEPAD_VULKAN_COMPOSITOR_PCI="$render_on_pci"
|
|
echo "display-layout: MAKEPAD_VULKAN_COMPOSITOR_PCI=$render_on_pci (saved render-on GPU)" >&2
|
|
else
|
|
echo "display-layout: saved render-on GPU $render_on_pci $render_on_ids is not present on this machine; using the automatic GPU" >&2
|
|
fi
|
|
else
|
|
echo "display-layout: no saved render-on GPU; using the automatic GPU" >&2
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
if ! _mdl_main; then
|
|
echo "display-layout: internal error; leaving the environment as it was" >&2
|
|
fi
|
|
|
|
unset -f _mdl_main _mdl_set_env _mdl_legacy_gpu_choice _mdl_table_has_match \
|
|
_mdl_card_for_pci _mdl_build_card_table _mdl_mode_is_valid _mdl_virtual_mode_is_valid _mdl_is_pci_ids \
|
|
_mdl_is_pci_address
|