diff --git a/Runner/suites/Multimedia/Graphics/KMSCube/run.sh b/Runner/suites/Multimedia/Graphics/KMSCube/run.sh index c94119a7..825e4042 100755 --- a/Runner/suites/Multimedia/Graphics/KMSCube/run.sh +++ b/Runner/suites/Multimedia/Graphics/KMSCube/run.sh @@ -2,7 +2,9 @@ # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear -# KMSCube Validator Script (Yocto-Compatible) +# KMSCube Validator Script (Yocto-Compatible, POSIX sh) + +# --- Robustly find and source init_env --------------------------------------- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" INIT_ENV="" SEARCH="$SCRIPT_DIR" @@ -19,19 +21,25 @@ if [ -z "$INIT_ENV" ]; then exit 1 fi +# Only source once (idempotent) if [ -z "$__INIT_ENV_LOADED" ]; then # shellcheck disable=SC1090 . "$INIT_ENV" fi +# Always source functestlib.sh, using $TOOLS exported by init_env # shellcheck disable=SC1090,SC1091 . "$TOOLS/functestlib.sh" +# --- Test metadata ----------------------------------------------------------- TESTNAME="KMSCube" -FRAME_COUNT=999 -EXPECTED_FRAMES=$((FRAME_COUNT - 1)) -test_path=$(find_test_case_by_name "$TESTNAME") +FRAME_COUNT="${FRAME_COUNT:-999}" # allow override via env +EXPECTED_MIN=$((FRAME_COUNT - 1)) # tolerate off-by-one under-reporting + +# Ensure we run from the testcase directory so .res/logs land next to run.sh +test_path="$(find_test_case_by_name "$TESTNAME")" cd "$test_path" || exit 1 + RES_FILE="./$TESTNAME.res" LOG_FILE="./${TESTNAME}_run.log" rm -f "$RES_FILE" "$LOG_FILE" @@ -39,36 +47,75 @@ rm -f "$RES_FILE" "$LOG_FILE" log_info "-------------------------------------------------------------------" log_info "------------------- Starting $TESTNAME Testcase -------------------" -if check_dependencies "$TESTNAME"; then - log_fail "$TESTNAME : kmscube binary not found" - echo "$TESTNAME SKIP" > "$RES_FILE" - exit 1 +# --- Dependencies ------------------------------------------------------------ +# Note: check_dependencies returns 0 when present, non-zero if missing. +check_dependencies kmscube || { + log_skip "$TESTNAME SKIP: missing dependencies: kmscube" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +} +KMSCUBE_BIN="$(command -v kmscube 2>/dev/null || true)" +log_info "Using kmscube: ${KMSCUBE_BIN:-}" + +# --- Basic DRM availability guard ------------------------------------------- +set -- /dev/dri/card* 2>/dev/null +if [ ! -e "$1" ]; then + log_skip "$TESTNAME SKIP: no /dev/dri/card* nodes" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 fi +# --- If Weston is running, stop it so KMS has control ------------------------ weston_was_running=0 if weston_is_running; then weston_stop weston_was_running=1 fi -log_info "Running kmscube test with --count=$FRAME_COUNT..." -if kmscube --count="$FRAME_COUNT" > "$LOG_FILE" 2>&1; then - if grep -q "Rendered $EXPECTED_FRAMES frames" "$LOG_FILE"; then - log_pass "$TESTNAME : Test Passed" - echo "$TESTNAME PASS" > "$RES_FILE" - else - log_fail "$TESTNAME : Expected output not found (Rendered $EXPECTED_FRAMES frames)" - echo "$TESTNAME FAIL" > "$RES_FILE" - exit 1 +# --- Execute kmscube --------------------------------------------------------- +log_info "Running kmscube with --count=${FRAME_COUNT} ..." +if kmscube --count="${FRAME_COUNT}" >"$LOG_FILE" 2>&1; then :; else + rc=$? + log_fail "$TESTNAME : Execution failed (rc=$rc) — see $LOG_FILE" + echo "$TESTNAME FAIL" >"$RES_FILE" + if [ "$weston_was_running" -eq 1 ]; then + log_info "Restarting Weston after failure" + weston_start fi + exit 1 +fi + +# --- Parse 'Rendered N frames' (case-insensitive), use the last N ----------- +FRAMES_RENDERED="$( + awk 'BEGIN{IGNORECASE=1} + /Rendered[[:space:]][0-9]+[[:space:]]+frames/{ + # capture the numeric token on that line; remember the last match + for(i=1;i<=NF;i++) if ($i ~ /^[0-9]+$/) n=$i + last=n + } + END{if (last!="") print last}' "$LOG_FILE" +)" +[ -n "$FRAMES_RENDERED" ] || FRAMES_RENDERED=0 +[ "$EXPECTED_MIN" -lt 0 ] && EXPECTED_MIN=0 +log_info "kmscube reported: Rendered ${FRAMES_RENDERED} frames (requested ${FRAME_COUNT}, min acceptable ${EXPECTED_MIN})" + +# --- Verdict ----------------------------------------------------------------- +if [ "$FRAMES_RENDERED" -ge "$EXPECTED_MIN" ]; then + log_pass "$TESTNAME : PASS" + echo "$TESTNAME PASS" >"$RES_FILE" else - log_fail "$TESTNAME : Execution failed (non-zero exit code)" - echo "$TESTNAME FAIL" > "$RES_FILE" + log_fail "$TESTNAME : FAIL (rendered ${FRAMES_RENDERED} < ${EXPECTED_MIN})" + echo "$TESTNAME FAIL" >"$RES_FILE" + if [ "$weston_was_running" -eq 1 ]; then + log_info "Restarting Weston after failure" + weston_start + fi exit 1 fi +# --- Restore Weston if we stopped it ----------------------------------------- if [ "$weston_was_running" -eq 1 ]; then - log_info "weston realuching after $TESTNAME completion" + log_info "Restarting Weston after $TESTNAME completion" weston_start fi diff --git a/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh b/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh index 995ae502..9107651c 100755 --- a/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh +++ b/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh @@ -1,37 +1,39 @@ #!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear -# Description: Script to test the 'weston-simple-egl' Wayland client for 30 seconds and log the result. +# Validate weston-simple-egl runs under a working Wayland session. +# - Robust Wayland env resolution (adopts socket & fixes XDG_RUNTIME_DIR perms) +# - CI-friendly logs and PASS/FAIL semantics +# - Optional FPS parsing if build prints it (lenient if not present) -# Robustly find and source init_env +# ---------- Source init_env and functestlib ---------- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" INIT_ENV="" SEARCH="$SCRIPT_DIR" while [ "$SEARCH" != "/" ]; do - if [ -f "$SEARCH/init_env" ]; then - INIT_ENV="$SEARCH/init_env" - break - fi - SEARCH=$(dirname "$SEARCH") + if [ -f "$SEARCH/init_env" ]; then INIT_ENV="$SEARCH/init_env"; break; fi + SEARCH=$(dirname "$SEARCH") done - if [ -z "$INIT_ENV" ]; then - echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 - exit 1 + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 fi - -# Only source if not already loaded (idempotent) if [ -z "$__INIT_ENV_LOADED" ]; then - # shellcheck disable=SC1090 - . "$INIT_ENV" + # shellcheck disable=SC1090 + . "$INIT_ENV" fi - -# Always source functestlib.sh, using $TOOLS exported by init_env # shellcheck disable=SC1090,SC1091 . "$TOOLS/functestlib.sh" TESTNAME="weston-simple-egl" +# Tunables (env override) +DURATION="${DURATION:-30s}" # how long to run the client +STOP_GRACE="${STOP_GRACE:-3s}" # grace period on stop +EXPECT_FPS="${EXPECT_FPS:-60}" # nominal refresh (used for logging) +FPS_TOL_PCT="${FPS_TOL_PCT:-10}" # +/- tolerance % +REQUIRE_FPS="${REQUIRE_FPS:-0}" # 1=require FPS lines & threshold; 0=best effort -test_path=$(find_test_case_by_name "$TESTNAME") +test_path="$(find_test_case_by_name "$TESTNAME" 2>/dev/null || echo "$SCRIPT_DIR")" cd "$test_path" || exit 1 RES_FILE="./$TESTNAME.res" LOG_FILE="./${TESTNAME}_run.log" @@ -39,42 +41,158 @@ rm -f "$RES_FILE" "$LOG_FILE" log_info "--------------------------------------------------------------------------" log_info "------------------- Starting $TESTNAME Testcase --------------------------" +# FIX #1: Use ASCII '+/-' and keep it in a normal string +log_info "Config: DURATION=$DURATION STOP_GRACE=$STOP_GRACE EXPECT_FPS=${EXPECT_FPS}+/-${FPS_TOL_PCT}% REQUIRE_FPS=$REQUIRE_FPS" + +# Dependencies +check_dependencies weston-simple-egl || { + log_fail "$TESTNAME : weston-simple-egl binary not found in PATH" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 0 +} +BIN="$(command -v weston-simple-egl 2>/dev/null || true)" +log_info "Using weston-simple-egl: ${BIN:-}" + +# Resolve Wayland socket: +# 1) If current env points to a real socket, use it. +sock="" +if [ -n "$XDG_RUNTIME_DIR" ] && [ -n "$WAYLAND_DISPLAY" ] && [ -e "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then + sock="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" +fi -# Only start if not already running -if ! weston_is_running; then +# 2) Otherwise, scan common locations. +if [ -z "$sock" ]; then + for s in $(find_wayland_sockets); do + if [ -e "$s" ]; then sock="$s"; break; fi + done +fi + +# 3) If still no socket, try to start Weston and wait a bit. +if [ -z "$sock" ]; then + if weston_is_running; then + log_warn "Weston running but no Wayland socket visible; attempting to continue." + else log_info "Weston not running. Attempting to start..." weston_start + fi + # Wait for socket to appear (up to ~5s) + n=0 + while [ $n -lt 5 ] && [ -z "$sock" ]; do + for s in $(find_wayland_sockets); do + if [ -e "$s" ]; then sock="$s"; break; fi + done + [ -n "$sock" ] && break + sleep 1 + n=$((n+1)) + done fi -if check_dependencies weston-simple-egl; then - log_fail "$TESTNAME : weston-simple-egl binary not found" - echo "$TESTNAME SKIP" > "$RES_FILE" - exit 1 +if [ -z "$sock" ]; then + log_fail "$TESTNAME : FAIL (no Wayland socket found after attempting to start Weston)" + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 1 +fi + +# Adopt env and fix runtime dir perms (done inside helper(s)) +adopt_wayland_env_from_socket "$sock" + +# FIX #2: Avoid duplicate prints — helpers can log the chosen socket/env once. +# If your helpers are quiet instead, uncomment the two lines below: +# log_info "Selected Wayland socket: $sock" +# log_info "Wayland env: XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR WAYLAND_DISPLAY=$WAYLAND_DISPLAY" + +if wayland_connection_ok; then + log_info "Wayland connection test: OK (wayland-info/env)" +else + log_fail "$TESTNAME : FAIL (Wayland connection test failed)" + print_path_meta "$XDG_RUNTIME_DIR" | sed 's/^/[DBG] /' + stat "$XDG_RUNTIME_DIR" 2>/dev/null | sed 's/^/[DBG] /' || true + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 1 fi -XDG_RUNTIME_DIR="/dev/socket/weston" -WAYLAND_DISPLAY="wayland-1" -export XDG_RUNTIME_DIR WAYLAND_DISPLAY - -mkdir -p "$XDG_RUNTIME_DIR" -chmod 0700 "$XDG_RUNTIME_DIR" -log_info "Running weston-simple-egl for 30 seconds..." -script -q -c "weston-simple-egl" "$LOG_FILE" 2>/dev/null & -EGL_PID=$! -sleep 30 -kill "$EGL_PID" 2>/dev/null -wait "$EGL_PID" 2>/dev/null - -count=$(grep -i -o "5 seconds" "$LOG_FILE" | wc -l) -if [ "$count" -ge 5 ]; then - log_pass "$TESTNAME : Test Passed" +# Try to enable FPS prints if supported by the client build (best effort). +export SIMPLE_EGL_FPS=1 +export WESTON_SIMPLE_EGL_FPS=1 + +# Run the client for DURATION with a stopwatch for CI logs. +log_info "Running weston-simple-egl for $DURATION ..." +start_ts="$(date +%s 2>/dev/null || echo 0)" +if command -v run_with_timeout >/dev/null 2>&1; then + run_with_timeout "$DURATION" weston-simple-egl >"$LOG_FILE" 2>&1 + rc=$? +else + if command -v timeout >/dev/null 2>&1; then + timeout "$DURATION" weston-simple-egl >"$LOG_FILE" 2>&1 + rc=$? + else + # Last resort: background and sleep. + sh -c 'weston-simple-egl' >"$LOG_FILE" 2>&1 & + pid=$! + # DURATION like "30s" → "30" + dur_s="$(printf '%s' "$DURATION" | sed -n 's/^\([0-9][0-9]*\)s$/\1/p')" + [ -z "$dur_s" ] && dur_s="$DURATION" + sleep "$dur_s" + kill "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + rc=143 + fi +fi +end_ts="$(date +%s 2>/dev/null || echo 0)" +elapsed=$(( end_ts - start_ts )) +[ "$elapsed" -lt 0 ] && elapsed=0 + +# FPS parsing (best effort) +fps="-" +fps_line="$(grep -E '([Ff][Pp][Ss]|frames per second|^fps:)' "$LOG_FILE" 2>/dev/null | tail -n 1)" +if [ -n "$fps_line" ]; then + # Extract last numeric token (integer or float) + fps="$(printf '%s\n' "$fps_line" | awk '{ + for (i=NF;i>=1;i--) if ($i ~ /^[0-9]+(\.[0-9]+)?$/) {print $i; exit} + }')" + [ -z "$fps" ] && fps="-" +fi + +# CI debugging summary +log_info "Result summary: rc=$rc elapsed=${elapsed}s fps=${fps} (expected ~${EXPECT_FPS}+/-${FPS_TOL_PCT}%)" +# Quick duration gate: must have run at least (DURATION-1) seconds to be considered OK. +dur_s="$(printf '%s' "$DURATION" | sed -n 's/^\([0-9][0-9]*\)s$/\1/p')" +[ -z "$dur_s" ] && dur_s="$DURATION" +min_ok=$(( dur_s - 1 )) +[ "$min_ok" -lt 0 ] && min_ok=0 + +final="PASS" + +if [ "$elapsed" -lt "$min_ok" ]; then + final="FAIL" + log_fail "$TESTNAME : FAIL (exited after ${elapsed}s; expected ~${dur_s}s) — rc=$rc" +fi + +# Optional FPS gate +if [ "$final" = "PASS" ] && [ "$REQUIRE_FPS" -eq 1 ]; then + if [ "$fps" = "-" ]; then + final="FAIL" + log_fail "$TESTNAME : FAIL (no FPS lines found but REQUIRE_FPS=1)" + else + # Integer-only tolerance check (portable) + lo=$(( (EXPECT_FPS * (100 - FPS_TOL_PCT)) / 100 )) + hi=$(( (EXPECT_FPS * (100 + FPS_TOL_PCT)) / 100 )) + fps_int="$(printf '%s' "$fps" | cut -d. -f1)" + if [ "$fps_int" -lt "$lo" ] || [ "$fps_int" -gt "$hi" ]; then + final="FAIL" + log_fail "$TESTNAME : FAIL (fps=$fps outside ${lo}-${hi})" + fi + fi +fi + +case "$final" in + PASS) + log_pass "$TESTNAME : PASS" echo "$TESTNAME PASS" > "$RES_FILE" exit 0 -else - log_fail "$TESTNAME : Test Failed" + ;; + *) echo "$TESTNAME FAIL" > "$RES_FILE" exit 1 -fi - -log_info "------------------- Completed $TESTNAME Testcase ------------------------" -exit 0 + ;; +esac diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index 34411810..45db233f 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -1136,41 +1136,45 @@ start_remoteproc() { printf 'start\n' >"$statef" 2>/dev/null || return 1 wait_remoteproc_state "$path" running 6 } -# Validate remoteproc running state with retries and logging + +# Returns 0 if every rproc using $1 firmware is running; non-zero otherwise. validate_remoteproc_running() { - fw_name="$1" - log_file="${2:-/dev/null}" - max_wait_secs="${3:-10}" - delay_per_try_secs="${4:-1}" - - rproc_path=$(get_remoteproc_path_by_firmware "$fw_name") - if [ -z "$rproc_path" ]; then - echo "[ERROR] Remoteproc for '$fw_name' not found" >> "$log_file" - { - echo "---- Last 20 remoteproc dmesg logs ----" - dmesg | grep -i "remoteproc" | tail -n 20 - echo "----------------------------------------" - } >> "$log_file" + fw="$1" + + # Fast skip: if DT doesn't advertise this firmware, there may be nothing to validate + if command -v dt_has_remoteproc_fw >/dev/null 2>&1; then + if ! dt_has_remoteproc_fw "$fw"; then + log_skip "DT does not list '$fw' remoteproc; skipping rproc state check" + return 0 + fi + fi + + # functestlib helper prints "path|state|firmware|name" (one line per instance) + entries="$(get_remoteproc_by_firmware "$fw" "" all 2>/dev/null)" || entries="" + + if [ -z "$entries" ]; then + log_fail "No /sys/class/remoteproc entry found for firmware '$fw'" return 1 fi - - total_waited=0 - while [ "$total_waited" -lt "$max_wait_secs" ]; do - state=$(get_remoteproc_state "$rproc_path") - if [ "$state" = "running" ]; then - return 0 + + fail=0 + while IFS='|' read -r rpath rstate rfirm rname; do + [ -n "$rpath" ] || continue + inst="$(basename "$rpath")" + log_info "remoteproc entry: path=$rpath state=$rstate firmware=$rfirm name=$rname" + if [ "$rstate" = "running" ]; then + log_pass "$inst: running" + else + # If you prefer to re-query, uncomment next line: + # rstate="$(get_remoteproc_state "$rpath" 2>/dev/null || echo "$rstate")" + log_fail "$inst: not running (state=$rstate)" + fail=$((fail + 1)) fi - sleep "$delay_per_try_secs" - total_waited=$((total_waited + delay_per_try_secs)) - done - - echo "[ERROR] $fw_name remoteproc did not reach 'running' state within ${max_wait_secs}s (last state: $state)" >> "$log_file" - { - echo "---- Last 20 remoteproc dmesg logs ----" - dmesg | grep -i "remoteproc" | tail -n 20 - echo "----------------------------------------" - } >> "$log_file" - return 1 + done <<__RPROC_LIST__ +$entries +__RPROC_LIST__ + + [ "$fail" -eq 0 ] } # acquire_test_lock @@ -2000,3 +2004,212 @@ execute_capture_block() { done } +# ---- Wayland/Weston helpers ----------------------- +# Ensure a private XDG runtime directory exists and is usable (0700). +ensure_private_runtime_dir() { + d="$1" + [ -n "$d" ] || return 1 + [ -d "$d" ] || mkdir -p "$d" 2>/dev/null || return 1 + chmod 0700 "$d" 2>/dev/null || return 1 + : >"$d/.xdg-test" 2>/dev/null || return 1 + rm -f "$d/.xdg-test" 2>/dev/null + return 0 +} + +# Return first Wayland socket under a base dir (prints path or fails). +find_wayland_socket_in() { + base="$1" + [ -d "$base" ] || return 1 + for s in "$base"/wayland-*; do + [ -S "$s" ] || continue + printf '%s\n' "$s" + return 0 + done + return 1 +} + +# Best-effort discovery of a usable Wayland socket anywhere. +discover_wayland_socket_anywhere() { + uid="$(id -u 2>/dev/null || echo 0)" + bases="" + [ -n "$XDG_RUNTIME_DIR" ] && bases="$bases $XDG_RUNTIME_DIR" + bases="$bases /dev/socket/weston /run/user/$uid /tmp/wayland-$uid /dev/shm" + for b in $bases; do + ensure_private_runtime_dir "$b" >/dev/null 2>&1 || true + if s="$(find_wayland_socket_in "$b")"; then + printf '%s\n' "$s" + return 0 + fi + done + return 1 +} + +# Adopt env from a Wayland socket path like /run/user/0/wayland-0 +# Sets XDG_RUNTIME_DIR and WAYLAND_DISPLAY. Returns 0 on success. +adopt_wayland_env_from_socket() { + sock="$1" + [ -n "$sock" ] || { log_warn "adopt_wayland_env_from_socket: no socket path given"; return 1; } + [ -S "$sock" ] || { log_warn "adopt_wayland_env_from_socket: not a socket: $sock"; return 1; } + + # Derive components without external dirname/basename + XDG_RUNTIME_DIR=${sock%/*} + WAYLAND_DISPLAY=${sock##*/} + + export XDG_RUNTIME_DIR + export WAYLAND_DISPLAY + + log_info "Selected Wayland socket: $sock" + log_info "Wayland env: XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR WAYLAND_DISPLAY=$WAYLAND_DISPLAY" + return 0 +} + +# Try to connect to Wayland. Returns 0 on OK. +wayland_can_connect() { + if command -v weston-info >/dev/null 2>&1; then + weston-info >/dev/null 2>&1 + return $? + fi + # fallback: quick client probe + ( env -i XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" WAYLAND_DISPLAY="$WAYLAND_DISPLAY" true ) >/dev/null 2>&1 + return $? +} + +# Ensure a Weston socket exists; if not, stop+start Weston and adopt helper socket. +weston_pick_env_or_start() { + sock="$(discover_wayland_socket_anywhere 2>/dev/null || true)" + if [ -n "$sock" ]; then + adopt_wayland_env_from_socket "$sock" + log_info "Selected Wayland socket: $sock" + return 0 + fi + + if weston_is_running; then + log_info "Stopping Weston..." + weston_stop + i=0; while weston_is_running && [ "$i" -lt 5 ]; do i=$((i+1)); sleep 1; done + fi + + log_info "Starting Weston..." + weston_start + i=0; sock="" + while [ "$i" -lt 6 ]; do + sock="$(find_wayland_socket_in /dev/socket/weston 2>/dev/null || true)" + [ -n "$sock" ] && break + sleep 1; i=$((i+1)) + done + if [ -z "$sock" ]; then + log_fail "Could not find Wayland socket after starting Weston." + return 1 + fi + adopt_wayland_env_from_socket "$sock" + log_info "Weston started; socket: $sock" + return 0 +} + +# Find candidate Wayland sockets in common locations. +# Prints absolute socket paths, one per line, most-preferred first. +find_wayland_sockets() { + uid="$(id -u 2>/dev/null || echo 0)" + + # Start with $XDG_RUNTIME_DIR if set. + if [ -n "$XDG_RUNTIME_DIR" ] && [ -d "$XDG_RUNTIME_DIR" ]; then + if [ -e "$XDG_RUNTIME_DIR/wayland-1" ]; then + printf '%s\n' "$XDG_RUNTIME_DIR/wayland-1" + fi + if [ -e "$XDG_RUNTIME_DIR/wayland-0" ]; then + printf '%s\n' "$XDG_RUNTIME_DIR/wayland-0" + fi + fi + + # Qualcomm/Yocto common path. + if [ -e /dev/socket/weston/wayland-1 ]; then + printf '%s\n' /dev/socket/weston/wayland-1 + fi + if [ -e /dev/socket/weston/wayland-0 ]; then + printf '%s\n' /dev/socket/weston/wayland-0 + fi + + # XDG spec default per-user location. + if [ -e "/run/user/$uid/wayland-1" ]; then + printf '%s\n' "/run/user/$uid/wayland-1" + fi + if [ -e "/run/user/$uid/wayland-0" ]; then + printf '%s\n' "/run/user/$uid/wayland-0" + fi +} + +# Ensure XDG_RUNTIME_DIR has owner=current-user and mode 0700. +# Returns 0 if OK (or fixed), non-zero if still not compliant. +ensure_wayland_runtime_dir_perms() { + dir="$1" + [ -n "$dir" ] && [ -d "$dir" ] || return 1 + + cur_uid="$(id -u 2>/dev/null || echo 0)" + cur_gid="$(id -g 2>/dev/null || echo 0)" + + # Best-effort fixups first (don’t error if chown/chmod fail) + chown "$cur_uid:$cur_gid" "$dir" 2>/dev/null || true + chmod 0700 "$dir" 2>/dev/null || true + + # Verify using stat (GNU first, then BSD). If stat is unavailable, + # we can’t verify—assume OK to avoid SC2012 (ls) usage. + if command -v stat >/dev/null 2>&1; then + # Mode: GNU: %a ; BSD: %Lp + mode="$(stat -c '%a' "$dir" 2>/dev/null || stat -f '%Lp' "$dir" 2>/dev/null || echo '')" + # Owner uid: GNU: %u ; BSD: %u + uid="$(stat -c '%u' "$dir" 2>/dev/null || stat -f '%u' "$dir" 2>/dev/null || echo '')" + + [ "$mode" = "700" ] && [ "$uid" = "$cur_uid" ] && return 0 + return 1 + fi + + # No stat available: directory exists and we attempted to fix perms/owner. + # Treat as success so clients can try; avoids SC2012 warnings. + return 0 +} + +# Quick Wayland handshake check. +# Prefers `wayland-info` with a short timeout; otherwise validates socket presence. +# Also enforces/fixes XDG_RUNTIME_DIR permissions so clients won’t reject it. +wayland_connection_ok() { + if [ -n "$XDG_RUNTIME_DIR" ]; then + ensure_wayland_runtime_dir_perms "$XDG_RUNTIME_DIR" || return 1 + fi + + if command -v wayland-info >/dev/null 2>&1; then + if command -v timeout >/dev/null 2>&1; then + timeout 3s wayland-info >/dev/null 2>&1 + return $? + fi + wayland-info >/dev/null 2>&1 + return $? + fi + + # Fallback: env variables and socket must exist. + if [ -n "$XDG_RUNTIME_DIR" ] && [ -n "$WAYLAND_DISPLAY" ] && [ -e "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then + return 0 + fi + return 1 +} + +# Print concise metadata for a path (portable). +# Prefers stat(1) (GNU or BSD); falls back to ls(1) only if needed. +# Usage: print_path_meta "/some/path" +print_path_meta() { + p=$1 + if [ -z "$p" ]; then + return 1 + fi + # GNU stat + if stat -c '%A %U %G %a %n' "$p" >/dev/null 2>&1; then + stat -c '%A %U %G %a %n' "$p" + return 0 + fi + # BSD/Mac stat + if stat -f '%Sp %Su %Sg %OLp %N' "$p" >/dev/null 2>&1; then + stat -f '%Sp %Su %Sg %OLp %N' "$p" + return 0 + fi + # shellcheck disable=SC2012 + ls -ld -- "$p" 2>/dev/null +}