Skip to content

Instantly share code, notes, and snippets.

@crysterbater
Created November 19, 2017 11:11
Show Gist options
  • Select an option

  • Save crysterbater/75148a8c999497dc1bd75f1d64fb2250 to your computer and use it in GitHub Desktop.

Select an option

Save crysterbater/75148a8c999497dc1bd75f1d64fb2250 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
#
# Automagically hide/show a window by its name when the cursor is
# within a defined region or you mouse over it.
#
# This script was initially written to imitate gnome-shell's systray
# but should be generic enough to do other things as well.
#
# Requirements:
# bash, xdotool, xwininfo
#
# Global variables used throughout the script
win_id=""
win_name=""
win_width=""
win_height=""
win_posX=""
win_posY=""
screen_width=""
screen_height=""
minX=""
minY=""
maxX=""
maxY=""
hover=1
signal=1
interval=1
peek=3
direction="left"
steps=3
no_trans=1
toggle=1
_is_hidden=1
_has_region=1
_affi_pid=""
usage() {
# Print usage
printf "usage: $0 [options]\n"
printf "\n"
printf "Required:\n"
printf " -N, --name [pattern]\n"
printf " The name of the window to hide.\n"
printf " This is the same string that is displayed in the window titlebar.\n"
printf "\n"
printf "Optional:\n"
printf " -r, --region [posXxposY+offsetX+offsetY]\n"
printf " Cursor region at which to trigger.\n"
printf " Examples:\n"
printf " --region 0x1080+10+-10 (Bottom left incl. a 10 pixel offset)\n"
printf " --region 1920x1080+0+0 (Bottom right without offset)\n"
printf "\n"
printf " -H, --hover\n"
printf " Show the window when hovering over it.\n"
printf " If --region was defined, --hover will be ignored!\n"
printf " This will only work if --peek is greater 0.\n"
printf " By default, hover is off.\n"
printf "\n"
printf " -S, --signal\n"
printf " Toggle the visibility by sending a 'SIGUSR1' signal.\n"
printf " Both --region and --hover will be ignored.\n"
printf "\n"
printf " -i, --interval [interval]\n"
printf " Interval in seconds to check the cursors location.\n"
printf " Defaults to 1.\n"
printf "\n"
printf " -p, --peek [amount]\n"
printf " When hidden, peek 'amount' of pixels to indicate the window.\n"
printf " Required if --hover is used."
printf " Defaults to 3.\n"
printf "\n"
printf " -d, --direction [left|right|top|bottom]\n"
printf " Direction in which to move the window.\n"
printf " Defaults to left.\n"
printf "\n"
printf " -s, --steps [amount]\n"
printf " Steps in pixel used to move the window. The higher the value,\n"
printf " the faster it will move at the cost of smoothness.\n"
printf " Defaults to 3.\n"
printf "\n"
printf " -T, --no-trans\n"
printf " Turn of the transition effect.\n"
printf "\n"
printf " -t, --toggle\n"
printf " Try to send a SIGUSR1 to the process running with the SAME NAME.\n"
printf " If the process can not be uniquely identified, do nothing.\n"
printf "\n\n"
printf "Examples:\n"
printf " Dropdown Terminal:\n"
printf " # Start a terminal with a unique name\n"
printf " # (Make sure yourself it is positioned correctly)\n"
printf " $ termite --title=dropdown-terminal &\n"
printf "\n"
printf " # Hide it and wait for a SIGUSR1 signal\n"
printf " $ hideIt.sh --name '^dropdown-terminal$' --direction top --steps 5 --signal\n"
printf "\n"
printf " # Send a SIGUSR1 signal (This could be mapped to a keyboard shortcut)\n"
printf " $ hideIt.sh --name '^dropdown-terminal$' --toggle\n"
}
argparse() {
# Parse system args
while [ $# -gt 0 ]; do
case $1 in
"-N"|"--name")
win_name="$2"
shift
;;
"-H"|"--hover")
hover=0
;;
"-S"|"--signal")
signal=0
;;
"-r"|"--region")
local posX posY offsetX offsetY
read posX posY offsetX offsetY <<<$(echo "$2" | \
sed -rn 's/^([0-9]+)x([0-9]+)\+(-?[0-9]+)\+(-?[0-9]+)/\1 \2 \3 \4/p')
# Test if we have proper values by trying
# to add them all together
expr $posX + $posY + $offsetX + $offsetY > /dev/null 2>&1
if [ $? -ne 0 ]; then
printf "Invalid region. See --help for usage.\n" 1>&2
exit 1
fi
minX=$posX
maxX=$((${minX} + ${offsetX}))
if [ $minX -gt $maxX ]; then
read minX maxX <<< "$maxX $minX"
fi
minY=$posY
maxY=$((${minY} + ${offsetY}))
if [ $minY -gt $maxY ]; then
read minY maxY <<< "$maxY $minY"
fi
if [[ ! $minX =~ [0-9]+ ]] || [[ ! $minY =~ [0-9]+ ]] \
|| [[ ! $maxY =~ [0-9]+ ]] || [[ ! $maxY =~ [0-9]+ ]]; then
printf "Missing or invalid region. See --help for usage.\n" 1>&2
exit 1
fi
_has_region=0
shift
;;
"-i"|"--interval")
interval="$2"
if [[ ! $interval =~ [0-9]+ ]]; then
printf "Interval should be a number. " 1>&2
exit 1
fi
shift
;;
"-p"|"--peek")
peek="$2"
if [[ ! $peek =~ [0-9]+ ]]; then
printf "Peek should be a number. " 1>&2
exit 1
fi
shift
;;
"-d"|"--direction")
direction="$2"
if [[ ! "$direction" =~ ^(left|right|top|bottom)$ ]]; then
printf "Invalid direction. See --help for usage.\n" 1>&2
exit 1
fi
shift
;;
"-s"|"--steps")
steps="$2"
if [[ ! $steps =~ [0-9]+ ]]; then
printf "Steps should be a number. " 1>&2
exit 1
fi
shift
;;
"-T"|"--no-trans")
no_trans=0
;;
"-t"|"--toggle")
toggle=0
;;
"-h"|"--help")
usage
exit 0
;;
**)
printf "Didn't understand '$1'\n" 1>&2
printf "See --help for usage.\n"
exit 1
;;
esac
shift
done
# Check required arguments
if [ -z "$win_name" ]; then
printf "Window name required. See --help for usage.\n" 1>&2
exit 1
fi
if [ $toggle -ne 0 ] && [ $signal -ne 0 ] && [ $_has_region -ne 0 ] \
&& [ $hover -ne 0 ]; then
printf "At least one of --toggle, --signal, --hover or" 1>&2
printf " --region is required!\n" 1>&2
exit 1
fi
}
function fetch_window_id() {
# Sets the values for the following global
# win_id
local windows=($(xdotool search --name "$win_name"))
if [ ${#windows[@]} -lt 1 ]; then
win_id=""
elif [ ${#windows[@]} -eq 1 ]; then
win_id=${windows[0]}
elif [ ${#windows[@]} -gt 1 ]; then
printf "Found more than one window matching the "
printf "pattern '$win_name'\n" 1>&2
printf "Using the first one!\n" 1>&2
win_id=${windows[0]}
fi
}
function fetch_screen_dimensions() {
# Sets the values for the following globals
# screen_width, screen_height
local win_info=$(xwininfo -root)
screen_width=$(echo "$win_info" | sed -rn 's/.*Width: +([0-9]+)/\1/p')
screen_height=$(echo "$win_info" | sed -rn 's/.*Height: +([0-9]+)/\1/p')
}
function fetch_window_dimensions() {
# Sets the values for the following globals unless no win_id exists
# win_width, win_height, win_posX, win_posY
if [[ ! $win_id =~ [0-9]+ ]]; then
return
fi
local win_info=$(xwininfo -id $win_id)
win_width=$(echo "$win_info" | sed -rn 's/.*Width: +([0-9]+)/\1/p')
win_height=$(echo "$win_info" | sed -rn 's/.*Height: +([0-9]+)/\1/p')
if [ ! -z "$1" ] && [ $1 -eq 0 ]; then
win_posX=$(echo "$win_info" | \
sed -rn 's/.*Absolute upper-left X: +(-?[0-9]+)/\1/p')
win_posY=$(echo "$win_info" | \
sed -rn 's/.*Absolute upper-left Y: +(-?[0-9]+)/\1/p')
fi
}
function fetch_affiliated_pid() {
# Sets the values for the following global
# _affi_pid
local _self=($$)
local _name=`basename "$0"`
local _escaped="$(printf "%q" $win_name)"
local pids=($(pgrep -f ".*${_name}.*${_escaped}.*"))
# Remove ourself from the list of pids
pids=(${pids[@]/$_self})
if [ ${#pids[@]} -eq 1 ]; then
_affi_pid=${pids[-1]}
else
printf "Couldn't uniquely identify pid\n" 1>&2
_affi_pid=""
fi
}
function hide_window() {
# Move the window in or out
# Args:
# hide: 0 to hide, 1 to show
local hide=$1
_is_hidden=$hide
# Update win_width, win_height in case they changed
fetch_window_dimensions
# Activate the window.
# Should bring it to the front, change workspace etc.
if [ $hide -ne 0 ]; then
xdotool windowactivate $win_id > /dev/null 2>&1
fi
# Generate the sequence used to move the window
local to=""
local sequence=""
if [ "$direction" == "left" ]; then
to=-$(($win_width - $peek))
if [ $hide -eq 0 ]; then
sequence=($(seq $win_posX -$steps $to))
sequence+=($to)
else
sequence=($(seq $to $steps $win_posX))
sequence+=($win_posX)
fi
elif [ "$direction" == "right" ]; then
to=$(($screen_width - $peek))
if [ $hide -eq 0 ]; then
sequence=($(seq $win_posX $steps $to))
sequence+=($to)
else
sequence=($(seq $to -$steps $win_posX))
sequence+=($win_posX)
fi
elif [ "$direction" == "bottom" ]; then
to=$(($screen_height - $peek))
if [ $hide -eq 0 ]; then
sequence=($(seq $win_posY $steps $to))
sequence+=($to)
else
sequence=($(seq $to -$steps $win_posY))
sequence+=($win_posY)
fi
elif [ "$direction" == "top" ]; then
to=-$(($win_height - $peek))
if [ $hide -eq 0 ]; then
sequence=($(seq $win_posY -$steps $to))
sequence+=($to)
else
sequence=($(seq $to $steps $win_posY))
sequence+=($win_posY)
fi
fi
# Actually move the window
if [ $no_trans -ne 0 ]; then
for pos in ${sequence[@]}; do
if [[ "$direction" =~ ^(left|right)$ ]]; then
xdotool windowmove $win_id $pos $win_posY
elif [[ "$direction" =~ ^(top|bottom)$ ]]; then
xdotool windowmove $win_id $win_posX $pos
fi
done
else
pos=${sequence[-1]}
if [[ "$direction" =~ ^(left|right)$ ]]; then
xdotool windowmove $win_id $pos $win_posY
elif [[ "$direction" =~ ^(top|bottom)$ ]]; then
xdotool windowmove $win_id $win_posX $pos
fi
fi
# In case we hid the window, try to give focus to whatever is
# underneath the cursor.
if [ $hide -eq 0 ]; then
eval $(xdotool getmouselocation --shell)
xdotool windowactivate $WINDOW > /dev/null 2>&1
fi
}
function serve() {
# Check the cursors location and act accordingly
local _hide=0
while true; do
# If signal-based, we just block to cause no cpu time and 'read'
# works well for that.
# If you the user sends a return, we just start over
if [ $signal -eq 0 ]; then
read
continue
fi
# Get cursor x, y position and active window
eval $(xdotool getmouselocation --shell)
if [ $_has_region -eq 0 ]; then
# Test if the cursor is within the region
if [ $X -ge $minX -a $X -le $maxX ] \
&& [ $Y -ge $minY -a $Y -le $maxY ]; then
_hide=1
else
_hide=0
fi
elif [ $hover -eq 0 ]; then
# Test if cursor hovers the window
if [ $WINDOW -eq $win_id ]; then
_hide=1
else
_hide=0
fi
fi
# Don't hide if the cursor is still above the window
if [ $_is_hidden -ne 0 ] \
&& [ $_hide -eq 0 ] \
&& [ $WINDOW -eq $win_id ]; then
_hide=1
fi
# Only do something if necessary
if [ $_is_hidden -ne $_hide ]; then
hide_window $_hide
fi
# Cut some slack
sleep $interval
done
}
function restore() {
# Called by trap once we receive an EXIT
if [ $_is_hidden -eq 0 ]; then
printf "Restoring original window position...\n"
hide_window 1
fi
exit 0
}
function toggle() {
# Called by trap once we receive a SIGUSR1
if [ $_is_hidden -eq 0 ]; then
hide_window 1
else
hide_window 0
fi
}
function main() {
# Entry point for hideIt
# Parse all the args!
argparse "$@"
printf "Searching window...\n"
fetch_window_id
if [[ ! $win_id =~ [0-9]+ ]]; then
printf "No window found!\n" 1>&2
exit 1
else
printf "Found window with id: $win_id\n"
fi
if [ $toggle -eq 0 ]; then
printf "Toggeling window...\n"
fetch_affiliated_pid
if [[ $_affi_pid =~ [0-9]+ ]]; then
kill -SIGUSR1 $_affi_pid
exit 0
else
exit 1
fi
fi
printf "Fetching window dimensions...\n"
fetch_window_dimensions 0
printf "Fetching screen dimensions...\n"
fetch_screen_dimensions
trap restore EXIT
trap toggle SIGUSR1
printf "Initially hiding window...\n"
hide_window 0
if [ $signal -eq 0 ]; then
printf "Waiting for SIGUSR1...\n"
elif [ $_has_region -eq 0 ]; then
printf "Defined region:\n"
printf " X: $minX $maxX\n"
printf " Y: $minY $maxY\n"
printf "\n"
printf "Waiting for region...\n"
elif [ $hover -eq 0 ]; then
printf "Waiting for hover...\n"
fi
# Start observing the cursor etc.
serve
}
# Lets do disss!
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment