From ca7e8ec75a85214a778b562d25039e2ee4d57b70 Mon Sep 17 00:00:00 2001 From: Justin Garrison Date: Mon, 4 May 2026 21:12:03 -0700 Subject: [PATCH] Add xdg-open-wrapper.sh --- xdg-open-wrapper.sh | 146 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100755 xdg-open-wrapper.sh diff --git a/xdg-open-wrapper.sh b/xdg-open-wrapper.sh new file mode 100755 index 0000000..83b6007 --- /dev/null +++ b/xdg-open-wrapper.sh @@ -0,0 +1,146 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REAL_XDG_OPEN="/run/current-system/sw/bin/xdg-open" +CONFIG_FILE="$HOME/.config/xdg-open-wrapper/config" +LOG_FILE="$HOME/.config/xdg-open-wrapper/log" + +log() { + echo "$(date "+%Y-%m-%d %H:%M:%S") $*" >> "$LOG_FILE" +} + +get_caller() { + local pid=$$ + local depth=0 + local max_depth=10 + local via_portal=false + + while [[ $depth -lt $max_depth ]]; do + pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ') + [[ -z "$pid" || "$pid" == "0" || "$pid" == "1" ]] && break + + local comm + comm=$(ps -o comm= -p "$pid" 2>/dev/null | tr -d ' ') + local cmdline + cmdline=$(ps -o args= -p "$pid" 2>/dev/null) + + case "$comm" in + bash|zsh|sh|fish|dash|csh|tcsh) ;; + xdg-desktop-portal*|systemd) + via_portal=true + break + ;; + *) echo "$comm|$cmdline"; return ;; + esac + + depth=$((depth + 1)) + done + + if $via_portal; then + local portal_caller + portal_caller=$(get_caller_from_scopes) + if [[ -n "$portal_caller" ]]; then + echo "$portal_caller|portal:$portal_caller" + return + fi + fi + + echo "unknown|unknown" +} + +get_caller_from_scopes() { + local scopes + scopes=$(systemctl --user list-units --type=scope --state=active --no-legend --no-pager 2>/dev/null) || return + + local patterns=() + while IFS= read -r line; do + [[ "$line" =~ ^[[:space:]]*# ]] && continue + [[ -z "${line// /}" ]] && continue + local pat="${line%%=*}" + pat="$(echo "$pat" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + [[ "$pat" == "default" || "$pat" == cmd:* || -z "$pat" ]] && continue + patterns+=("$pat") + done < "$CONFIG_FILE" + + while IFS= read -r scope_line; do + local scope_name + scope_name=$(echo "$scope_line" | awk '{print $1}') + for pat in "${patterns[@]}"; do + if echo "$scope_name" | grep -qi "$pat"; then + echo "$pat" + return + fi + done + done <<< "$scopes" +} + +is_url() { + [[ "$1" =~ ^https?:// || "$1" =~ ^www\. ]] +} + +if [[ $# -eq 0 ]]; then + exec "$REAL_XDG_OPEN" +fi + +arg="$1" + +if ! is_url "$arg" || [[ ! -f "$CONFIG_FILE" ]]; then + exec "$REAL_XDG_OPEN" "$@" +fi + +caller_info=$(get_caller) +caller_name="${caller_info%%|*}" +caller_cmd="${caller_info#*|}" + +log "URL=$arg CALLER=$caller_name CMD=$caller_cmd" + +matched_browser="" +default_browser="" + +while IFS= read -r line; do + [[ "$line" =~ ^[[:space:]]*# ]] && continue + [[ -z "${line// /}" ]] && continue + + pattern="${line%%=*}" + browser="${line#*=}" + pattern="$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + browser="$(echo "$browser" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + + [[ -z "$pattern" || -z "$browser" ]] && continue + + if [[ "$pattern" == "default" ]]; then + default_browser="$browser" + continue + fi + + if [[ "$pattern" == cmd:* ]]; then + match_against="$caller_cmd" + pattern="${pattern#cmd:}" + else + match_against="$caller_name" + fi + + if echo "$match_against" | grep -qi "$pattern"; then + matched_browser="$browser" + log "MATCH pattern=$pattern browser=$matched_browser" + break + fi +done < "$CONFIG_FILE" + +if [[ -n "$matched_browser" ]]; then + if [[ "$matched_browser" == "xdg-open" ]]; then + exec "$REAL_XDG_OPEN" "$@" + fi + log "OPENING with: $matched_browser $arg" + eval "$matched_browser" '"$arg"' +elif [[ -n "$default_browser" ]]; then + if [[ "$default_browser" == "xdg-open" ]]; then + exec "$REAL_XDG_OPEN" "$@" + fi + log "DEFAULT: $default_browser $arg" + eval "$default_browser" '"$arg"' +else + log "NO MATCH, falling back to firefox" + exec firefox "$arg" +fi -- 2.51.2