#!/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] or one of 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=2 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 and Settings # get_window_manager() { # xfwm4 and fvwm at least will give two names (hence piping into head) 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}' | head -n 1 } # tilers w/ floating support: settings for floating a window when mapping it # can set up a rule for always floating a window or term type/class # alternatively, can use these settings to float just the dropdown # for both tdrop and tdrop wm_autoset_for_both() { wm=$(get_window_manager) # bspwm will use previous size when floating already if [[ $wm == bspwm ]]; then map_pre() { bspc rule -a "$1" -o floating=on } elif [[ $wm == awesome ]]; then # awesome remembers size, but need to float and then set size first map_post_oneshot() { # need sleep time to wait for window be created or will float wrong one.. not sure if way to float by window id sleep 0.1 && echo 'local awful = require("awful") ; awful.client.floating.set(c, true)' | awesome-client && \ xdotool windowmove "$1" "$xoff" "$yoff" windowsize "$1" "$width" "$height" } map_post() { echo 'local awful = require("awful") ; awful.client.floating.set(c, true)' | awesome-client } # tilers that won't remember sizing elif [[ $wm == i3 ]]; then map_post() { i3-msg floating enable && \ xdotool windowmove "$1" "$xoff" "$yoff" windowsize "$1" "$width" "$height" } fi } # settings for maintining previous size/placement of windows when unhiding them wm_autoset_for_hide_show() { # tilers: is_floating function so that hidden tiling windows don't become floating if [[ $wm == bspwm ]]; then is_floating() { # checking if the window id (converted from decimal to hex) is floating; empty if not floating bspc query -T | grep -i "$(printf 0x%x "$1").*f-" } elif [[ $wm == i3 ]]; then is_floating() { # do you even sed? i3-msg -t get_tree | awk 'gsub(/{"id"/, "\n{\"id\"")' | awk '/focused":true.*floating":"user_on/ {print $1}' } # settings for stacking/floating wms where can't get right position easily from xwininfo # take borders into account elif [[ $wm == Blackbox ]]; then dec_fix_auto="1x22" fi } wm_autoset_for_window() { wm=$(get_window_manager) # tilers without floating support: can toggle fullscreen instead for the dropdown if [[ $wm == herbstluftwm ]]; then map_post() { herbstclient fullscreen on } unmap_post() { herbstclient fullscreen off } # stacking/floating wms: settings for maintaining geometry after mapping # 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 move a window after unmapping then mapping it elif [[ $wm =~ ^(pekwm|fluxbox|Blackbox|xfwm4|Metacity|FVWM|Sawfish|GoomwW)$ ]]; then # most will center # xfwm4 will normally move to top left; metacity will move close to top left # sawfish just moves all over the place map_post() { xdotool windowmove "$1" "$xoff" "$yoff" } fi } # # Helper Functions for Specific Dropdowns and Auto Hide/Show # get_class_name() { xprop -id "$1" WM_CLASS | awk '{gsub(/"/, ""); print $4}' } get_visibility() { xwininfo -id "$1" | awk '/Map State/ {print $3}' } 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 $(type map_post_oneshot) ]] && [[ $1 == oneshot ]]; then map_post_oneshot "$2" 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 wids=$(xdotool search --pid $!) wid1=$(echo "$wids" | head -n 1) wid2=$(echo "$wids" | tail -n 1) if [[ -n $wid2 ]]; then if [[ $(get_visibility "$wid2") == IsUnMapped ]]; then wid=$wid1 else wid=$wid2 fi else wid=$wid1 fi # only works with pre-command xdotool windowmove "$wid" "$xoff" "$yoff" windowsize "$wid" "$width" "$height" echo "$wid" > /tmp/tdrop/"$term$num" } 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 echo "$wid" } 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=$(get_visibility "$wid") # sometimes xwininfo will still report a window as existing hence xprop check if [[ -z $visibility ]] || [[ -z $(xprop -id "$wid") ]]; then # window no longer exists exists=false fi else exists=false fi if $exists; 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 wid=$(current_create) xdotool windowunmap "$wid" else if $normal_window; then win_create else term_create fi # get saved wid of newly created dropdown wid="" count=0 while [[ -z $wid ]] && [[ $count -lt 100 ]]; do wid=$(< /tmp/tdrop/"$term$num") sleep 0.01 ((count++)) done map_post_command oneshot "$wid" fi 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 "$1") ]]; then # window is not floating; don't bother saving geometry 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 $auto_detect_wm; then wm_autoset_for_both if [[ $term == auto_show ]] || [[ $term == auto_hide ]]; then wm_autoset_for_hide_show else wm_autoset_for_window fi fi 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: