xrandr display-toolkit setup

Slug: xrandr

115623 characters 12841 words
Resolution (Width x Height) Aspect Ratio Common Name(s) Total Pixels
3840x1080 32:9 DFHD (Dual Full HD) 4,147,200
5120x1440 32:9 DQHD (Dual QHD) 7,372,800
2560x1080 21:9 UltraWide FHD 2,764,800
3440x1440 21:9 UltraWide QHD 4,953,600
3840x1600 21:9 UWQHD+ 6,144,000
5120x2160 21:9 5K Ultrawide (UW5K) 11,059,200
2048x1080 17:9 2K 2,211,840
4096x2160 17:9 DCI 4K 8,847,360
1280x800 16:10 WXGA 1,024,000
1440x900 16:10 WXGA+ 1,296,000
1680x1050 16:10 WSXGA+ 1,764,000
1920x1200 16:10 WUXGA 2,304,000
2560x1600 16:10 WQXGA 4,096,000
1280x720 16:9 HD, 720p 921,600
1360x768 16:9 HD 1,044,480
1366x768 16:9 HD 1,049,088
1536x864 16:9 - 1,327,104
1600x900 16:9 HD+ 1,440,000
1920x1080 16:9 Full HD, FHD, 1080p 2,073,600
2048x1152 16:9 QWXGA 2,359,296
2560x1440 16:9 QHD, WQHD, 1440p 3,686,400
3840x2160 16:9 4K UHD 8,294,400
5120x2880 16:9 5K 14,745,600
7680x4320 16:9 8K UHD 33,177,600
1280x1024 5:4 SXGA 1,310,720
1280x768 5:3 WXGA 983,040
640x480 4:3 VGA 307,200
800x600 4:3 SVGA 480,000
1024x768 4:3 XGA 786,432
1152x864 4:3 XGA+ 995,328
1280x960 4:3 SXGA- 1,228,800
1400x1050 4:3 SXGA+ 1,470,000
1600x1200 4:3 UXGA 1,920,000
#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' # rk3588-bullseye-display-toolkit-setup-integrated.sh # # Target: Debian 11 (Bullseye) on ARM64 (aarch64) RK3588 Rockchip bare-metal # Run as: normal user with sudo privileges (recommended from within ~) # # Purpose: # - Install a small baseline of dependencies (idempotent) # - Generate a display/audit toolkit under ~/rk3588-display-toolkit # - Keep changes safe and explicit: scripts that modify display settings require confirmation # # This integrated build combines: # - Installer safety (atomic writes + backups + explicit overwrite confirm) # - Deeper audit capabilities (optional, confirmed actions; broader subsystem coverage) # - Safer interactive display picking (SafeMode-first candidates, connector ordering) # - Optional autorandr persistence wiring (user-level systemd/XDG autostart) # - Optional PowerShell (pwsh) configurator (written to disk; requires pwsh installed separately) VERSION="2025-12-18-integrated" # ----------------------------- # Globals / configuration # ----------------------------- TOOLKIT_DIR_DEFAULT="$HOME/rk3588-display-toolkit" TOOLKIT_DIR="${TOOLKIT_DIR:-$TOOLKIT_DIR_DEFAULT}" BIN_DIR="$TOOLKIT_DIR/bin" LOG_DIR="$TOOLKIT_DIR/logs" STATE_DIR="$TOOLKIT_DIR/state" AUDIT_SCRIPT="$BIN_DIR/rk3588_audit.sh" MENU_SCRIPT="$BIN_DIR/debian_display_setup.sh" INTERACTIVE_SCRIPT="$BIN_DIR/monitor_setup_interactive.sh" DUAL_FIX_SCRIPT="$BIN_DIR/rk3588_dual_display_setup.sh" BOOTSTRAP_SCRIPT="$BIN_DIR/rk3588_autorandr_bootstrap.sh" PWSH_SCRIPT="$BIN_DIR/pwsh_monitor_setup.ps1" CONFIRM_TOKEN="${CONFIRM_TOKEN:-yes}" NONINTERACTIVE="${NONINTERACTIVE:-0}" # if 1, will NOT overwrite existing files AUTO_CONFIRM_OVERWRITE="${AUTO_CONFIRM_OVERWRITE:-0}" # if 1, auto-confirms overwrites (still makes backups) TMPDIR_CREATED="" cleanup() { if [[ -n "${TMPDIR_CREATED:-}" && -d "$TMPDIR_CREATED" ]]; then rm -rf "$TMPDIR_CREATED" || true fi } trap cleanup EXIT SIGINT SIGTERM # ----------------------------- # Helper functions # ----------------------------- log() { printf '[%(%F %T)T] %s\n' -1 "$*"; } err() { printf '[%(%F %T)T] ERROR: %s\n' -1 "$*" >&2; } die() { err "$*"; exit 1; } have_cmd() { command -v "$1" >/dev/null 2>&1; } require_home_context() { if [[ "$PWD" != "$HOME"* ]]; then err "This script is intended to be run from within your home directory (~). Current PWD: $PWD" err "Continuing anyway, but generated files will be written to: $TOOLKIT_DIR" fi } check_platform() { local debver arch debver="$(cut -d'.' -f1 < /etc/debian_version 2>/dev/null || echo unknown)" arch="$(uname -m 2>/dev/null || echo unknown)" if [[ "$debver" != "11" ]]; then err "Detected Debian major version '$debver' (expected 11/Bullseye). Continuing, but packages may differ." fi if [[ "$arch" != "aarch64" && "$arch" != "arm64" ]]; then err "Detected architecture '$arch' (expected ARM64/aarch64). Continuing, but this is tuned for ARM64." fi } need_sudo() { if ! have_cmd sudo; then die "sudo is required. Install sudo or run as root (not recommended for X tooling)." fi if ! sudo -v; then die "sudo authentication failed." fi } apt_update() { log "Updating APT package lists..." sudo apt-get update } pkg_installed() { dpkg -s "$1" >/dev/null 2>&1; } package_available() { local pkg="$1" apt-cache policy "$pkg" 2>/dev/null | awk '/Candidate:/ {print $2}' | grep -vq "(none)" } apt_install_pkgs() { local pkgs=("$@") local to_install=() for p in "${pkgs[@]}"; do if pkg_installed "$p"; then log "Package already installed: $p" continue fi if ! package_available "$p"; then err "Package not available in APT sources (skipping): $p" continue fi to_install+=("$p") done if (( ${#to_install[@]} == 0 )); then log "All requested packages are already installed (or unavailable and skipped)." return 0 fi log "Installing packages: ${to_install[*]}" sudo apt-get install -y --no-install-recommends "${to_install[@]}" } confirm_high_risk() { local message="$1" if [[ "$AUTO_CONFIRM_OVERWRITE" == "1" ]]; then log "AUTO_CONFIRM_OVERWRITE=1 set; proceeding without prompt: $message" return 0 fi if [[ "$NONINTERACTIVE" == "1" ]]; then err "NONINTERACTIVE=1 set; refusing interactive confirmation for: $message" return 1 fi echo echo "WARNING: $message" read -r -p "Type '$CONFIRM_TOKEN' to continue, anything else to cancel: " ans [[ "$ans" == "$CONFIRM_TOKEN" ]] || { err "Cancelled by user."; return 1; } } backup_if_exists() { local path="$1" if [[ -e "$path" ]]; then local ts bak ts="$(date +%Y%m%d_%H%M%S)" bak="${path}.bak.${ts}" log "Backing up existing file: $path -> $bak" cp -a "$path" "$bak" fi } safe_write_file() { local path="$1" local mode="${2:-644}" mkdir -p "$(dirname "$path")" if [[ -e "$path" ]]; then if [[ "$NONINTERACTIVE" == "1" && "$AUTO_CONFIRM_OVERWRITE" != "1" ]]; then err "File exists and NONINTERACTIVE=1. Skipping: $path" return 0 fi if ! confirm_high_risk "This will overwrite: $path (a backup will be created)."; then err "Skipping write to $path" return 0 fi backup_if_exists "$path" fi TMPDIR_CREATED="$(mktemp -d)" local tmpfile="$TMPDIR_CREATED/tmp.out" cat > "$tmpfile" install -m "$mode" "$tmpfile" "$path" rm -rf "$TMPDIR_CREATED" || true TMPDIR_CREATED="" log "Wrote: $path" } # ----------------------------- # Step 1: Install baseline dependencies # ----------------------------- install_dependencies() { log "=== Step 1/4: Installing baseline dependencies ===" need_sudo apt_update apt_install_pkgs \ ca-certificates curl wget \ x11-xserver-utils xrandr \ autorandr \ python3 python3-tk \ coreutils gawk bc jq \ procps pciutils usbutils apt_install_pkgs edid-decode read-edid ddcutil inxi lshw hwinfo if ! have_cmd timeout; then die "'timeout' command not found after installing coreutils; unexpected." fi log "Dependencies installed/verified." } # ----------------------------- # Step 2: Generate toolkit scripts # ----------------------------- generate_audit_script() { log "Generating: $AUDIT_SCRIPT" safe_write_file "$AUDIT_SCRIPT" 755 <<'EOF' #!/usr/bin/env bash set -euo pipefail set -o errtrace IFS=$'\n\t' trap 'echo "Error on or near line ${LINENO}; command exited with status $?" >&2' ERR # rk3588_audit.sh # # Purpose: # - Create a timestamped audit directory under ~/rk3588_audit_<timestamp>/ # - Collect system/display/GPU/VPU/network/storage snapshots # - Install diagnostic packages idempotently (skips missing packages) # - Avoid destructive actions; optional actions require explicit confirmation START_TS="$(date +%Y%m%d_%H%M%S)" AUDIT_DIR="$HOME/rk3588_audit_${START_TS}" LOG_DIR="$AUDIT_DIR/logs" OUT_DIR="$AUDIT_DIR/out" REPORT_MD="$AUDIT_DIR/REPORT.md" LOG_FILE="$LOG_DIR/audit.log" ENV_FILE="$LOG_DIR/env.txt" DMESG_FILE="$LOG_DIR/dmesg.txt" CMD_FAIL_HINT="Check ${LOG_FILE} and ${REPORT_MD} for details." : "${NET_TIMEOUT:=5}" : "${FIO_SIZE_MB:=512}" : "${FIO_BS:=1M}" : "${FIO_IODEPTH:=16}" : "${RUN_COLORS:=1}" TMPDIR_CREATED="" cleanup() { if [[ -n "${TMPDIR_CREATED:-}" && -d "$TMPDIR_CREATED" ]]; then rm -rf "$TMPDIR_CREATED" || true fi } trap cleanup EXIT SIGINT SIGTERM if [[ -t 1 && "$RUN_COLORS" -eq 1 ]] && command -v tput >/dev/null 2>&1; then GREEN="$(tput setaf 2)"; YELLOW="$(tput setaf 3)"; RED="$(tput setaf 1)"; BLUE="$(tput setaf 4)"; BOLD="$(tput bold)"; RESET="$(tput sgr0)" else GREEN=""; YELLOW=""; RED=""; BLUE=""; BOLD=""; RESET="" fi log() { echo -e "$*" | tee -a "$LOG_FILE"; } section() { log "\n${BOLD}${BLUE}==> $1${RESET}"; } ensure_dir() { [[ -d "$1" ]] || mkdir -p "$1"; } ask_yes() { local prompt="$1" ans echo read -r -p "${YELLOW}${prompt}${RESET} (type 'yes' to continue, anything else to cancel): " ans [[ "$ans" == "yes" ]] || { echo "Cancelled by user."; return 1; } } need_sudo() { command -v sudo >/dev/null 2>&1 || { echo "Error: sudo required." >&2; exit 1; } sudo -v || { echo "Error: sudo auth failed." >&2; exit 1; } } package_available() { local pkg="$1" apt-cache policy "$pkg" 2>/dev/null | awk '/Candidate:/ {print $2}' | grep -vq "(none)" } is_installed() { dpkg -s "$1" >/dev/null 2>&1; } ensure_package() { local pkg="$1" if is_installed "$pkg"; then log "Package already installed: ${GREEN}${pkg}${RESET}" return 0 fi if ! package_available "$pkg"; then log "Package not available (skipping): ${YELLOW}${pkg}${RESET}" return 0 fi log "Installing package: ${GREEN}${pkg}${RESET}" sudo apt-get install -y --no-install-recommends "$pkg" >>"$LOG_FILE" 2>&1 || { echo "Error: Failed to install '$pkg'. ${CMD_FAIL_HINT}" >&2 exit 1 } } ensure_packages() { for pkg in "$@"; do ensure_package "$pkg"; done; } run_continue() { local title="$1"; shift log "\n--- $title ---" "$@" >>"$LOG_FILE" 2>&1 || log " (Command failed but continuing): $*" } run_fail() { local title="$1"; shift log "\n>>> $title" "$@" >>"$LOG_FILE" 2>&1 || { echo "Error: $title failed. ${CMD_FAIL_HINT}" >&2; exit 1; } } quick_net_check() { section "Quick network check" if command -v ping >/dev/null 2>&1 && ping -c1 -W "$NET_TIMEOUT" deb.debian.org >/dev/null 2>&1; then log "Network reachable." return 0 fi log "Network check failed; installs/downloads may be limited." return 1 } check_platform() { section "Platform checks" local debver arch debver="$(cut -d'.' -f1 < /etc/debian_version 2>/dev/null || echo unknown)" arch="$(uname -m 2>/dev/null || echo unknown)" log "Debian major: $debver" log "Arch : $arch" [[ "$debver" == "11" ]] || log "WARNING: tuned for Debian 11 (Bullseye)." [[ "$arch" == "aarch64" || "$arch" == "arm64" ]] || log "WARNING: tuned for ARM64." } enable_nonfree_optional() { section "Optional: Enable contrib/non-free (Bullseye)" if ! ask_yes "Enable 'contrib non-free' in /etc/apt/sources.list (backup + apt update)?"; then log "Skipped enabling contrib/non-free." return 0 fi local src="/etc/apt/sources.list" if [[ ! -f "$src" ]]; then log "No $src found; skipping." return 0 fi sudo cp -a "$src" "${src}.bak.${START_TS}" TMPDIR_CREATED="$(mktemp -d)" local tmp="$TMPDIR_CREATED/sources.list" sudo awk '{ if ($1=="deb" || $1=="deb-src") { line=$0 has_contrib=match(line,/(^| )contrib( |$)/) has_nonfree=match(line,/(^| )non-free( |$)/) if (!has_contrib) line=line" contrib" if (!has_nonfree) line=line" non-free" print line } else { print } }' "$src" | sudo tee "$tmp" >/dev/null sudo mv "$tmp" "$src" run_fail "apt-get update (after enabling contrib/non-free)" sudo apt-get update rm -rf "$TMPDIR_CREATED" || true TMPDIR_CREATED="" } main() { ensure_dir "$AUDIT_DIR"; ensure_dir "$LOG_DIR"; ensure_dir "$OUT_DIR" : >"$LOG_FILE" section "Start" log "Audit directory: $AUDIT_DIR" log "Log file : $LOG_FILE" need_sudo { echo "===== ENVIRONMENT =====" echo "Timestamp: $START_TS" uname -a || true echo echo "----- /etc/os-release -----" cat /etc/os-release 2>/dev/null || true echo echo "----- /proc/cmdline -----" cat /proc/cmdline 2>/dev/null || true echo echo "----- CPU -----" lscpu 2>/dev/null || true echo echo "----- Memory/CMA -----" grep -E 'CmaTotal|CmaFree|MemTotal|MemFree|HugePages' /proc/meminfo 2>/dev/null || true } >"$ENV_FILE" run_continue "Collect dmesg" bash -lc "sudo dmesg -T > '$DMESG_FILE'" check_platform quick_net_check || true section "APT update" run_fail "apt-get update" sudo apt-get update enable_nonfree_optional || true section "Install baseline diagnostic tools (idempotent; skips unavailable)" ensure_packages \ curl wget ca-certificates \ pciutils usbutils lshw hwinfo inxi \ ethtool iproute2 net-tools jq \ i2c-tools lm-sensors \ v4l-utils \ gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ ffmpeg alsa-utils \ vulkan-tools mesa-utils kmscube \ xrandr x11-xserver-utils autorandr \ edid-decode read-edid ddcutil \ fio hdparm nvme-cli smartmontools \ iw wireless-tools bluez can-utils section "System overview" run_continue "lshw (short)" sudo lshw -short run_continue "lsblk -O" lsblk -O run_continue "df -hT" df -hT run_continue "lsusb -t" lsusb -t run_continue "lspci -nnk" lspci -nnk run_continue "Kernel warnings/errors (last 200)" bash -lc 'dmesg -T --level=err,warn | tail -n 200' section "GPU & Display" run_continue "GPU modules loaded" bash -lc "lsmod | egrep -i 'panthor|panfrost|mali|kbase' || true" run_continue "GPU-related dmesg" bash -lc "dmesg -T | egrep -i 'mali|panthor|panfrost|csf|gpu' || true" run_continue "DRM connectors (modetest -c)" modetest -c run_continue "kmscube smoke test" bash -lc "kmscube -i 100 >/dev/null 2>&1 || true" run_continue "Vulkan summary" vulkaninfo --summary run_continue "xrandr --props (if X running)" bash -lc "DISPLAY=\${DISPLAY:-:0} xrandr --props 2>/dev/null || true" section "Video / V4L2 / Codecs" run_continue "List V4L2 devices" v4l2-ctl --list-devices run_continue "FFmpeg hwaccels" ffmpeg -hide_banner -hwaccels run_continue "GStreamer rockchip-ish plugins" bash -lc "gst-inspect-1.0 | egrep -i 'v4l2|rkv|hantro|rockchip' || true" section "Audio" run_continue "ALSA playback" aplay -l run_continue "ALSA capture" arecord -l section "Network" run_continue "ip -details addr" ip -details address run_continue "iw dev" iw dev section "Storage" run_continue "lsblk (model/serial)" lsblk -o NAME,SIZE,TYPE,MOUNTPOINTS,MODEL,SERIAL,TRAN run_continue "SATA/NVMe/PCIe dmesg" bash -lc "dmesg -T | egrep -i 'sata|ahci|nvme|pcie' || true" run_continue "nvme list (if any)" bash -lc "ls /dev/nvme*n1 >/dev/null 2>&1 && sudo nvme list || true" section "Optional actions" if ask_yes "Run powertop --auto-tune (changes power tunables until reboot)?"; then run_fail "powertop --auto-tune" sudo powertop --auto-tune fi if ask_yes "Run sensors-detect (interactive; may load modules)?"; then run_fail "sensors-detect" sudo sensors-detect fi if ask_yes "Run quick fio seq read/write in $HOME (~${FIO_SIZE_MB}MB temp file, then removed)?"; then TMPDIR_CREATED="$(mktemp -d)" local fiofile="$TMPDIR_CREATED/fio_test.dat" run_fail "dd create file" dd if=/dev/zero of="$fiofile" bs=1M count="$FIO_SIZE_MB" status=none run_fail "fio seq rw" fio --name=seqrw --filename="$fiofile" --rw=readwrite --bs="$FIO_BS" --direct=1 --numjobs=1 --iodepth="$FIO_IODEPTH" --size="${FIO_SIZE_MB}M" --group_reporting rm -rf "$TMPDIR_CREATED" || true TMPDIR_CREATED="" fi section "REPORT.md" { echo "# RK3588 Capability Audit — $START_TS" echo echo "Audit directory: $AUDIT_DIR" echo "Log file: $LOG_FILE" echo "Env snapshot: $ENV_FILE" echo "dmesg: $DMESG_FILE" } >"$REPORT_MD" ( cd "$HOME" && tar czf "${AUDIT_DIR}.tar.gz" "$(basename "$AUDIT_DIR")" ) section "Done" log "Report : $REPORT_MD" log "Archive: ${AUDIT_DIR}.tar.gz" } main "$@" EOF } generate_menu_script() { log "Generating: $MENU_SCRIPT" safe_write_file "$MENU_SCRIPT" 755 <<'EOF' #!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' # debian_display_setup.sh # Menu-driven helper for: # - One-time package install # - Monitor info collection (xrandr + sysfs EDID + optional root-only tools) # - Custom xrandr script template # - Autorandr profile save MONITOR_INFO_OUTDIR="" CUSTOM_XRANDR_SCRIPT_PATH="${HOME}/my_custom_display_config.sh" TOOLKIT_HOME="${HOME}/rk3588-display-toolkit" CONFIG_DIR="${TOOLKIT_HOME}/state/debian_display_master" PREP_DONE_FLAG="${CONFIG_DIR}/system_prep_done.flag" log() { printf '[%(%F %T)T] %s\n' -1 "$*"; } ensure_config_dir() { mkdir -p "$CONFIG_DIR" if [[ $EUID -eq 0 && -n "${SUDO_USER:-}" && "${SUDO_USER}" != "root" ]]; then chown -R "$SUDO_USER:$(id -gn "$SUDO_USER")" "$CONFIG_DIR" 2>/dev/null || true fi } require_root_or_return() { if [[ $EUID -ne 0 ]]; then echo "ERROR: This step must be run with sudo/root." >&2 echo "Re-run with: sudo bash $0" >&2 return 1 fi return 0 } system_prep() { echo echo "--- Stage 1: One-Time System Preparation (sudo required) ---" require_root_or_return || return 1 echo ">>> Updating apt lists..." apt-get update echo ">>> Installing autorandr + tools..." apt-get install -y --no-install-recommends \ autorandr xrandr x11-xserver-utils python3 python3-tk \ read-edid ddcutil hwinfo inxi lshw edid-decode bc if systemctl list-unit-files | grep -q '^autorandr\.service'; then systemctl enable --now autorandr.service || true fi if systemctl list-unit-files | grep -q '^autorandr-resume\.service'; then systemctl enable --now autorandr-resume.service || true fi ensure_config_dir date > "$PREP_DONE_FLAG" echo ">>> System preparation complete." } _mi_setup_outdir() { local real_user home_base if [[ $EUID -eq 0 && -n "${SUDO_USER:-}" && "$SUDO_USER" != "root" ]]; then real_user="$SUDO_USER" else real_user="$(whoami)" fi home_base="$(getent passwd "$real_user" | cut -d: -f6)" [[ -n "$home_base" && -d "$home_base" ]] || home_base="/tmp" MONITOR_INFO_OUTDIR="${home_base}/monitor-info-$(date +%Y%m%d-%H%M%S)" mkdir -p "$MONITOR_INFO_OUTDIR" : >"$MONITOR_INFO_OUTDIR/summary.txt" if [[ $EUID -eq 0 && -n "${SUDO_USER:-}" ]]; then chown -R "$SUDO_USER:$(id -gn "$SUDO_USER")" "$MONITOR_INFO_OUTDIR" 2>/dev/null || true fi } _mi_log_cmd() { local logfile="$1"; shift { echo "===== $(date '+%F %T') : $* =====" "$@" 2>&1 || echo "(ERROR: '$*' failed code $?)" echo } >>"$MONITOR_INFO_OUTDIR/$logfile" } _mi_detect_connectors_sysfs() { local regex='^[a-zA-Z]+(-[a-zA-Z0-9]+)*-[0-9]+$' echo "--- DRM connectors (sysfs) ---" >>"$MONITOR_INFO_OUTDIR/summary.txt" for card in /sys/class/drm/card*; do [[ -d "$card" ]] || continue for p in "$card"/*; do [[ -d "$p" ]] || continue local name name="$(basename "$p")" if [[ "$name" =~ $regex ]]; then echo "DRM connector: $name" >>"$MONITOR_INFO_OUTDIR/summary.txt" fi done done } _mi_collect_sysfs_edid() { command -v edid-decode >/dev/null 2>&1 || return 0 echo "--- EDID decode (sysfs) ---" >>"$MONITOR_INFO_OUTDIR/summary.txt" while IFS= read -r edid; do [[ -s "$edid" ]] || continue _mi_log_cmd "edid_sysfs.log" edid-decode "$edid" done < <(find /sys/class/drm -name edid 2>/dev/null || true) } _mi_generate_cvt_interactive() { if ! command -v cvt >/dev/null 2>&1; then echo "Note: 'cvt' not found; skipping modeline generator." >>"$MONITOR_INFO_OUTDIR/summary.txt" return 0 fi echo read -r -p "Generate a modeline via cvt? Enter 'width height refresh' (or Enter to skip): " W H R if [[ -n "${W:-}" && -n "${H:-}" && -n "${R:-}" ]]; then cvt "$W" "$H" "$R" | tee -a "$MONITOR_INFO_OUTDIR/cvt_modeline.log" >/dev/null echo "Wrote: $MONITOR_INFO_OUTDIR/cvt_modeline.log" fi } monitor_info() { echo echo "--- Stage 2: Collect Monitor Information ---" _mi_setup_outdir echo "Collecting monitor information into: $MONITOR_INFO_OUTDIR" _mi_detect_connectors_sysfs if command -v xrandr >/dev/null 2>&1; then _mi_log_cmd "xrandr_verbose.log" xrandr --verbose xrandr | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" >/dev/null || true else echo "xrandr not found. Run Stage 1 first." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" >/dev/null fi _mi_collect_sysfs_edid if [[ $EUID -eq 0 ]]; then command -v ddcutil >/dev/null 2>&1 && _mi_log_cmd "ddcutil_detect.log" ddcutil detect --verbose || true command -v get-edid >/dev/null 2>&1 && _mi_log_cmd "get_edid.log" get-edid || true command -v hwinfo >/dev/null 2>&1 && _mi_log_cmd "hwinfo_monitor.log" hwinfo --monitor || true command -v lshw >/dev/null 2>&1 && _mi_log_cmd "lshw_display.log" lshw -C display || true else echo "Note: root-only tools (ddcutil, get-edid, hwinfo, lshw) are skipped unless run with sudo." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" >/dev/null fi _mi_generate_cvt_interactive echo "Done. Summary: $MONITOR_INFO_OUTDIR/summary.txt" } create_or_edit_custom_xrandr() { echo echo "--- Stage 3: Create/Edit Custom Xrandr Configuration Script ---" if [[ ! -f "$CUSTOM_XRANDR_SCRIPT_PATH" ]]; then cat >"$CUSTOM_XRANDR_SCRIPT_PATH" <<'SH' #!/usr/bin/env bash set -euo pipefail # Custom Xrandr Configuration Script # Edit this file to match your connector names and desired modes. command -v xrandr >/dev/null 2>&1 || { echo "Error: xrandr not found" >&2; exit 1; } echo "Applying custom xrandr settings..." # Example: # OUTPUT_1_NAME="HDMI-1" # MODE_1_NAME="1920x1080" # xrandr --output "$OUTPUT_1_NAME" --mode "$MODE_1_NAME" --primary # Safe fallback: xrandr --auto SH chmod +x "$CUSTOM_XRANDR_SCRIPT_PATH" echo "Created template: $CUSTOM_XRANDR_SCRIPT_PATH" else echo "Custom script exists: $CUSTOM_XRANDR_SCRIPT_PATH" fi read -r -p "Open with nano now? (y/N): " ans if [[ "$ans" =~ ^[Yy]$ ]]; then nano "$CUSTOM_XRANDR_SCRIPT_PATH" fi } test_custom_xrandr() { echo echo "--- Stage 4: Test Custom Xrandr Script (must be in X session) ---" if [[ -z "${DISPLAY:-}" ]]; then echo "ERROR: DISPLAY is not set. Run from an active X session." >&2 return 1 fi if [[ $EUID -eq 0 ]]; then echo "WARNING: Running xrandr as root is not recommended. Prefer running as your user." >&2 fi if ! bash "$CUSTOM_XRANDR_SCRIPT_PATH"; then echo "Error: custom xrandr script failed." >&2 echo "Recovery hint: switch to a TTY (Ctrl+Alt+F2), then try: DISPLAY=:0 xrandr --auto (if possible), or reboot." >&2 return 1 fi echo "Custom xrandr script executed." } save_autorandr_profile() { echo echo "--- Stage 5: Save Autorandr Profile (run in X session) ---" command -v autorandr >/dev/null 2>&1 || { echo "Error: autorandr not found. Run Stage 1." >&2; return 1; } read -r -p "Enter profile name: " pname pname="$(echo "$pname" | tr -s ' /\:&?' '_')" [[ -n "$pname" ]] || { echo "No profile name provided." >&2; return 1; } autorandr --save "$pname" echo "Saved autorandr profile: $pname" } main_menu() { ensure_config_dir echo echo "Debian Bullseye Display Setup & Management Utility" echo "==================================================" echo "NOTE: Some actions require sudo, others require an active X session." PS3="Choose an option: " local options=( "1) One-time system preparation (sudo required)" "2) Collect monitor information" "3) Create/edit custom xrandr script" "4) Test custom xrandr script (X session)" "5) Save autorandr profile (X session)" "6) Exit" ) select opt in "${options[@]}"; do case "$REPLY" in 1) system_prep ;; 2) monitor_info ;; 3) create_or_edit_custom_xrandr ;; 4) test_custom_xrandr ;; 5) save_autorandr_profile ;; 6) echo "Exiting."; exit 0 ;; *) echo "Invalid option." ;; esac echo read -r -p "Press Enter to return to the menu..." _ done } main_menu EOF } generate_interactive_script() { log "Generating: $INTERACTIVE_SCRIPT" safe_write_file "$INTERACTIVE_SCRIPT" 755 <<'EOF' #!/usr/bin/env bash set -Eeuo pipefail IFS=$'\n\t' # monitor_setup_interactive.sh # # Safer interactive mode picker with visual verification: # - SafeMode-first: tries a short list of common resolutions first # - Output ordering: HDMI/DP/DVI/eDP/VGA then the rest # - Tk overlay: draws a red border on the active monitor # # WARNING: This script changes display modes. # Recovery: switch to a TTY (Ctrl+Alt+F2) and run: DISPLAY=:0 xrandr --auto (if possible) or reboot. LOGF="${LOGF:-/tmp/monitor-setup.log}" exec > >(tee -a "$LOGF") 2>&1 log() { printf '[%(%F %T)T] %s\n' -1 "$*"; } fail() { log "ERROR: $*"; exit 1; } need() { command -v "$1" >/dev/null 2>&1 || fail "$1 not found"; } need xrandr need python3 need timeout python3 - <<'PY' 2>/dev/null || fail "python3-tk not available. Install python3-tk." import tkinter PY [[ -n "${DISPLAY:-}" ]] || fail "DISPLAY is not set. Run from an active X session." SAFEMODES=( 1920x1080 1680x1050 1600x900 1440x900 1366x768 1280x1024 1280x960 1280x800 1280x720 1024x768 800x600 640x480 ) get_outputs() { xrandr --query | awk '/ connected/{print $1}'; } sort_outputs() { local -a outs mapfile -t outs < <(get_outputs) local prefs=(HDMI DP DVI eDP VGA) for p in "${prefs[@]}"; do for o in "${outs[@]}"; do [[ "$o" == ${p}* ]] && echo "$o"; done done for o in "${outs[@]}"; do local hit=0 for p in "${prefs[@]}"; do [[ "$o" == ${p}* ]] && hit=1; done ((hit==0)) && echo "$o" done } get_modes_for_output() { local out="$1" xrandr --query | awk -v o="$out" ' $1==o {inblock=1; next} /^[A-Z]/ {inblock=0} inblock && $1 ~ /^[0-9]+x[0-9]+/ {print $1} ' | awk '!seen[$0]++' } current_mode_for_output() { local out="$1" xrandr --query | awk -v o="$out" ' $1==o {inblock=1; next} /^[A-Z]/ {inblock=0} inblock && $1 ~ /^[0-9]+x[0-9]+/ { if ($0 ~ /\*/) {print $1; exit} } ' } pick_safelist() { local -a avail=() mapfile -t avail local -a picked=() for safe in "${SAFEMODES[@]}"; do for m in "${avail[@]}"; do [[ "$m" == "$safe" ]] && { picked+=("$m"); break; } done (( ${#picked[@]} >= 4 )) && break done if (( ${#picked[@]} > 0 )); then printf '%s\n' "${picked[@]}" else printf '%s\n' "${avail[@]}" | head -n 4 fi } current_geometry() { local out="$1" xrandr --query | awk -v o="$out" ' $1==o { if (match($0, /[0-9]+x[0-9]+\+[0-9]+\+[0-9]+/)) { print substr($0, RSTART, RLENGTH) } } ' } show_rectangle() { local W="$1" H="$2" X="$3" Y="$4" log " overlay ${W}x${H}+${X}+${Y} for 5s" timeout 7s python3 - "$W" "$H" "$X" "$Y" <<'PY' import sys, tkinter as t W,H,X,Y = map(int, sys.argv[1:]) root = t.Tk() root.overrideredirect(True) root.geometry(f"{W}x{H}+{X}+{Y}") frame = t.Frame(root, width=W, height=H, highlightbackground='red', highlightthickness=8) frame.pack() root.attributes('-topmost', True) root.after(5000, root.quit) root.mainloop() try: root.destroy() except Exception: pass PY } ask_ynq() { local prompt="$1" ch while true; do printf '%s' "$prompt" > /dev/tty IFS= read -r -n1 ch < /dev/tty || ch="" printf '\n' > /dev/tty case "$ch" in y|Y) return 0;; n|N) return 1;; q|Q) return 2;; *) prompt="Please type y (accept), n (next), or q (quit): ";; esac done } restore_mode_best_effort() { local out="$1" prev="$2" if [[ -n "$prev" ]]; then xrandr --output "$out" --mode "$prev" >/dev/null 2>&1 || true else xrandr --output "$out" --auto >/dev/null 2>&1 || true fi } try_modes_for_output() { local out="$1"; shift local -a modes=("$@") local prev prev="$(current_mode_for_output "$out" || true)" for mode in "${modes[@]}"; do log "Trying $out -> $mode" if ! xrandr --output "$out" --mode "$mode"; then log "Failed to set $mode on $out" continue fi sleep 0.4 local geom geom="$(current_geometry "$out" || true)" if [[ "$geom" =~ ([0-9]+)x([0-9]+)\+([0-9]+)\+([0-9]+) ]]; then show_rectangle "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" "${BASH_REMATCH[4]}" if ask_ynq "Keep $mode for $out? [y/n/q] "; then log "Accepted $mode for $out" return 0 else local rc=$? if ((rc==2)); then restore_mode_best_effort "$out" "$prev" return 2 fi fi else log "Could not parse geometry for $out after applying $mode (continuing)." fi done restore_mode_best_effort "$out" "$prev" return 1 } log "Starting interactive monitor configuration..." mapfile -t outputs < <(sort_outputs) (( ${#outputs[@]} > 0 )) || fail "No connected outputs detected by xrandr." for out in "${outputs[@]}"; do log "Configuring $out..." mapfile -t avail < <(get_modes_for_output "$out") (( ${#avail[@]} > 0 )) || { log "No modes found for $out (skipping)."; continue; } mapfile -t testlist < <(printf '%s\n' "${avail[@]}" | pick_safelist) if try_modes_for_output "$out" "${testlist[@]}"; then continue else rc=$? ((rc==2)) && { log "Quit requested."; exit 0; } fi if ask_ynq "No SafeMode accepted for $out. Try ALL modes (riskier)? [y/n/q] "; then if try_modes_for_output "$out" "${avail[@]}"; then continue fi else rc=$? ((rc==2)) && { log "Quit requested."; exit 0; } fi done log "Done. For persistence, save an autorandr profile using debian_display_setup.sh." EOF } generate_dual_fix_script() { log "Generating: $DUAL_FIX_SCRIPT" safe_write_file "$DUAL_FIX_SCRIPT" 755 <<'EOF' #!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' # rk3588_dual_display_setup.sh # # Hard-coded dual-monitor fix (HDMI + DP) with autorandr persistence. # # OUT1 (default HDMI-1): 800x600@60 primary # OUT2 (default DP-1) : 640x480@60 right-of OUT1 # Includes VGA fallback timing for OUT2. # # WARNING: This script changes display modes. OUT1="${OUT1:-HDMI-1}" OUT2="${OUT2:-DP-1}" MODE1_NAME="800x600_60.00" MODE1_LINE="38.25 800 832 912 1024 600 603 607 624 -hsync +vsync" MODE2_CVT_NAME="640x480_60.00" MODE2_CVT_LINE="23.75 640 664 720 800 480 483 487 500 -hsync +vsync" MODE2_VGA_NAME="640x480_60_std" MODE2_VGA_LINE="25.175 640 656 752 800 480 490 492 525 -hsync -vsync" AR_PROFILE="rk3588_dual" log() { printf '[INFO ] %s\n' "$*"; } err() { printf '[ERROR] %s\n' "$*" >&2; } need_x() { [[ -n "${DISPLAY:-}" ]] || { err "DISPLAY not set. Run from an active X session."; exit 1; } } need_cmd() { command -v "$1" >/dev/null 2>&1 || { err "Missing command: $1"; exit 1; } } confirm_yes() { local msg="$1" ans echo echo "WARNING: $msg" read -r -p "Type 'yes' to continue, anything else to cancel: " ans [[ "$ans" == "yes" ]] || { err "Cancelled by user."; exit 1; } } apply_layout() { local right_mode="$1" xrandr \ --output "$OUT1" --mode "$MODE1_NAME" --primary \ --output "$OUT2" --mode "$right_mode" --right-of "$OUT1" } main() { need_x need_cmd xrandr need_cmd autorandr confirm_yes "This will change active display modes for $OUT1 and $OUT2. Ensure you can recover via TTY." log "Defining modelines (best-effort)..." xrandr --newmode "$MODE1_NAME" $MODE1_LINE 2>/dev/null || true xrandr --newmode "$MODE2_CVT_NAME" $MODE2_CVT_LINE 2>/dev/null || true xrandr --newmode "$MODE2_VGA_NAME" $MODE2_VGA_LINE 2>/dev/null || true xrandr --addmode "$OUT1" "$MODE1_NAME" 2>/dev/null || true xrandr --addmode "$OUT2" "$MODE2_CVT_NAME" 2>/dev/null || true xrandr --addmode "$OUT2" "$MODE2_VGA_NAME" 2>/dev/null || true log "Applying layout (try CVT timing first)..." if ! apply_layout "$MODE2_CVT_NAME"; then log "CVT timing failed; trying VGA fallback..." apply_layout "$MODE2_VGA_NAME" fi log "Creating autorandr preswitch hook for persistence..." local ar_dir="$HOME/.config/autorandr/$AR_PROFILE" mkdir -p "$ar_dir" cat >"$ar_dir/preswitch" <<SH #!/bin/sh xrandr --newmode "$MODE1_NAME" $MODE1_LINE 2>/dev/null || true xrandr --newmode "$MODE2_CVT_NAME" $MODE2_CVT_LINE 2>/dev/null || true xrandr --newmode "$MODE2_VGA_NAME" $MODE2_VGA_LINE 2>/dev/null || true xrandr --addmode "$OUT1" "$MODE1_NAME" 2>/dev/null || true xrandr --addmode "$OUT2" "$MODE2_CVT_NAME" 2>/dev/null || true xrandr --addmode "$OUT2" "$MODE2_VGA_NAME" 2>/dev/null || true SH chmod +x "$ar_dir/preswitch" log "Saving autorandr profile '$AR_PROFILE' (forced)..." autorandr --save "$AR_PROFILE" --force log "Done. Profile saved: $AR_PROFILE" } main "$@" EOF } generate_bootstrap_script() { log "Generating: $BOOTSTRAP_SCRIPT" safe_write_file "$BOOTSTRAP_SCRIPT" 755 <<'EOF' #!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' # rk3588_autorandr_bootstrap.sh # # Purpose: # - Ensure autorandr/xrandr tooling exists # - Help select a profile # - Install a preswitch hook template # - Optionally enable autorandr at login via: # * XDG autostart (desktop environments) # * systemd user unit # - Optionally enable system-wide autorandr services if present AUTORANDR_PROFILE="${AUTORANDR_PROFILE:-}" TOOLKIT_HOME="${TOOLKIT_HOME:-$HOME/rk3588-display-toolkit}" log() { printf '[%(%F %T)T] %s\n' -1 "$*"; } err() { printf '[%(%F %T)T] ERROR: %s\n' -1 "$*" >&2; } have_cmd() { command -v "$1" >/dev/null 2>&1; } confirm_yes() { local msg="$1" ans echo echo "WARNING: $msg" read -r -p "Type 'yes' to continue, anything else to cancel: " ans [[ "$ans" == "yes" ]] || { err "Cancelled by user."; return 1; } } pkg_installed() { dpkg -s "$1" >/dev/null 2>&1; } package_available() { apt-cache policy "$1" 2>/dev/null | awk '/Candidate:/ {print $2}' | grep -vq "(none)"; } apt_install_if_missing() { local pkg="$1" if pkg_installed "$pkg"; then log "Package already installed: $pkg" return 0 fi if ! package_available "$pkg"; then err "Package not available in APT sources (skipping): $pkg" return 0 fi log "Installing package: $pkg" sudo apt-get install -y --no-install-recommends "$pkg" } ensure_deps() { log "Ensuring dependencies..." sudo apt-get update apt_install_if_missing autorandr apt_install_if_missing x11-xserver-utils apt_install_if_missing xrandr } choose_profile() { if [[ -n "$AUTORANDR_PROFILE" ]]; then return 0 fi have_cmd autorandr || { err "autorandr not installed"; exit 1; } log "Available autorandr profiles:" local profiles profiles="$(autorandr --list 2>/dev/null || true)" if [[ -z "$profiles" ]]; then err "No autorandr profiles found. Create one first (e.g., using debian_display_setup.sh Stage 5)." exit 1 fi echo "$profiles" | sed 's/^/ - /' read -r -p "Enter the profile name to use: " AUTORANDR_PROFILE [[ -n "$AUTORANDR_PROFILE" ]] || { err "No profile provided"; exit 1; } } install_preswitch_hook() { log "Installing preswitch hook template for profile: $AUTORANDR_PROFILE" local ar_dir="$HOME/.config/autorandr/$AUTORANDR_PROFILE" mkdir -p "$ar_dir" local hook="$ar_dir/preswitch" cat >"$hook" <<'SH' #!/bin/sh # autorandr preswitch hook (template) # Runs before switching to the profile. # Use this to (re-)add custom modelines if your hardware/firmware forgets them. # Example: # xrandr --newmode "1920x1080_60.00" ... || true # xrandr --addmode HDMI-1 "1920x1080_60.00" || true exit 0 SH chmod +x "$hook" log "Hook installed: $hook" } install_xdg_autostart() { local auto_dir="$HOME/.config/autostart" local desktop_file="$auto_dir/autorandr-change.desktop" mkdir -p "$auto_dir" if [[ -f "$desktop_file" ]]; then if ! confirm_yes "This will overwrite $desktop_file (a backup will be created). Proceed?"; then log "Skipped XDG autostart." return 0 fi cp -a "$desktop_file" "${desktop_file}.bak.$(date +%Y%m%d_%H%M%S)" || true fi cat >"$desktop_file" <<'SH' [Desktop Entry] Type=Application Name=Autorandr Apply Comment=Apply autorandr profile at login Exec=autorandr --change X-GNOME-Autostart-enabled=true SH log "Installed XDG autostart: $desktop_file" } install_systemd_user_unit() { local unit_dir="$HOME/.config/systemd/user" local unit_file="$unit_dir/autorandr-change.service" mkdir -p "$unit_dir" if [[ -f "$unit_file" ]]; then if ! confirm_yes "This will overwrite $unit_file (a backup will be created). Proceed?"; then log "Skipped systemd user unit." return 0 fi cp -a "$unit_file" "${unit_file}.bak.$(date +%Y%m%d_%H%M%S)" || true fi cat >"$unit_file" <<'UNIT' [Unit] Description=Apply autorandr profile After=graphical-session.target [Service] Type=oneshot ExecStart=/usr/bin/autorandr --change [Install] WantedBy=default.target UNIT systemctl --user daemon-reload systemctl --user enable autorandr-change.service log "Installed and enabled systemd user unit: $unit_file" } enable_system_services_if_present() { if systemctl list-unit-files | grep -q '^autorandr\.service'; then if confirm_yes "Enable system-wide autorandr.service now?"; then sudo systemctl enable --now autorandr.service || true fi fi if systemctl list-unit-files | grep -q '^autorandr-resume\.service'; then if confirm_yes "Enable system-wide autorandr-resume.service now?"; then sudo systemctl enable --now autorandr-resume.service || true fi fi } try_apply_now() { if [[ -n "${DISPLAY:-}" ]]; then log "DISPLAY is set; attempting autorandr --change now..." if ! autorandr --change; then err "autorandr --change failed in-session (you can run it manually later)." return 0 fi log "autorandr applied." else log "DISPLAY not set; skipping immediate autorandr apply." fi } main() { have_cmd sudo || { err "sudo required"; exit 1; } sudo -v || { err "sudo auth failed"; exit 1; } ensure_deps choose_profile install_preswitch_hook if confirm_yes "Enable system-wide autorandr services (if present)?"; then enable_system_services_if_present fi if confirm_yes "Install XDG autostart entry to run 'autorandr --change' at login?"; then install_xdg_autostart fi if confirm_yes "Install a systemd user unit to run 'autorandr --change' at login?"; then install_systemd_user_unit fi try_apply_now log "Done. Profile selected: $AUTORANDR_PROFILE" log "Profile config path: ~/.config/autorandr/$AUTORANDR_PROFILE" } main "$@" EOF } generate_pwsh_script() { log "Generating (optional): $PWSH_SCRIPT" safe_write_file "$PWSH_SCRIPT" 755 <<'EOF' #!/usr/bin/env pwsh <### Pwsh Monitor Setup — Integrated, Optimized Build TARGET: Linux + X11 (xrandr), PowerShell Core (pwsh) Highlights: - Dependency detection + optional auto-install (supports apt/dnf/zypper/pacman). - X session readiness checks. - Tkinter overlay verification. - Interactive mode selection per output, with ordering prompt. - Saves layout to JSON for reuse. NOTE: This script is generated by the RK3588 toolkit installer. ###> [CmdletBinding()] param( [switch] $DebugMode, [switch] $AutoInstall, [switch] $NoInstall, [int] $OverlaySeconds = 5, [string] $ConfigPath ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ($DebugMode) { $VerbosePreference = 'Continue' } function Stop-TranscriptSafe { try { Stop-Transcript | Out-Null } catch {} } $tsFile = "/tmp/pwsh_monitor_setup-$(Get-Date -Format 'yyyyMMdd-HHmmss').log" try { Start-Transcript -Path $tsFile -Force | Out-Null } catch {} function Test-Cmd([string]$Name) { $null -ne (Get-Command -Name $Name -ErrorAction SilentlyContinue) } function Test-IsRoot { try { ((& id '-u' 2>$null) -eq 0) } catch { $false } } function Detect-PackageManager { if (Test-Cmd 'apt-get') { return 'apt' } if (Test-Cmd 'dnf') { return 'dnf' } if (Test-Cmd 'zypper') { return 'zypper' } if (Test-Cmd 'pacman') { return 'pacman' } return $null } function Install-Dep([string]$pkg) { $pm = Detect-PackageManager if (-not $pm) { throw 'No supported package manager found.' } if (-not (Test-IsRoot)) { throw 'Auto-install requires root.' } switch ($pm) { 'apt' { & apt-get update; & apt-get install -y $pkg } 'dnf' { & dnf install -y $pkg } 'zypper' { & zypper -n install $pkg } 'pacman' { & pacman -Sy --noconfirm $pkg } } } function Ensure-DepMapped([string]$cmd, [hashtable]$pkgMap, [string]$friendly) { if (Test-Cmd $cmd) { return } if ($NoInstall) { throw "Missing dependency '$friendly' and -NoInstall specified." } if (-not $AutoInstall) { throw "Missing dependency '$friendly'. Rerun with -AutoInstall." } $pm = Detect-PackageManager if (-not $pkgMap.ContainsKey($pm)) { throw "Unsupported PM for '$friendly'." } Install-Dep $pkgMap[$pm] if (-not (Test-Cmd $cmd)) { throw "Dependency '$friendly' missing after install." } } function Test-PythonTk { @' import sys try: import tkinter as t root=t.Tk(); root.withdraw() print('OK') except Exception: sys.exit(1) '@ | Set-Content -Path ($tmp = [IO.Path]::GetTempFileName() + '.py') -Encoding UTF8 try { $p = Start-Process -FilePath 'python3' -ArgumentList @($tmp) -PassThru -NoNewWindow -Wait return ($p.ExitCode -eq 0) } finally { try { Remove-Item $tmp -Force -ErrorAction SilentlyContinue } catch {} } } function Assert-XSessionOrExit { if (-not $env:DISPLAY) { $env:DISPLAY = ':0' } for ($i=0; $i -lt 6; $i++) { try { & xrandr --current | Out-Null; return } catch { Start-Sleep -Milliseconds 300 } } Write-Error 'No X11 display/xrandr available.' Stop-TranscriptSafe exit 20 } try { Ensure-DepMapped 'xrandr' @{ apt='xrandr'; dnf='xrandr'; zypper='xrandr'; pacman='xorg-xrandr' } 'xrandr' Ensure-DepMapped 'python3' @{ apt='python3'; dnf='python3'; zypper='python3'; pacman='python' } 'python3' } catch { Write-Error $_.Exception.Message Stop-TranscriptSafe exit 2 } Assert-XSessionOrExit if (-not (Test-PythonTk)) { Write-Error "python3-tk check failed. Install tkinter (python3-tk)." Stop-TranscriptSafe exit 2 } function Get-XrandrLines { (& xrandr --query | Out-String) -split "`n" } function Get-ConnectedOutputs { Get-XrandrLines | ForEach-Object { if ($_ -match '^\s*(\S+)\s+connected') { $Matches[1] } } } function Invoke-Xrandr([string[]]$XRArgs) { $p = Start-Process -FilePath 'xrandr' -ArgumentList $XRArgs -PassThru -NoNewWindow -Wait if ($DebugMode) { Write-Verbose ("xrandr " + ($XRArgs -join ' ') + " => " + $p.ExitCode) } $p.ExitCode } function Get-OutputModeCandidates([string]$Output) { $lines = Get-XrandrLines $in = $false $cands = New-Object 'System.Collections.Generic.List[object]' foreach ($ln in $lines) { if (-not $in) { if ($ln -match "^\s*$([Regex]::Escape($Output))\s+connected") { $in = $true } } else { if ($ln -match '^\S+\s+(connected|disconnected)') { break } if ($ln -match '^\s+((\d{3,5}x\d{3,5})(i)?\S*)\s+(.+)$') { $tok=$Matches[1]; $res=$Matches[2]; $rest=$Matches[4] $w=[int]($res -split 'x')[0]; $h=[int]($res -split 'x')[1] $mAll = [regex]::Matches($rest, '(\d+(?:\.\d+)?)([*+]{0,2})') foreach ($m in $mAll) { $hz=$m.Groups[1].Value if ([string]::IsNullOrWhiteSpace($hz)) { continue } $cands.Add([pscustomobject]@{Output=$Output; ModeToken=$tok; W=$w; H=$h; Rate=[double]$hz; Label="$tok@$hz"; Prefer=$m.Groups[2].Value -match '\+'; Current=$m.Groups[2].Value -match '\*'}) } } } } return $cands | Sort-Object @{e={$_.W*$_.H};d=$true}, @{e={$_.Rate};d=$true} } function Show-OverlayTk([int]$W,[int]$H,[int]$X,[int]$Y,[int]$Seconds) { $py = @' import sys, tkinter as t w, h, x, y, sec = map(int, sys.argv[1:6]) root = t.Tk(); root.overrideredirect(1); root.attributes("-topmost", True) root.geometry(f"{w}x{h}+{x}+{y}") t.Frame(root, width=w, height=h, highlightbackground="red", highlightthickness=8).pack() root.after(sec*1000, root.destroy); root.mainloop() '@ $tmp = [IO.Path]::GetTempFileName() + '.py' [IO.File]::WriteAllText($tmp, $py) $p = Start-Process -FilePath 'python3' -ArgumentList @($tmp, $W, $H, $X, $Y, $Seconds) -PassThru -NoNewWindow try { Wait-Process -Id $p.Id -Timeout ([Math]::Max($Seconds+3, 8)) } catch { try { Stop-Process -Id $p.Id -Force } catch {} } try { Remove-Item $tmp -Force -ErrorAction SilentlyContinue } catch {} } $outs = Get-ConnectedOutputs if (-not $outs) { Write-Error 'No connected monitors detected.' Stop-TranscriptSafe exit 21 } $ordered = New-Object 'System.Collections.Generic.List[string]' if ($outs.Count -gt 1) { Write-Host "Monitors: $($outs -join ', ')" -ForegroundColor Cyan $rem = [System.Collections.Generic.List[string]]::new(); $rem.AddRange([string[]]$outs) while ($rem.Count -gt 0) { $q = if ($ordered.Count -eq 0) { 'Leftmost monitor?' } else { "Right of $($ordered[-1])?" } for ($i=0; $i -lt $rem.Count; $i++) { Write-Host " [$($i+1)] $($rem[$i])" } $ans = Read-Host "$q (1-$($rem.Count))" if ($ans -match '^\d+$' -and [int]$ans -ge 1 -and [int]$ans -le $rem.Count) { $idx=[int]$ans-1; $ordered.Add($rem[$idx]); $rem.RemoveAt($idx) } } } else { $ordered.Add($outs[0]) } $all = @{}; foreach ($o in $ordered) { $all[$o] = Get-OutputModeCandidates $o } $chosen = [ordered]@{}; foreach ($o in $ordered) { $chosen[$o] = $null } foreach ($current in $ordered) { Write-Host "Select mode for $current" -ForegroundColor Cyan $cands = @($all[$current]) if (-not $cands -or $cands.Count -lt 1) { continue } $idx=1 foreach ($c in $cands) { Write-Host (" [{0}] {1} ({2}x{3})" -f $idx++, $c.Label, $c.W, $c.H) } $ans = Read-Host "Choice (1-$($cands.Count)), or Enter to keep current" if ($ans -match '^\d+$' -and [int]$ans -ge 1 -and [int]$ans -le $cands.Count) { $c = $cands[[int]$ans-1] $x=0 foreach ($o in $ordered) { $tgt = if ($o -eq $current) { $c } else { $chosen[$o] ?? $all[$o][0] } if ($tgt) { $args = @('--output', $o, '--mode', $tgt.ModeToken, '--pos', "${x}x0") if ($tgt.Rate) { $args += @('--rate', ("{0:0.##}" -f $tgt.Rate)) } Invoke-Xrandr $args | Out-Null $x += $tgt.W } } Show-OverlayTk $c.W $c.H 0 0 $OverlaySeconds if ((Read-Host 'Keep? (y/n)') -eq 'y') { $chosen[$current] = $c } } } $persist = [ordered]@{} foreach ($o in $ordered) { $c = $chosen[$o] if ($null -eq $c) { continue } $persist[$o] = [ordered]@{ mode=$c.ModeToken; rate=$(if($c.Rate){[double]$c.Rate}else{$null}) } } $target = $ConfigPath if ([string]::IsNullOrWhiteSpace($target)) { $target = "$HOME/.config/pwsh-monitor-layout.json" } $dir = Split-Path -Parent $target if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null } ($persist | ConvertTo-Json) | Set-Content -Path $target Write-Host "Saved layout to: $target" -ForegroundColor Green Stop-TranscriptSafe exit 0 EOF } generate_toolkit_scripts() { log "=== Step 2/4: Generating toolkit scripts under: $TOOLKIT_DIR ===" mkdir -p "$BIN_DIR" "$LOG_DIR" "$STATE_DIR" generate_audit_script generate_menu_script generate_interactive_script generate_dual_fix_script generate_bootstrap_script generate_pwsh_script log "Toolkit scripts generated successfully." } # ----------------------------- # Step 3: Self-tests (syntax / presence) # ----------------------------- run_self_tests() { log "=== Step 3/4: Running basic self-tests (syntax checks) ===" local failed=0 for f in "$AUDIT_SCRIPT" "$MENU_SCRIPT" "$INTERACTIVE_SCRIPT" "$DUAL_FIX_SCRIPT" "$BOOTSTRAP_SCRIPT"; do if [[ -f "$f" ]]; then if bash -n "$f"; then log "OK: bash -n $(basename "$f")" else err "FAIL: bash -n $(basename "$f")" failed=1 fi else err "Missing generated file: $f" failed=1 fi done if [[ -f "$PWSH_SCRIPT" ]]; then if head -n 1 "$PWSH_SCRIPT" | grep -q 'pwsh'; then log "OK: pwsh script present (pwsh not validated here): $(basename "$PWSH_SCRIPT")" else err "WARN: pwsh script shebang unexpected: $PWSH_SCRIPT" fi fi if [[ "$failed" -ne 0 ]]; then err "One or more self-tests failed. Review logs above." return 1 fi log "Self-tests passed." } # ----------------------------- # Step 4: Final guidance # ----------------------------- print_next_steps() { log "=== Step 4/4: Next steps ===" cat <<EOF Toolkit location: $TOOLKIT_DIR Generated scripts: 1) Capability audit (safe; optional prompts for tunings): $AUDIT_SCRIPT 2) Menu-driven display setup wrapper: $MENU_SCRIPT 3) Interactive mode picker (REQUIRES X SESSION; CHANGES DISPLAY MODES): $INTERACTIVE_SCRIPT 4) RK3588 dual-monitor fix (REQUIRES X SESSION; CHANGES DISPLAY MODES): $DUAL_FIX_SCRIPT 5) Autorandr bootstrap (optional autostart/systemd integration): $BOOTSTRAP_SCRIPT 6) Optional PowerShell (pwsh) interactive configurator (requires pwsh installed): $PWSH_SCRIPT Recommended flow: # Optional: baseline audit "$AUDIT_SCRIPT" # Install display tools / collect info / create custom xrandr / save autorandr profile "$MENU_SCRIPT" # In X session: safe interactive mode picking "$INTERACTIVE_SCRIPT" # If you have the specific HDMI+DP low-mode issue "$DUAL_FIX_SCRIPT" # Wire autorandr persistence at login/resume "$BOOTSTRAP_SCRIPT" EOF } # ----------------------------- # Main # ----------------------------- main() { log "RK3588 Display Toolkit Installer — ${VERSION}" require_home_context check_platform install_dependencies generate_toolkit_scripts run_self_tests print_next_steps log "Setup complete." } main "$@"

#Recommended flow:

#Optional: baseline audit

/home/linaro/rk3588-display-toolkit/bin/rk3588_audit.sh

#Install display tools / collect info / create custom xrandr / save autorandr profile

bash /home/linaro/rk3588-display-toolkit/bin/debian_display_setup.sh

#In X session: safe interactive mode picking

bash /home/linaro/rk3588-display-toolkit/bin/monitor_setup_interactive.sh pwsh /home/linaro/rk3588-display-toolkit/bin/pwsh_monitor_setup.ps1

#If you have the specific HDMI+DP low-mode issue

bash /home/linaro/rk3588-display-toolkit/bin/rk3588_dual_display_setup.sh

#Wire autorandr persistence at login/resume

bash /home/linaro/rk3588-display-toolkit/bin/rk3588_autorandr_bootstrap.sh


#/home/linaro/rk3588-display-toolkit/bin/debian_display_setup.sh

#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' # debian_display_setup.sh # Menu-driven helper for: # - One-time package install # - Monitor info collection (xrandr + sysfs EDID + optional root-only tools) # - Custom xrandr script template # - Autorandr profile save MONITOR_INFO_OUTDIR="" CUSTOM_XRANDR_SCRIPT_PATH="${HOME}/my_custom_display_config.sh" TOOLKIT_HOME="${HOME}/rk3588-display-toolkit" CONFIG_DIR="${TOOLKIT_HOME}/state/debian_display_master" PREP_DONE_FLAG="${CONFIG_DIR}/system_prep_done.flag" log() { printf '[%(%F %T)T] %s\n' -1 "$*"; } ensure_config_dir() { mkdir -p "$CONFIG_DIR" if [[ $EUID -eq 0 && -n "${SUDO_USER:-}" && "${SUDO_USER}" != "root" ]]; then chown -R "$SUDO_USER:$(id -gn "$SUDO_USER")" "$CONFIG_DIR" 2>/dev/null || true fi } require_root_or_return() { if [[ $EUID -ne 0 ]]; then echo "ERROR: This step must be run with sudo/root." >&2 echo "Re-run with: sudo bash $0" >&2 return 1 fi return 0 } system_prep() { echo echo "--- Stage 1: One-Time System Preparation (sudo required) ---" require_root_or_return || return 1 echo ">>> Updating apt lists..." apt-get update echo ">>> Installing autorandr + tools..." apt-get install -y --no-install-recommends \ autorandr xrandr x11-xserver-utils python3 python3-tk \ read-edid ddcutil hwinfo inxi lshw edid-decode bc if systemctl list-unit-files | grep -q '^autorandr\.service'; then systemctl enable --now autorandr.service || true fi if systemctl list-unit-files | grep -q '^autorandr-resume\.service'; then systemctl enable --now autorandr-resume.service || true fi ensure_config_dir date > "$PREP_DONE_FLAG" echo ">>> System preparation complete." } _mi_setup_outdir() { local real_user home_base if [[ $EUID -eq 0 && -n "${SUDO_USER:-}" && "$SUDO_USER" != "root" ]]; then real_user="$SUDO_USER" else real_user="$(whoami)" fi home_base="$(getent passwd "$real_user" | cut -d: -f6)" [[ -n "$home_base" && -d "$home_base" ]] || home_base="/tmp" MONITOR_INFO_OUTDIR="${home_base}/monitor-info-$(date +%Y%m%d-%H%M%S)" mkdir -p "$MONITOR_INFO_OUTDIR" : >"$MONITOR_INFO_OUTDIR/summary.txt" if [[ $EUID -eq 0 && -n "${SUDO_USER:-}" ]]; then chown -R "$SUDO_USER:$(id -gn "$SUDO_USER")" "$MONITOR_INFO_OUTDIR" 2>/dev/null || true fi } _mi_log_cmd() { local logfile="$1"; shift { echo "===== $(date '+%F %T') : $* =====" "$@" 2>&1 || echo "(ERROR: '$*' failed code $?)" echo } >>"$MONITOR_INFO_OUTDIR/$logfile" } _mi_detect_connectors_sysfs() { local regex='^[a-zA-Z]+(-[a-zA-Z0-9]+)*-[0-9]+$' echo "--- DRM connectors (sysfs) ---" >>"$MONITOR_INFO_OUTDIR/summary.txt" for card in /sys/class/drm/card*; do [[ -d "$card" ]] || continue for p in "$card"/*; do [[ -d "$p" ]] || continue local name name="$(basename "$p")" if [[ "$name" =~ $regex ]]; then echo "DRM connector: $name" >>"$MONITOR_INFO_OUTDIR/summary.txt" fi done done } _mi_collect_sysfs_edid() { command -v edid-decode >/dev/null 2>&1 || return 0 echo "--- EDID decode (sysfs) ---" >>"$MONITOR_INFO_OUTDIR/summary.txt" while IFS= read -r edid; do [[ -s "$edid" ]] || continue _mi_log_cmd "edid_sysfs.log" edid-decode "$edid" done < <(find /sys/class/drm -name edid 2>/dev/null || true) } _mi_generate_cvt_interactive() { if ! command -v cvt >/dev/null 2>&1; then echo "Note: 'cvt' not found; skipping modeline generator." >>"$MONITOR_INFO_OUTDIR/summary.txt" return 0 fi echo read -r -p "Generate a modeline via cvt? Enter 'width height refresh' (or Enter to skip): " W H R if [[ -n "${W:-}" && -n "${H:-}" && -n "${R:-}" ]]; then cvt "$W" "$H" "$R" | tee -a "$MONITOR_INFO_OUTDIR/cvt_modeline.log" >/dev/null echo "Wrote: $MONITOR_INFO_OUTDIR/cvt_modeline.log" fi } monitor_info() { echo echo "--- Stage 2: Collect Monitor Information ---" _mi_setup_outdir echo "Collecting monitor information into: $MONITOR_INFO_OUTDIR" _mi_detect_connectors_sysfs if command -v xrandr >/dev/null 2>&1; then _mi_log_cmd "xrandr_verbose.log" xrandr --verbose xrandr | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" >/dev/null || true else echo "xrandr not found. Run Stage 1 first." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" >/dev/null fi _mi_collect_sysfs_edid if [[ $EUID -eq 0 ]]; then command -v ddcutil >/dev/null 2>&1 && _mi_log_cmd "ddcutil_detect.log" ddcutil detect --verbose || true command -v get-edid >/dev/null 2>&1 && _mi_log_cmd "get_edid.log" get-edid || true command -v hwinfo >/dev/null 2>&1 && _mi_log_cmd "hwinfo_monitor.log" hwinfo --monitor || true command -v lshw >/dev/null 2>&1 && _mi_log_cmd "lshw_display.log" lshw -C display || true else echo "Note: root-only tools (ddcutil, get-edid, hwinfo, lshw) are skipped unless run with sudo." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" >/dev/null fi _mi_generate_cvt_interactive echo "Done. Summary: $MONITOR_INFO_OUTDIR/summary.txt" } create_or_edit_custom_xrandr() { echo echo "--- Stage 3: Create/Edit Custom Xrandr Configuration Script ---" if [[ ! -f "$CUSTOM_XRANDR_SCRIPT_PATH" ]]; then cat >"$CUSTOM_XRANDR_SCRIPT_PATH" <<'SH' #!/usr/bin/env bash set -euo pipefail # Custom Xrandr Configuration Script # Edit this file to match your connector names and desired modes. command -v xrandr >/dev/null 2>&1 || { echo "Error: xrandr not found" >&2; exit 1; } echo "Applying custom xrandr settings..." # Example: # OUTPUT_1_NAME="HDMI-1" # MODE_1_NAME="1920x1080" # xrandr --output "$OUTPUT_1_NAME" --mode "$MODE_1_NAME" --primary # Safe fallback: xrandr --auto SH chmod +x "$CUSTOM_XRANDR_SCRIPT_PATH" echo "Created template: $CUSTOM_XRANDR_SCRIPT_PATH" else echo "Custom script exists: $CUSTOM_XRANDR_SCRIPT_PATH" fi read -r -p "Open with nano now? (y/N): " ans if [[ "$ans" =~ ^[Yy]$ ]]; then nano "$CUSTOM_XRANDR_SCRIPT_PATH" fi } test_custom_xrandr() { echo echo "--- Stage 4: Test Custom Xrandr Script (must be in X session) ---" if [[ -z "${DISPLAY:-}" ]]; then echo "ERROR: DISPLAY is not set. Run from an active X session." >&2 return 1 fi if [[ $EUID -eq 0 ]]; then echo "WARNING: Running xrandr as root is not recommended. Prefer running as your user." >&2 fi if ! bash "$CUSTOM_XRANDR_SCRIPT_PATH"; then echo "Error: custom xrandr script failed." >&2 echo "Recovery hint: switch to a TTY (Ctrl+Alt+F2), then try: DISPLAY=:0 xrandr --auto (if possible), or reboot." >&2 return 1 fi echo "Custom xrandr script executed." } save_autorandr_profile() { echo echo "--- Stage 5: Save Autorandr Profile (run in X session) ---" command -v autorandr >/dev/null 2>&1 || { echo "Error: autorandr not found. Run Stage 1." >&2; return 1; } read -r -p "Enter profile name: " pname pname="$(echo "$pname" | tr -s ' /\:&?' '_')" [[ -n "$pname" ]] || { echo "No profile name provided." >&2; return 1; } autorandr --save "$pname" echo "Saved autorandr profile: $pname" } main_menu() { ensure_config_dir echo echo "Debian Bullseye Display Setup & Management Utility" echo "==================================================" echo "NOTE: Some actions require sudo, others require an active X session." PS3="Choose an option: " local options=( "1) One-time system preparation (sudo required)" "2) Collect monitor information" "3) Create/edit custom xrandr script" "4) Test custom xrandr script (X session)" "5) Save autorandr profile (X session)" "6) Exit" ) select opt in "${options[@]}"; do case "$REPLY" in 1) system_prep ;; 2) monitor_info ;; 3) create_or_edit_custom_xrandr ;; 4) test_custom_xrandr ;; 5) save_autorandr_profile ;; 6) echo "Exiting."; exit 0 ;; *) echo "Invalid option." ;; esac echo read -r -p "Press Enter to return to the menu..." _ done } main_menu

#/home/linaro/rk3588-display-toolkit/bin/monitor_setup_interactive.sh

#!/usr/bin/env bash set -Eeuo pipefail IFS=$'\n\t' # monitor_setup_interactive.sh # # Safer interactive mode picker with visual verification: # - SafeMode-first: tries a short list of common resolutions first # - Output ordering: HDMI/DP/DVI/eDP/VGA then the rest # - Tk overlay: draws a red border on the active monitor # # WARNING: This script changes display modes. # Recovery: switch to a TTY (Ctrl+Alt+F2) and run: DISPLAY=:0 xrandr --auto (if possible) or reboot. LOGF="${LOGF:-/tmp/monitor-setup.log}" exec > >(tee -a "$LOGF") 2>&1 log() { printf '[%(%F %T)T] %s\n' -1 "$*"; } fail() { log "ERROR: $*"; exit 1; } need() { command -v "$1" >/dev/null 2>&1 || fail "$1 not found"; } need xrandr need python3 need timeout python3 - <<'PY' 2>/dev/null || fail "python3-tk not available. Install python3-tk." import tkinter PY [[ -n "${DISPLAY:-}" ]] || fail "DISPLAY is not set. Run from an active X session." SAFEMODES=( 1920x1080 1680x1050 1600x900 1440x900 1366x768 1280x1024 1280x960 1280x800 1280x720 1024x768 800x600 640x480 ) get_outputs() { xrandr --query | awk '/ connected/{print $1}'; } sort_outputs() { local -a outs mapfile -t outs < <(get_outputs) local prefs=(HDMI DP DVI eDP VGA) for p in "${prefs[@]}"; do for o in "${outs[@]}"; do [[ "$o" == ${p}* ]] && echo "$o"; done done for o in "${outs[@]}"; do local hit=0 for p in "${prefs[@]}"; do [[ "$o" == ${p}* ]] && hit=1; done ((hit==0)) && echo "$o" done } get_modes_for_output() { local out="$1" xrandr --query | awk -v o="$out" ' $1==o {inblock=1; next} /^[A-Z]/ {inblock=0} inblock && $1 ~ /^[0-9]+x[0-9]+/ {print $1} ' | awk '!seen[$0]++' } current_mode_for_output() { local out="$1" xrandr --query | awk -v o="$out" ' $1==o {inblock=1; next} /^[A-Z]/ {inblock=0} inblock && $1 ~ /^[0-9]+x[0-9]+/ { if ($0 ~ /\*/) {print $1; exit} } ' } pick_safelist() { local -a avail=() mapfile -t avail local -a picked=() for safe in "${SAFEMODES[@]}"; do for m in "${avail[@]}"; do [[ "$m" == "$safe" ]] && { picked+=("$m"); break; } done (( ${#picked[@]} >= 4 )) && break done if (( ${#picked[@]} > 0 )); then printf '%s\n' "${picked[@]}" else printf '%s\n' "${avail[@]}" | head -n 4 fi } current_geometry() { local out="$1" xrandr --query | awk -v o="$out" ' $1==o { if (match($0, /[0-9]+x[0-9]+\+[0-9]+\+[0-9]+/)) { print substr($0, RSTART, RLENGTH) } } ' } show_rectangle() { local W="$1" H="$2" X="$3" Y="$4" log " overlay ${W}x${H}+${X}+${Y} for 5s" timeout 7s python3 - "$W" "$H" "$X" "$Y" <<'PY' import sys, tkinter as t W,H,X,Y = map(int, sys.argv[1:]) root = t.Tk() root.overrideredirect(True) root.geometry(f"{W}x{H}+{X}+{Y}") frame = t.Frame(root, width=W, height=H, highlightbackground='red', highlightthickness=8) frame.pack() root.attributes('-topmost', True) root.after(5000, root.quit) root.mainloop() try: root.destroy() except Exception: pass PY } ask_ynq() { local prompt="$1" ch while true; do printf '%s' "$prompt" > /dev/tty IFS= read -r -n1 ch < /dev/tty || ch="" printf '\n' > /dev/tty case "$ch" in y|Y) return 0;; n|N) return 1;; q|Q) return 2;; *) prompt="Please type y (accept), n (next), or q (quit): ";; esac done } restore_mode_best_effort() { local out="$1" prev="$2" if [[ -n "$prev" ]]; then xrandr --output "$out" --mode "$prev" >/dev/null 2>&1 || true else xrandr --output "$out" --auto >/dev/null 2>&1 || true fi } try_modes_for_output() { local out="$1"; shift local -a modes=("$@") local prev prev="$(current_mode_for_output "$out" || true)" for mode in "${modes[@]}"; do log "Trying $out -> $mode" if ! xrandr --output "$out" --mode "$mode"; then log "Failed to set $mode on $out" continue fi sleep 0.4 local geom geom="$(current_geometry "$out" || true)" if [[ "$geom" =~ ([0-9]+)x([0-9]+)\+([0-9]+)\+([0-9]+) ]]; then show_rectangle "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" "${BASH_REMATCH[4]}" if ask_ynq "Keep $mode for $out? [y/n/q] "; then log "Accepted $mode for $out" return 0 else local rc=$? if ((rc==2)); then restore_mode_best_effort "$out" "$prev" return 2 fi fi else log "Could not parse geometry for $out after applying $mode (continuing)." fi done restore_mode_best_effort "$out" "$prev" return 1 } log "Starting interactive monitor configuration..." mapfile -t outputs < <(sort_outputs) (( ${#outputs[@]} > 0 )) || fail "No connected outputs detected by xrandr." for out in "${outputs[@]}"; do log "Configuring $out..." mapfile -t avail < <(get_modes_for_output "$out") (( ${#avail[@]} > 0 )) || { log "No modes found for $out (skipping)."; continue; } mapfile -t testlist < <(printf '%s\n' "${avail[@]}" | pick_safelist) if try_modes_for_output "$out" "${testlist[@]}"; then continue else rc=$? ((rc==2)) && { log "Quit requested."; exit 0; } fi if ask_ynq "No SafeMode accepted for $out. Try ALL modes (riskier)? [y/n/q] "; then if try_modes_for_output "$out" "${avail[@]}"; then continue fi else rc=$? ((rc==2)) && { log "Quit requested."; exit 0; } fi done log "Done. For persistence, save an autorandr profile using debian_display_setup.sh."

#/home/linaro/rk3588-display-toolkit/bin/pwsh_monitor_setup.ps1

#!/usr/bin/env pwsh <### Pwsh Monitor Setup — Integrated, Optimized Build TARGET: Linux + X11 (xrandr), PowerShell Core (pwsh) Highlights: - Dependency detection + optional auto-install (supports apt/dnf/zypper/pacman). - X session readiness checks. - Tkinter overlay verification. - Interactive mode selection per output, with ordering prompt. - Saves layout to JSON for reuse. NOTE: This script is generated by the RK3588 toolkit installer. ###> [CmdletBinding()] param( [switch] $DebugMode, [switch] $AutoInstall, [switch] $NoInstall, [int] $OverlaySeconds = 5, [string] $ConfigPath ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ($DebugMode) { $VerbosePreference = 'Continue' } function Stop-TranscriptSafe { try { Stop-Transcript | Out-Null } catch {} } $tsFile = "/tmp/pwsh_monitor_setup-$(Get-Date -Format 'yyyyMMdd-HHmmss').log" try { Start-Transcript -Path $tsFile -Force | Out-Null } catch {} function Test-Cmd([string]$Name) { $null -ne (Get-Command -Name $Name -ErrorAction SilentlyContinue) } function Test-IsRoot { try { ((& id '-u' 2>$null) -eq 0) } catch { $false } } function Detect-PackageManager { if (Test-Cmd 'apt-get') { return 'apt' } if (Test-Cmd 'dnf') { return 'dnf' } if (Test-Cmd 'zypper') { return 'zypper' } if (Test-Cmd 'pacman') { return 'pacman' } return $null } function Install-Dep([string]$pkg) { $pm = Detect-PackageManager if (-not $pm) { throw 'No supported package manager found.' } if (-not (Test-IsRoot)) { throw 'Auto-install requires root.' } switch ($pm) { 'apt' { & apt-get update; & apt-get install -y $pkg } 'dnf' { & dnf install -y $pkg } 'zypper' { & zypper -n install $pkg } 'pacman' { & pacman -Sy --noconfirm $pkg } } } function Ensure-DepMapped([string]$cmd, [hashtable]$pkgMap, [string]$friendly) { if (Test-Cmd $cmd) { return } if ($NoInstall) { throw "Missing dependency '$friendly' and -NoInstall specified." } if (-not $AutoInstall) { throw "Missing dependency '$friendly'. Rerun with -AutoInstall." } $pm = Detect-PackageManager if (-not $pkgMap.ContainsKey($pm)) { throw "Unsupported PM for '$friendly'." } Install-Dep $pkgMap[$pm] if (-not (Test-Cmd $cmd)) { throw "Dependency '$friendly' missing after install." } } function Test-PythonTk { @' import sys try: import tkinter as t root=t.Tk(); root.withdraw() print('OK') except Exception: sys.exit(1) '@ | Set-Content -Path ($tmp = [IO.Path]::GetTempFileName() + '.py') -Encoding UTF8 try { $p = Start-Process -FilePath 'python3' -ArgumentList @($tmp) -PassThru -NoNewWindow -Wait return ($p.ExitCode -eq 0) } finally { try { Remove-Item $tmp -Force -ErrorAction SilentlyContinue } catch {} } } function Assert-XSessionOrExit { if (-not $env:DISPLAY) { $env:DISPLAY = ':0' } for ($i=0; $i -lt 6; $i++) { try { & xrandr --current | Out-Null; return } catch { Start-Sleep -Milliseconds 300 } } Write-Error 'No X11 display/xrandr available.' Stop-TranscriptSafe exit 20 } try { Ensure-DepMapped 'xrandr' @{ apt='xrandr'; dnf='xrandr'; zypper='xrandr'; pacman='xorg-xrandr' } 'xrandr' Ensure-DepMapped 'python3' @{ apt='python3'; dnf='python3'; zypper='python3'; pacman='python' } 'python3' } catch { Write-Error $_.Exception.Message Stop-TranscriptSafe exit 2 } Assert-XSessionOrExit if (-not (Test-PythonTk)) { Write-Error "python3-tk check failed. Install tkinter (python3-tk)." Stop-TranscriptSafe exit 2 } function Get-XrandrLines { (& xrandr --query | Out-String) -split "`n" } function Get-ConnectedOutputs { Get-XrandrLines | ForEach-Object { if ($_ -match '^\s*(\S+)\s+connected') { $Matches[1] } } } function Invoke-Xrandr([string[]]$XRArgs) { $p = Start-Process -FilePath 'xrandr' -ArgumentList $XRArgs -PassThru -NoNewWindow -Wait if ($DebugMode) { Write-Verbose ("xrandr " + ($XRArgs -join ' ') + " => " + $p.ExitCode) } $p.ExitCode } function Get-OutputModeCandidates([string]$Output) { $lines = Get-XrandrLines $in = $false $cands = New-Object 'System.Collections.Generic.List[object]' foreach ($ln in $lines) { if (-not $in) { if ($ln -match "^\s*$([Regex]::Escape($Output))\s+connected") { $in = $true } } else { if ($ln -match '^\S+\s+(connected|disconnected)') { break } if ($ln -match '^\s+((\d{3,5}x\d{3,5})(i)?\S*)\s+(.+)$') { $tok=$Matches[1]; $res=$Matches[2]; $rest=$Matches[4] $w=[int]($res -split 'x')[0]; $h=[int]($res -split 'x')[1] $mAll = [regex]::Matches($rest, '(\d+(?:\.\d+)?)([*+]{0,2})') foreach ($m in $mAll) { $hz=$m.Groups[1].Value if ([string]::IsNullOrWhiteSpace($hz)) { continue } $cands.Add([pscustomobject]@{Output=$Output; ModeToken=$tok; W=$w; H=$h; Rate=[double]$hz; Label="$tok@$hz"; Prefer=$m.Groups[2].Value -match '\+'; Current=$m.Groups[2].Value -match '\*'}) } } } } return $cands | Sort-Object @{e={$_.W*$_.H};d=$true}, @{e={$_.Rate};d=$true} } function Show-OverlayTk([int]$W,[int]$H,[int]$X,[int]$Y,[int]$Seconds) { $py = @' import sys, tkinter as t w, h, x, y, sec = map(int, sys.argv[1:6]) root = t.Tk(); root.overrideredirect(1); root.attributes("-topmost", True) root.geometry(f"{w}x{h}+{x}+{y}") t.Frame(root, width=w, height=h, highlightbackground="red", highlightthickness=8).pack() root.after(sec*1000, root.destroy); root.mainloop() '@ $tmp = [IO.Path]::GetTempFileName() + '.py' [IO.File]::WriteAllText($tmp, $py) $p = Start-Process -FilePath 'python3' -ArgumentList @($tmp, $W, $H, $X, $Y, $Seconds) -PassThru -NoNewWindow try { Wait-Process -Id $p.Id -Timeout ([Math]::Max($Seconds+3, 8)) } catch { try { Stop-Process -Id $p.Id -Force } catch {} } try { Remove-Item $tmp -Force -ErrorAction SilentlyContinue } catch {} } $outs = Get-ConnectedOutputs if (-not $outs) { Write-Error 'No connected monitors detected.' Stop-TranscriptSafe exit 21 } $ordered = New-Object 'System.Collections.Generic.List[string]' if ($outs.Count -gt 1) { Write-Host "Monitors: $($outs -join ', ')" -ForegroundColor Cyan $rem = [System.Collections.Generic.List[string]]::new(); $rem.AddRange([string[]]$outs) while ($rem.Count -gt 0) { $q = if ($ordered.Count -eq 0) { 'Leftmost monitor?' } else { "Right of $($ordered[-1])?" } for ($i=0; $i -lt $rem.Count; $i++) { Write-Host " [$($i+1)] $($rem[$i])" } $ans = Read-Host "$q (1-$($rem.Count))" if ($ans -match '^\d+$' -and [int]$ans -ge 1 -and [int]$ans -le $rem.Count) { $idx=[int]$ans-1; $ordered.Add($rem[$idx]); $rem.RemoveAt($idx) } } } else { $ordered.Add($outs[0]) } $all = @{}; foreach ($o in $ordered) { $all[$o] = Get-OutputModeCandidates $o } $chosen = [ordered]@{}; foreach ($o in $ordered) { $chosen[$o] = $null } foreach ($current in $ordered) { Write-Host "Select mode for $current" -ForegroundColor Cyan $cands = @($all[$current]) if (-not $cands -or $cands.Count -lt 1) { continue } $idx=1 foreach ($c in $cands) { Write-Host (" [{0}] {1} ({2}x{3})" -f $idx++, $c.Label, $c.W, $c.H) } $ans = Read-Host "Choice (1-$($cands.Count)), or Enter to keep current" if ($ans -match '^\d+$' -and [int]$ans -ge 1 -and [int]$ans -le $cands.Count) { $c = $cands[[int]$ans-1] $x=0 foreach ($o in $ordered) { $tgt = if ($o -eq $current) { $c } else { $chosen[$o] ?? $all[$o][0] } if ($tgt) { $args = @('--output', $o, '--mode', $tgt.ModeToken, '--pos', "${x}x0") if ($tgt.Rate) { $args += @('--rate', ("{0:0.##}" -f $tgt.Rate)) } Invoke-Xrandr $args | Out-Null $x += $tgt.W } } Show-OverlayTk $c.W $c.H 0 0 $OverlaySeconds if ((Read-Host 'Keep? (y/n)') -eq 'y') { $chosen[$current] = $c } } } $persist = [ordered]@{} foreach ($o in $ordered) { $c = $chosen[$o] if ($null -eq $c) { continue } $persist[$o] = [ordered]@{ mode=$c.ModeToken; rate=$(if($c.Rate){[double]$c.Rate}else{$null}) } } $target = $ConfigPath if ([string]::IsNullOrWhiteSpace($target)) { $target = "$HOME/.config/pwsh-monitor-layout.json" } $dir = Split-Path -Parent $target if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null } ($persist | ConvertTo-Json) | Set-Content -Path $target Write-Host "Saved layout to: $target" -ForegroundColor Green Stop-TranscriptSafe exit 0

#/home/linaro/rk3588-display-toolkit/bin/rk3588_audit.sh

#!/usr/bin/env bash set -euo pipefail set -o errtrace IFS=$'\n\t' trap 'echo "Error on or near line ${LINENO}; command exited with status $?" >&2' ERR # rk3588_audit.sh # # Purpose: # - Create a timestamped audit directory under ~/rk3588_audit_<timestamp>/ # - Collect system/display/GPU/VPU/network/storage snapshots # - Install diagnostic packages idempotently (skips missing packages) # - Avoid destructive actions; optional actions require explicit confirmation START_TS="$(date +%Y%m%d_%H%M%S)" AUDIT_DIR="$HOME/rk3588_audit_${START_TS}" LOG_DIR="$AUDIT_DIR/logs" OUT_DIR="$AUDIT_DIR/out" REPORT_MD="$AUDIT_DIR/REPORT.md" LOG_FILE="$LOG_DIR/audit.log" ENV_FILE="$LOG_DIR/env.txt" DMESG_FILE="$LOG_DIR/dmesg.txt" CMD_FAIL_HINT="Check ${LOG_FILE} and ${REPORT_MD} for details." : "${NET_TIMEOUT:=5}" : "${FIO_SIZE_MB:=512}" : "${FIO_BS:=1M}" : "${FIO_IODEPTH:=16}" : "${RUN_COLORS:=1}" TMPDIR_CREATED="" cleanup() { if [[ -n "${TMPDIR_CREATED:-}" && -d "$TMPDIR_CREATED" ]]; then rm -rf "$TMPDIR_CREATED" || true fi } trap cleanup EXIT SIGINT SIGTERM if [[ -t 1 && "$RUN_COLORS" -eq 1 ]] && command -v tput >/dev/null 2>&1; then GREEN="$(tput setaf 2)"; YELLOW="$(tput setaf 3)"; RED="$(tput setaf 1)"; BLUE="$(tput setaf 4)"; BOLD="$(tput bold)"; RESET="$(tput sgr0)" else GREEN=""; YELLOW=""; RED=""; BLUE=""; BOLD=""; RESET="" fi log() { echo -e "$*" | tee -a "$LOG_FILE"; } section() { log "\n${BOLD}${BLUE}==> $1${RESET}"; } ensure_dir() { [[ -d "$1" ]] || mkdir -p "$1"; } ask_yes() { local prompt="$1" ans echo read -r -p "${YELLOW}${prompt}${RESET} (type 'yes' to continue, anything else to cancel): " ans [[ "$ans" == "yes" ]] || { echo "Cancelled by user."; return 1; } } need_sudo() { command -v sudo >/dev/null 2>&1 || { echo "Error: sudo required." >&2; exit 1; } sudo -v || { echo "Error: sudo auth failed." >&2; exit 1; } } package_available() { local pkg="$1" apt-cache policy "$pkg" 2>/dev/null | awk '/Candidate:/ {print $2}' | grep -vq "(none)" } is_installed() { dpkg -s "$1" >/dev/null 2>&1; } ensure_package() { local pkg="$1" if is_installed "$pkg"; then log "Package already installed: ${GREEN}${pkg}${RESET}" return 0 fi if ! package_available "$pkg"; then log "Package not available (skipping): ${YELLOW}${pkg}${RESET}" return 0 fi log "Installing package: ${GREEN}${pkg}${RESET}" sudo apt-get install -y --no-install-recommends "$pkg" >>"$LOG_FILE" 2>&1 || { echo "Error: Failed to install '$pkg'. ${CMD_FAIL_HINT}" >&2 exit 1 } } ensure_packages() { for pkg in "$@"; do ensure_package "$pkg"; done; } run_continue() { local title="$1"; shift log "\n--- $title ---" "$@" >>"$LOG_FILE" 2>&1 || log " (Command failed but continuing): $*" } run_fail() { local title="$1"; shift log "\n>>> $title" "$@" >>"$LOG_FILE" 2>&1 || { echo "Error: $title failed. ${CMD_FAIL_HINT}" >&2; exit 1; } } quick_net_check() { section "Quick network check" if command -v ping >/dev/null 2>&1 && ping -c1 -W "$NET_TIMEOUT" deb.debian.org >/dev/null 2>&1; then log "Network reachable." return 0 fi log "Network check failed; installs/downloads may be limited." return 1 } check_platform() { section "Platform checks" local debver arch debver="$(cut -d'.' -f1 < /etc/debian_version 2>/dev/null || echo unknown)" arch="$(uname -m 2>/dev/null || echo unknown)" log "Debian major: $debver" log "Arch : $arch" [[ "$debver" == "11" ]] || log "WARNING: tuned for Debian 11 (Bullseye)." [[ "$arch" == "aarch64" || "$arch" == "arm64" ]] || log "WARNING: tuned for ARM64." } enable_nonfree_optional() { section "Optional: Enable contrib/non-free (Bullseye)" if ! ask_yes "Enable 'contrib non-free' in /etc/apt/sources.list (backup + apt update)?"; then log "Skipped enabling contrib/non-free." return 0 fi local src="/etc/apt/sources.list" if [[ ! -f "$src" ]]; then log "No $src found; skipping." return 0 fi sudo cp -a "$src" "${src}.bak.${START_TS}" TMPDIR_CREATED="$(mktemp -d)" local tmp="$TMPDIR_CREATED/sources.list" sudo awk '{ if ($1=="deb" || $1=="deb-src") { line=$0 has_contrib=match(line,/(^| )contrib( |$)/) has_nonfree=match(line,/(^| )non-free( |$)/) if (!has_contrib) line=line" contrib" if (!has_nonfree) line=line" non-free" print line } else { print } }' "$src" | sudo tee "$tmp" >/dev/null sudo mv "$tmp" "$src" run_fail "apt-get update (after enabling contrib/non-free)" sudo apt-get update rm -rf "$TMPDIR_CREATED" || true TMPDIR_CREATED="" } main() { ensure_dir "$AUDIT_DIR"; ensure_dir "$LOG_DIR"; ensure_dir "$OUT_DIR" : >"$LOG_FILE" section "Start" log "Audit directory: $AUDIT_DIR" log "Log file : $LOG_FILE" need_sudo { echo "===== ENVIRONMENT =====" echo "Timestamp: $START_TS" uname -a || true echo echo "----- /etc/os-release -----" cat /etc/os-release 2>/dev/null || true echo echo "----- /proc/cmdline -----" cat /proc/cmdline 2>/dev/null || true echo echo "----- CPU -----" lscpu 2>/dev/null || true echo echo "----- Memory/CMA -----" grep -E 'CmaTotal|CmaFree|MemTotal|MemFree|HugePages' /proc/meminfo 2>/dev/null || true } >"$ENV_FILE" run_continue "Collect dmesg" bash -lc "sudo dmesg -T > '$DMESG_FILE'" check_platform quick_net_check || true section "APT update" run_fail "apt-get update" sudo apt-get update enable_nonfree_optional || true section "Install baseline diagnostic tools (idempotent; skips unavailable)" ensure_packages \ curl wget ca-certificates \ pciutils usbutils lshw hwinfo inxi \ ethtool iproute2 net-tools jq \ i2c-tools lm-sensors \ v4l-utils \ gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ ffmpeg alsa-utils \ vulkan-tools mesa-utils kmscube \ xrandr x11-xserver-utils autorandr \ edid-decode read-edid ddcutil \ fio hdparm nvme-cli smartmontools \ iw wireless-tools bluez can-utils section "System overview" run_continue "lshw (short)" sudo lshw -short run_continue "lsblk -O" lsblk -O run_continue "df -hT" df -hT run_continue "lsusb -t" lsusb -t run_continue "lspci -nnk" lspci -nnk run_continue "Kernel warnings/errors (last 200)" bash -lc 'dmesg -T --level=err,warn | tail -n 200' section "GPU & Display" run_continue "GPU modules loaded" bash -lc "lsmod | egrep -i 'panthor|panfrost|mali|kbase' || true" run_continue "GPU-related dmesg" bash -lc "dmesg -T | egrep -i 'mali|panthor|panfrost|csf|gpu' || true" run_continue "DRM connectors (modetest -c)" modetest -c run_continue "kmscube smoke test" bash -lc "kmscube -i 100 >/dev/null 2>&1 || true" run_continue "Vulkan summary" vulkaninfo --summary run_continue "xrandr --props (if X running)" bash -lc "DISPLAY=\${DISPLAY:-:0} xrandr --props 2>/dev/null || true" section "Video / V4L2 / Codecs" run_continue "List V4L2 devices" v4l2-ctl --list-devices run_continue "FFmpeg hwaccels" ffmpeg -hide_banner -hwaccels run_continue "GStreamer rockchip-ish plugins" bash -lc "gst-inspect-1.0 | egrep -i 'v4l2|rkv|hantro|rockchip' || true" section "Audio" run_continue "ALSA playback" aplay -l run_continue "ALSA capture" arecord -l section "Network" run_continue "ip -details addr" ip -details address run_continue "iw dev" iw dev section "Storage" run_continue "lsblk (model/serial)" lsblk -o NAME,SIZE,TYPE,MOUNTPOINTS,MODEL,SERIAL,TRAN run_continue "SATA/NVMe/PCIe dmesg" bash -lc "dmesg -T | egrep -i 'sata|ahci|nvme|pcie' || true" run_continue "nvme list (if any)" bash -lc "ls /dev/nvme*n1 >/dev/null 2>&1 && sudo nvme list || true" section "Optional actions" if ask_yes "Run powertop --auto-tune (changes power tunables until reboot)?"; then run_fail "powertop --auto-tune" sudo powertop --auto-tune fi if ask_yes "Run sensors-detect (interactive; may load modules)?"; then run_fail "sensors-detect" sudo sensors-detect fi if ask_yes "Run quick fio seq read/write in $HOME (~${FIO_SIZE_MB}MB temp file, then removed)?"; then TMPDIR_CREATED="$(mktemp -d)" local fiofile="$TMPDIR_CREATED/fio_test.dat" run_fail "dd create file" dd if=/dev/zero of="$fiofile" bs=1M count="$FIO_SIZE_MB" status=none run_fail "fio seq rw" fio --name=seqrw --filename="$fiofile" --rw=readwrite --bs="$FIO_BS" --direct=1 --numjobs=1 --iodepth="$FIO_IODEPTH" --size="${FIO_SIZE_MB}M" --group_reporting rm -rf "$TMPDIR_CREATED" || true TMPDIR_CREATED="" fi section "REPORT.md" { echo "# RK3588 Capability Audit — $START_TS" echo echo "Audit directory: $AUDIT_DIR" echo "Log file: $LOG_FILE" echo "Env snapshot: $ENV_FILE" echo "dmesg: $DMESG_FILE" } >"$REPORT_MD" ( cd "$HOME" && tar czf "${AUDIT_DIR}.tar.gz" "$(basename "$AUDIT_DIR")" ) section "Done" log "Report : $REPORT_MD" log "Archive: ${AUDIT_DIR}.tar.gz" } main "$@"

#/home/linaro/rk3588-display-toolkit/bin/rk3588_autorandr_bootstrap.sh

#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' # rk3588_autorandr_bootstrap.sh # # Fixes vs. original: # - Works when launched as root *or* as an unprivileged user (fixes "Failed to connect to bus"). # - Protects existing preswitch hooks generated by setup scripts (Stage 1 protection). # - Treats xrandr correctly on Debian 11 (x11-xserver-utils). # - Performs "offline enable" for systemd units when running as root. # - Adds a login wrapper to wait for the X11 display server. # # Optional inputs: # AUTORANDR_PROFILE=<name> # TARGET_USER=<username> # ASSUME_YES=1 # TOOLKIT_HOME=<path> # # CLI flags: # --profile <name> # --user <username> # --yes AUTORANDR_PROFILE="${AUTORANDR_PROFILE:-}" TARGET_USER="${TARGET_USER:-${SUDO_USER:-${USER:-}}}" ASSUME_YES="${ASSUME_YES:-0}" TOOLKIT_HOME="${TOOLKIT_HOME:-}" log() { printf '[%(%F %T)T] %s\n' -1 "$*"; } err() { printf '[%(%F %T)T] ERROR: %s\n' -1 "$*" >&2; } warn() { printf '[%(%F %T)T] WARN: %s\n' -1 "$*" >&2; } have_cmd() { command -v "$1" >/dev/null 2>&1; } usage() { cat <<'USAGE' Usage: rk3588_autorandr_bootstrap.sh [--profile NAME] [--user USER] [--yes] Environment: AUTORANDR_PROFILE Profile to use (otherwise you will be prompted) TARGET_USER User whose autorandr/autostart/systemd-user config will be modified ASSUME_YES=1 Non-interactive: assume “yes” to confirmation prompts TOOLKIT_HOME Toolkit dir (defaults to ~TARGET_USER/rk3588-display-toolkit) USAGE } confirm_yes() { local msg="$1" ans if [[ "$ASSUME_YES" == "1" ]]; then log "ASSUME_YES=1 -> auto-accept: $msg" return 0 fi echo echo "WARNING: $msg" read -r -p "Type 'yes' to continue, anything else to cancel: " ans [[ "$ans" == "yes" ]] || { err "Cancelled by user."; return 1; } } # ----- privilege helpers ----- SUDO="" if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then have_cmd sudo || { err "sudo is required (or run as root)."; exit 1; } sudo -v || { err "sudo authentication failed."; exit 1; } SUDO="sudo" fi # ----- parse args ----- while [[ $# -gt 0 ]]; do case "$1" in --profile) shift; AUTORANDR_PROFILE="${1:-}";; --user) shift; TARGET_USER="${1:-}";; --yes) ASSUME_YES=1;; -h|--help) usage; exit 0;; *) err "Unknown argument: $1"; usage; exit 2;; esac shift || true done # ----- target user/home resolution ----- TARGET_HOME="" resolve_target_user() { local u="$TARGET_USER" if [[ -z "$u" ]]; then u="$USER" fi # If running as root and no meaningful target is set, pick the first regular user. if [[ "${EUID:-$(id -u)}" -eq 0 && ( -z "$u" || "$u" == "root" ) ]]; then u="$(getent passwd | awk -F: '$3>=1000 && $3<60000 && $6 ~ /^\// {print $1; exit}')" || true if [[ -z "$u" ]]; then err "Running as root but no non-root TARGET_USER could be auto-detected. Set TARGET_USER=<user>." exit 1 fi if ! confirm_yes "You are running as root. This script will configure autorandr/autostart/user-units for user '$u'. Proceed?"; then exit 1 fi fi if ! getent passwd "$u" >/dev/null; then err "Target user does not exist: $u" exit 1 fi TARGET_USER="$u" TARGET_HOME="$(getent passwd "$TARGET_USER" | awk -F: '{print $6}')" if [[ -z "$TARGET_HOME" || ! -d "$TARGET_HOME" ]]; then err "Could not resolve home directory for user '$TARGET_USER'" exit 1 fi if [[ -z "$TOOLKIT_HOME" ]]; then TOOLKIT_HOME="$TARGET_HOME/rk3588-display-toolkit" fi } run_as_target() { if [[ "${EUID:-$(id -u)}" -eq 0 && "$TARGET_USER" != "root" ]]; then sudo -u "$TARGET_USER" -H "$@" else "$@" fi } chown_target() { local path="$1" if [[ "${EUID:-$(id -u)}" -eq 0 && "$TARGET_USER" != "root" ]]; then chown -R "$TARGET_USER:$TARGET_USER" "$path" 2>/dev/null || true fi } # ----- apt helpers ----- pkg_installed() { dpkg -s "$1" >/dev/null 2>&1; } package_available() { have_cmd apt-cache || return 1 apt-cache policy "$1" 2>/dev/null | awk '/Candidate:/ {print $2}' | grep -vq "(none)" } apt_install_if_missing() { local pkg="$1" if pkg_installed "$pkg"; then log "Package already installed: $pkg" return 0 fi if ! package_available "$pkg"; then log "Package not available in APT sources (skipping): $pkg" return 0 fi log "Installing package: $pkg" ${SUDO} DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "$pkg" } ensure_deps() { log "Ensuring dependencies..." ${SUDO} apt-get update # On Debian 11, /usr/bin/xrandr is shipped by x11-xserver-utils. apt_install_if_missing autorandr apt_install_if_missing x11-xserver-utils # If the xrandr binary is still missing (rare/minimal setups), try the standalone package if the repo has it. if ! have_cmd xrandr; then log "xrandr binary not found after installing x11-xserver-utils; attempting standalone xrandr package if available..." apt_install_if_missing xrandr fi have_cmd autorandr || { err "autorandr is still missing after install attempts."; exit 1; } have_cmd xrandr || log "NOTE: xrandr not found. autorandr will not work in X11 sessions until xrandr is available." } # ----- profile selection/validation (from target user's config) ----- list_profiles() { local dir="$TARGET_HOME/.config/autorandr" [[ -d "$dir" ]] || return 0 find "$dir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null | sort } choose_profile() { if [[ -n "$AUTORANDR_PROFILE" ]]; then return 0 fi log "Available autorandr profiles for user '$TARGET_USER':" local profiles profiles="$(list_profiles || true)" if [[ -z "$profiles" ]]; then err "No autorandr profiles found in $TARGET_HOME/.config/autorandr" err "Create one first (e.g. 'autorandr --save <name>' from within an X11 session)." exit 1 fi echo "$profiles" | sed 's/^/ - /' read -r -p "Enter the profile name to use: " AUTORANDR_PROFILE [[ -n "$AUTORANDR_PROFILE" ]] || { err "No profile provided"; exit 1; } } validate_profile_exists() { local dir="$TARGET_HOME/.config/autorandr/$AUTORANDR_PROFILE" if [[ ! -d "$dir" ]]; then err "Profile '$AUTORANDR_PROFILE' does not exist in $TARGET_HOME/.config/autorandr" exit 1 fi } # ----- installs ----- install_preswitch_hook() { log "Installing preswitch hook template for profile: $AUTORANDR_PROFILE (user: $TARGET_USER)" local ar_dir="$TARGET_HOME/.config/autorandr/$AUTORANDR_PROFILE" mkdir -p "$ar_dir" local hook="$ar_dir/preswitch" # FIX: Don't overwrite existing hooks blindly. # This prevents destroying the custom modeline setup generated by rk3588_dual_display_setup.sh if [[ -s "$hook" ]]; then warn "A preswitch hook already exists at $hook" if ! confirm_yes "Overwrite existing preswitch hook with default template? (Select NO if you used the setup script)"; then log "Skipping preswitch hook installation (keeping existing configuration)." chown_target "$ar_dir" return 0 fi # Backup if overwriting cp -a "$hook" "${hook}.bak.$(date +%Y%m%d_%H%M%S)" || true fi cat >"$hook" <<'SH' #!/bin/sh # autorandr preswitch hook (template) # Runs before switching to the profile. # Use this to (re-)add custom modelines if your hardware/firmware forgets them. # Example: # xrandr --newmode "1920x1080_60.00" ... || true # xrandr --addmode HDMI-1 "1920x1080_60.00" || true exit 0 SH chmod +x "$hook" chown_target "$ar_dir" log "Hook installed: $hook" } install_wrapper_script() { # A tiny helper that waits until xrandr can talk to the X server before running autorandr. local bin_dir="$TARGET_HOME/.local/bin" local wrapper="$bin_dir/autorandr-change" mkdir -p "$bin_dir" cat >"$wrapper" <<'SH' #!/usr/bin/env bash set -euo pipefail # Wait until we have a working DISPLAY and xrandr can query it. # This makes autorandr more reliable at login across different desktop environments. AUTORANDR_BIN="${AUTORANDR_BIN:-/usr/bin/autorandr}" XRANDR_BIN="${XRANDR_BIN:-/usr/bin/xrandr}" MAX_WAIT_SECONDS="${MAX_WAIT_SECONDS:-20}" if [[ -z "${DISPLAY:-}" ]]; then echo "autorandr-change: DISPLAY is not set; nothing to do." >&2 exit 0 fi # Some environments don't export XAUTHORITY for systemd user units; # if it's missing, fall back to ~/.Xauthority. if [[ -z "${XAUTHORITY:-}" && -f "$HOME/.Xauthority" ]]; then export XAUTHORITY="$HOME/.Xauthority" fi for ((i=0; i<MAX_WAIT_SECONDS; i++)); do if "$XRANDR_BIN" --current >/dev/null 2>&1; then exec "$AUTORANDR_BIN" --change fi sleep 1 done echo "autorandr-change: timed out waiting for xrandr to become ready (DISPLAY=$DISPLAY)." >&2 exit 0 SH chmod +x "$wrapper" chown_target "$bin_dir" log "Installed wrapper: $wrapper" } install_xdg_autostart() { local auto_dir="$TARGET_HOME/.config/autostart" local desktop_file="$auto_dir/autorandr-change.desktop" mkdir -p "$auto_dir" if [[ -f "$desktop_file" ]]; then if ! confirm_yes "This will overwrite $desktop_file (a backup will be created). Proceed?"; then log "Skipped XDG autostart." return 0 fi cp -a "$desktop_file" "${desktop_file}.bak.$(date +%Y%m%d_%H%M%S)" || true fi # Use wrapper to avoid race conditions at login. cat >"$desktop_file" <<'DESKTOP' [Desktop Entry] Type=Application Name=Autorandr Apply Comment=Apply autorandr profile at login Exec=%h/.local/bin/autorandr-change X-GNOME-Autostart-enabled=true DESKTOP chown_target "$auto_dir" log "Installed XDG autostart: $desktop_file" } install_systemd_user_unit() { local unit_dir="$TARGET_HOME/.config/systemd/user" local unit_file="$unit_dir/autorandr-change.service" local wants_dir="$unit_dir/default.target.wants" mkdir -p "$unit_dir" "$wants_dir" if [[ -f "$unit_file" ]]; then if ! confirm_yes "This will overwrite $unit_file (a backup will be created). Proceed?"; then log "Skipped systemd user unit." return 0 fi cp -a "$unit_file" "${unit_file}.bak.$(date +%Y%m%d_%H%M%S)" || true fi cat >"$unit_file" <<'UNIT' [Unit] Description=Apply autorandr profile (login) # If graphical-session.target exists in the user manager, order after it. After=graphical-session.target [Service] Type=oneshot ExecStart=%h/.local/bin/autorandr-change [Install] WantedBy=default.target UNIT # Offline enable: create the symlink systemctl --user enable would create. ln -sf ../autorandr-change.service "$wants_dir/autorandr-change.service" chown_target "$unit_dir" # If we're *already* in the user's session bus, also run daemon-reload. if run_as_target systemctl --user show-environment >/dev/null 2>&1; then run_as_target systemctl --user daemon-reload || true fi log "Installed systemd user unit: $unit_file" log "Enabled (offline) via symlink: $wants_dir/autorandr-change.service" } enable_system_services_if_present() { # These units vary by distro/package; enable only if they exist. if ${SUDO} systemctl list-unit-files 2>/dev/null | grep -q '^autorandr\.service'; then if confirm_yes "Enable system-wide autorandr.service now?"; then ${SUDO} systemctl enable --now autorandr.service || true fi fi if ${SUDO} systemctl list-unit-files 2>/dev/null | grep -q '^autorandr-resume\.service'; then if confirm_yes "Enable system-wide autorandr-resume.service now?"; then ${SUDO} systemctl enable --now autorandr-resume.service || true fi fi } try_apply_now() { if [[ -z "${DISPLAY:-}" ]]; then log "DISPLAY not set; skipping immediate autorandr apply." return 0 fi log "DISPLAY is set; attempting autorandr apply now..." # Prefer wrapper (waits for xrandr readiness). local wrapper="$TARGET_HOME/.local/bin/autorandr-change" local xauth="${XAUTHORITY:-$TARGET_HOME/.Xauthority}" if [[ -x "$wrapper" ]]; then if ! run_as_target env DISPLAY="$DISPLAY" XAUTHORITY="$xauth" "$wrapper"; then err "autorandr apply failed in-session (you can run '$wrapper' manually later)." return 0 fi else if ! run_as_target env DISPLAY="$DISPLAY" XAUTHORITY="$xauth" /usr/bin/autorandr --change; then err "autorandr --change failed in-session (you can run it manually later)." return 0 fi fi log "autorandr applied." } main() { resolve_target_user log "Target user: $TARGET_USER" log "Target home: $TARGET_HOME" ensure_deps choose_profile validate_profile_exists install_preswitch_hook install_wrapper_script if confirm_yes "Enable system-wide autorandr services (if present)?"; then enable_system_services_if_present fi if confirm_yes "Install XDG autostart entry to run autorandr at login (recommended for desktop environments)?"; then install_xdg_autostart fi if confirm_yes "Install a systemd user unit to run autorandr at login (works even without DBUS during install)?"; then install_systemd_user_unit fi try_apply_now log "Done. Profile selected: $AUTORANDR_PROFILE" log "Profile config path: $TARGET_HOME/.config/autorandr/$AUTORANDR_PROFILE" } main "$@"

#/home/linaro/rk3588-display-toolkit/bin/rk3588_dual_display_setup.sh

#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' # rk3588_dual_display_setup.sh # # Hard-coded dual-monitor fix (HDMI + DP) with autorandr persistence. # # OUT1 (default HDMI-1): 1280x720@60 primary # OUT2 (default DP-1) : 800x600@60 right-of OUT1 # Includes SVGA (Standard VESA) fallback timing for OUT2. # # WARNING: This script changes display modes. OUT1="${OUT1:-HDMI-1}" OUT2="${OUT2:-DP-1}" # HDMI-1: 1280x720 59.86 Hz (CVT 0.92M9) MODE1_NAME="1280x720_60.00" MODE1_LINE="74.50 1280 1344 1472 1664 720 723 728 748 -hsync +vsync" # DP-1: 800x600 59.86 Hz (CVT 0.48M3) - Primary attempt MODE2_CVT_NAME="800x600_60.00" MODE2_CVT_LINE="38.25 800 832 912 1024 600 603 607 624 -hsync +vsync" # DP-1: 800x600 Standard VESA - Fallback # Note: Inferred standard VESA timings to maintain script fallback logic MODE2_VGA_NAME="800x600_60_std" MODE2_VGA_LINE="40.00 800 840 968 1056 600 601 605 628 +hsync +vsync" AR_PROFILE="rk3588_dual" log() { printf '[INFO ] %s\n' "$*"; } err() { printf '[ERROR] %s\n' "$*" >&2; } need_x() { [[ -n "${DISPLAY:-}" ]] || { err "DISPLAY not set. Run from an active X session."; exit 1; } } need_cmd() { command -v "$1" >/dev/null 2>&1 || { err "Missing command: $1"; exit 1; } } confirm_yes() { local msg="$1" ans echo echo "WARNING: $msg" read -r -p "Type 'yes' to continue, anything else to cancel: " ans [[ "$ans" == "yes" ]] || { err "Cancelled by user."; exit 1; } } apply_layout() { local right_mode="$1" xrandr \ --output "$OUT1" --mode "$MODE1_NAME" --primary \ --output "$OUT2" --mode "$right_mode" --right-of "$OUT1" } main() { need_x need_cmd xrandr need_cmd autorandr confirm_yes "This will change active display modes for $OUT1 and $OUT2. Ensure you can recover via TTY." log "Defining modelines (best-effort)..." xrandr --newmode "$MODE1_NAME" $MODE1_LINE 2>/dev/null || true xrandr --newmode "$MODE2_CVT_NAME" $MODE2_CVT_LINE 2>/dev/null || true xrandr --newmode "$MODE2_VGA_NAME" $MODE2_VGA_LINE 2>/dev/null || true xrandr --addmode "$OUT1" "$MODE1_NAME" 2>/dev/null || true xrandr --addmode "$OUT2" "$MODE2_CVT_NAME" 2>/dev/null || true xrandr --addmode "$OUT2" "$MODE2_VGA_NAME" 2>/dev/null || true log "Applying layout (try CVT timing first)..." if ! apply_layout "$MODE2_CVT_NAME"; then log "CVT timing failed; trying VESA/SVGA fallback..." apply_layout "$MODE2_VGA_NAME" fi log "Creating autorandr preswitch hook for persistence..." local ar_dir="$HOME/.config/autorandr/$AR_PROFILE" mkdir -p "$ar_dir" cat >"$ar_dir/preswitch" <<SH #!/bin/sh xrandr --newmode "$MODE1_NAME" $MODE1_LINE 2>/dev/null || true xrandr --newmode "$MODE2_CVT_NAME" $MODE2_CVT_LINE 2>/dev/null || true xrandr --newmode "$MODE2_VGA_NAME" $MODE2_VGA_LINE 2>/dev/null || true xrandr --addmode "$OUT1" "$MODE1_NAME" 2>/dev/null || true xrandr --addmode "$OUT2" "$MODE2_CVT_NAME" 2>/dev/null || true xrandr --addmode "$OUT2" "$MODE2_VGA_NAME" 2>/dev/null || true SH chmod +x "$ar_dir/preswitch" log "Saving autorandr profile '$AR_PROFILE' (forced)..." autorandr --save "$AR_PROFILE" --force log "Done. Profile saved: $AR_PROFILE" } main "$@"

#/home/linaro/my_custom_display_config.sh

#!/bin/bash # Xrandr dual-monitor setup (expanded, refactored) # HDMI-1 → 1280x720@60 (primary) # DP-1 → 800x600@60 (right of HDMI-1) # Modelines and outputs are fixed based on provided logs. No placeholders. set -Eeuo pipefail # -------------------------- # Utilities / Logging # -------------------------- log() { printf '[INFO ] %s\n' "$*"; } warn() { printf '[WARN ] %s\n' "$*" >&2; } err() { printf '[ERROR] %s\n' "$*" >&2; } # If DRY_RUN=1 is set in the environment, print commands instead of executing them. : "${DRY_RUN:=0}" run() { if [[ "$DRY_RUN" == "1" ]]; then printf '[DRYRUN] %s\n' "$*" return 0 fi eval "$@" } # -------------------------- # Pre-flight checks # -------------------------- if ! command -v xrandr >/dev/null 2>&1; then err 'xrandr not found' exit 127 fi # -------------------------- # Fixed configuration from logs # -------------------------- # Data derived from: Modeline "1280x720_60.00" 74.50 1280... -hsync +vsync OUTPUT_1_NAME="HDMI-1" MODE_1_NAME="1280x720_60.00" # Params: Clock HDisp HSyncStart HSyncEnd HTotal VDisp VSyncStart VSyncEnd VTotal Flags MODELINE_1_PARAMS="74.50 1280 1344 1472 1664 720 723 728 748 -hsync +vsync" # Data derived from: Modeline "800x600_60.00" 38.25 800... -hsync +vsync OUTPUT_2_NAME="DP-1" MODE_2_NAME="800x600_60.00" # Params: Clock HDisp HSyncStart HSyncEnd HTotal VDisp VSyncStart VSyncEnd VTotal Flags MODELINE_2_PARAMS="38.25 800 832 912 1024 600 603 607 624 -hsync +vsync" # -------------------------- # Helpers for xrandr state # -------------------------- connected() { xrandr | grep -q "^$1\s*connected" } mode_defined() { local name="$1" # A mode is "defined" if it appears anywhere in xrandr --query output xrandr --query | awk '{print $1}' | grep -Fxq "$name" } output_has_mode() { local out="$1" name="$2" # Extract the block for this output and list the mode names, then search for ours. xrandr --query | awk -v o="$out" ' $1==o {on=1; next} on && NF==0 {on=0} on && $1!~/^\t/ && $2=="connected" {on=0} on {print $1} ' | sed 's/^[[:space:]]*//' | grep -Fxq "$name" 2>/dev/null } active_geom() { # Prints WxH for given output if connected; empty otherwise local out="$1" xrandr --query | awk -v o="$out" '$1==o && $2=="connected" { match($0, /[0-9]+x[0-9]+\+[0-9]+\+[0-9]+/); if (RSTART) { s=substr($0,RSTART,RLENGTH); sub(/\+.*/, "", s); print s; } }' } # -------------------------- # Capture pre-state for reversible changes # -------------------------- PRESTATE_CMD=() for out in "$OUTPUT_1_NAME" "$OUTPUT_2_NAME"; do if connected "$out"; then # Best-effort revert uses preferred mode and auto placement for each output. PRESTATE_CMD+=(--output "$out" --auto) fi done revert() { if ((${#PRESTATE_CMD[@]})); then warn "Reverting to auto configuration" run xrandr "${PRESTATE_CMD[@]}" || warn "Revert failed" fi } trap 'err "Failure occurred"; revert' ERR # -------------------------- # Ensure modes exist and are assigned to outputs # -------------------------- ensure_mode_defined() { local name="$1"; shift if mode_defined "$name"; then # NOTE: This check only verifies the NAME exists. It does NOT verify if the # parameters match. If you changed modeline params but kept the name, # you must delete the mode manually or rename it. log "Mode present: $name (skipping definition)" else log "Defining mode: $name" run xrandr --newmode "$name" "$@" fi } ensure_output_has_mode() { local out="$1" name="$2" if output_has_mode "$out" "$name"; then log "Output $out already has mode $name" else log "Adding mode $name to $out" run xrandr --addmode "$out" "$name" fi } # Validate outputs are connected before modifying for out in "$OUTPUT_1_NAME" "$OUTPUT_2_NAME"; do if ! connected "$out"; then err "Output not connected: $out" exit 2 fi done # Define modes idempotently ensure_mode_defined "$MODE_1_NAME" $MODELINE_1_PARAMS ensure_mode_defined "$MODE_2_NAME" $MODELINE_2_PARAMS # Assign modes to each output ensure_output_has_mode "$OUTPUT_1_NAME" "$MODE_1_NAME" ensure_output_has_mode "$OUTPUT_2_NAME" "$MODE_2_NAME" # -------------------------- # Apply requested layout # -------------------------- LAYOUT_CMD=( --output "$OUTPUT_1_NAME" --mode "$MODE_1_NAME" --primary --output "$OUTPUT_2_NAME" --mode "$MODE_2_NAME" --right-of "$OUTPUT_1_NAME" ) log "Applying layout" run xrandr "${LAYOUT_CMD[@]}" # -------------------------- # Post-apply verification # -------------------------- geom1=$(active_geom "$OUTPUT_1_NAME") geom2=$(active_geom "$OUTPUT_2_NAME") if [[ "$geom1" != "1280x720" ]]; then err "Post-check failed: $OUTPUT_1_NAME geometry is '$geom1' (expected 1280x720)" revert; exit 3 fi if [[ "$geom2" != "800x600" ]]; then err "Post-check failed: $OUTPUT_2_NAME geometry is '$geom2' (expected 800x600)" revert; exit 4 fi log "Configuration successful: $OUTPUT_1_NAME=$geom1 (primary), $OUTPUT_2_NAME=$geom2 (right)" exit 0
URL: https://ib.bsb.br/xrandr