| 1 | #!/bin/sh |
| 2 | # Requires: xfconf-query (xfce4-settings) |
| 3 | # Points the XFCE keyboard shortcuts at the rofi menus in ../rofi/scripts, |
| 4 | # and can put back whatever those keys did before. |
| 5 | # |
| 6 | # This is a script rather than a checked-in xfconf XML file because xfconf |
| 7 | # rewrites its own config atomically, by rename — which would replace a |
| 8 | # symlink from this repo with a regular file the first time anything changed |
| 9 | # a shortcut through the settings GUI. |
| 10 | # |
| 11 | # Usage: rofi-keybinds.sh apply (idempotent, safe to re-run) |
| 12 | # rofi-keybinds.sh --remove restore what these keys did before |
| 13 | # rofi-keybinds.sh --list show which keys currently run rofi |
| 14 | # |
| 15 | # xfsettingsd watches xfconf, so changes take effect immediately — no logout. |
| 16 | set -u |
| 17 | |
| 18 | CHANNEL=xfce4-keyboard-shortcuts |
| 19 | BACKUP="${XDG_DATA_HOME:-$HOME/.local/share}/rofi-keybinds.backup" |
| 20 | |
| 21 | # Where the menu scripts live. The repo layout is the default; point |
| 22 | # ROFI_SCRIPTS somewhere else to bind an installed copy instead. |
| 23 | S=${ROFI_SCRIPTS:-$(CDPATH= cd -- "$(dirname -- "$0")/../rofi/scripts" 2>/dev/null && pwd)} |
| 24 | [ -n "${S:-}" ] && [ -d "$S" ] || S="$HOME/.config/rofi/scripts" |
| 25 | [ -d "$S" ] || { echo "rofi-keybinds: no rofi scripts directory found" >&2; exit 1; } |
| 26 | |
| 27 | command -v xfconf-query >/dev/null 2>&1 || |
| 28 | { echo "rofi-keybinds: xfconf-query not found — is this an XFCE session?" >&2; exit 1; } |
| 29 | |
| 30 | # key<TAB>command. Super+Tab, Super+f/h/l and Super+1-4 are missing from this |
| 31 | # list on purpose: xfwm4 grabs those for window and workspace handling, so a |
| 32 | # command bound to them would never fire. |
| 33 | binds() { |
| 34 | cat <<BINDS |
| 35 | /commands/custom/<Super>d rofi -show drun |
| 36 | /commands/custom/<Super>space rofi -show drun |
| 37 | /commands/custom/<Alt>F2 rofi -show run |
| 38 | /commands/custom/<Super>r rofi -show run |
| 39 | /commands/custom/<Super>w rofi -show window |
| 40 | /commands/custom/<Shift><Super>f rofi -show filebrowser |
| 41 | /commands/custom/<Super>v $S/rofi-clip |
| 42 | /commands/custom/<Shift><Super>e $S/rofi-emoji |
| 43 | /commands/custom/<Shift><Super>c $S/rofi-calc |
| 44 | /commands/custom/<Shift><Super>x $S/rofi-power |
| 45 | /commands/custom/<Shift><Super>n $S/rofi-wifi |
| 46 | /commands/custom/<Shift><Super>u $S/rofi-audio |
| 47 | /commands/custom/<Super>s $S/rofi-screenshot |
| 48 | /commands/custom/Print $S/rofi-screenshot |
| 49 | /commands/custom/<Super>slash $S/rofi-websearch |
| 50 | BINDS |
| 51 | } |
| 52 | |
| 53 | # Shortcuts for xfce4-screenshooter, which is not installed — these keys do |
| 54 | # nothing at all today, so they are dropped rather than repointed. |
| 55 | DEAD="/commands/custom/<Shift>Print |
| 56 | /commands/custom/<Alt>Print" |
| 57 | |
| 58 | get() { xfconf-query -c "$CHANNEL" -p "$1" 2>/dev/null; } |
| 59 | exists() { xfconf-query -c "$CHANNEL" -p "$1" >/dev/null 2>&1; } |
| 60 | |
| 61 | # One line per property, the first time it is touched: "prop<TAB>type<TAB>old |
| 62 | # value", with an empty value meaning the property did not exist. Re-running |
| 63 | # apply must not overwrite an entry, or the original would be lost behind a |
| 64 | # rofi command. |
| 65 | record() { |
| 66 | mkdir -p "$(dirname "$BACKUP")" |
| 67 | [ -f "$BACKUP" ] && |
| 68 | awk -F'\t' -v p="$1" '$1 == p { found = 1 } END { exit !found }' "$BACKUP" && |
| 69 | return 0 |
| 70 | printf '%s\t%s\t%s\n' "$1" "$2" "$3" >> "$BACKUP" |
| 71 | } |
| 72 | |
| 73 | put() { |
| 74 | if exists "$1"; then |
| 75 | xfconf-query -c "$CHANNEL" -p "$1" -s "$3" |
| 76 | else |
| 77 | xfconf-query -c "$CHANNEL" -p "$1" -n -t "$2" -s "$3" |
| 78 | fi |
| 79 | } |
| 80 | |
| 81 | apply() { |
| 82 | binds | while IFS=' ' read -r prop cmd; do |
| 83 | [ -n "$prop" ] || continue |
| 84 | old=$(get "$prop") |
| 85 | [ "$old" = "$cmd" ] && continue |
| 86 | |
| 87 | record "$prop" string "$old" |
| 88 | put "$prop" string "$cmd" >/dev/null |
| 89 | |
| 90 | # The appfinder keys carry startup-notify. rofi never sends the |
| 91 | # matching "I am up" message, so the busy cursor would spin for its |
| 92 | # full timeout on every launch. |
| 93 | sn="$prop/startup-notify" |
| 94 | if exists "$sn"; then |
| 95 | record "$sn" bool "$(get "$sn")" |
| 96 | xfconf-query -c "$CHANNEL" -p "$sn" -s false |
| 97 | fi |
| 98 | |
| 99 | printf ' %-40s → %s\n' "${prop#/commands/custom/}" "$cmd" |
| 100 | done |
| 101 | |
| 102 | printf '%s\n' "$DEAD" | while read -r prop; do |
| 103 | [ -n "$prop" ] && exists "$prop" || continue |
| 104 | record "$prop" string "$(get "$prop")" |
| 105 | xfconf-query -c "$CHANNEL" -p "$prop" -r |
| 106 | printf ' %-40s → removed (xfce4-screenshooter is not installed)\n' "${prop#/commands/custom/}" |
| 107 | done |
| 108 | } |
| 109 | |
| 110 | remove() { |
| 111 | [ -f "$BACKUP" ] || { echo "rofi-keybinds: nothing to restore"; exit 0; } |
| 112 | while IFS=' ' read -r prop type value; do |
| 113 | [ -n "$prop" ] || continue |
| 114 | if [ -z "$value" ]; then |
| 115 | xfconf-query -c "$CHANNEL" -p "$prop" -r 2>/dev/null |
| 116 | printf ' %-40s → unbound\n' "${prop#/commands/custom/}" |
| 117 | else |
| 118 | put "$prop" "$type" "$value" >/dev/null |
| 119 | printf ' %-40s → %s\n' "${prop#/commands/custom/}" "$value" |
| 120 | fi |
| 121 | done < "$BACKUP" |
| 122 | rm -f "$BACKUP" |
| 123 | } |
| 124 | |
| 125 | case "${1:-}" in |
| 126 | "") echo "Binding rofi menus in $S:"; apply ;; |
| 127 | --remove) echo "Restoring shortcuts from $BACKUP:"; remove ;; |
| 128 | --list) xfconf-query -c "$CHANNEL" -p /commands/custom -lv | grep -i rofi ;; |
| 129 | *) echo "usage: rofi-keybinds.sh [--remove|--list]" >&2; exit 2 ;; |
| 130 | esac |