Custom Window Snap

From TheBeard Science Project Wiki
Revision as of 22:30, 31 July 2020 by Beard (talk | contribs) (Gosnap Script)

Jump to: navigation, search

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

Download

Installing

wget https://beardedmaker.com/wiki/files/gosnap
chmod a+x gosnap
sudo mv gosnap /usr/bin

Usage

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 information

If 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 -l Super+Left
  • Snap Right: gosnap -r Super+Right
  • Snap Maximize: gosnap -m Super+Up
  • Snap Minimize: gosnap -l Super+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 #