forked from jc00ke/move-to-next-monitor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mmqtiling.sh
executable file
·371 lines (327 loc) · 16 KB
/
mmqtiling.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
#!/bin/bash -eu
# Fork of https://github.com/jc00ke/move-to-next-monitor with substantial changes
# used to detect static panels with `wmctrl -l | grep -E $panel_regex` that need to be subtracted from the total usable screen area
panels_regex="xfce4-panel|some-future-panel-regex"
# check dependencies
if ! which xdotool > /dev/null || ! which wmctrl >/dev/null; then
echo "You need to install: xdotool, wmctrl"
echo "sudo apt install xdotool wmctrl"
exit 1
fi
movement_direction="${1:-left}"
hop_only="${2:-hop_only}"
echo "Usage: $0 left/right/top/bottom [use_tiling]"
## TODO validate input
# find geometry and location of active window
window_id=$(xdotool getactivewindow)
window_info=$(xwininfo -id "$window_id")
src_window_abs_left=$(echo "$window_info" | awk '/Absolute upper-left X:/ { print $4 }')
src_window_abs_top=$(echo "$window_info" | awk '/Absolute upper-left Y:/ { print $4 }')
src_window_off_left=$(echo "$window_info" | awk '/Relative upper-left X:/ { print $4 }')
src_window_off_top=$(echo "$window_info" | awk '/Relative upper-left Y:/ { print $4 }')
src_window_abs_left=$((src_window_abs_left - src_window_off_left))
src_window_abs_top=$((src_window_abs_top - src_window_off_top))
src_window_width=$(echo "$window_info" | awk '/Width:/ { print $2 }')
src_window_height=$(echo "$window_info" | awk '/Height:/ { print $2 }')
src_window_width=$((src_window_width + 2*src_window_off_left)) # account for the left and right border
src_window_height=$((src_window_height + src_window_off_top + src_window_off_left)) # account for the window decorations and bottom border
src_window_abs_right=$((src_window_abs_left + src_window_width))
src_window_abs_bottom=$((src_window_abs_top + src_window_height))
src_window_horz_maxed=$(xprop -id "$window_id" _NET_WM_STATE | grep -c '_NET_WM_STATE_MAXIMIZED_HORZ') || true # bool
src_window_vert_maxed=$(xprop -id "$window_id" _NET_WM_STATE | grep -c '_NET_WM_STATE_MAXIMIZED_VERT') || true # bool
echo "Current window geometry ${src_window_width}x${src_window_height}+${src_window_abs_left}+${src_window_abs_top} (h=$src_window_horz_maxed,v=$src_window_vert_maxed)"
# find the monitor the active window is on
xrandr_connected_monitors=$(xrandr | grep -i ' connected')
src_monitor_found=0
while read line; do # loop over monitors (see @done)
src_monitor_properties=( $(echo "$line" | sed "s/.* \([[:digit:]]\+x[[:digit:]]\++[[:digit:]]\++[[:digit:]]\+\) .*/\1/" | tr 'x' ' ' | tr '+' ' ') ) # we need the regex here because the number of words in the string can vary depending on if the monitor is the primary monitor
src_monitor_width=${src_monitor_properties[0]}
src_monitor_height=${src_monitor_properties[1]}
src_monitor_left=${src_monitor_properties[2]}
src_monitor_top=${src_monitor_properties[3]}
src_monitor_right=$((src_monitor_left + src_monitor_width))
src_monitor_bottom=$((src_monitor_top + src_monitor_height))
if [[ "$src_window_abs_left" -ge "$src_monitor_left" &&
"$src_window_abs_right" -le "$src_monitor_right" &&
"$src_window_abs_top" -ge "$src_monitor_top" &&
"$src_window_abs_bottom" -le "$src_monitor_bottom" ]]; then
src_monitor_found=1
break
fi
done <<< "$xrandr_connected_monitors"
if [[ "$src_monitor_found" -ne "1" ]]; then
echo "Couldn't find the monitor the active window is on!"
exit 1
fi
# correct the available space due to panels
panels=$(wmctrl -lG | grep -E "$panels_regex")
while read line; do # loop over panels (see @done)
panel_properties=( $line )
panel_left=${panel_properties[2]}
panel_top=${panel_properties[3]}
panel_width=${panel_properties[4]}
panel_height=${panel_properties[5]}
panel_right=$((panel_left + panel_width))
panel_bottom=$((panel_top + panel_height))
if [[ "$panel_left" -ge "$src_monitor_left" &&
"$panel_right" -le "$src_monitor_right" &&
"$panel_top" -ge "$src_monitor_top" &&
"$panel_bottom" -le "$src_monitor_bottom" ]]; then
if [[ "$panel_width" -ge "$panel_height" ]]; then
echo "Subtracting height of panel \"${panel_properties[7]}\" from source monitor."
src_monitor_height=$((src_monitor_height - panel_height))
if [[ "$panel_top" -eq "$src_monitor_top" ]]; then
src_monitor_top=$((src_monitor_top + panel_height))
elif [[ "$panel_bottom" -eq "$src_monitor_bottom" ]]; then
src_monitor_bottom=$((src_monitor_bottom - panel_height))
else
echo "Panel is neither on top nor bottom of monitor."
fi
else
echo "Subtracting width of panel \"${panel_properties[7]}\" from source monitor."
src_monitor_width=$((src_monitor_width - panel_width))
if [[ "$panel_left" -eq "$src_monitor_left" ]]; then
src_monitor_left=$((src_monitor_left + panel_width))
elif [[ "$panel_right" -eq "$src_monitor_right" ]]; then
src_monitor_right=$((src_monitor_right - panel_width))
echo "Panel is neither on left nor right of monitor."
fi
fi
fi
done <<< "$panels"
# now we can get the window geometry relative to the current monitor
src_window_rel_left=$((src_window_abs_left - src_monitor_left))
src_window_rel_top=$((src_window_abs_top - src_monitor_top))
src_window_rel_right=$((src_window_rel_left + src_window_width))
src_window_rel_bottom=$((src_window_rel_top + src_window_height))
echo "Geometry of selected source monitor: ${src_monitor_width}x${src_monitor_height}+${src_monitor_left}+${src_monitor_top}"
# find out tiling of the active window on the source monitor
src_monitor_half_width=$((src_monitor_width / 2))
src_monitor_half_height=$((src_monitor_height / 2))
echo "Window position on monitor: ${src_window_width}x${src_window_height}+${src_window_rel_left}+${src_window_rel_top} (center at: $src_monitor_half_width+$src_monitor_half_height)"
vert_slack=$((src_window_height / 4))
horz_slack=$((src_window_width / 8))
if [[ "$src_window_horz_maxed" -eq 1 && "$src_window_vert_maxed" -eq 1 ]]; then
src_window_tiling_state="maximized"
else
if [[ "$src_window_rel_top" -ge "$((src_monitor_half_height - vert_slack))" ]]; then
src_window_tiling_state_tb="bottom"
elif [[ "$src_window_rel_bottom" -le "$((src_monitor_half_height + vert_slack))" ]]; then
src_window_tiling_state_tb="top"
else
src_window_tiling_state_tb=""
fi
if [[ "$src_window_rel_left" -ge "$((src_monitor_half_width - horz_slack))" ]]; then
src_window_tiling_state_lr="right"
elif [[ "$src_window_rel_right" -le "$((src_monitor_half_width + horz_slack))" ]]; then
src_window_tiling_state_lr="left"
else
src_window_tiling_state_lr=""
fi
src_window_tiling_state="$src_window_tiling_state_tb$src_window_tiling_state_lr"
if [[ -z "$src_window_tiling_state" ]]; then
src_window_tiling_state="floating"
fi
fi
echo "Current window tiling: $src_window_tiling_state"
# decide on the target window tiling based on the intended movement
if [[ "$hop_only" == "hop_only" ]]; then
dst_window_tiling_state="$src_window_tiling_state"
hop_screen="true"
else
dst_window_tiling_state=""
hop_screen="false"
if [[ "$src_window_tiling_state" == "maximized" ]]; then
dst_window_tiling_state="$movement_direction"
elif [[ "$src_window_tiling_state" == "floating" ]]; then
if [[ "$movement_direction" == "left" || "$movement_direction" == "right" ]]; then
dst_window_tiling_state="floating"
hop_screen="true"
else
dst_window_tiling_state="$movement_direction"
fi
else # tiling state == top/bottom or left/right, or a combination of one of the two
case "$movement_direction" in
"top" )
if [[ "$src_window_tiling_state" == "bottom" ]]; then
dst_window_tiling_state="maximized"
elif [[ "$src_window_tiling_state" =~ "bottom" ]]; then
dst_window_tiling_state="${src_window_tiling_state/#bottom/}" # prefix!
else
dst_window_tiling_state="${src_window_tiling_state/#top/}top"
fi
;;
"bottom" )
if [[ "$src_window_tiling_state" == "top" ]]; then
dst_window_tiling_state="maximized"
elif [[ "$src_window_tiling_state" =~ "top" ]]; then
dst_window_tiling_state="${src_window_tiling_state/#top/}" # prefix!
else
dst_window_tiling_state="${src_window_tiling_state/#bottom/}bottom"
fi
;;
"left" )
if [[ "$src_window_tiling_state" =~ "left" ]]; then
dst_window_tiling_state="${src_window_tiling_state/%left/right}" # suffix!
hop_screen="true"
elif [[ "$src_window_tiling_state" == "right" ]]; then
dst_window_tiling_state="maximized"
elif [[ "$src_window_tiling_state" =~ "right" ]]; then
dst_window_tiling_state="${src_window_tiling_state/%right/}" # suffix!
else
dst_window_tiling_state="${src_window_tiling_state/%left/}left" # suffix!
fi
;;
"right" )
if [[ "$src_window_tiling_state" =~ right ]]; then
echo move
dst_window_tiling_state="${src_window_tiling_state/%right/left}" # suffix!
hop_screen="true"
elif [[ "$src_window_tiling_state" == "left" ]]; then
dst_window_tiling_state="maximized"
elif [[ "$src_window_tiling_state" =~ "left" ]]; then
dst_window_tiling_state="${src_window_tiling_state/%left/}" # suffix!
else
dst_window_tiling_state="${src_window_tiling_state/%right/}right" # suffix!
fi
;;
* )
echo "Unknown movement direction."
exit 1
esac
fi
fi
echo "Target window tiling: $dst_window_tiling_state"
if [[ -z "$dst_window_tiling_state" ]]; then # sanity check
echo "Erroneous tiling decision"
exit 1
fi
# find the monitor to the left/right of the monitor the window is on (similar as above)
if [[ "$hop_screen" == "true" ]]; then
echo "Moving to $movement_direction monitor."
dst_monitor_found=0
while read line; do # loop over monitors (see @done)
dst_monitor_properties=( $(echo "$line" | sed "s/.* \([[:digit:]]\+x[[:digit:]]\++[[:digit:]]\++[[:digit:]]\+\) .*/\1/" | tr 'x' ' ' | tr '+' ' ') ) # we need the regex here because the number of words in the string can vary depending on if the monitor is the primary monitor
dst_monitor_width=${dst_monitor_properties[0]}
dst_monitor_height=${dst_monitor_properties[1]}
dst_monitor_left=${dst_monitor_properties[2]}
dst_monitor_top=${dst_monitor_properties[3]}
dst_monitor_right=$((dst_monitor_left + dst_monitor_width))
dst_monitor_bottom=$((dst_monitor_top + dst_monitor_height))
if [[ "$movement_direction" == "right" ]] && [[ "$dst_monitor_left" -eq "$src_monitor_right" ]]; then
dst_monitor_found=1
break
elif [[ "$movement_direction" == "left" ]] && [[ "$dst_monitor_right" -eq "$src_monitor_left" ]]; then
dst_monitor_found=1
break
fi
done <<< "$xrandr_connected_monitors"
if [[ "$dst_monitor_found" -ne "1" ]]; then
echo "No monitors to the $movement_direction side of the source monitor."
exit 0
fi
# correct the available space due to panels
panels=$(wmctrl -lG | grep -E "$panels_regex")
while read line; do # loop over panels (see @done)
panel_properties=( $line )
panel_left=${panel_properties[2]}
panel_top=${panel_properties[3]}
panel_width=${panel_properties[4]}
panel_height=${panel_properties[5]}
panel_right=$((panel_left + panel_width))
panel_bottom=$((panel_top + panel_height))
if [[ "$panel_left" -ge "$dst_monitor_left" &&
"$panel_right" -le "$dst_monitor_right" &&
"$panel_top" -ge "$dst_monitor_top" &&
"$panel_bottom" -le "$dst_monitor_bottom" ]]; then
if [[ "$panel_width" -ge "$panel_height" ]]; then # horizontal panel
echo "Subtracting height of panel \"${panel_properties[7]}\" from destination monitor."
dst_monitor_height=$((dst_monitor_height - panel_height))
if [[ "$panel_top" -eq "$dst_monitor_top" ]]; then
dst_monitor_top=$((dst_monitor_top + panel_height))
elif [[ "$panel_bottom" -eq "$dst_monitor_bottom" ]]; then
dst_monitor_bottom=$((dst_monitor_bottom - panel_height))
else
echo "Panel is neither on top nor bottom of monitor."
fi
else # vertical panel
echo "Subtracting width of panel \"${panel_properties[7]}\" from destination monitor."
dst_monitor_width=$((dst_monitor_width - panel_width))
if [[ "$panel_left" -eq "$dst_monitor_left" ]]; then
dst_monitor_left=$((dst_monitor_left + panel_width))
elif [[ "$panel_right" -eq "$dst_monitor_right" ]]; then
dst_monitor_right=$((dst_monitor_right - panel_width))
echo "Panel is neither on left nor right of monitor."
fi
fi
fi
done <<< "$panels"
echo "Geometry of selected destination monitor: ${dst_monitor_width}x${dst_monitor_height}+${dst_monitor_left}+${dst_monitor_top}"
else
dst_monitor_width=$src_monitor_width
dst_monitor_height=$src_monitor_height
dst_monitor_left=$src_monitor_left
dst_monitor_top=$src_monitor_top
dst_monitor_right=$src_monitor_right
dst_monitor_bottom=$src_monitor_bottom
echo "Staying on the same monitor."
fi
# calculate the target position on the destination monitor
dst_monitor_half_width=$((dst_monitor_width / 2))
dst_monitor_half_height=$((dst_monitor_height / 2))
dst_window_rel_left="$src_window_rel_left"
dst_window_rel_top="$src_window_rel_top"
dst_window_width="$src_window_width"
dst_window_height="$src_window_height"
if [[ "$dst_window_tiling_state" =~ top ]]; then
dst_window_rel_top=0
dst_window_height="$dst_monitor_half_height"
elif [[ "$dst_window_tiling_state" =~ bottom ]]; then
dst_window_rel_top="$dst_monitor_half_height"
dst_window_height="$dst_monitor_half_height"
fi
if [[ "$dst_window_tiling_state" =~ left ]]; then
dst_window_rel_left=0
dst_window_width="$dst_monitor_half_width"
elif [[ "$dst_window_tiling_state" =~ right ]]; then
dst_window_rel_left="$dst_monitor_half_width"
dst_window_width="$dst_monitor_half_width"
fi
# monitor relative to X display relative
dst_window_abs_left=$((dst_window_rel_left + dst_monitor_left))
dst_window_abs_top=$((dst_window_rel_top + dst_monitor_top))
# disable maximized, so we can move the window
if [[ "$src_window_horz_maxed" -ne 0 || "$src_window_vert_maxed" -ne 0 ]]; then
wmctrl -i -r "$window_id" -b remove,maximized_horz,maximized_vert
fi
# before we can calculate the width and height we need to update the relative window offset which e.g. are different when maximized
if [[ "$src_window_tiling_state" == "maximized" && "$dst_window_tiling_state" != "maximized" ]]; then
window_info=$(xwininfo -id "$window_id")
src_window_off_left=$(echo "$window_info" | awk '/Relative upper-left X:/ { print $4 }')
src_window_off_top=$(echo "$window_info" | awk '/Relative upper-left Y:/ { print $4 }')
if [[ "$src_window_off_left" -eq 0 ]]; then
echo "This should not have happened: window offset is zero for non maximized window! Where is the border?"
echo "$window_info"
exit 1
fi
fi
dst_window_abs_width=$((dst_window_width - 2*src_window_off_left)) # account for left and right border
dst_window_abs_height=$((dst_window_height - src_window_off_top - src_window_off_left)) # account for window decorations and bottom border
echo "Move window to ${dst_window_width}x${dst_window_height}+${dst_window_abs_left}+${dst_window_abs_top} ($dst_window_rel_left+$dst_window_rel_top)"
# move and resize window
wmctrl -i -r "$window_id" -e "0,$dst_window_abs_left,$dst_window_abs_top,$dst_window_abs_width,$dst_window_abs_height"
# we need a little delay before setting the maximized states, otherwise the maximized states are sometimes not set
# TODO is there a better way?
sleep 0.1
# Maximize window again, if it was before
if [[ "$dst_window_tiling_state" == "maximized" ]]; then
echo "Maximize full"
wmctrl -i -r "$window_id" -b add,maximized_vert,maximized_horz
elif [[ "$dst_window_tiling_state" == "left" || "$dst_window_tiling_state" == "right" ]]; then
echo "Maximize vertically"
wmctrl -i -r "$window_id" -b add,maximized_vert
elif [[ "$dst_window_tiling_state" == "top" || "$dst_window_tiling_state" == "bottom" ]]; then
echo "Maximized horizontally"
wmctrl -i -r "$window_id" -b add,maximized_horz
fi
exit 0