294 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			294 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env bash
 | 
						|
# https://gitlab.com/wef/dotfiles/-/blob/master/bin/sway-select-window
 | 
						|
# shellcheck disable=SC2034
 | 
						|
TIME_STAMP="20230129.084152"
 | 
						|
 | 
						|
# Copyright (C) 2019-2023 Bob Hepple <bob dot hepple at gmail dot com>
 | 
						|
 | 
						|
# This program is free software: you can redistribute it and/or modify
 | 
						|
# it under the terms of the GNU General Public License as published by
 | 
						|
# the Free Software Foundation, either version 3 of the License, or (at
 | 
						|
# your option) any later version.
 | 
						|
# 
 | 
						|
# This program is distributed in the hope that it will be useful, but
 | 
						|
# WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 | 
						|
# General Public License for more details.
 | 
						|
# 
 | 
						|
# You should have received a copy of the GNU General Public License
 | 
						|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
# CUSTOMISE HERE IF YOU'RE NOT GETTING ALL YOUR ICONS:
 | 
						|
 | 
						|
# This array contains my most commonly used apps on f34, cached here
 | 
						|
# for speed. If they don't work for you, you can either just remove
 | 
						|
# these entries and rely on on-the-fly lookup of icons (which may be
 | 
						|
# slow). Or, for speedier operation, you can:
 | 
						|
 | 
						|
# 1. get all your common apps running
 | 
						|
# 2. remove the entries in the array
 | 
						|
# 3. run the script from the CLI
 | 
						|
# 4. copy-paste the list of "[app]=icon" lines into the array
 | 
						|
 | 
						|
# Also, if all else fails, you can hardcode your app here:
 | 
						|
declare -A icons=(
 | 
						|
    [floating-kitty]=/usr/share/icons/hicolor/256x256/apps/kitty.png
 | 
						|
    [kitty]=/usr/share/icons/hicolor/256x256/apps/kitty.png
 | 
						|
    [foot]=/usr/share/icons/hicolor/48x48/apps/foot.png
 | 
						|
    [emacs]=/usr/share/icons/breeze/apps/48/emacs.svg
 | 
						|
    [firefox]=/usr/share/icons/hicolor/48x48/apps/firefox.png
 | 
						|
    [mythfrontend]=/usr/share/pixmaps/mythfrontend.png
 | 
						|
    [kitty]=/usr/share/icons/hicolor/256x256/apps/kitty.png
 | 
						|
    [Gdcalc]=/usr/share/pixmaps/HP-16C-48.xpm
 | 
						|
    [pavucontrol]=/usr/share/icons/gnome/48x48/apps/multimedia-volume-control.png
 | 
						|
    [pulseeffects]=/usr/share/icons/hicolor/scalable/apps/pulseeffects.svg
 | 
						|
    [blueman-manager]=/usr/share/icons/hicolor/scalable/apps/blueman.svg
 | 
						|
    [gnome-system-monitor]=/usr/share/icons/hicolor/scalable/apps/org.gnome.SystemMonitor.svg
 | 
						|
    [gnome-disks]=/usr/share/icons/hicolor/scalable/apps/org.gnome.DiskUtility.svg
 | 
						|
    [org.gnome.Weather]=/usr/share/icons/hicolor/scalable/apps/org.gnome.Weather.svg
 | 
						|
    [xfce4-appfinder]=/usr/share/icons/hicolor/scalable/apps/org.xfce.appfinder.svg
 | 
						|
    [LibreWolf]=/var/lib/flatpak/exports/share/icons/hicolor/128x128/apps/io.gitlab.librewolf-community.png
 | 
						|
    [firefox]=/var/lib/flatpak/exports/share/icons/hicolor/128x128/apps/org.mozilla.firefox.png
 | 
						|
)
 | 
						|
 | 
						|
KEYBOARD_DEVICE="/dev/input/event4"
 | 
						|
verbose=""
 | 
						|
 | 
						|
PROG=$( basename "$0" )
 | 
						|
case "$1" in
 | 
						|
    -h|--help)
 | 
						|
        echo "Usage: $PROG"
 | 
						|
        echo
 | 
						|
        echo "show running programs and select one to display (uses wofi)."
 | 
						|
        
 | 
						|
        echo "If evtest(1) is installed and sudo is available then you can press"
 | 
						|
        echo "Shift-Enter to move the window here rather than move to"
 | 
						|
        echo "the windows workspace. You may need to change KEYBOARD_DEVICE above."
 | 
						|
 | 
						|
        exit 0
 | 
						|
        ;;
 | 
						|
    -v|--verbose)
 | 
						|
        verbose="set"
 | 
						|
        shift
 | 
						|
        ;;
 | 
						|
esac
 | 
						|
 | 
						|
jq_get_windows='
 | 
						|
     # descend to workspace or scratchpad
 | 
						|
     .nodes[].nodes[]
 | 
						|
     # save workspace name as .w
 | 
						|
     | {"w": .name} + (
 | 
						|
       if (.nodes|length) > 0 then # workspace
 | 
						|
         [recurse(.nodes[])]
 | 
						|
       else # scratchpad
 | 
						|
         []
 | 
						|
       end
 | 
						|
       + .floating_nodes
 | 
						|
       | .[]
 | 
						|
       # select nodes with no children (windows)
 | 
						|
       | select(.nodes==[])
 | 
						|
     )'
 | 
						|
 | 
						|
jq_windows_to_tsv='
 | 
						|
    [
 | 
						|
      (.id | tostring),
 | 
						|
      # remove markup and index from workspace name, replace scratch with "[S]"
 | 
						|
      (.w | gsub("<[^>]*>|:$"; "") | sub("__i3_scratch"; "[S]")),
 | 
						|
      # get app name (or window class if xwayland)
 | 
						|
      (.app_id // .window_properties.class),
 | 
						|
      (.name),
 | 
						|
      (.pid)
 | 
						|
    ]
 | 
						|
    | @tsv'
 | 
						|
 | 
						|
get_fs_win_in_ws() {
 | 
						|
    id="${1:?}"
 | 
						|
 | 
						|
    swaymsg -t get_tree |
 | 
						|
    jq -e -r --argjson id "$id" "
 | 
						|
    [ $jq_get_windows  ]
 | 
						|
    | (.[]|select(.id == \$id)) as \$selected_workspace
 | 
						|
    | .[]
 | 
						|
    | select( .w == \$selected_workspace.w and .fullscreen_mode == 1 )
 | 
						|
    | $jq_windows_to_tsv
 | 
						|
    "
 | 
						|
}
 | 
						|
 | 
						|
get_hardcoded_icon() {
 | 
						|
    icon="${icons[$1]}"
 | 
						|
    echo "$icon"
 | 
						|
}
 | 
						|
 | 
						|
get_desktop() {
 | 
						|
    app="$1"
 | 
						|
    p="/usr/share/applications"
 | 
						|
 | 
						|
    # fast and easy cases first:
 | 
						|
    for prefix in "" org.kde. org.gnome. org.freedesktop.; do
 | 
						|
        d="$p/$prefix$app.desktop"
 | 
						|
        [[ -r "$d" ]] && echo "$d" && return
 | 
						|
    done
 | 
						|
 | 
						|
    # maybe lowercase
 | 
						|
    for prefix in "" org.kde. org.gnome. org.freedesktop.; do
 | 
						|
        d="$p/$prefix${app,,}.desktop"
 | 
						|
        [[ -r "$d" ]] && echo "$d" && return
 | 
						|
    done
 | 
						|
 | 
						|
    # this is fairly reliable but slow:
 | 
						|
    # look for a .desktop file with Exec=$app eg
 | 
						|
    # gnome-disks (but exclude gnome-disk-image-writer.desktop)
 | 
						|
    # gnome-font-viewer
 | 
						|
    GREP='egrep -r'
 | 
						|
    type rg &>/dev/null && GREP=rg
 | 
						|
    d=$( $GREP -il "^exec=$app( %u)*[[:space:]]*$" $p | head -n 1)
 | 
						|
    [[ -r "$d" ]] && echo "$d" && return
 | 
						|
 | 
						|
    # desperation - weird apps like com.github.wwmm.pulseeffects.desktop!!
 | 
						|
    # shellcheck disable=SC2012
 | 
						|
    d=$( ls "$p/"*".$app.desktop" 2>/dev/null | head -n 1 )
 | 
						|
    [[ -r "$d" ]] && echo "$d" && return
 | 
						|
}
 | 
						|
 | 
						|
get_icon() {
 | 
						|
    icon_locations=(
 | 
						|
        icons/hicolor/scalable/apps
 | 
						|
        icons/hicolor/48x48/apps
 | 
						|
        icons/hicolor/128x128/apps
 | 
						|
        icons/hicolor/256x256/apps
 | 
						|
        icons/gnome/48x48/apps
 | 
						|
        icons/gnome/48x48/devices
 | 
						|
        pixmaps
 | 
						|
    )
 | 
						|
 | 
						|
    app="$1"
 | 
						|
 | 
						|
    icon=$( get_hardcoded_icon "$app_name" )
 | 
						|
    [[ "$icon" && -r "$icon" ]] && echo "$icon" && return
 | 
						|
 | 
						|
    # let's go poke in the .desktop files:
 | 
						|
    icon_name=""
 | 
						|
    dt=$( get_desktop "$app" )
 | 
						|
 | 
						|
    # sometimes we get the 'class' rather than the exe - so try this:
 | 
						|
    [[ "$dt" ]] || {
 | 
						|
        app=$( tr '\0' '\n' < "/proc/$pid/cmdline" | head -n 1 )
 | 
						|
        dt=$( get_desktop "$( basename "$app" )" )
 | 
						|
    }
 | 
						|
    
 | 
						|
    [[ "$dt" ]] && {
 | 
						|
        icon_name=$( awk -v IGNORECASE="set" -F"=" '/^icon/ {print $2}' "$dt" )
 | 
						|
        [[ "$icon_name" ]] && {
 | 
						|
            for d in "${icon_locations[@]}"; do
 | 
						|
                for s in .svg .png ""; do
 | 
						|
                    icon=/usr/share/$d/$icon_name$s
 | 
						|
                    [[ -r $icon ]] && echo "$icon" && return
 | 
						|
                done
 | 
						|
            done
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    # desperation street:
 | 
						|
    icon=$( find /usr/share/icons /usr/share/pixmaps | grep -E -i "/$app.(png|svg)" | head -n 1 )
 | 
						|
    [[ "$icon" && -r "$icon" ]] && echo "$icon" && return
 | 
						|
 | 
						|
    # failed:
 | 
						|
    #echo "/usr/share/icons/Adwaita/32x32/status/image-missing-symbolic.symbolic.png"
 | 
						|
    echo "$HOME/.local/share/icons/img-missing-sym.png"
 | 
						|
}
 | 
						|
 | 
						|
focus_window() {
 | 
						|
    id="${1}"
 | 
						|
    ws_name="${2}"
 | 
						|
    app_name="${3}"
 | 
						|
    win_title="${4}"
 | 
						|
 | 
						|
    [[ "$verbose" ]] && printf "focusing window (in workspace %s): [%s] (%s) \`%s\`\n" "$ws_name" "$id" "$app_name" "$win_title" >&2
 | 
						|
 | 
						|
    # look for fullscreen among other windows in selected window's workspace
 | 
						|
    if fs_win_tsv="$( get_fs_win_in_ws "$id" )" ; then
 | 
						|
        read -r win_id ws_name app_name win_title <<<"$fs_win_tsv"
 | 
						|
        if [ "$win_id" != "$id" ] ; then
 | 
						|
            [[ "$verbose" ]] && printf 'found fullscreen window in target workspace (%s): [%s] (%s) "%s"\n' "$ws_name" "$win_id" "$app_name" "$win_title" >&2
 | 
						|
            swaymsg "[con_id=$win_id] fullscreen disable"
 | 
						|
        fi
 | 
						|
    fi
 | 
						|
 | 
						|
    swaymsg "[con_id=$id]" focus
 | 
						|
}
 | 
						|
 | 
						|
id_fmt='<span stretch="ultracondensed" size="xx-small">%s</span>'
 | 
						|
ws_fmt='<span size="small">%s</span>'
 | 
						|
app_fmt='<span weight="bold">%s</span>'
 | 
						|
title_fmt='<span style="italic">%s</span>'
 | 
						|
 | 
						|
#id_fmt='%s'
 | 
						|
#ws_fmt='%s'
 | 
						|
#app_fmt='%s'
 | 
						|
#title_fmt='%s'
 | 
						|
 | 
						|
# allow Alt+Tab etc to not be caught by sway:
 | 
						|
trap 'swaymsg "mode default"' EXIT
 | 
						|
swaymsg "mode passthrough"
 | 
						|
 | 
						|
swaymsg -t get_tree |
 | 
						|
# get list of windows as tab-separated columns
 | 
						|
jq -r "$jq_get_windows | $jq_windows_to_tsv" |
 | 
						|
# align columns w/ spaces (keep tab as separator)
 | 
						|
column -s $'\t' -o $'\t' -t |
 | 
						|
# pango format the window list
 | 
						|
while IFS=$'\t' read -r win_id ws_name app_name win_title pid; do
 | 
						|
    shopt -s extglob
 | 
						|
    app_name="${app_name%%*( )}"
 | 
						|
    icon=$( get_icon "$app_name" "$pid" )
 | 
						|
    [[ "$verbose" ]] && printf "[%s]=>%s\n" "$app_name" "$icon" >&2
 | 
						|
    # shellcheck disable=SC2059
 | 
						|
    # hide win_id for now
 | 
						|
    #win_id=""
 | 
						|
    # simplify ws_name instead of Scratch
 | 
						|
    #(( ${#ws_name} > 8 )) && ws_name="${ws_name:0:1}"
 | 
						|
    printf "img:$icon:text:${id_fmt}\t${ws_fmt}\t${app_fmt}\t${title_fmt}\n" \
 | 
						|
           "$win_id" \
 | 
						|
           "$ws_name" \
 | 
						|
           "$app_name" \
 | 
						|
           "$win_title"
 | 
						|
done |
 | 
						|
# sort by workspace name and then app_name:
 | 
						|
sort -k2 -k3 |
 | 
						|
# defeat the initial cache in order to preserve the order:
 | 
						|
wofi -m --cache-file=/dev/null --insensitive --allow-images --show dmenu --prompt='Focus a window' | {
 | 
						|
    FS=$'\t' read -r win_id ws_name app_name win_title
 | 
						|
    # printf "win_id='$win_id' ws_name='$ws_name' app_name='$app_name' win_title='$win_title'" >&2
 | 
						|
    # use evtest (if installed) to detect the state of the left shift
 | 
						|
    # key and if it's 'down' then move the window here rather than move
 | 
						|
    # to the window's workspace. Should be safe if evtest or sudo are
 | 
						|
    # not available.
 | 
						|
    ## i've disabled this
 | 
						|
    type evtest && sudo evtest --query $KEYBOARD_DEVICE EV_KEY KEY_LEFTSHIFT
 | 
						|
    SHIFT_STATE=$?
 | 
						|
    if [[ "$win_id" ]]; then
 | 
						|
        if (( SHIFT_STATE == 10 )); then
 | 
						|
            # bring the window here
 | 
						|
            swaymsg "[con_id=$win_id] move window to workspace current, focus"
 | 
						|
        else
 | 
						|
            focus_window "$win_id" "$ws_name" "$app_name" "$win_title"
 | 
						|
        fi
 | 
						|
    fi
 | 
						|
}
 | 
						|
 | 
						|
exit 0
 | 
						|
 | 
						|
# this is the example documented in 'man 7 wofi' but it doesn't understand the scratchpad:
 | 
						|
swaymsg -t get_tree |
 | 
						|
jq -r '.nodes[].nodes[] | if .nodes then [recurse(.nodes[])] else [] end +  .floating_nodes  |  .[]  |  select(.nodes==[]) | ((.id | tostring) + " " + .name)' |
 | 
						|
wofi --show dmenu | {
 | 
						|
    read -r id name
 | 
						|
    swaymsg "[con_id=$id]" focus
 | 
						|
}
 | 
						|
 | 
						|
# Local Variables:
 | 
						|
# mode: shell-script
 | 
						|
# time-stamp-pattern: "4/TIME_STAMP=\"%:y%02m%02d.%02H%02M%02S\""
 | 
						|
# eval: (add-hook 'before-save-hook 'time-stamp)
 | 
						|
# End:
 |