Skip to content

Commit ff26d8b

Browse files
A Dynamic Collection of Shell Scripts with Educational Purpose
1 parent 9ad922d commit ff26d8b

5 files changed

Lines changed: 922 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env bash
2+
# Script Wrapper to be used in the same folder
3+
4+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
PREVIEW_SCRIPT="${SCRIPT_DIR}/script-base-to-be-used-in-the-same-folder"

scripts/sway-window-preview

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env bash
2+
# License: GPLv3
3+
# Credits: Felipe Facundes
4+
5+
# Sway Window Switcher in Bash
6+
# Behavior: native focus right/left + notification of the focused window
7+
# --list shows all windows via rofi
8+
9+
show_preview=1
10+
11+
# Check dependencies at the start
12+
check_deps() {
13+
local deps=("swaymsg" "jq")
14+
if [[ "$1" == "--list" ]] || [[ "${mode:-}" == "list" ]]; then
15+
deps+=("rofi")
16+
fi
17+
18+
for dep in "${deps[@]}"; do
19+
if ! command -v "$dep" >/dev/null 2>&1; then
20+
echo "Error: $dep not found" >&2
21+
exit 1
22+
fi
23+
done
24+
}
25+
26+
get_focused_window_info() {
27+
# Get information about the currently focused window
28+
local tree
29+
tree=$(swaymsg -t get_tree 2>/dev/null) || return 1
30+
31+
local name app_id class_name
32+
33+
name=$(echo "$tree" | jq -r '
34+
.. | select(.focused? == true) |
35+
.name // "Untitled" |
36+
if . == "" then "Untitled" else . end
37+
' 2>/dev/null)
38+
39+
app_id=$(echo "$tree" | jq -r '
40+
.. | select(.focused? == true) |
41+
.app_id // ""
42+
' 2>/dev/null)
43+
44+
class_name=$(echo "$tree" | jq -r '
45+
.. | select(.focused? == true) |
46+
.window_properties.class // ""
47+
' 2>/dev/null)
48+
49+
# Return empty if nothing useful was found
50+
[[ -z "$name" ]] && return 1
51+
52+
echo "$name|$app_id|$class_name"
53+
}
54+
55+
switch_window() {
56+
local direction="$1" # "next" or "prev"
57+
58+
if [[ "$direction" == "next" ]]; then
59+
swaymsg focus right >/dev/null 2>&1
60+
else
61+
swaymsg focus left >/dev/null 2>&1
62+
fi
63+
64+
# Small wait for the focus to update
65+
sleep 0.08
66+
67+
(( show_preview == 0 )) && return
68+
69+
local info
70+
info=$(get_focused_window_info) || return
71+
72+
IFS='|' read -r name app_id class_name <<< "$info"
73+
74+
local icon="${app_id:-${class_name:-window}}"
75+
local clean_name="${name//$'\n'/ }"
76+
77+
notify-send \
78+
--app-name "Window Switcher" \
79+
--icon "$icon" \
80+
--expire-time 1400 \
81+
--urgency low \
82+
"$clean_name" >/dev/null 2>&1
83+
}
84+
85+
show_rofi_list() {
86+
local tree
87+
tree=$(swaymsg -t get_tree 2>/dev/null) || exit 1
88+
89+
# Extract all windows with jq - more robust
90+
local windows
91+
mapfile -t windows < <(echo "$tree" | jq -r '
92+
.. | select(.type? == "con" or .type? == "floating_con") |
93+
select(.name != null and .name != "") |
94+
.name as $n |
95+
.app_id as $a |
96+
(.window_properties.class // "") as $c |
97+
.id as $id |
98+
.focused as $f |
99+
[$n, ($a // $c // "?"), $id, $f] | @tsv
100+
' 2>/dev/null)
101+
102+
(( ${#windows[@]} == 0 )) && exit 0
103+
104+
local rofi_lines=() id_map=()
105+
local name app id focused prefix line
106+
107+
for win in "${windows[@]}"; do
108+
IFS=$'\t' read -r name app id focused <<< "$win"
109+
110+
# Skip unwanted windows (case insensitive)
111+
local lower_all="${app,,}${name,,}"
112+
[[ "$lower_all" =~ waybar|wofi|rofi|dmenu|swaync|swaylock ]] && continue
113+
114+
# Clean name
115+
name="${name//$'\n'/ }"
116+
117+
# Prefix based on focus
118+
prefix=" "
119+
[[ "$focused" == "true" ]] && prefix=""
120+
121+
line="${prefix}${name} <small>(${app})</small>"
122+
rofi_lines+=("$line")
123+
id_map+=("$id")
124+
done
125+
126+
(( ${#rofi_lines[@]} == 0 )) && exit 0
127+
128+
local choice
129+
choice=$(printf '%s\n' "${rofi_lines[@]}" | rofi -dmenu -i -p "Windows" -format i -no-custom 2>/dev/null)
130+
131+
if [[ -n "$choice" && "$choice" =~ ^[0-9]+$ && "$choice" -lt ${#rofi_lines[@]} ]]; then
132+
local selected_id="${id_map[$choice]}"
133+
[[ -n "$selected_id" ]] && swaymsg "[con_id=$selected_id] focus" >/dev/null 2>&1
134+
fi
135+
}
136+
137+
# Parse arguments
138+
mode=""
139+
direction="next" # default
140+
141+
while [[ $# -gt 0 ]]; do
142+
case $1 in
143+
--next) direction="next" ;;
144+
--prev) direction="prev" ;;
145+
--list) mode="list" ;;
146+
--disable-preview) show_preview=0 ;;
147+
*)
148+
echo "Usage: ${0##*/} [--next] [--prev] [--list] [--disable-preview]" >&2
149+
exit 1
150+
;;
151+
esac
152+
shift
153+
done
154+
155+
# Check dependencies
156+
check_deps "${1:-}"
157+
158+
# Execute
159+
if [[ "$mode" == "list" ]]; then
160+
show_rofi_list
161+
else
162+
switch_window "$direction"
163+
fi

scripts/sway-window-preview-cli.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/bin/env python3
2+
# License: GPLv3
3+
# Credits: Felipe Facundes
4+
5+
"""
6+
Sway Window Switcher with notification
7+
Behaves like sway's focus right / focus left + notification of the focused window
8+
"""
9+
import subprocess
10+
import json
11+
import sys
12+
13+
14+
class SwayFastSwitcher:
15+
def __init__(self):
16+
self.show_preview = True
17+
18+
def _get_focused_window_info(self):
19+
"""Returns information about the currently focused window"""
20+
try:
21+
tree = json.loads(subprocess.run(
22+
['swaymsg', '-t', 'get_tree'],
23+
capture_output=True, text=True, check=True
24+
).stdout)
25+
26+
def find_focused(node):
27+
if not isinstance(node, dict):
28+
return None
29+
if node.get('focused'):
30+
name = (node.get('name') or '').strip() or 'Untitled'
31+
app_id = node.get('app_id') or ''
32+
class_name = node.get('window_properties', {}).get('class', '')
33+
return {
34+
'name': name,
35+
'app_id': app_id,
36+
'class': class_name,
37+
'id': node.get('id')
38+
}
39+
for key in ['nodes', 'floating_nodes']:
40+
if key in node:
41+
for child in node[key]:
42+
found = find_focused(child)
43+
if found:
44+
return found
45+
return None
46+
47+
return find_focused(tree)
48+
except:
49+
return None
50+
51+
def switch_window(self, direction='next'):
52+
if direction not in ('next', 'prev'):
53+
return
54+
55+
sway_direction = 'right' if direction == 'next' else 'left'
56+
57+
# Execute the native sway command
58+
subprocess.run(
59+
['swaymsg', 'focus', sway_direction],
60+
capture_output=True, check=False
61+
)
62+
63+
# Wait a very short moment for the focus to be updated
64+
# (generally 20-80ms is enough)
65+
import time
66+
time.sleep(0.06)
67+
68+
# Get information about the window that was just focused
69+
info = self._get_focused_window_info()
70+
if not info:
71+
return
72+
73+
if self.show_preview:
74+
try:
75+
icon = info['app_id'] or info['class'] or 'window'
76+
subprocess.run([
77+
'notify-send',
78+
'--app-name', 'Window Switcher',
79+
'--icon', icon,
80+
'--expire-time', '1400',
81+
'--urgency', 'low',
82+
f"→ {info['name']}"
83+
], capture_output=True)
84+
except:
85+
pass
86+
87+
def show_rofi_list(self):
88+
"""List ALL windows (all workspaces)"""
89+
try:
90+
tree = json.loads(subprocess.run(
91+
['swaymsg', '-t', 'get_tree'],
92+
capture_output=True, text=True, check=True
93+
).stdout)
94+
95+
windows = []
96+
def collect(node):
97+
if not isinstance(node, dict):
98+
return
99+
if node.get('type') in ['con', 'floating_con'] and node.get('name'):
100+
name = (node.get('name') or '').strip() or 'Untitled'
101+
app_id = node.get('app_id') or ''
102+
class_name = node.get('window_properties', {}).get('class', '')
103+
skip = ['waybar', 'wofi', 'rofi', 'dmenu', 'swaync', 'swaylock']
104+
if any(p in (app_id + name + class_name).lower() for p in skip):
105+
return
106+
windows.append({
107+
'id': node['id'],
108+
'name': name,
109+
'app': app_id or class_name or '?',
110+
'focused': node.get('focused', False)
111+
})
112+
for k in ['nodes', 'floating_nodes']:
113+
if k in node:
114+
for c in node[k]:
115+
collect(c)
116+
117+
collect(tree)
118+
119+
if not windows:
120+
return
121+
122+
lines = []
123+
id_map = {}
124+
for i, w in enumerate(windows):
125+
prefix = "● " if w['focused'] else " "
126+
line = f"{prefix}{w['name']} <small>({w['app']})</small>"
127+
lines.append(line)
128+
id_map[i] = w['id']
129+
130+
rofi_input = "\n".join(lines)
131+
132+
result = subprocess.run(
133+
['rofi', '-dmenu', '-i', '-p', 'Windows', '-format', 'i', '-no-custom'],
134+
input=rofi_input, text=True, capture_output=True, check=False
135+
)
136+
137+
if result.returncode == 0 and result.stdout.strip():
138+
try:
139+
idx = int(result.stdout.strip())
140+
if idx in id_map:
141+
subprocess.run(
142+
['swaymsg', f'[con_id={id_map[idx]}]', 'focus'],
143+
capture_output=True
144+
)
145+
except:
146+
pass
147+
148+
except Exception as e:
149+
print(f"Error listing windows: {e}", file=sys.stderr)
150+
151+
152+
def main():
153+
import argparse
154+
155+
parser = argparse.ArgumentParser(description="Sway Window Switcher")
156+
parser.add_argument('--next', action='store_true')
157+
parser.add_argument('--prev', action='store_true')
158+
parser.add_argument('--list', action='store_true')
159+
parser.add_argument('--disable-preview', action='store_true')
160+
161+
args = parser.parse_args()
162+
163+
switcher = SwayFastSwitcher()
164+
165+
if args.disable_preview:
166+
switcher.show_preview = False
167+
168+
if args.list:
169+
switcher.show_rofi_list()
170+
elif args.prev:
171+
switcher.switch_window('prev')
172+
else:
173+
switcher.switch_window('next') # default and --next
174+
175+
176+
if __name__ == '__main__':
177+
main()

0 commit comments

Comments
 (0)