Custom Window Snap
Many desktop environments for Linux have had window snapping (a.k.a window tiling or edge snapping) for a long time. It is a feature that I use very heavily, especially with hotkeys. I have been very dissatisfied with the way Linux desktop environments have handled this. They almost work the way I want, but not quite. I decided to take control of this.
Gosnap stands for "Good Old Snap" and replaces the window snapping/tiling feature on just about any Linux distro that uses the X Window System.
Download
| Filename | Description | Size | Modified | Link |
|---|---|---|---|---|
|
gosnap |
The Gosnap bash script. | 6.4K | 7/31/2020 |
Installing
wget https://beardedmaker.com/wiki/files/gosnap
chmod a+x gosnap
sudo mv gosnap /usr/binUsage
Usage: gosnap [-L | -l | -R | -r | -M | -m | -N | -n | -d | -i]
-L snap left
-l snap left dynamically
-R snap right
-r snap right dynamically
-M maximize window
-m toggle maximize window
-N minimize window
-n minimize window dynamically
-d resize to default geometry (current default: 0,10,60,826,556)
-i print window informationIf your desktop environment supports custom hotkeys, you can simply map any hotkey you want to execute this script with the appropriate parameter. For example:
- Snap Left:
gosnap -lSuper+Left - Snap Right:
gosnap -rSuper+Right - Snap Maximize:
gosnap -mSuper+Up - Snap Minimize:
gosnap -lSuper+Down
Note: To "snap dynamically" means to go to a "default" position as an intermediate step depending on the window's current position. For example, if I minimize the window using gosnap -n, but the window was previously maximized, I'd rather the window pop into the middle of the screen on the first invocation, and then when I minimize again I want the window to collapse to the taskbar. To just immediately minimize the window, use gosnap -N instead.
Gosnap Script
1 #!/bin/bash
2 #
3 # GOSNAP: Good Old Snap
4 #
5 # Script to emulate the Aero Snap feature in Windows 7
6 # Use -L or -R flags to snap left or right
7 # Use -l or -r flags to snap dynamically (intermediate default position)
8 # Use -M flag to maximize
9 # Use -m flag to toggle maximized state
10 # Use -N flag to minimize
11 # Use -n flag to minimize dynamically (intermediate default position)
12 # Use -d flag to reset the window state to default
13 # Use -i flag to print window info to stdout
14 #
15 # wmctrl -e g,x,y,w,h (any value set to -1 won't be changed)
16 # g = window gravity (just set to 0)
17 # x = window x position
18 # y = window y position
19 # w = window width
20 # h = window height
21 #
22
23 # CONFIGURATION
24 # =============
25
26 # Default window geometry to use with -d
27 # Format: gravity, x_pos, y_pos, width, height)
28 window_default="0,10,60,826,556"
29
30 # The x position reported by xdotool when the window is snapped left/right.
31 # For some reason, snapping the window to 0,0 does not actually register as 0,0.
32 # Use the -i flag while the terminal window is snapped to the left or the right
33 # to get the values unique to your screen.
34 left_origin_x=10
35 right_origin_x=692
36
37 # Add this to the half screen width value for snapping left/right.
38 # If you see gaps on the left or right wien you snap, you may need to adjust this.
39 half_width_adjust_left=1
40 half_width_adjust_right=1
41
42 # Adjust these to avoid issues with gaps
43 snap_left_x=0
44 snap_left_y=0
45 snap_right_x=0
46 snap_right_y=1
47
48 # Use wmctrl bug avoidance
49 # For some reason, wmctrl intermittently snaps to the wrong position.
50 # Doing "snap_default && snap_maximize" before snapping left/right seems to fix it.
51 # The trade-off using this fix is that snapping left/right has a very slight delay.
52 wmctrl_bug_fix=true
53
54 function main
55 {
56 check_args $@
57 check_command wmctrl
58 check_command xdotool
59 get_window_state
60
61 while getopts ":LlRrMmNndi" opt;do
62 case $opt in
63 L)
64 snap_left
65 ;;
66 l)
67 if $window_snapped_right;then
68 snap_default
69 else
70 snap_left
71 fi
72 ;;
73 R)
74 snap_right
75 ;;
76 r)
77 if $window_snapped_left;then
78 snap_default
79 else
80 snap_right
81 fi
82 ;;
83 M)
84 snap_maximize
85 ;;
86 m)
87 if $window_maximized;then
88 snap_default
89 else
90 snap_maximize
91 fi
92 ;;
93 N)
94 snap_minimize
95 ;;
96 n)
97 if $window_maximized || $window_snapped_left || $window_snapped_right;then
98 snap_default
99 else
100 snap_minimize
101 fi
102 ;;
103 d)
104 snap_default
105 ;;
106 i)
107 print_info
108 ;;
109 \?)
110 echo "Invalid option: -$OPTARG" >&2
111 usage
112 exit 1
113 ;;
114 esac
115 done
116 }
117
118 function check_args
119 {
120 if [[ "$@" == "" ]];then
121 usage
122 fi
123 }
124
125 function usage
126 {
127 echo
128 echo "Usage: gosnap [-L | -l | -R | -r | -M | -m | -N | -n | -d | -i]"
129 echo " -L snap left"
130 echo " -l snap left dynamically"
131 echo " -R snap right"
132 echo " -r snap right dynamically"
133 echo " -M maximize window"
134 echo " -m toggle maximize window"
135 echo " -N minimize window"
136 echo " -n minimize window dynamically"
137 echo " -d resize to default geometry (current default: $window_default)"
138 echo " -i print window information"
139 echo
140 }
141
142 function check_command
143 {
144 if [[ ! -e $(which $1) ]];then
145 echo -e "\nERROR: $1 was not found on your system!\n"
146 # If we're on Ubuntu, use the c-n-f functionality
147 if [[ -x /usr/lib/command-not-found ]];then
148 python /usr/lib/command-not-found -- $1
149 elif [[ -x /usr/share/command-not-found ]];then
150 python /usr/share/command-not-found -- $1
151 fi
152 usage
153 exit 1
154 fi
155 }
156
157 function get_window_state
158 {
159 screen_width=$(xdpyinfo | grep 'dimensions:' | cut -f 2 -d ':' | cut -f 1 -d 'x' | xargs)
160 window_id=$(xdotool getactivewindow)
161 window_id_hex=$(printf 0x%x $window_id)
162 window_properties=$(xprop -id $window_id)
163 window_state=$(echo $window_properties | grep "_NET_WM_STATE(ATOM)")
164 window_geometry=$(xdotool getwindowgeometry $window_id)
165 window_pos_x=$(echo $window_geometry | awk '{print $4}' | awk -F, '{print $1}')
166 window_pos_y=$(echo $window_geometry | awk '{print $4}' | awk -F, '{print $2}')
167 window_width=$(echo $window_geometry | awk '{print $8}' | awk -Fx '{print $1}')
168 window_height=$(echo $window_geometry | awk '{print $8}' | awk -Fx '{print $2}')
169 window_max_horiz=false
170 if [ $(echo $window_state | grep -o "_NET_WM_STATE_MAXIMIZED_HORZ") ];then
171 window_max_horiz=true
172 fi
173 window_max_vert=false
174 if [ $(echo $window_state | grep -o "_NET_WM_STATE_MAXIMIZED_VERT") ];then
175 window_max_vert=true
176 fi
177 window_maximized=false
178 if $window_max_horiz && $window_max_vert;then
179 window_maximized=true
180 fi
181 window_snapped_left=false
182 if $window_max_vert && [ $window_pos_x == $left_origin_x ];then
183 window_snapped_left=true
184 fi
185 window_snapped_right=false
186 if $window_max_vert && [ $window_pos_x == $right_origin_x ];then
187 window_snapped_right=true
188 fi
189 }
190
191 function snap_maximize
192 {
193 wmctrl -r :ACTIVE: -b add,maximized_vert,maximized_horz
194 }
195
196 function snap_minimize
197 {
198 xdotool getactivewindow windowminimize
199 }
200
201 function snap_default
202 {
203 wmctrl -r :ACTIVE: -b remove,maximized_vert,maximized_horz
204 wmctrl -r :ACTIVE: -e $window_default
205 }
206
207 function snap_left
208 {
209 if $wmctrl_bug_fix; then snap_default && snap_maximize;fi
210 halfw=$((($screen_width/2)+$half_width_adjust_left))
211 wmctrl -r :ACTIVE: -b remove,maximized_vert,maximized_horz &&\
212 wmctrl -r :ACTIVE: -b add,maximized_vert &&\
213 wmctrl -r :ACTIVE: -e 0,$snap_left_x,$snap_left_y,$halfw,-1
214 }
215
216 function snap_right
217 {
218 if $wmctrl_bug_fix; then snap_default && snap_maximize;fi
219 halfw=$((($screen_width/2)+$half_width_adjust_right+$snap_right_x))
220 wmctrl -r :ACTIVE: -b remove,maximized_vert,maximized_horz &&\
221 wmctrl -r :ACTIVE: -b add,maximized_vert &&\
222 wmctrl -r :ACTIVE: -e 0,$halfw,$snap_right_y,$halfw,-1
223 }
224
225 function print_info
226 {
227 echo "screen_width = $screen_width (half = $(($screen_width/2)))"
228 echo "Window $window_id ($window_id_hex)"
229 echo "$(echo $window_geometry | tr -d '\n' | grep -o 'Position.*' | xargs)"
230 echo "window_max_horiz = $window_max_horiz"
231 echo "window_max_vert = $window_max_vert"
232 echo "window_maximized = $window_maximized"
233 echo "window_snapped_left = $window_snapped_left"
234 echo "window_snapped_right = $window_snapped_right"
235 exit
236 }
237
238 main $@
239
240 # OTHER NOTES
241 #
242 # xdotool windowmove $(xdotool getactivewindow) 0 0
243 # xdotool windowmove $(xdotool getactivewindow) 100% 100%
244 # xdotool windowsize $(xdotool getactivewindow) 50% 100%
245 # xdotool getwindowgeometry $(xdotool getactivewindow)
246 #
247 # No window shadow: add this line to /etc/environment then relog
248 # MUFFIN_NO_SHADOWS=1
249 #