浏览代码

Sync: 2026-08-01 12:13:46

Gabriel Capella 2 天之前
父节点
当前提交
1cd2259eef
共有 2 个文件被更改,包括 134 次插入9 次删除
  1. 37 8
      dots/.config/sway/scripts/fuzzel-run-or-search.sh
  2. 97 1
      start

+ 37 - 8
dots/.config/sway/scripts/fuzzel-run-or-search.sh

@@ -2,15 +2,44 @@
 # Run a command, or fall back to a web search if it isn't one.
 # fuzzel --dmenu prints the typed string as-is when nothing matches,
 # which is what makes the search fallback work.
-result=$(compgen -c | sort -u | fuzzel --dmenu)
 
-[ -z "$result" ] && exit 0
+browser_app_id="google-chrome"
+search_url="https://duckduckgo.com/?q="
 
-if command -v "${result%% *}" &>/dev/null; then
+# Executables in PATH. Deliberately not `compgen -c`: that also lists shell
+# builtins and keywords (cd, alias, if, ...), which `command -v` reports as
+# found but `exec` cannot run.
+list_path_bins() {
+    local IFS=: dir file
+    for dir in $PATH; do
+        [[ -d $dir ]] || continue
+        for file in "$dir"/*; do
+            [[ -f $file && -x $file ]] && printf '%s\n' "${file##*/}"
+        done
+    done | sort -u
+}
+
+result=$(list_path_bins | fuzzel --dmenu)
+[[ -z $result ]] && exit 0
+
+# type -P looks only at PATH, so builtins and keywords correctly fall through
+# to the search branch instead of failing silently in exec.
+if type -P "${result%% *}" >/dev/null 2>&1; then
     exec $result
-else
-    query=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote_plus(sys.argv[1]))" "$result")
-    xdg-open "https://duckduckgo.com/?q=$query" &
-    sleep 0.5
-    swaymsg '[app_id="google-chrome"] focus'
 fi
+
+query=$(jq -rn --arg q "$result" '$q|@uri')
+xdg-open "$search_url$query" &
+
+# Wait for the browser window instead of guessing at a fixed sleep: a cold
+# start takes well over half a second, while an already-running Chrome is
+# ready almost immediately.
+for _ in $(seq 40); do
+    if swaymsg -t get_tree |
+        jq -e --arg id "$browser_app_id" \
+            'recurse(.nodes[]?, .floating_nodes[]?) | select(.app_id == $id)' >/dev/null; then
+        swaymsg "[app_id=\"$browser_app_id\"] focus" >/dev/null
+        exit 0
+    fi
+    sleep 0.05
+done

+ 97 - 1
start

@@ -42,6 +42,28 @@ get_sync_list() {
     } | grep -v '^\s*$' | sort -u || true
 }
 
+# Get combined package list (common + groups + host-specific, deduplicated).
+# Unlike dotfiles, package lists are ADDITIVE: every applicable list is unioned
+# rather than overridden, so a host list adds to the common one, never replaces it.
+get_pkg_list() {
+    {
+        cat "$script_dir/pkglist" 2>/dev/null || true
+        while IFS= read -r group; do
+            cat "$script_dir/pkglist.@$group" 2>/dev/null || true
+        done < <(get_groups)
+        cat "$script_dir/pkglist.$hostname_id" 2>/dev/null || true
+    } | grep -v '^\s*#' | grep -v '^\s*$' | sort -u || true
+}
+
+# Explicitly-installed packages on this host.
+# -Qqe (explicit) keeps AUR *dependencies* out of the list; they get pulled in
+# automatically by their parent. The -debug filter drops artifacts makepkg
+# generates when building locally -- they only exist on the machine that built
+# them and mostly aren't installable elsewhere.
+installed_packages() {
+    yay -Qqe | grep -v -- '-debug$' | sort -u || true
+}
+
 # Resolve source path: common -> groups (alpha) -> host-specific (last wins)
 resolve_source() {
     local p="$1"
@@ -158,6 +180,8 @@ Subcommands:
                                  --group <name>: add to group-specific config
     list                         List currently synced dotfiles
     status                       Show git status of dotfiles repo
+    packages [install|diff|save] Sync packages (additive: pkglist + pkglist.@<group>
+                                 + pkglist.$hostname_id). Not run by 'sync'.
 
 Host: $hostname_id
 Groups: ${groups:-none}
@@ -508,12 +532,84 @@ sub_status(){
 }
 
 
+sub_packages(){
+    local action=${1:-install}
+
+    if [[ "$action" == "-h" || "$action" == "--help" ]]; then
+        cat << EOF
+Usage: $ProgName packages [install|diff|save]
+
+    install   Install tracked packages missing on this host (default)
+    diff      Show drift in both directions, change nothing
+    save      Write this host's explicit packages to pkglist.$hostname_id
+
+Lists are additive and resolved as:
+    pkglist + pkglist.@<group> + pkglist.$hostname_id
+
+Not run by '$ProgName sync': installing needs sudo and takes time, whereas
+sync is unprivileged and fast. Keep them separate.
+EOF
+        return 0
+    fi
+
+    local tracked installed missing extra
+    tracked=$(get_pkg_list)
+
+    if [[ -z "$tracked" ]]; then
+        log_error "No package lists found (pkglist, pkglist.@<group>, pkglist.$hostname_id)"
+        log_error "Seed one with: $ProgName packages save"
+        return 1
+    fi
+
+    installed=$(installed_packages)
+    missing=$(comm -23 <(echo "$tracked") <(echo "$installed"))
+    extra=$(comm -13 <(echo "$tracked") <(echo "$installed"))
+
+    case $action in
+        install)
+            if [[ -z "$missing" ]]; then
+                log_info "All $(echo "$tracked" | wc -l) tracked packages already installed"
+                return 0
+            fi
+            log_info "Installing $(echo "$missing" | wc -l) missing package(s):"
+            echo "$missing" | sed 's/^/  /'
+            # Pass targets as arguments rather than piping to stdin: yay opens
+            # /dev/tty for its prompts, and feeding it stdin breaks that.
+            echo "$missing" | xargs -r yay -S --needed
+            ;;
+        diff)
+            if [[ -n "$missing" ]]; then
+                log_warn "Tracked but NOT installed ($(echo "$missing" | wc -l)):"
+                echo "$missing" | sed 's/^/  /'
+            fi
+            if [[ -n "$extra" ]]; then
+                log_warn "Installed but NOT tracked ($(echo "$extra" | wc -l)):"
+                echo "$extra" | sed 's/^/  /'
+            fi
+            if [[ -z "$missing" && -z "$extra" ]]; then
+                log_info "In sync: $(echo "$tracked" | wc -l) packages"
+            fi
+            ;;
+        save)
+            echo "$installed" > "$script_dir/pkglist.$hostname_id"
+            log_info "Wrote $(echo "$installed" | wc -l) packages to pkglist.$hostname_id"
+            log_warn "Move entries shared with other hosts into pkglist so they are not duplicated"
+            ;;
+        *)
+            log_error "Unknown packages action: '$action'"
+            log_error "Run '$ProgName packages --help' for usage."
+            return 1
+            ;;
+    esac
+}
+
+
 subcommand=${1:-}
 case $subcommand in
     "" | "-h" | "--help")
         sub_help
         ;;
-    sync|list|status)
+    sync|list|status|packages)
         shift
         "sub_${subcommand}" "$@"
         ;;