Difference between revisions of "Custom Window Snap"
| Line 56: | Line 56: | ||
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 <code>gosnap -n</code>, 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 <code>gosnap -N</code> instead. | 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 <code>gosnap -n</code>, 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 <code>gosnap -N</code> instead. | ||
| + | |||
| + | <b>Gosnap Script:</b> | ||
| + | <source lang="sh"> | ||
| + | #!/bin/bash | ||
| + | # | ||
| + | # GOSNAP: Good Old Snap | ||
| + | # | ||
| + | # Script to emulate the Aero Snap feature in Windows 7 | ||
| + | # Use -L or -R flags to snap left or right | ||
| + | # Use -l or -r flags to snap dynamically (intermediate default position) | ||
| + | # Use -M flag to maximize | ||
| + | # Use -m flag to toggle maximized state | ||
| + | # Use -N flag to minimize | ||
| + | # Use -n flag to minimize dynamically (intermediate default position) | ||
| + | # Use -d flag to reset the window state to default | ||
| + | # Use -i flag to print window info to stdout | ||
| + | # | ||
| + | # wmctrl -e g,x,y,w,h (any value set to -1 won't be changed) | ||
| + | # g = window gravity (just set to 0) | ||
| + | # x = window x position | ||
| + | # y = window y position | ||
| + | # w = window width | ||
| + | # h = window height | ||
| + | # | ||
| + | |||
| + | # CONFIGURATION | ||
| + | # ============= | ||
| + | |||
| + | # Default window geometry to use with -d | ||
| + | # Format: gravity, x_pos, y_pos, width, height) | ||
| + | window_default="0,10,60,826,556" | ||
| + | |||
| + | # The x position reported by xdotool when the window is snapped left/right. | ||
| + | # For some reason, snapping the window to 0,0 does not actually register as 0,0. | ||
| + | # Use the -i flag while the terminal window is snapped to the left or the right | ||
| + | # to get the values unique to your screen. | ||
| + | left_origin_x=10 | ||
| + | right_origin_x=692 | ||
| + | |||
| + | # Add this to the half screen width value for snapping left/right. | ||
| + | # If you see gaps on the left or right wien you snap, you may need to adjust this. | ||
| + | half_width_adjust_left=1 | ||
| + | half_width_adjust_right=1 | ||
| + | |||
| + | # Adjust these to avoid issues with gaps | ||
| + | snap_left_x=0 | ||
| + | snap_left_y=0 | ||
| + | snap_right_x=0 | ||
| + | snap_right_y=1 | ||
| + | |||
| + | # Use wmctrl bug avoidance | ||
| + | # For some reason, wmctrl intermittently snaps to the wrong position. | ||
| + | # Doing "snap_default && snap_maximize" before snapping left/right seems to fix it. | ||
| + | # The trade-off using this fix is that snapping left/right has a very slight delay. | ||
| + | wmctrl_bug_fix=true | ||
| + | |||
| + | function main | ||
| + | { | ||
| + | check_args $@ | ||
| + | check_command wmctrl | ||
| + | check_command xdotool | ||
| + | get_window_state | ||
| + | |||
| + | while getopts ":LlRrMmNndi" opt;do | ||
| + | case $opt in | ||
| + | L) | ||
| + | snap_left | ||
| + | ;; | ||
| + | l) | ||
| + | if $window_snapped_right;then | ||
| + | snap_default | ||
| + | else | ||
| + | snap_left | ||
| + | fi | ||
| + | ;; | ||
| + | R) | ||
| + | snap_right | ||
| + | ;; | ||
| + | r) | ||
| + | if $window_snapped_left;then | ||
| + | snap_default | ||
| + | else | ||
| + | snap_right | ||
| + | fi | ||
| + | ;; | ||
| + | M) | ||
| + | snap_maximize | ||
| + | ;; | ||
| + | m) | ||
| + | if $window_maximized;then | ||
| + | snap_default | ||
| + | else | ||
| + | snap_maximize | ||
| + | fi | ||
| + | ;; | ||
| + | N) | ||
| + | snap_minimize | ||
| + | ;; | ||
| + | n) | ||
| + | if $window_maximized || $window_snapped_left || $window_snapped_right;then | ||
| + | snap_default | ||
| + | else | ||
| + | snap_minimize | ||
| + | fi | ||
| + | ;; | ||
| + | d) | ||
| + | snap_default | ||
| + | ;; | ||
| + | i) | ||
| + | print_info | ||
| + | ;; | ||
| + | \?) | ||
| + | echo "Invalid option: -$OPTARG" >&2 | ||
| + | usage | ||
| + | exit 1 | ||
| + | ;; | ||
| + | esac | ||
| + | done | ||
| + | } | ||
| + | |||
| + | function check_args | ||
| + | { | ||
| + | if [[ "$@" == "" ]];then | ||
| + | usage | ||
| + | fi | ||
| + | } | ||
| + | |||
| + | function usage | ||
| + | { | ||
| + | echo | ||
| + | echo "Usage: gosnap [-L | -l | -R | -r | -M | -m | -N | -n | -d | -i]" | ||
| + | echo " -L snap left" | ||
| + | echo " -l snap left dynamically" | ||
| + | echo " -R snap right" | ||
| + | echo " -r snap right dynamically" | ||
| + | echo " -M maximize window" | ||
| + | echo " -m toggle maximize window" | ||
| + | echo " -N minimize window" | ||
| + | echo " -n minimize window dynamically" | ||
| + | echo " -d resize to default geometry (current default: $window_default" | ||
| + | echo " -i print window information" | ||
| + | echo | ||
| + | } | ||
| + | |||
| + | function check_command | ||
| + | { | ||
| + | if [[ ! -e $(which $1) ]];then | ||
| + | echo -e "\nERROR: $1 was not found on your system!\n" | ||
| + | # If we're on Ubuntu, use the c-n-f functionality | ||
| + | if [[ -x /usr/lib/command-not-found ]];then | ||
| + | python /usr/lib/command-not-found -- $1 | ||
| + | elif [[ -x /usr/share/command-not-found ]];then | ||
| + | python /usr/share/command-not-found -- $1 | ||
| + | fi | ||
| + | usage | ||
| + | exit 1 | ||
| + | fi | ||
| + | } | ||
| + | |||
| + | function get_window_state | ||
| + | { | ||
| + | screen_width=`xdpyinfo | grep 'dimensions:' | cut -f 2 -d ':' | cut -f 1 -d 'x' | xargs` | ||
| + | window_id=$(xdotool getactivewindow) | ||
| + | window_id_hex=$(printf 0x%x $window_id) | ||
| + | window_properties=$(xprop -id $window_id) | ||
| + | window_state=$(echo $window_properties | grep "_NET_WM_STATE(ATOM)") | ||
| + | window_geometry=$(xdotool getwindowgeometry $window_id) | ||
| + | window_pos_x=$(echo $window_geometry | awk '{print $4}' | awk -F, '{print $1}') | ||
| + | window_pos_y=$(echo $window_geometry | awk '{print $4}' | awk -F, '{print $2}') | ||
| + | window_width=$(echo $window_geometry | awk '{print $8}' | awk -Fx '{print $1}') | ||
| + | window_height=$(echo $window_geometry | awk '{print $8}' | awk -Fx '{print $2}') | ||
| + | window_max_horiz=false | ||
| + | if [ $(echo $window_state | grep -o "_NET_WM_STATE_MAXIMIZED_HORZ") ];then | ||
| + | window_max_horiz=true | ||
| + | fi | ||
| + | window_max_vert=false | ||
| + | if [ $(echo $window_state | grep -o "_NET_WM_STATE_MAXIMIZED_VERT") ];then | ||
| + | window_max_vert=true | ||
| + | fi | ||
| + | window_maximized=false | ||
| + | if $window_max_horiz && $window_max_vert;then | ||
| + | window_maximized=true | ||
| + | fi | ||
| + | window_snapped_left=false | ||
| + | if $window_max_vert && [ $window_pos_x == $left_origin_x ];then | ||
| + | window_snapped_left=true | ||
| + | fi | ||
| + | window_snapped_right=false | ||
| + | if $window_max_vert && [ $window_pos_x == $right_origin_x ];then | ||
| + | window_snapped_right=true | ||
| + | fi | ||
| + | } | ||
| + | |||
| + | function snap_maximize | ||
| + | { | ||
| + | wmctrl -r :ACTIVE: -b add,maximized_vert,maximized_horz | ||
| + | } | ||
| + | |||
| + | function snap_minimize | ||
| + | { | ||
| + | xdotool getactivewindow windowminimize | ||
| + | } | ||
| + | |||
| + | function snap_default | ||
| + | { | ||
| + | wmctrl -r :ACTIVE: -b remove,maximized_vert,maximized_horz | ||
| + | wmctrl -r :ACTIVE: -e $window_default | ||
| + | } | ||
| + | |||
| + | function snap_left | ||
| + | { | ||
| + | if $wmctrl_bug_fix; then snap_default && snap_maximize;fi | ||
| + | halfw=$((($screen_width/2)+$half_width_adjust_left)) | ||
| + | wmctrl -r :ACTIVE: -b remove,maximized_vert,maximized_horz &&\ | ||
| + | wmctrl -r :ACTIVE: -b add,maximized_vert &&\ | ||
| + | wmctrl -r :ACTIVE: -e 0,$snap_left_x,$snap_left_y,$halfw,-1 | ||
| + | } | ||
| + | |||
| + | function snap_right | ||
| + | { | ||
| + | if $wmctrl_bug_fix; then snap_default && snap_maximize;fi | ||
| + | halfw=$((($screen_width/2)+$half_width_adjust_right+$snap_right_x)) | ||
| + | wmctrl -r :ACTIVE: -b remove,maximized_vert,maximized_horz &&\ | ||
| + | wmctrl -r :ACTIVE: -b add,maximized_vert &&\ | ||
| + | wmctrl -r :ACTIVE: -e 0,$halfw,$snap_right_y,$halfw,-1 | ||
| + | } | ||
| + | |||
| + | function print_info | ||
| + | { | ||
| + | echo "screen_width = $screen_width (half = $(($screen_width/2)))" | ||
| + | echo "Window $window_id ($window_id_hex)" | ||
| + | echo "$(echo $window_geometry | tr -d '\n' | grep -o 'Position.*' | xargs)" | ||
| + | echo "window_max_horiz = $window_max_horiz" | ||
| + | echo "window_max_vert = $window_max_vert" | ||
| + | echo "window_maximized = $window_maximized" | ||
| + | echo "window_snapped_left = $window_snapped_left" | ||
| + | echo "window_snapped_right = $window_snapped_right" | ||
| + | exit | ||
| + | } | ||
| + | |||
| + | main $@ | ||
| + | |||
| + | # OTHER NOTES | ||
| + | # | ||
| + | # xdotool windowmove $(xdotool getactivewindow) 0 0 | ||
| + | # xdotool windowmove $(xdotool getactivewindow) 100% 100% | ||
| + | # xdotool windowsize $(xdotool getactivewindow) 50% 100% | ||
| + | # xdotool getwindowgeometry $(xdotool getactivewindow) | ||
| + | # | ||
| + | # No window shadow: add this line to /etc/environment then relog | ||
| + | # MUFFIN_NO_SHADOWS=1 | ||
| + | # | ||
| + | </source> | ||
Revision as of 19:19, 31 July 2020
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 cp 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:
#!/bin/bash
#
# GOSNAP: Good Old Snap
#
# Script to emulate the Aero Snap feature in Windows 7
# Use -L or -R flags to snap left or right
# Use -l or -r flags to snap dynamically (intermediate default position)
# Use -M flag to maximize
# Use -m flag to toggle maximized state
# Use -N flag to minimize
# Use -n flag to minimize dynamically (intermediate default position)
# Use -d flag to reset the window state to default
# Use -i flag to print window info to stdout
#
# wmctrl -e g,x,y,w,h (any value set to -1 won't be changed)
# g = window gravity (just set to 0)
# x = window x position
# y = window y position
# w = window width
# h = window height
#
# CONFIGURATION
# =============
# Default window geometry to use with -d
# Format: gravity, x_pos, y_pos, width, height)
window_default="0,10,60,826,556"
# The x position reported by xdotool when the window is snapped left/right.
# For some reason, snapping the window to 0,0 does not actually register as 0,0.
# Use the -i flag while the terminal window is snapped to the left or the right
# to get the values unique to your screen.
left_origin_x=10
right_origin_x=692
# Add this to the half screen width value for snapping left/right.
# If you see gaps on the left or right wien you snap, you may need to adjust this.
half_width_adjust_left=1
half_width_adjust_right=1
# Adjust these to avoid issues with gaps
snap_left_x=0
snap_left_y=0
snap_right_x=0
snap_right_y=1
# Use wmctrl bug avoidance
# For some reason, wmctrl intermittently snaps to the wrong position.
# Doing "snap_default && snap_maximize" before snapping left/right seems to fix it.
# The trade-off using this fix is that snapping left/right has a very slight delay.
wmctrl_bug_fix=true
function main
{
check_args $@
check_command wmctrl
check_command xdotool
get_window_state
while getopts ":LlRrMmNndi" opt;do
case $opt in
L)
snap_left
;;
l)
if $window_snapped_right;then
snap_default
else
snap_left
fi
;;
R)
snap_right
;;
r)
if $window_snapped_left;then
snap_default
else
snap_right
fi
;;
M)
snap_maximize
;;
m)
if $window_maximized;then
snap_default
else
snap_maximize
fi
;;
N)
snap_minimize
;;
n)
if $window_maximized || $window_snapped_left || $window_snapped_right;then
snap_default
else
snap_minimize
fi
;;
d)
snap_default
;;
i)
print_info
;;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
exit 1
;;
esac
done
}
function check_args
{
if [[ "$@" == "" ]];then
usage
fi
}
function usage
{
echo
echo "Usage: gosnap [-L | -l | -R | -r | -M | -m | -N | -n | -d | -i]"
echo " -L snap left"
echo " -l snap left dynamically"
echo " -R snap right"
echo " -r snap right dynamically"
echo " -M maximize window"
echo " -m toggle maximize window"
echo " -N minimize window"
echo " -n minimize window dynamically"
echo " -d resize to default geometry (current default: $window_default"
echo " -i print window information"
echo
}
function check_command
{
if [[ ! -e $(which $1) ]];then
echo -e "\nERROR: $1 was not found on your system!\n"
# If we're on Ubuntu, use the c-n-f functionality
if [[ -x /usr/lib/command-not-found ]];then
python /usr/lib/command-not-found -- $1
elif [[ -x /usr/share/command-not-found ]];then
python /usr/share/command-not-found -- $1
fi
usage
exit 1
fi
}
function get_window_state
{
screen_width=`xdpyinfo | grep 'dimensions:' | cut -f 2 -d ':' | cut -f 1 -d 'x' | xargs`
window_id=$(xdotool getactivewindow)
window_id_hex=$(printf 0x%x $window_id)
window_properties=$(xprop -id $window_id)
window_state=$(echo $window_properties | grep "_NET_WM_STATE(ATOM)")
window_geometry=$(xdotool getwindowgeometry $window_id)
window_pos_x=$(echo $window_geometry | awk '{print $4}' | awk -F, '{print $1}')
window_pos_y=$(echo $window_geometry | awk '{print $4}' | awk -F, '{print $2}')
window_width=$(echo $window_geometry | awk '{print $8}' | awk -Fx '{print $1}')
window_height=$(echo $window_geometry | awk '{print $8}' | awk -Fx '{print $2}')
window_max_horiz=false
if [ $(echo $window_state | grep -o "_NET_WM_STATE_MAXIMIZED_HORZ") ];then
window_max_horiz=true
fi
window_max_vert=false
if [ $(echo $window_state | grep -o "_NET_WM_STATE_MAXIMIZED_VERT") ];then
window_max_vert=true
fi
window_maximized=false
if $window_max_horiz && $window_max_vert;then
window_maximized=true
fi
window_snapped_left=false
if $window_max_vert && [ $window_pos_x == $left_origin_x ];then
window_snapped_left=true
fi
window_snapped_right=false
if $window_max_vert && [ $window_pos_x == $right_origin_x ];then
window_snapped_right=true
fi
}
function snap_maximize
{
wmctrl -r :ACTIVE: -b add,maximized_vert,maximized_horz
}
function snap_minimize
{
xdotool getactivewindow windowminimize
}
function snap_default
{
wmctrl -r :ACTIVE: -b remove,maximized_vert,maximized_horz
wmctrl -r :ACTIVE: -e $window_default
}
function snap_left
{
if $wmctrl_bug_fix; then snap_default && snap_maximize;fi
halfw=$((($screen_width/2)+$half_width_adjust_left))
wmctrl -r :ACTIVE: -b remove,maximized_vert,maximized_horz &&\
wmctrl -r :ACTIVE: -b add,maximized_vert &&\
wmctrl -r :ACTIVE: -e 0,$snap_left_x,$snap_left_y,$halfw,-1
}
function snap_right
{
if $wmctrl_bug_fix; then snap_default && snap_maximize;fi
halfw=$((($screen_width/2)+$half_width_adjust_right+$snap_right_x))
wmctrl -r :ACTIVE: -b remove,maximized_vert,maximized_horz &&\
wmctrl -r :ACTIVE: -b add,maximized_vert &&\
wmctrl -r :ACTIVE: -e 0,$halfw,$snap_right_y,$halfw,-1
}
function print_info
{
echo "screen_width = $screen_width (half = $(($screen_width/2)))"
echo "Window $window_id ($window_id_hex)"
echo "$(echo $window_geometry | tr -d '\n' | grep -o 'Position.*' | xargs)"
echo "window_max_horiz = $window_max_horiz"
echo "window_max_vert = $window_max_vert"
echo "window_maximized = $window_maximized"
echo "window_snapped_left = $window_snapped_left"
echo "window_snapped_right = $window_snapped_right"
exit
}
main $@
# OTHER NOTES
#
# xdotool windowmove $(xdotool getactivewindow) 0 0
# xdotool windowmove $(xdotool getactivewindow) 100% 100%
# xdotool windowsize $(xdotool getactivewindow) 50% 100%
# xdotool getwindowgeometry $(xdotool getactivewindow)
#
# No window shadow: add this line to /etc/environment then relog
# MUFFIN_NO_SHADOWS=1
#