Difference between revisions of "Custom Window Snap"

From TheBeard Science Project Wiki
Jump to: navigation, search
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

Download

Installing:

wget https://beardedmaker.com/wiki/files/gosnap
chmod a+x gosnap
sudo cp 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:

#!/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
#