Custom Window Snap

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

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