# 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-` 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 ` ` line per usable card under # $1 (the sysfs DRM root): a `cardN` entry (not a `cardN-` # 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 # " :", with or without a final newline, # nothing else in the file. Prints " " 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