├── sway-get-current-workspace ├── LICENSE ├── sway-is-scratchpad-focused ├── sway-move-to-scratchpad ├── sway-get-next-empty-workspace ├── sway-workspace ├── sway-scratchpad-toggle ├── Makefile ├── sway-launch └── README.md /sway-get-current-workspace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sway-get-workspace 4 | # - Print name of current workspace 5 | 6 | shopt -s xpg_echo 7 | 8 | bold="\033[1m" 9 | green="\033[32m" 10 | reset="\033[0m" 11 | 12 | # print usage message on -h/--help 13 | if [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then 14 | echo "${bold}Usage:${reset} ${green}sway-get-current-workspace${reset}" 15 | echo " - Print name of current workspace" 16 | exit 0 17 | fi 18 | 19 | # swaymsg, fallback to i3-msg 20 | swaymsg() { 21 | if command -v swaymsg >/dev/null 2>&1; then 22 | command swaymsg "$@" 23 | else 24 | command i3-msg "$@" 25 | fi 26 | } 27 | 28 | # get number of first empty workspace to the right of the current one 29 | workspaces=$(swaymsg -t get_workspaces) 30 | current_workspace=$(echo "${workspaces}" | jq '.[] | select(.focused==true).num') 31 | 32 | echo "${current_workspace}" 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Sebastian Carlos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /sway-is-scratchpad-focused: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sway-is-scratchpad-focused 4 | 5 | # swaymsg, fallback to i3-msg 6 | swaymsg() { 7 | if command -v swaymsg >/dev/null 2>&1; then 8 | command swaymsg "$@" 9 | else 10 | command i3-msg "$@" 11 | fi 12 | } 13 | 14 | # print usage on -h/--help 15 | if [[ "$1" == '-h' || "$1" == '--help' ]]; then 16 | echo 'Usage: sway-is-scratchpad-focused' 17 | echo ' - If focused, returns 0 and print "true"' 18 | echo ' - If not, returns 1 and print "false"' 19 | exit 0 20 | fi 21 | 22 | quiet=0 23 | if [[ "$1" == '-q' || "$1" == '--quiet' ]]; then 24 | quiet=1 25 | fi 26 | 27 | # Query sway for the focused container id and scratchpad id. 28 | focused_container=$(swaymsg -t get_tree | jq -r 'recurse(.nodes[]) | recurse(.floating_nodes[]) | select(.focused) | .id') 29 | 30 | scratchpad_id=$(swaymsg -t get_tree | jq -r 'recurse(.nodes[]) | recurse(.floating_nodes[]) | select(.marks[0] == "scratchpad") | .id') 31 | 32 | if [ "$focused_container" == "$scratchpad_id" ]; then 33 | [[ $quiet -eq 0 ]] && echo "true" 34 | exit 0 35 | else 36 | [[ $quiet -eq 0 ]] && echo "false" 37 | exit 1 38 | fi 39 | -------------------------------------------------------------------------------- /sway-move-to-scratchpad: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sway-move-to-scratchpad 4 | # - Move the focused window to the scratchpad (and add a border) 5 | 6 | green='\033[0;32m' 7 | red='\033[0;31m' 8 | bold='\033[1m' 9 | reset='\033[0m' 10 | 11 | shopt -s xpg_echo 12 | 13 | # print usage on -h or --help 14 | if [[ "$1" == "-h" || "$1" == "--help" ]]; then 15 | echo "${bold}Usage:${reset} ${green}sway-move-to-scratchpad${reset}" 16 | echo " - Move the focused window to the scratchpad" 17 | echo " - Also add a border and a mark called \"scratchpad\"" 18 | exit 0 19 | fi 20 | 21 | # swaymsg, fallback to i3-msg 22 | swaymsg() { 23 | if command -v swaymsg >/dev/null 2>&1; then 24 | command swaymsg "$@" 25 | else 26 | command i3-msg "$@" 27 | fi 28 | } 29 | 30 | # this is a personal preference 31 | swaymsg border pixel 1 32 | 33 | # Mark the window as "scratchpad", needed to interact with it. 34 | # - Note: The name "scratchpad" might not be accurate since there's a 35 | # difference between the scratchpad and a window in the scratchpad 36 | # (this). However, my Sway workflow relies on having only one window in 37 | # the scratchpad at a time, so this is fine for me. 38 | swaymsg mark 'scratchpad' 39 | 40 | swaymsg move scratchpad 41 | 42 | echo "Moved window to scratchpad." 43 | -------------------------------------------------------------------------------- /sway-get-next-empty-workspace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sway-get-next-empty-workspace 4 | # - Print number of the first empty workspace to the right of the current one 5 | # (or the current workspace if it is empty). 6 | 7 | shopt -s xpg_echo 8 | 9 | bold="\033[1m" 10 | green="\033[32m" 11 | reset="\033[0m" 12 | 13 | # print usage message on -h/--help 14 | if [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then 15 | echo "${bold}Usage:${reset} ${green}sway-get-next-empty-workspace${reset}" 16 | echo " - Print number of the first empty workspace to the right of the" 17 | echo " current one (or the current workspace if it is empty)." 18 | exit 0 19 | fi 20 | 21 | # swaymsg, fallback to i3-msg 22 | swaymsg() { 23 | if command -v swaymsg >/dev/null 2>&1; then 24 | command swaymsg "$@" 25 | else 26 | command i3-msg "$@" 27 | fi 28 | } 29 | 30 | # get number of first empty workspace to the right of the current one 31 | workspaces=$(swaymsg -t get_workspaces) 32 | current_workspace=$(echo "${workspaces}" | jq '.[] | select(.focused==true).num') 33 | 34 | 35 | next_workspace="" 36 | i=$current_workspace 37 | while [[ -z "${browser_workspace}" ]]; do 38 | # if workspace doesn't exist, set browser workspace to current workspace 39 | workspace=$(echo "${workspaces}" | jq ".[] | select(.num==${i})") 40 | if [[ -z "$workspace" ]]; then 41 | next_workspace="${i}" 42 | break 43 | fi 44 | 45 | # if workspace is empty, set browser workspace to current workspace 46 | workspace_nodes=$(swaymsg -t get_tree | jq -r --arg workspace "$i" '.nodes[].nodes[] | select(.name==$workspace) | .nodes[]') 47 | if [[ -z "$workspace_nodes" ]]; then 48 | next_workspace="${i}" 49 | break 50 | fi 51 | 52 | # else, increment current workspace and try again 53 | i=$((i+1)) 54 | done 55 | 56 | echo "${next_workspace}" 57 | -------------------------------------------------------------------------------- /sway-workspace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sway-launch 4 | 5 | shopt -s xpg_echo 6 | 7 | bold="\033[1m" 8 | green="\033[32m" 9 | red="\033[31m" 10 | reset="\033[0m" 11 | 12 | # swaymsg, fallback to i3-msg 13 | swaymsg() { 14 | if command -v swaymsg >/dev/null 2>&1; then 15 | command swaymsg "$@" 16 | else 17 | command i3-msg "$@" 18 | fi 19 | } 20 | 21 | usage() { 22 | echo "${bold}Usage:${reset} ${green}sway-workspace${reset} [OPTIONS] [VALUE]" 23 | echo 24 | echo "- Go to provided workspace." 25 | echo "- If no value is provided, print the current workspace." 26 | echo 27 | echo "VALUE can be:" 28 | echo '- "next", "prev", "next-empty", or a number.' 29 | echo 30 | echo "OPTIONS:" 31 | echo ' -q, --quiet Do not print any output. (Useful for wrapping scripts' 32 | echo ' with their own outputs.)' 33 | echo " -h, --help Show this help message." 34 | } 35 | 36 | 37 | # parse options 38 | quiet=0 39 | workspace="" 40 | while [[ "$#" -gt 0 ]]; do 41 | case "$1" in 42 | -q|--quiet) 43 | quiet=1 44 | shift 45 | ;; 46 | -h|--help) 47 | usage 48 | exit 0 49 | ;; 50 | current|next|prev|next-empty|[0-9]*) 51 | workspace=$1 52 | shift 53 | ;; 54 | *) 55 | echo "Invalid argument: $1" 56 | [[ $quiet -eq 0 ]] && usage 57 | exit 1 58 | ;; 59 | esac 60 | done 61 | 62 | # if no argument, print current workspace 63 | if [[ "$workspace" == "" ]]; then 64 | sway-get-current-workspace 65 | exit 0 66 | fi 67 | 68 | # handle "next", "prev" and number, in which case we message sway 69 | if [[ "$workspace" =~ ^[0-9]+$ ]] || [[ "$workspace" == "next" ]] || [[ "$workspace" == "prev" ]]; then 70 | [[ $quiet -eq 0 ]] && echo "Going to workspace ${green}${workspace}${reset} ..." 71 | swaymsg workspace "$workspace" 72 | exit 0 73 | fi 74 | 75 | # handle "next-empty" 76 | if [[ "$workspace" == "next-empty" ]]; then 77 | workspace=$(sway-get-next-empty-workspace) 78 | [[ $quiet -eq 0 ]] && echo "Going to workspace ${green}${workspace}${reset} ..." 79 | swaymsg workspace "$workspace" 80 | exit 0 81 | fi 82 | -------------------------------------------------------------------------------- /sway-scratchpad-toggle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sway-scratchpad-toggle 4 | # - "advanced" version of "swaymsg scratchpad show" with one advantage: 5 | # It toggles the scratchpad even if it's not currently focused, but only 6 | # visible in the current workspace. 7 | # - Relies on the scratchpad having the mark "scratchpad". 8 | 9 | shopt -s xpg_echo 10 | 11 | bold="\033[1m" 12 | green="\033[32m" 13 | reset="\033[0m" 14 | 15 | usage() { 16 | echo "${bold}Usage:${reset} ${green}sway-scratchpad-toggle${reset} [-h|--help] [-q|--quiet]" 17 | echo " - \"advanced\" version of \"swaymsg scratchpad show\" with one advantage:" 18 | echo " It toggles the scratchpad even if it's not currently focused, but only visible" 19 | echo " in the current workspace." 20 | echo " - Relies on the scratchpad having the mark \"scratchpad\"." 21 | } 22 | 23 | # swaymsg, fallback to i3-msg 24 | swaymsg() { 25 | if command -v swaymsg >/dev/null 2>&1; then 26 | command swaymsg "$@" 27 | else 28 | command i3-msg "$@" 29 | fi 30 | } 31 | 32 | if [[ "$1" == "-h" || "$1" == "--help" ]]; then 33 | usage 34 | exit 0 35 | fi 36 | 37 | quiet=0 38 | if [[ "$1" == "-q" || "$1" == "--quiet" ]]; then 39 | quiet=1 40 | fi 41 | 42 | # sanity check: bail if window with mark "scratchpad" doesn't exist 43 | scratchpad=$(swaymsg -t get_tree | jq -r '.nodes[] .nodes[] | recurse(.floating_nodes[]) | select(.marks[0] == "scratchpad")') 44 | if [[ -z "$scratchpad" ]]; then 45 | echo "No scratchpad found" 46 | echo "Make sure you have a floating window with the mark \"scratchpad\"" 47 | echo "(Did you forget to bing your bind sway-move-to-scratchpad? Or to use it?)" 48 | exit 1 49 | fi 50 | 51 | # check if scratchpad is viisble by checking if a floating container in the current workspace has the scratchpad mark 52 | workspace=$(sway-get-current-workspace) 53 | scratchpad_in_current_workspace=$(swaymsg -t get_tree | jq -r --arg workspace "$workspace" '.nodes[] .nodes[] | select(.type == "workspace" and .name == $workspace) | recurse(.floating_nodes[]) | select(.marks[0] == "scratchpad")') 54 | 55 | is_scratchpad_visible=0 56 | if [ -n "$scratchpad_in_current_workspace" ]; then 57 | is_scratchpad_visible=1 58 | fi 59 | 60 | if [[ $is_scratchpad_visible -eq 1 ]]; then 61 | [[ $quiet -eq 0 ]] && echo "Hiding scratchpad" 62 | 63 | # hiding the scratchpad requires focusing it first. 64 | swaymsg '[con_mark="scratchpad"] focus' 65 | swaymsg scratchpad show 66 | 67 | else 68 | [[ $quiet -eq 0 ]] && echo "Showing scratchpad" 69 | swaymsg scratchpad show 70 | fi 71 | 72 | exit 0 73 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #### Start of standard makefile configuration. #### 2 | 3 | SHELL := /usr/bin/env bash 4 | LN_S := ln -sf 5 | 6 | # Root of the installation 7 | prefix := /usr/local 8 | 9 | # Root of the executables 10 | exec_prefix := $(prefix) 11 | 12 | # Executables 13 | bindir := $(exec_prefix)/bin 14 | 15 | # Set space as the recipe prefix, instead of tab 16 | # Note: It also works with multiple spaces before the recipe text 17 | empty := 18 | space := $(empty) $(empty) 19 | .RECIPEPREFIX := $(space) $(space) 20 | 21 | # Enable delete on error, which is disabled by default for legacy reasons 22 | .DELETE_ON_ERROR: 23 | 24 | #### End of standard makefile configuration. #### 25 | 26 | # Project specific absolute path 27 | srcdir := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) 28 | 29 | green := \\e[32m 30 | blue := \\e[34m 31 | bold := \\e[1m 32 | reset := \\e[0m 33 | 34 | .PHONY: all 35 | all: install 36 | 37 | .PHONY: install 38 | install: installdirs 39 | @echo -e $(blue)Installing sway-talisman$(reset) 40 | @$(LN_S) $(srcdir)/sway-launch $(DESTDIR)$(bindir)/sway-launch 41 | @echo -e ' 'Installing $(green)sway-launch$(reset) in $(green)$(DESTDIR)$(bindir)/$(reset) 42 | @$(LN_S) $(srcdir)/sway-get-current-workspace $(DESTDIR)$(bindir)/sway-get-current-workspace 43 | @echo -e ' 'Installing $(green)sway-get-current-workspace$(reset) in $(green)$(DESTDIR)$(bindir)/$(reset) 44 | @$(LN_S) $(srcdir)/sway-get-next-empty-workspace $(DESTDIR)$(bindir)/sway-get-next-empty-workspace 45 | @echo -e ' 'Installing $(green)sway-get-next-empty-workspace$(reset) in $(green)$(DESTDIR)$(bindir)/$(reset) 46 | @$(LN_S) $(srcdir)/sway-is-scratchpad-focused $(DESTDIR)$(bindir)/sway-is-scratchpad-focused 47 | @echo -e ' 'Installing $(green)sway-is-scratchpad-focused$(reset) in $(green)$(DESTDIR)$(bindir)/$(reset) 48 | @$(LN_S) $(srcdir)/sway-move-to-scratchpad $(DESTDIR)$(bindir)/sway-move-to-scratchpad 49 | @echo -e ' 'Installing $(green)sway-move-to-scratchpad$(reset) in $(green)$(DESTDIR)$(bindir)/$(reset) 50 | @$(LN_S) $(srcdir)/sway-scratchpad-toggle $(DESTDIR)$(bindir)/sway-scratchpad-toggle 51 | @echo -e ' 'Installing $(green)sway-scratchpad-toggle$(reset) in $(green)$(DESTDIR)$(bindir)/$(reset) 52 | @$(LN_S) $(srcdir)/sway-workspace $(DESTDIR)$(bindir)/sway-workspace 53 | @echo -e ' 'Installing $(green)sway-workspace$(reset) in $(green)$(DESTDIR)$(bindir)/$(reset) 54 | @echo -e $(blue)Installing$(reset) $(green)DONE$(reset) 55 | 56 | .PHONY: installdirs 57 | installdirs: 58 | @echo -e $(blue)Creating directories ...$(reset) 59 | @mkdir -p $(DESTDIR)$(bindir) 60 | @echo -e ' 'Creating directory $(green)$(DESTDIR)$(bindir)$(reset) 61 | @echo -e $(blue)Creating directories$(reset) $(green)DONE$(reset)\\n 62 | 63 | .PHONY: uninstall 64 | uninstall: 65 | @echo -e $(blue)Uninstalling sway-talisman$(reset) 66 | @rm -f $(DESTDIR)$(bindir)/sway-get-current-workspace 67 | @echo -e ' 'Deleting $(green)sway-get-current-workspace$(reset) from $(green)$(DESTDIR)$(bindir)/$(reset) 68 | @rm -f $(DESTDIR)$(bindir)/sway-get-next-empty-workspace 69 | @echo -e ' 'Deleting $(green)sway-get-next-empty-workspace$(reset) from $(green)$(DESTDIR)$(bindir)/$(reset) 70 | @rm -f $(DESTDIR)$(bindir)/sway-is-scratchpad-focused 71 | @echo -e ' 'Deleting $(green)sway-is-scratchpad-focused$(reset) from $(green)$(DESTDIR)$(bindir)/$(reset) 72 | @rm -f $(DESTDIR)$(bindir)/sway-move-to-scratchpad 73 | @echo -e ' 'Deleting $(green)sway-move-to-scratchpad$(reset) from $(green)$(DESTDIR)$(bindir)/$(reset) 74 | @rm -f $(DESTDIR)$(bindir)/sway-scratchpad-toggle 75 | @echo -e ' 'Deleting $(green)sway-scratchpad-toggle$(reset) from $(green)$(DESTDIR)$(bindir)/$(reset) 76 | @rm -f $(DESTDIR)$(bindir)/sway-workspace 77 | @echo -e ' 'Deleting $(green)sway-workspace$(reset) from $(green)$(DESTDIR)$(bindir)/$(reset) 78 | @rm -f $(DESTDIR)$(bindir)/sway-launch 79 | @echo -e ' 'Deleting $(green)sway-launch$(reset) from $(green)$(DESTDIR)$(bindir)/$(reset) 80 | @echo -e $(green)Uninstalling DONE$(reset) 81 | -------------------------------------------------------------------------------- /sway-launch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sway-launch 4 | 5 | shopt -s xpg_echo 6 | 7 | bold="\033[1m" 8 | green="\033[32m" 9 | red="\033[31m" 10 | reset="\033[0m" 11 | 12 | # swaymsg, fallback to i3-msg 13 | swaymsg() { 14 | if command -v swaymsg >/dev/null 2>&1; then 15 | command swaymsg "$@" 16 | else 17 | command i3-msg "$@" 18 | fi 19 | } 20 | 21 | # get ids and app_ids of all containers 22 | # - this is used to figure out when the program has launched 23 | get_tree_info() { 24 | swaymsg -t get_tree | jq -r \ 25 | 'recurse(.nodes[]) | recurse(.floating_nodes[]) | select(.type == "con") | "\(.id) \(.app_id) \(.window_properties.instance)"' 26 | } 27 | 28 | usage() { 29 | echo "${bold}Usage:${reset} ${green}sway-launch${reset} [OPTIONS] [--] [arguments...]" 30 | echo " ${green}sway-launch${reset} [OPTIONS] [--] ' [arguments...]'" 31 | echo 32 | echo "- Launch a command. Meant to be called from a wrapper script or alias, which in turn" 33 | echo " is called from a terminal running in the Sway/i3 scratchpad." 34 | echo 35 | echo "- After running it, the scratchpad is hidden. This is meant to simulate the UX of an" 36 | echo " application launcher like rofi, but from a terminal running in the scratchpad." 37 | echo 38 | echo "Options:" 39 | echo " -w, --workspace= Launch the command in the given workspace." 40 | echo " - VALUE can be \"current\" (default), \"next\", \"prev\"," 41 | echo " \"next-empty\", or a number." 42 | echo " -n, --no-hide-scratchpad Do not hide the scratchpad after launching the command." 43 | echo " -e, --window-name Used to check for the new window's existence (by app_id" 44 | echo " or instance). When the window exists, sway-launch exits." 45 | echo " With no value (default), exit as soon as a new window" 46 | echo " is created." 47 | echo " -t, --no-wait Do not wait for a window to open. Exit immediately." 48 | echo " -q, --quiet Do not print any output. (Useful for wrapping scripts" 49 | echo " with their own outputs.)" 50 | echo " -d, --debug Print debug output." 51 | echo " -h, --help Show this help message." 52 | echo 53 | echo "Example:" 54 | echo " # in .bashrc" 55 | echo " alias firefox='sway-launch --workspace=next-empty -- firefox'" 56 | echo 57 | echo " - # open scratchpad" 58 | echo " firefox # launch firefox, scratchpad is then hidden." 59 | 60 | } 61 | 62 | # parse options 63 | hide_scratchpad=1 64 | quiet=0 65 | debug=0 66 | wait_for_window=1 67 | go_to_workspace="current" 68 | window_name="" 69 | while true; do 70 | case "$1" in 71 | -n|--no-hide-scratchpad) 72 | hide_scratchpad=0 73 | shift 74 | ;; 75 | -q|--quiet) 76 | quiet=1 77 | shift 78 | ;; 79 | -d|--debug) 80 | debug=1 81 | shift 82 | ;; 83 | -h|--help) 84 | usage 85 | exit 0 86 | ;; 87 | -w|--workspace) 88 | go_to_workspace=$2 89 | shift 2 90 | ;; 91 | -e|--window-name) 92 | window_name=$2 93 | shift 2 94 | ;; 95 | -t|--no-wait) 96 | wait_for_window=0 97 | shift 98 | ;; 99 | --) 100 | shift 101 | break 102 | ;; 103 | *) 104 | break 105 | ;; 106 | esac 107 | done 108 | 109 | # print usage on invalid arguments 110 | if [[ $# -eq 0 ]]; then 111 | usage 112 | exit 1 113 | fi 114 | 115 | # handle workspace 116 | if [[ $go_to_workspace != "current" ]]; then 117 | sway-workspace --quiet $go_to_workspace 118 | if [[ $? -ne 0 ]]; then 119 | exit 1 120 | fi 121 | fi 122 | 123 | # check if the scratchpad is focused at the time of calling this script. 124 | is_scratchpad_focused=$(sway-is-scratchpad-focused) 125 | 126 | # store current list of container IDs and 'app_id's 127 | old_tree=$(get_tree_info) 128 | 129 | # launch the command 130 | 131 | [[ $quiet -eq 0 ]] && echo "${bold}sway-launch:${reset} Launching ${green}$@${reset} ..." 132 | 133 | # prepare by cleaning latest exit code, if any 134 | exit_code_file="/tmp/sway-launch-exit-code.${USER}" 135 | rm -f ${exit_code_file} > /dev/null 2>&1 136 | 137 | # parse arguments into "cmd" and "args" 138 | if [[ "$#" -eq 1 ]]; then 139 | # if there is only one argument, expand it into parameters with 'eval' to 140 | # handle quoted arguments 141 | eval "args=($1)" 142 | cmd=${args[0]} 143 | args=("${args[@]:1}") 144 | else 145 | cmd=$1 146 | shift 147 | args=("$@") 148 | fi 149 | 150 | # debug info 151 | if [[ $debug -eq 1 ]]; then 152 | echo sway-launch: cmd: $cmd 153 | echo sway-launch: args: "${args[@]}" 154 | for arg in "${args[@]}"; do 155 | echo "sway-launch: $arg" 156 | done 157 | fi 158 | 159 | # This is where the magic happens: 160 | # - Launch in the background 161 | # - Launch in a subshell to avoid adding to the shell's job control 162 | # - Redirect all output to /dev/null 163 | # - Make exit code available to this script 164 | ( 165 | ${cmd} "${args[@]}" > /dev/null 2>&1 166 | echo $? 167 | ) > ${exit_code_file} & 168 | 169 | # If exit code exists and is not 0, exit with that code 170 | exit_code=$(cat ${exit_code_file}) 171 | if [[ $exit_code -ne 0 ]]; then 172 | [[ $quiet -eq 0 ]] && echo "${bold}sway-launch:${reset} ${red}Error:${reset} Command exited with code ${bold}$exit_code${reset}" 173 | rm -f ${exit_code_file} > /dev/null 2>&1 174 | exit $exit_code 175 | fi 176 | 177 | 178 | # wait for the program to launch 179 | # - this is done by checking if the tree has changed 180 | # - skip if --no-wait was provided 181 | waiting_time=0 182 | printed_message=0 183 | if [[ $wait_for_window -eq 1 ]]; then 184 | while true; do 185 | # print message after 5 seconds 186 | if [[ $waiting_time -gt 5000 && $printed_message -eq 0 ]]; then 187 | if [[ $quiet -eq 0 ]]; then 188 | echo "${bold}sway-launch:${reset} The program is taking a long time to launch." 189 | printed_message=1 190 | fi 191 | fi 192 | new_tree=$(get_tree_info) 193 | if [[ "$new_tree" != "$old_tree" ]]; then 194 | # if "window-name" was provided, check if diff contains the string 195 | if [[ -n "$window_name" ]]; then 196 | diff=$(diff <(echo "$old_tree") <(echo "$new_tree")) 197 | if [[ "$diff" == *"$window_name"* ]]; then 198 | break 199 | fi 200 | else 201 | break 202 | fi 203 | fi 204 | sleep 0.001 205 | waiting_time=$((waiting_time+1)) 206 | done 207 | fi 208 | 209 | [[ $quiet -eq 0 ]] && echo "${bold}sway-launch:${reset} ${green}Done!${reset}" 210 | 211 | # If launching from the scratchpad, hide it aftrewards 212 | # - This simulates the UX of application launchers like rofi, but for a 213 | # terminal running in the scratchpad. 214 | if [[ $hide_scratchpad -eq 1 && $is_scratchpad_focused == "true" ]]; then 215 | sway-scratchpad-toggle --quiet 216 | if [[ $? -ne 0 ]]; then 217 | exit 1 218 | fi 219 | fi 220 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This repo is now Archived 2 | All the scripts from this repo are now available at [SWABAI](https://github.com/sebastiancarlos/swabai), including other window-manager commands and Yabai support. 3 | 4 | At the time of writing, the workflow described here works exactly the same in SWABAI. As SWABAI is now my daily driver, I'll continue development there. 5 | 6 | # Sway-Talisman: Sway – Terminal Application Launcher in Scratchpad, Minimalist And Native 7 |

8 | 9 |

10 | 11 | Welcome to `sway-talisman`, the ultra-minimalist approach to launching 12 | applications within the **Sway/i3** tiling window managers. 13 | 14 | This project leverages the power of terminals and Sway's scratchpad to function 15 | as an **app launcher** with no dependencies. 16 | 17 | This is __not__ a TUI launcher; It just makes your terminal even __more__ of a launcher. 18 | 19 | ## Introduction 20 | GUI application launchers like [rofi](https://github.com/davatorium/rofi) are cool, 21 | but if you're a Sway user, you might be interested in taking your minimalism to 22 | the next level. 23 | 24 | It turns out we've had application launchers since the 1960's: Terminals and 25 | shells! Indeed, shells are the only programming languages in which launching a new 26 | process is a first-class citizen. 27 | 28 | So, why not embrace a minimalist tiling-window-manager workflow in which your terminal 29 | ___is___ your application launcher? 30 | 31 | If you go that route, `sway-talisman` might be for you. 32 | 33 | Let's look at it from first principles. **Application launchers are:** 34 | 1. Floating windows 35 | 2. They open in response to a keybinding. 36 | 3. They display and autocomplete a list of applications. 37 | 4. They close after opening an application. 38 | 39 | `sway` provides features **1 through 3** if your workflow consists of having a 40 | single terminal in your scratchpad. 41 | 42 | `sway-talisman` is here to help you with **feature 4**, providing the missing UX 43 | piece of closing the scratchpad after launching the app. 44 | 45 | Another issue with GUI application launchers is that they can be overwhelming: 46 | They usually show every single GUI application in your system, even those that 47 | came with packages you never intend to open. 48 | 49 | `sway-talisman` provides instead an **additive approach**: You are responsible for 50 | defining every app you want to launch. And nothing stops you from 51 | adding extra automation and configuration before and after opening your app. 52 | 53 | ## Features 54 | - Use the same keybinding for two tasks: Opening your ever-ready scratchpad terminal, and 55 | launching apps. Killer combo! 56 | - Every benefit of your shell (history, tab completion) is still there 57 | in your minimalist app launcher. 58 | - Automatically hides after launch, mimicking dedicated launcher behavior. 59 | - Error handling. Returns exit code if the app failed to launch. 60 | - Configure custom pre-launch, post-launch, and window placement logistics. 61 | Just make a wrapper script and do whatever you want. 62 | - **Technically simple.** Main logic is just [10 lines of code](https://github.com/sebastiancarlos/sway-talisman/blob/main/sway-launch#L159). Everything else is UX and glue code. 63 | - Easily configure in which workspace your app will launch. For example, the 64 | **next empty workspace to the right**. 65 | - Detaches application processes for a clutter-free terminal session. No app logs. 66 | - **Modular design.** Comes with several utility scripts, useful on their own. 67 | - Easy installation method – **It's just a handful of bash scripts!** 68 | - Unlike Sway's `exec`, it **blocks untli a new window opens**. Great 69 | for chaining commands in your initialization! 70 | - **No dependencies** besides Sway. 71 | - Supports both **Sway and i3**. 72 | 73 | ## Installation 74 | 1. Clone the repo and run: 75 | ```bash 76 | make 77 | make install # This will install seven bash scripts in your path. 78 | # They can be removed with `make uninstall` 79 | ``` 80 | 81 | 2. In your Sway config, replace your bindings to move a window to the 82 | scratchpad and to toggle it. 83 | 84 | **Note:** This is needed to add a mark to the scratchpad terminal, so it can be found by `sway-talisman`. 85 | 86 | ```bash 87 | ... 88 | # Move the currently focused window to the scratchpad 89 | # bindsym $mod+Shift+minus move scratchpad 90 | bindsym $mod+Shift+minus exec sway-move-to-scratchpad 91 | ... 92 | # Show the next scratchpad window or hide the focused scratchpad window. 93 | # If there are multiple scratchpad windows, this command cycles through them. 94 | # bindsym $mod+minus scratchpad show 95 | bindsym $mod+minus exec sway-scratchpad-toggle 96 | ... 97 | ``` 98 | 99 | 3. Open a new terminal and move it to your scratchpad. (If you have one already, you'll need to close it and open a new one for the changes to apply.) 100 | 101 | 4. Create aliases or scripts which call `sway-launch`, and call them from your 102 | regular terminals, from keybindings, or your brand-new 103 | terminal-scratchpad launcher! 104 | 105 | Example: 106 | ```bash 107 | # in .bashrc 108 | alias firefox='sway-launch --workspace=next-empty -- firefox' 109 | ``` 110 | 111 | Then run: 112 | ```bash 113 | - # open scratchpad 114 | firefox # launch firefox, scratchpad is then hidden. 115 | ``` 116 | 117 | For a full list of options and customizations, see the individual script 118 | documentation below. 119 | 120 | ## Usage 121 | ``` 122 | Usage: sway-launch [OPTIONS] [--] [arguments...] 123 | sway-launch [OPTIONS] [--] ' [arguments...]' 124 | 125 | - Launch a command. Meant to be called from a wrapper script or alias, which in turn 126 | is called from a terminal running in the Sway/i3 scratchpad. 127 | 128 | - After running it, the scratchpad is hidden. This is meant to simulate the UX of an 129 | application launcher like rofi, but from a terminal running in the scratchpad. 130 | 131 | Options: 132 | -w, --workspace= Launch the command in the given workspace. 133 | - VALUE can be "current" (default), "next", "prev", 134 | "next-empty", or a number. 135 | -n, --no-hide-scratchpad Do not hide the scratchpad after launching the command. 136 | -e, --window-name Used to check for the new window's existence (by app_id 137 | or instance). When the window exists, sway-launch exits. 138 | With no value (default), exit as soon as a new window 139 | is created. 140 | -t, --no-wait Do not wait for a window to open. Exit immediately. 141 | -q, --quiet Do not print any output. (Useful for wrapping scripts 142 | with their own outputs.) 143 | -d, --debug Print debug output. 144 | -h, --help Show this help message. 145 | 146 | Example: 147 | # in .bashrc 148 | alias firefox='sway-launch --workspace=next-empty -- firefox' 149 | 150 | - # open scratchpad 151 | firefox # launch firefox, scratchpad is then hidden. 152 | ``` 153 | 154 | ## Usage of utility scripts 155 | These other bash scripts are used by `sway-launch`, the main entry point. But 156 | they are also yours to use elsewhere. 157 | 158 | #### sway-get-workspace 159 | ``` 160 | Usage: sway-get-workspace 161 | - Print name of current workspace 162 | ``` 163 | 164 | #### sway-get-next-empty-workspace 165 | ``` 166 | Usage: sway-get-next-empty-workspace 167 | - Print number of the first empty workspace to the right of the 168 | current one (or the current workspace if it is empty). 169 | ``` 170 | 171 | #### sway-workspace 172 | ``` 173 | Usage: sway-workspace [OPTIONS] [VALUE] 174 | 175 | - Go to provided workspace. 176 | - If no value is provided, print the current workspace. 177 | 178 | VALUE can be: 179 | - "next", "prev", "next-empty", or a number. 180 | 181 | OPTIONS: 182 | -q, --quiet Do not print any output. (Useful for wrapping scripts 183 | with their own outputs.) 184 | -h, --help Show this help message. 185 | ``` 186 | 187 | #### sway-is-scratchpad-focused 188 | ``` 189 | Usage: sway-is-scratchpad-focused 190 | - If focused, returns 0 and print "true" 191 | - If not, returns 1 and print "false" 192 | ``` 193 | 194 | #### sway-move-to-scratchpad 195 | ``` 196 | Usage: sway-move-to-scratchpad 197 | - Move the focused window to the scratchpad 198 | - Also add a border and a mark called "scratchpad" 199 | ``` 200 | 201 | #### sway-scratchpad-toggle 202 | ``` 203 | Usage: sway-scratchpad-toggle [-h|--help] [-q|--quiet] 204 | - "advanced" version of "swaymsg scratchpad show" with one advantage: 205 | It toggles the scratchpad even if it's not currently focused, but only visible 206 | in the current workspace. 207 | - Relies on the scratchpad having the mark "scratchpad". 208 | ``` 209 | 210 | ## As featured in 211 | - [Are application launchers overrated?](https://sebastiancarlos.com/are-application-launchers-overrated-e0407e220dc7) 212 | 213 | ## You might also like 214 | - [sway-launcher-desktop](https://github.com/Biont/sway-launcher-desktop) 215 | - [fzf-nova](https://github.com/gotbletu/fzf-nova) 216 | 217 | ## License 218 | MIT 219 | 220 | ## Contributing 221 | 222 | We welcome contributions of all kinds. If you have a suggestion or fix, please 223 | feel free to open an issue or pull request. 224 | 225 | _Enjoy a lightweight, efficient, and terminal-centric launching experience with 226 | `sway-talisman`!_ 227 | 228 | ## See also 229 | - **[swabai](https://github.com/sebastiancarlos/swabai):** A rework of sway-talisman is included there. It also supports the Yabai tiling window manager for macOS, and many more commands. 230 | --------------------------------------------------------------------------------