#!/bin/bash
print_help() {
echo "
Used for hiding/unhiding programs to acheive quake-dropdown-like-functionality. Can create a dropdown window if one does not already exist and provides options to control the intial size and position, e.g. to leave panels visible. When used with a terminal, provides the option to specify the name of a tmuxinator or tmux session to automatically start or attach to.
Also supports the ability to auto-hide and auto-show. For example, this can be used to automatically hide a window when opening something, e.g. image viewer, video player, etc. from it, and then re-show the window whenever the image view, video player, etc. is closed.
Takes a window as an argument or one of auto_show, auto_hide, or toggle_auto_hide. 'toggle_auto_hide' toggles whether calling 'auto_hide' or 'auto_show' will have any effect. 'current' will turn the current window into a dropdown.
usage: tdrop [options] <program or 'current'> or one of <auto_show/auto_hide/toggle_auto_hide>
options:
-h height specify a height for a newly created term (default: 100%)
-w width specify a width for a newly created term (default: 45%)
-x pos specify x offset for a newly created term (default: 0)
-y pos specify y offset for a newly created term (default: 1, see BUGS in man)
-s name name for tmux or tmuxinator session (if not given, will not use tmux)
-n num num or extra text; only needed if want multiple dropdowns of same program (default: "")
-p cmd provide a pre-map command to float the window if necessary
-P cmd provide a post-map command to float the window if necessary
-M cmd provide a post-unmap command; can be used for example with a window manager that doesn't support floating to turn fullscreen on when mapping a terminal then off when unmapping it
-O cmd provide a one time command only for when a dropdown is created/initiated (useful for 'tdrop -c')
-d XxY give decoration/border size to accurately save position; only applicable with auto_show; on applicable with a few window managers (e.g. blackbox)
-a automatically detect window manager and if settings exist for it, automatically set -p, -P, and -d values as necessary; this can have affect when used with a terminal or with auto_show or auto_hide (default: false)
-W the given program is not a terminal (or lacks an -e flag) (default: assume it IS a terminal)
--clear clear saved window id; useful accidentally make a window a dropdown (e.g. '$ tdrop --clear current')
--no-cancel don't cancel auto-showing
(default is to prevent this when manually toggling a term after it is auto-hidden)
--help print help
See man page for more details.
"
if [[ $1 == illegal_opt ]]; then
exit 1
else
exit 0
fi
}
#
# Default Options
#
# xdotool can take percentages; cannot take decimal percentages though
width="100%"
height="45%"
xoff=0
yoff=1
map_pre=""
map_post=""
unmap_post=""
oneshot_post=""
session_name=""
num=""
normal_window=false
clearwid=false
cancel_auto_show=true
term=${*:0-1}
auto_detect_wm=false
while getopts :h:w:x:y:s:n:p:P:M:O:d:-:Wa opt
do
case $opt in
h) height=$OPTARG;;
w) width=$OPTARG;;
x) xoff=$OPTARG;;
y) yoff=$OPTARG;;
s) session_name=$OPTARG;;
n) num=$OPTARG;;
p) map_pre=$OPTARG;;
P) map_post=$OPTARG;;
M) unmap_post=$OPTARG;;
O) oneshot_post=$OPTARG;;
d) dec_fix=$OPTARG;;
W) normal_window=true;;
a) auto_detect_wm=true;;
-)
OPTION=$(echo "$OPTARG" | awk -F '=' '{print $1}')
OPTARG=$(echo "$OPTARG" | awk -F '=' '{print $2}')
case $OPTION in
height) height=$OPTARG;;
width) width=$OPTARG;;
x-offset) xoff=$OPTARG;;
y-offset) yoff=$OPTARG;;
session) session_name=$OPTARG;;
number) num=$OPTARG;;
pre-command) map_pre=$OPTARG;;
post-command) map_post=$OPTARG;;
post-unmap) unmap_post=$OPTARG;;
oneshot-post) oneshot_post=$OPTARG;;
decoration-fix) dec_fix=$OPTARG;;
normal-window) normal_window=true;;
auto-detect-wm) auto_detect_wm=true;;
clear) clearwid=true;;
no-cancel) cancel_auto_show=false;;
help) print_help;;
esac;;
*) print_help illegal_opt;;
esac
done
#
# WM Detection
#
wm_autodetect_settings() {
wm=$(xprop -notype -id "$(xprop -root -notype | awk '$1=="_NET_SUPPORTING_WM_CHECK:"{print $5}')" -f _NET_WM_NAME 8u | awk -F "\"" '/WM_NAME/ {print $2}')
# floating or position saving setup
if [[ $wm == bspwm ]]; then
map_pre() {
bspc rule -a "$1" -o floating=on
}
# tiling window manager with no support for floating; can toggle fullscreen instead for the dropdown
elif [[ $wm == herbstluftwm ]]; then
map_post() {
herbstclient fullscreen on
echo "yes" > /tmp/tdrop/herb
}
unmap_post() {
herbstclient fullscreen off
}
# floating window managers that may both move and resize a window after unmapping then mapping it
elif [[ $wm == Openbox ]]; then
# openbox will resize window to be slightly less than the width of the screen when mapping
# this is necessary when want width to be 100%
map_post() {
xdotool windowmove "$1" "$xoff" "$yoff" windowsize "$1" "$width" "$height"
}
# floating window managers that may both move a window after unmapping then mapping it
elif [[ $wm == pekwm ]] || [[ $wm == fluxbox ]] || [[ $wm == Blackbox ]]; then
map_post() {
xdotool windowmove "$1" "$xoff" "$yoff"
}
elif [[ $wm == i3 ]]; then
map_post() {
i3-msg floating enable && \
xdotool windowmove "$1" "$xoff" "$yoff" windowsize "$1" "$width" "$height"
}
# function to check if floating when auto-hiding so restores properly (prevent floating a previously tiled window)
is_floating() {
# do you even sed?
i3-msg -t get_tree | awk 'gsub(/{"id"/, "\n{\"id\"")' | awk '/focused":true.*floating":"user_on/ {print $1}'
}
fi
# for auto_show proper positioning
if [[ $wm == Blackbox ]]; then
dec_fix_auto="1x22"
fi
}
if $auto_detect_wm; then
wm_autodetect_settings
fi
#
# Helper Functions for Specific Dropdowns and Auto Hide/Show
#
get_class_name() {
xprop -id "$1" WM_CLASS | awk '{gsub(/"/, ""); print $4}'
}
map_pre_command() {
# a user set option has higher priority
if [[ -n $map_pre ]]; then
eval "$map_pre"
# use automatically set function if exists
elif [[ -n $(type map_pre) ]]; then
# needed when creating oneshot rules for programs where cmd differs from actual class name
if [[ $1 == "gnome-terminal" ]]; then
map_pre "Gnome-terminal"
else
map_pre "$1"
fi
fi
}
map_post_command() {
if [[ -n $oneshot_post ]] && [[ $1 == oneshot ]]; then
eval "$oneshot_post"
elif [[ -n $map_post ]]; then
eval "$map_post"
elif [[ -n $(type map_post) ]]; then
map_post "$1"
fi
}
unmap_post_command() {
if [[ -n $unmap_post ]]; then
eval "$unmap_post"
elif [[ -n $(type unmap_post) ]]; then
unmap_post
fi
}
maybe_cancel_auto_show() {
if $cancel_auto_show && [[ $1 == "$(< /tmp/tdrop/auto_hidden/wid)" ]]; then
> /tmp/tdrop/auto_hidden/wid
fi
}
#
# Dropdown Initialization
#
term_create() {
if [[ -n $session_name ]]; then
# ugly workarounds due to how different terms different -e flags work
if [[ $term == urxvt ]]; then
$term -e bash -c "sleep 0.01 && xdotool getactivewindow > /tmp/tdrop/$term$num && xdotool getactivewindow windowmove $xoff $yoff windowsize $width $height && tmux attach-session -dt $session_name || tmuxinator start $session_name || tmux new-session -s $session_name" &
else
# starting with '/bin/bash -c' because required by termite
$term -e "/bin/bash -c 'sleep 0.01 && xdotool getactivewindow > /tmp/tdrop/$term$num && xdotool getactivewindow windowmove $xoff $yoff windowsize $width $height && tmux attach-session -dt $session_name || tmuxinator start $session_name || tmux new-session -s $session_name'" &
fi
else
# not using hold, because flag is different for different terminals
if [[ $term == urxvt ]]; then
$term -e bash -c "sleep 0.01 && xdotool getactivewindow > /tmp/tdrop/$term$num && xdotool getactivewindow windowmove $xoff $yoff windowsize $width $height && $SHELL" &
else
# not using hold, because flag is different for different terminals
$term -e "/bin/bash -c 'sleep 0.01 && xdotool getactivewindow > /tmp/tdrop/$term$num && xdotool getactivewindow windowmove $xoff $yoff windowsize $width $height && $SHELL'" &
fi
fi
}
win_create() {
$term &
# need to wait for window to be created
sleep 0.5
# often pids have two wids; first one has always been the right one for what I've tested
wid=$(xdotool search --pid $! | head -n 1)
echo "$wid" > /tmp/tdrop/"$term$num"
xdotool windowmove "$wid" "$xoff" "$yoff" windowsize "$wid" "$width" "$height"
}
current_create() {
# turns active window into a dropdown
wid=$(xdotool getactivewindow)
echo "$wid" > /tmp/tdrop/current"$num"
get_class_name "$wid" > /tmp/tdrop/current"$num"_type
}
wid_toggle() {
mkdir -p /tmp/tdrop
# get saved window id if already created
wid=$(< /tmp/tdrop/"$term$num")
exists=true
if [[ -n $wid ]]; then
visibility=$(xwininfo -id "$wid" | awk '/Map State/ {print $3}')
if [[ -z $visibility ]]; then
# window no longer exists
exists=false
fi
else
exists=false
fi
if $exists && [[ $visibility != IsUnviewable ]]; then
if [[ $visibility == IsUnMapped ]]; then
if [[ $term == current ]]; then
map_pre_command "$(< /tmp/tdrop/current"$num"_type)"
else
map_pre_command "$term"
fi
xdotool windowmap "$wid"
map_post_command "$wid"
maybe_cancel_auto_show "$wid"
else
xdotool windowunmap "$wid"
fi
else
# make it
map_pre_command "$term"
if [[ $term == current ]]; then
current_create
elif $normal_window; then
win_create
else
term_create
fi
map_post_command oneshot
fi
}
#
# Helper Functions for Auto Hiding/Showing
#
get_geometry() {
# so that won't float a tiled window later when showing
if [[ -n $(type is_floating) ]] && [[ -z $(is_floating) ]]; then
# window is not floating; don't bother saving geometry
# this is going to be eval'd in set_geometry.. maybe not best/ most explicit way to do this but funny to me
echo "false"
else
wininfo=$(xwininfo -id "$1")
x=$(echo "$wininfo" | awk '/Absolute.*X/ {print $4}')
y=$(echo "$wininfo" | awk '/Absolute.*Y/ {print $4}')
rel_x=$(echo "$wininfo" | awk '/Relative.*X/ {print $4}')
rel_y=$(echo "$wininfo" | awk '/Relative.*Y/ {print $4}')
if [[ $x -ne $rel_x ]]; then
x=$((x-rel_x))
fi
if [[ $y -ne $rel_y ]]; then
y=$((y-rel_y))
fi
echo -e "X=$x\nY=$y"
fi
}
set_geometry() {
eval "$(< /tmp/tdrop/auto_hidden/geometry)"
if [[ -n $dec_fix ]]; then
x_fix=$(echo "$dec_fix" | awk -F "x" '{print $1}')
y_fix=$(echo "$dec_fix" | awk -F "x" '{print $2}')
X=$((X-x_fix))
Y=$((Y-y_fix))
elif [[ -n $dec_fix_auto ]]; then
x_fix=$(echo "$dec_fix_auto" | awk -F "x" '{print $1}')
y_fix=$(echo "$dec_fix_auto" | awk -F "x" '{print $2}')
X=$((X-x_fix))
Y=$((Y-y_fix))
fi
xdotool windowmove "$1" "$X" "$Y"
}
toggle_auto_hide() {
no_hide=$(< /tmp/tdrop/auto_hidden/no_hide)
mkdir -p /tmp/tdrop/auto_hidden
if [[ -z $no_hide ]]; then
echo "true" > /tmp/tdrop/auto_hidden/no_hide
else
> /tmp/tdrop/auto_hidden/no_hide
fi
}
#
# Auto Hiding/Showing
#
auto_hide() {
no_hide=$(< /tmp/tdrop/auto_hidden/no_hide)
if [[ -z $no_hide ]]; then
wid=$(xdotool getactivewindow)
mkdir -p /tmp/tdrop/auto_hidden
echo "$wid" > /tmp/tdrop/auto_hidden/wid
get_class_name "$wid" > /tmp/tdrop/auto_hidden/class
get_geometry "$wid" > /tmp/tdrop/auto_hidden/geometry
xdotool windowunmap "$wid"
fi
}
auto_show() {
no_hide=$(< /tmp/tdrop/auto_hidden/no_hide)
if [[ -z $no_hide ]]; then
wid=$(< /tmp/tdrop/auto_hidden/wid)
class=$(< /tmp/tdrop/auto_hidden/class)
was_floating=$(< /tmp/tdrop/auto_hidden/geometry)
if [[ $was_floating != false ]]; then
map_pre_command "$class"
fi
xdotool windowmap "$wid"
if [[ $was_floating != false ]]; then
map_post_command "$wid"
set_geometry "$wid"
fi
fi
}
#
# Main
#
if [[ -n $1 ]]; then
if $clearwid; then
> /tmp/tdrop/"$term$num"
elif [[ $term == toggle_auto_hide ]]; then
toggle_auto_hide
elif [[ $term == auto_hide ]]; then
auto_hide
elif [[ $term == auto_show ]]; then
auto_show
else
wid_toggle
fi
else
print_help illegal_opt
fi
# vim is dumb
# vim: set ft=sh: