├── .github └── workflows │ └── shellcheck.yml ├── Changelog.org ├── LICENSE ├── Makefile ├── README.org ├── tdrop └── tdrop.1 /.github/workflows/shellcheck.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | shellcheck: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: shellcheck 10 | uses: ludeeus/action-shellcheck@0.1.0 11 | -------------------------------------------------------------------------------- /Changelog.org: -------------------------------------------------------------------------------- 1 | * 0.5.0 2 | - Add tmuxp support for =-s= / =--session= 3 | - Add herbstluftwm =-a= floating and =-m= / =--monitor-aware= support 4 | - Allow unmanaging geometry by setting =-x=, =-y=, =-w=, and =-h= to empty strings 5 | - Add =-N= / =--no-manage= as a shorthand for setting all geometry settings to empty strings 6 | - Improve debug logging 7 | - Add =-r= / =--remember= flag to save geometry when hiding and restore when showing 8 | - Add ~foreach~ command 9 | - Fix support for floating point geometry percentages 10 | - Fix long time issue where tdrop could incorrectly grab the wrong window if it had been reassigned the window id of a previously closed dropdown 11 | - Fix Openbox support on empty desktops 12 | - Error if dependencies are not installed 13 | - Allow using tdrop with multiple open X sessions 14 | - Make other minor bug fixes 15 | 16 | * 0.4.0 17 | - Deprecate -f flag and allow specifying program arguments after the program 18 | - Fix -a for emacslient 19 | - Add hide_all command 20 | - Add --timeout flag 21 | - Give more specific error message for unknown flags 22 | - Add github actions shellcheck workflow 23 | - Silence irrelevant errors 24 | - Allow specifying empty values for -w, -h, -x, and -y to not alter the window size 25 | - Add --debug flag 26 | - Add workaround for using flatpack applications 27 | - Add support for firefox and brave 28 | - Add --monitor flag to specify monitor name to create dropdown on 29 | - Fix -s flag for termite, xterm, etc. 30 | - Fix for latest alacritty 31 | - Fix for latest i3 32 | - Add support for tabbed 33 | - Add -A / --activate flag to always activate/show dropdown if it is unfocused 34 | 35 | * 0.3.0 36 | - Significant performance improvements, especially with -m 37 | - Optionally detect current screen for -m based on pointer position 38 | - Discord support 39 | - Emacsclient support 40 | - Qutebsrowser support 41 | - Trinity Konsole support 42 | - Other minor fixes/improvements 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Fox Kiester 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX ?= /usr/local 2 | BINDIR = $(PREFIX)/bin 3 | MANDIR = $(PREFIX)/share/man 4 | LICENSEDIR = $(PREFIX)/share/licenses 5 | 6 | install: 7 | # 755 is default 8 | install -D -m 755 tdrop "$(DESTDIR)$(BINDIR)"/tdrop 9 | install -D -m 644 tdrop.1 "$(DESTDIR)$(MANDIR)"/man1/tdrop.1 10 | install -D -m 644 LICENSE "$(DESTDIR)$(LICENSEDIR)"/tdrop/LICENSE 11 | 12 | uninstall: 13 | rm -f "$(DESTDIR)$(BINDIR)"/tdrop 14 | rm -f "$(DESTDIR)$(MANDIR)"/man1/tdrop.1 15 | rm -rf "$(DESTDIR)$(LICENSEDIR)/tdrop" 16 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | [[https://github.com/noctuid/tdrop/actions?query=workflow%3Alint][https://github.com/noctuid/tdrop/workflows/lint/badge.svg]] 2 | 3 | * Demos 4 | This is a basic demo where tdrop is used to hide and show a terminal emulator on different desktops/workspaces: 5 | 6 | [[http://noctuid.github.io/tdrop/assets/termite.gif]] 7 | 8 | This is a basic demo where tdrop is used to turn the current window (emacs) into a dropdown and then hide and show it on different desktops/workspaces: 9 | 10 | [[http://noctuid.github.io/tdrop/assets/current.gif]] 11 | 12 | * Advantages Over Other Dropdown Terminals 13 | - Supports essentially any terminal or other program of your choice 14 | - Supports many window managers 15 | - Supports turning any window into a dropdown on the fly 16 | - Supports enforcing dropdown sizing and placement (e.g. to prevent panels from being hidden) 17 | - Supports tiled and floating dropdowns 18 | - Supports floating dropdown instances without requiring the user to create a rule to float all program instances (for some window managers that support both tiling and floating) 19 | - Supports using multiple dropdowns of the same program (see =-n=) 20 | - Supports automatically hiding a dropdown when opening a new window from it and then optionally re-showing the dropdown when the window is closed (this is somewhat comparable to "swallowing", e.g. in dwm) 21 | - Supports automatically starting programs and tmux, tmuxinator, tmuxifier or tmuxp sessions 22 | - Supports automatically resizing dropdowns based on the current monitor's size 23 | - Has hooks for executing user commands 24 | 25 | * Requirements 26 | - bash 27 | - basic utilities (probably already installed on linux) 28 | - GNU coreutils (for install, tee, cat, mkdir, head, etc.) 29 | - gawk 30 | - GNU grep 31 | - procps-ng (for pgrep) 32 | - xprop 33 | - xwininfo 34 | - xdotool 35 | 36 | Optional: 37 | - xrandr (required for -m) 38 | - tmux (required for -s) 39 | - tmuxinator (optional for -s) 40 | - tmuxifier (optional for -s) 41 | - tmuxp (optional for -s) 42 | 43 | * Installation and Usage 44 | ** Installation 45 | Tdrop is in the AUR as =tdrop-git= and is packaged for Void Linux as =tdrop=. It can also be installed by cloning this repo and running ~sudo make install~. One can then bind a key to it (e.g. with sxhkd). 46 | 47 | ** Basic Sxhkd Example 48 | #+BEGIN_EXAMPLE 49 | # negative -w arg to account for a border width (default: 100%) 50 | alt + s 51 | tdrop -ma -w -4 -y "$PANEL_HEIGHT" -s dropdown kitty 52 | #+END_EXAMPLE 53 | 54 | The positional argument should be the name of a program in =$PATH= (as opposed to the full path to a program). It should always come after any tdrop flags. Flags for the program can come after it (e.g. ~tdrop urxvt -title foo~). 55 | 56 | ** Basic Flags 57 | For a full list of flags and documentation, see the manpage. 58 | 59 | =-w= / =--width=, =-h= / =--height=, =-x= / =--xoff=, and =-y= / =--yoff= can be used to set the window size/position. The argument to any of these can be a number (e.g. =-w 800=) or a percentage (e.g. =-w 100%= or =-w 33.3%=) or empty (e.g. =-w "" -h ""= to prevent altering the window's size). Negative numbers correspond to that many pixels less than 100% of the screen width (e.g. =-w -4=). To unmanage all geometry settings, you can specify =-N= / =--no-manage=. This is the same as =-w "" -h "" -x "" -y ""= and should not be used with any other geometry flags. 60 | 61 | If you want to be able to resize the dropdown after creating it, you can use the =-r= / =--remember= flag to store/restore the dropdown geometry when hiding/showing. 62 | 63 | By default, tdrop will hide the dropdown if it is shown anywhere. =-A= / =--activate= can be used to always activate/show the dropdown if it is not focused. 64 | 65 | =-s= / =--session= should only be used for supported terminals and if the user wants to start/attach to a tmux, tmuxinator, tmuxifier or tmxup session. Note that you do not need to use =-s= to start tmux. You can always pass arguments/flags to the program that tdrop runs, e.g. =tdrop kitty tmux=. 66 | 67 | Tdrop has basic checks to print errors for malformed commands (e.g. to require one positional argument). If a tdrop command does not work, please run it in a terminal or check =/tmp/tdrop/log= for error messages and consult the manpage before making an issue. For more information, see the [[#troubleshooting][Troubleshooting]] section. 68 | 69 | *** Changes 70 | Long options can now be used with whitespace instead of requiring a ~=~ (i.e. =--long-opt value= and ~--long-opt=value~ are both fine). 71 | 72 | Old users please note that =-W|--normal-window=, =-z|--sleep-terminal=, and =-Z|--sleep-window= are no longer necessary and have been removed. Similarly, the old hook flags (=-p=, =-P=, =-M=, and =-O= as well as =--create-hook= and =--map-hook=) have been replaced with more specific and useful versions. 73 | 74 | ** Automatic Window Manager Detection (=-a=) 75 | =-a= / =--auto-detect-wm= can be specified to automatically set certain options (=-l=, =-L=, =-d=, and/or =-i=) based on the current window manager. These flags (whether automatically or manually set) may be necessary for tdrop to behave correctly (e.g. they are required for =-w=, =-h=, =-x=, and =-y= to work correctly for tiling window managers with floating support). See the manpage for further details about these flags. 76 | 77 | Note that if you've used [[https://tools.suckless.org/x/wmname][wmname]] to change your window manager's name, tdrop will use any settings for that name instead. In this case, you will generally need to specify the real name of your window manager using the =--wm= flag in order for =-a= to work correctly (e.g. if you are using bspwm and have run =wmname LG3D=, you will need to specify =--wm bspwm=). 78 | 79 | ** Monitor Awareness (=-m=) 80 | =-m= / =--monitor-aware= can be specified to automatically resize the dropdown based on the current monitor's size when percentages are used for =-w= and/or =-h=. This may be helpful for users of multiple monitors who don't want dropdowns spanning across monitors. 81 | 82 | This is particularly useful when using a percentage or negative value with =-w=, =-h=, =-x=, and/or =-y=. For example, =-w -4= normally corresponds to a width 4 pixels less than 100% of the screen width (potentially the combined width of multiple monitors). With =-m=, the pixel values are calculated using the dimensions of the current monitor alone. Negative values may be useful when the window manager (possibly due to window decorations) causes a dropdown with =-w 100%= to go over the edge of the screen. The =-m= option will also automatically resize and/or reposition the dropdown when opening it on a different monitor when one or more of the geometry arguments are negative or percentages. 83 | 84 | Some window managers allow querying what the current monitor is or directly for its geometry (e.g. bspwm, i3, and herbstluftwm), but for other window managers, tdrop determines the current monitor based on the position of the active window. For these window managers, if the desktop is empty, tdrop must wait for the dropdown to be created or mapped before getting the monitor info. This may cause a slight delay before the dropdown is properly resized. If =-m= does not work at all or there is a specific way to query for the current monitor in your window manager, please make an issue. 85 | 86 | See the manpage for more information. 87 | 88 | ** Wayland support 89 | Tdrop does not support programs that use Wayland directly, but it does work under Wayland if the program uses XWayland. If your program defaults to using Wayland, you can generally force it to use XWayland by setting the environment variable =WAYLAND_DISPLAY=no=. 90 | 91 | Also note that certain tdrop features don't work under Wayland due to limitations of =xdotool=. For instance, =-m= / =--monitor-aware= only works when combined with =-t= / =--pointer-monitor-detection=. 92 | 93 | Example: 94 | #+begin_example 95 | WAYLAND_DISPLAY=no tdrop -mta alacritty 96 | #+end_example 97 | 98 | In general, I do not recommend using tdrop in Wayland. The following projects emulate the main features of =tdrop=: 99 | 100 | | Project | Window Manager | 101 | | [[https://github.com/Schweber/hdrop][hdrop]] | [[https://github.com/hyprwm/Hyprland][Hyprland]] | 102 | | [[https://github.com/Schweber/ndrop][ndrop]] | [[https://github.com/YaLTeR/niri][niri]] | 103 | 104 | For hyprland, pyrprland also has [[https://hyprland-community.github.io/pyprland/scratchpads.html][scratchpad functionality]]. Hyprland's special workspaces can also achieve the main functionality of tdrop without any external scripts: 105 | 106 | #+begin_example 107 | bind = $hotkey, t, togglespecialworkspace, wezterm 108 | 109 | # automatically create the window when opening the workspace 110 | # works well with close_special_on_empty = true (default) 111 | workspace = special:wezterm, on-created-empty:wezterm connect --workspace dropdown unix 112 | 113 | # optional: float and control size and position if you don't want it to take up the full space 114 | windowrule = float, onworkspace:name:special:wezterm, class:^org.wezfurlong.wezterm$ 115 | windowrule = move 0.9% 5%, onworkspace:name:special:wezterm, class:^org.wezfurlong.wezterm$ 116 | windowrule = size 98.2% 45%, onworkspace:name:special:wezterm, class:^org.wezfurlong.wezterm$ 117 | 118 | # open new windows outside the special workspace; useful for e.g. opening apps from yazi 119 | windowrule = workspace +0, onworkspace:name:special:wezterm 120 | #+end_example 121 | 122 | Hyprland has swallow builtin, which could be used to replace tdrop's auto hide functionality, but I prefer to just call a script to run ~hyprctl dispatch workspace +0~ to hide special workspaces when opening new windows (with =hide_special_on_workspace_change= true). 123 | 124 | ** Flatpak 125 | As [[https://www.flatpak.org/][Flatpak]] jails applications, the PID cannot be used to find the attached window. A class name has to be given in order to find it, with =--class=. 126 | 127 | As Flatpak is considered by tdrop as the program to run, tdrop cannot differentiate 2 different flatpak applications. Use the =-n= option for this purpose. 128 | 129 | Example: 130 | #+begin_example 131 | tdrop -ma -n signal --class=signal flatpak run org.signal.Signal 132 | tdrop -ma -n firefox --class=firefox flatpak run org.mozilla.firefox 133 | #+end_example 134 | 135 | ** Flicker 136 | For some window managers that require a window to be repositioned after re-mapping it, some flicker may be noticeable. This flicker has been mostly fixed for some window managers (e.g. in the Gnome Shell and Cinnamon DEs) and improved for others. It is usually worse on tiling managers where the window must be re-floated every time it is mapped. The way around this is to use rules to either always have the class or name (see =--name=) floated or one-time rules to only float the next instance of a class. For example, since bspwm has oneshot rules and generally doesn't alter the size/position of a window, there isn't any movement flicker. 137 | 138 | A more consistent workaround to improve visual flickering regardless of the window manager is to enable fade-in for the compositor. For picom this can be done by setting =fading = true;= and adjusting the =fade-delta= in the =~/.config/picom.conf= accordingly. 139 | 140 | ** Hooks 141 | Tdrop provides hook flags that the user can specify to run commands at various stages during execution. These commands can make use of any global, internal tdrop variable, such as =$width=, =$height=, =$xoff=, =$yoff=, =$class=, and =$wid= (to prevent evaluation of these variables, the user can specify the hook command in single quotes). For example, to set a dropdown as always on top, the user could specify =-P 'wmctrl -i -r $wid -b add,above'=. 142 | 143 | Note that for =--pre-map-hook= and =--pre-map-float-command=, the window id is not guarunteed to be known (since the window may not have yet been created), so any script that makes use of these flags should first check if =$wid= is defined. The window id will not be defined for =--pre-create-hook= (even for =current=; I can change this if there is a use case for it). 144 | 145 | *** Pre Create 146 | =-c= / =--pre-create-hook= 147 | 148 | *Program* The command will run once before the program is started. 149 | 150 | *Current* The command will run once before unmapping the current window. 151 | 152 | *Hide and Show* No effect. 153 | 154 | *** Post Create 155 | =-C= / =--post-create-hook= 156 | 157 | *Program* The command will run once after the program is started and its window is active. 158 | 159 | *Current* The command will run once after unmapping the current window. 160 | 161 | *Hide and Show* No effect. 162 | 163 | *** Pre Map 164 | =-p= / =--pre-map-hook= 165 | 166 | *Program* / *Current* / *Show* The command will run before creating the window and before subsequently mapping the window. 167 | 168 | *Hide* No effect. 169 | 170 | *** Post Map 171 | =-P= / =--post-map-hook= 172 | 173 | *Program* / *Current* / *Show* The command will run after creating the window and after subsequently mapping the window. Note that unlike the pre-map hook, this will always run when showing the window, even if it was not previously unmapped (e.g. it is just being activated if =-A= is specified, or it is just being moved from another desktop). If you need different behavior (e.g. you need newly added distinct =--(pre|post)-show-hook= flags), please comment on [[https://github.com/noctuid/tdrop/issues/354][this issue]]. 174 | 175 | *Hide* No effect. 176 | 177 | *** Pre Unmap 178 | =-u= / =--pre-unmap-hook= 179 | 180 | *Program* / *Current* / *Hide* The command will run before unmapping the window. 181 | 182 | *Show* No effect. 183 | 184 | *** Post Unmap 185 | =-U= / =--post-unmap-hook= 186 | 187 | *Program* / *Current* / *Hide* The command will run after unmapping the window. 188 | 189 | *Show* No effect. 190 | 191 | *** Pre Float 192 | =-l= / =--pre-map-float-command= 193 | 194 | A command specifically meant to float the window. Note that if you specify this, it will override any defaults from =-a=. 195 | 196 | *Program* / *Current* The command will run before mapping the window. 197 | 198 | *Hide* No effect. 199 | 200 | *Show* The command will run before mapping the window only if it was previously floating. 201 | 202 | *** Post Float 203 | =-L= / =--post-map-float-command= 204 | 205 | A command specifically meant to float the window. Note that if you specify this, it will override any defaults from =-a=. 206 | 207 | *Program* / *Current* The command will run after mapping the window. 208 | 209 | *Hide* No effect. 210 | 211 | *Show* The command will run after mapping the window only if it was previously floating. 212 | 213 | ** Auto-hiding 214 | In addition to creating dropdowns, tdrop can automatically hide a window and later un-hide it. For example, if gvim is opened to write a git commit message from the terminal, tdrop can automatically hide the terminal (dropdown or not) and restore it after the user is finished writing the commit message: 215 | 216 | #+BEGIN_EXAMPLE 217 | hide_on_open() { 218 | tdrop -a auto_hide && "$@" && tdrop -a auto_show 219 | } 220 | alias gc='hide_on_open git commit' 221 | #+END_EXAMPLE 222 | 223 | The most useful application of this functionality is probably when opening videos, images, etc. in an external program from a file manager like ranger. For example, in the =rifle.conf=: 224 | 225 | #+BEGIN_EXAMPLE 226 | mime ^video, has mpv, X, flag f = tdrop -a auto_hide && mpv -- "$@" && tdrop -a auto_show 227 | #+END_EXAMPLE 228 | 229 | ** Other Commands 230 | If =hide_all= is given instead of a program name, tdrop will hide all visible dropdowns. 231 | 232 | If =foreach= is specified, tdrop will evaluate the following command for each dropdown. Here are some example commands: 233 | #+begin_src sh 234 | # same as hide_all 235 | tdrop foreach 'unmap $wid' 236 | 237 | # hide only floating dropdowns on herbstluftwm 238 | tdrop foreach 'herbstclient compare clients.$(printf 0x%x $wid).floating = on && unmap $wid' 239 | #+end_src 240 | 241 | * Tested With 242 | ** Terminals 243 | These terminals have been tested with tdrop and support the =-s= and =-a= flags unless otherwise specified: 244 | 245 | - Alacritty 246 | - cool-retro-term 247 | - GNOME terminal (GNOME, Unity, Cinnamon, etc.) 248 | - [[https://github.com/kovidgoyal/kitty][kitty]] 249 | - Konsole (KDE) 250 | - LilyTerm (requires =confirm_to_execute_command 0= in config for =-s= or =-f '-e...'=) 251 | - LXTerminal (LXDE) 252 | - MATE terminal (MATE) 253 | - QTerminal (LXDE) 254 | - Roxterm 255 | - Sakura 256 | - Terminology (Enlightenment) 257 | - Termite 258 | - Tilix (previously terminix) 259 | - tinyterm/minyterm 260 | - URxvt (including urxvtd) 261 | - Wezterm 262 | - Xfce4-terminal (XFCE) 263 | - xiate 264 | - XTerm 265 | 266 | If your terminal doesn't work with tdrop, feel free to make an issue. Please follow the steps in the [[#troubleshooting][Troubleshooting]] section. 267 | 268 | ** Other Programs 269 | - Chrome/chromium 270 | - Firefox 271 | - Brave 272 | - Emacs and emacsclient 273 | - Discord 274 | - Tabbed 275 | - Todoist 276 | - Postman 277 | - Spotify 278 | - Clementine and Strawberry 279 | - etc. 280 | 281 | ** Window Managers 282 | The primary goal of tdrop is to "just work" with any window manager. The primary differences between how tdrop deals with different window managers is the strategy it takes for floating only the dropdown (as opposed to all instances of the class that the dropdown is). There are three types of window managers as far as tdrop is concerned: 283 | 284 | *** Tiling without Floating Support 285 | If your window manager does not support floating, there's nothing to worry about. Binding a key to =tdrop terminal= should work. Options for resizing and movement that work only with floating window managers are not supported. One can, however, add post-map and post-unmap commands to do something like change the layout to fullscreen when showing a dropdown then revert the layout when hiding the dropdown. Previously (=-a=) would automatically do this for herbstluftwm, but it now supports floating. 286 | 287 | *** Floating/Stacking 288 | For floating window managers, tdrop should also generally "just work", but you may need to add the =-a= option for auto-showing to correctly restore the previous geometry. 289 | 290 | That said, these are the floating window managers that currently have been tested: 291 | - mutter (gnome shell) 292 | - muffin (cinnamon) 293 | - xfwm4 (xfce) 294 | - metacity (gnome 2) 295 | - marco (mate) 296 | - kwin (kde) 297 | - openbox (lxde) 298 | - compiz (unity) 299 | - pekwm 300 | - fluxbox 301 | - blackbox 302 | - fvwm 303 | - sawfish 304 | - goomwwm 305 | 306 | If your dropdown moves out of place when being shown, make an issue, and I will add settings for it. 307 | 308 | *** Tiling with Floating Support 309 | These window managers currently will work with =-a= for a floating (instead of tiled) dropdown: 310 | - bspwm (support for versions prior to 0.9.1 was dropped on 2016/09/22) 311 | - herbstluftwm (v0.8.0 or higher) 312 | - i3 313 | - awesome 314 | 315 | Awesome support may be buggy; if you encounter problems, please report them. 316 | 317 | * Why Not Use wmctrl? 318 | Necessary features don't work on many window managers, including mine. 319 | 320 | * Why Not Use wmutils? 321 | Maybe in the future. The only advantage I can see over xdotool is that it can toggle mapping (=mapw -t=), but this wouldn't be used in this script anyway since different code is executed depending on whether or not the window is mapped or unmapped. Also the command names are somewhat cryptic. 322 | 323 | * Similar 324 | - [[https://github.com/lharding/lsh-bin/blob/master/drawer][drawer]] 325 | - [[https://github.com/Schweber/hdrop/tree/main][hdrop]] 326 | 327 | * Troubleshooting 328 | :PROPERTIES: 329 | :CUSTOM_ID: troubleshooting 330 | :END: 331 | 332 | You can specify the =--debug= flag to have tdrop print more verbose debugging output. Tdrop will automatically also save this output in =/tmp/tdrop_"$USER"_"$DISPLAY"/log=. If tdrop does not appear at all, or there is some error, you can add the =--debug= flag and examine the log file or run the tdrop command in a terminal to see if the issue is obvious (e.g. tdrop will error if you do not have the required dependencies installed). 333 | 334 | ** Tdrop does not work with some terminal/program 335 | Please make an issue. Including the following information would help resolve the problem more quickly. 336 | 337 | Basic: 338 | - The incorrect behavior: Does the window appear at all? Is the problem that it is not floated correctly in a supported wm? Or is it a feature request for =-a= support? 339 | - Whether things work as expected with a basic =tdrop = (no flags) or whether the issue occurs with a specific flag (probably =-s=) 340 | 341 | Additional helpful information: 342 | - If the problem only occurs with the =-s= flag, the issue is likely due to the fact that not all terminals have compatible =-e= flags. It would be helpful if information on how the terminal's flag for executing a command works. Is it something other than =-e=? Are quotations required or incorrect ("-e 'command -flags ...'" vs "-e command flags")? 343 | - If the issue is with the dropdown behavior (e.g. tdrop keeps opening new windows for the program), does the program share a PID across all instances (e.g. open several windows and provide the output of =pgrep -l =)? Does the program have a daemon and client? 344 | -------------------------------------------------------------------------------- /tdrop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | MUTDROP_PATH=/tmp/tdrop_"$USER"_"$DISPLAY" 4 | LOG_FILE="$MUTDROP_PATH"/log 5 | NOAUTOHIDE_FILE="$MUTDROP_PATH"/no_autohide 6 | GEO_DIR="$MUTDROP_PATH"/geometries 7 | WID_DIR="$MUTDROP_PATH"/wids 8 | CLASS_DIR="$MUTDROP_PATH"/classes 9 | HIDE_DIR="$MUTDROP_PATH"/auto_hidden 10 | # shellcheck disable=SC2174 11 | mkdir -m 700 -p "$MUTDROP_PATH"/{auto_hidden,classes,geometries,wids} 12 | 13 | FLOATING_WMS_REGEXP='Openbox|pekwm|Fluxbox|Blackbox|xfwm4|Metacity|FVWM|Sawfish|GoomwW|Mutter|GNOME Shell|Mutter \(Muffin\)|KWin|Metacity \(Marco\)|[Cc]ompiz' 14 | GEO_REGEXP='^xoff=-?[0-9]+ 15 | yoff=-?[0-9]+ 16 | width=?[0-9]+ 17 | height=?[0-9]+$' 18 | 19 | print_help() { 20 | echo " 21 | usage: tdrop [options] [program options ...] 22 | or 'current' 23 | or one of 'auto_show'/'auto_hide'/'toggle_auto_hide' 24 | or 'hide_all' 25 | or 'foreach' 26 | options: 27 | -h height specify a height for a newly created term (default: 45%) 28 | -w width specify a width for a newly created term (default: 100%) 29 | -x pos specify x offset for a newly created term (default: 0) 30 | -y pos specify y offset for a newly created term (default: 1, see man) 31 | -s name name for tmux/tmuxinator/tmuxifier/tmuxp session (supported 32 | terminal required) 33 | -n num num or extra text; only needed if for the purpose of using 34 | multiple dropdowns of same program 35 | -c cmd provide a pre-create command 36 | -C cmd provide a post-create command 37 | -l cmd provide a command to float the window before it is mapped 38 | -L cmd provide a command to float the window after it is mapped 39 | -p cmd provide a pre-map command 40 | -P cmd provide a post-map command 41 | -u cmd provide a pre-unmap command 42 | -U cmd provide a post-unmap command 43 | -d XxY give decoration/border size to accurately restore window 44 | position; only applicable with auto_show 45 | -S can be used to fix saved geometry with auto_hide; see manpage 46 | -i cmd provide a command to detect whether the current window is a 47 | floating window; on applicable with auto_hide 48 | -f flags specify flags/options to be used when creating the term or 49 | window (e.g. -f '--title mytitle'; default: none). 50 | NOTE: This flag is deprecated. Specify flags after the program name 51 | instead. This flag may be removed in the future. 52 | Caution: if there is a tmux session specified (with -s), the option 53 | to execute a program (usually -e for terminal programs) is 54 | implicitly added by tdrop 55 | -a automatically detect window manager and set relevant options 56 | (e.g. this makes specifying -l/-L, -d, and -i unnecessary 57 | for supported WMs) (default: false) 58 | -m for use with multiple monitors and only with dropdowns 59 | (i.e. not for auto_show or auto_hide); convert percentages used 60 | for width or height to values relative to the size of the 61 | current monitor and force resizing of the dropdown when 62 | the monitor changes (default: false) 63 | -t use mouse pointer location for detecting which monitor is the current 64 | one 65 | -A always show/activate the window if it is not focused 66 | -r save geometry when hiding, restore geometry when showing 67 | -N same as -x '' -y '' -w '' -h '' (do not use with those options) 68 | --wm set the window manager name to mimic another window manager 69 | (for use with -a) 70 | --class name manually specify the class of the window (can be obtained with xprop) 71 | --name name set a new name for the dropdown window 72 | --clear clear saved window id; useful after accidentally make a 73 | window a dropdown (e.g. '$ tdrop --clear current') 74 | --no-cancel don't cancel auto-showing (default is to prevent this when 75 | manually toggling a window after it is auto-hidden) 76 | --timeout set the timeout (in seconds) that tdrop will wait for a window 77 | to appear before giving up in case the program fails to start 78 | (default: 10) 79 | --debug print debugging information to /tmp/tdrop_/log 80 | --help print help 81 | 82 | See man page for more options and details. 83 | " 84 | } 85 | 86 | first_message=true 87 | msg() { 88 | if $first_message; then 89 | echo "--------------------------------------------------" >> "$LOG_FILE" 90 | first_message=false 91 | fi 92 | echo "$(date "+%F %T") $*" > >(tee -a "$LOG_FILE" >&2) 93 | } 94 | 95 | error() { 96 | msg "Error: $*" 97 | exit 1 98 | } 99 | 100 | debug=false 101 | debug() { 102 | if $debug; then 103 | msg "Debug $*" 104 | fi 105 | } 106 | 107 | debug "command: tdrop $*" 108 | 109 | # * Default Options and Option Parsing 110 | # xdotool can take percentages; cannot take decimal percentages though 111 | width="100%" 112 | height="45%" 113 | xoff=0 114 | yoff=2 115 | session_name= 116 | num= 117 | pre_create= 118 | post_create= 119 | pre_float= 120 | post_float= 121 | pre_map= 122 | post_map= 123 | pre_unmap= 124 | post_unmap= 125 | dec_fix= 126 | # NOTE: 127 | # pekwm, xfwm4, sawfish, openbox need subtract_when_same to be true 128 | # for awesome, fluxbox, blackbox, mutter, fvwm, and metacity, the value 129 | # does not matter 130 | # set in decoration_settings 131 | subtract_when_same= 132 | is_floating= 133 | program_flags=() 134 | clearwid=false 135 | cancel_auto_show=true 136 | auto_detect_wm=false 137 | always_activate=false 138 | monitor_aware=false 139 | monitor= 140 | pointer_monitor_detection=false 141 | wm= 142 | wm_wid= 143 | user_set_wm=false 144 | class= 145 | name= 146 | timeout=10 147 | remember_geometry=false 148 | while getopts :h:w:x:y:s:n:c:C:l:L:p:P:u:U:d:S:i:f:-:aAmNtr opt 149 | do 150 | case $opt in 151 | h) height=$OPTARG;; 152 | w) width=$OPTARG;; 153 | x) xoff=$OPTARG;; 154 | y) yoff=$OPTARG;; 155 | s) session_name=$OPTARG;; 156 | n) num=$OPTARG;; 157 | c) pre_create=$OPTARG;; 158 | C) post_create=$OPTARG;; 159 | l) pre_float=$OPTARG;; 160 | L) post_float=$OPTARG;; 161 | p) pre_map=$OPTARG;; 162 | P) post_map=$OPTARG;; 163 | u) pre_unmap=$OPTARG;; 164 | U) post_unmap=$OPTARG;; 165 | d) dec_fix=$OPTARG;; 166 | S) subtract_when_same=false;; 167 | i) is_floating=$OPTARG;; 168 | f) eval "program_flags=($OPTARG)";; 169 | a) auto_detect_wm=true;; 170 | A) always_activate=true;; 171 | m) monitor_aware=true;; 172 | t) pointer_monitor_detection=true;; 173 | r) remember_geometry=true;; 174 | N) xoff= 175 | yoff= 176 | width= 177 | height=;; 178 | -) 179 | if [[ $OPTARG =~ ^(auto-detect-wm|monitor-aware|pointer-monitor-detection|clear|no-cancel|debug|remember|no-manage|help)$ ]] || \ 180 | [[ $OPTARG == *=* ]]; then 181 | OPTION=${OPTARG%%=*} 182 | OPTARG=${OPTARG#*=} 183 | else 184 | OPTION=$OPTARG 185 | # shellcheck disable=SC2124 186 | OPTARG=${@:$OPTIND:1} 187 | ((OPTIND++)) 188 | fi 189 | case $OPTION in 190 | height) height=$OPTARG;; 191 | width) width=$OPTARG;; 192 | x-offset) xoff=$OPTARG;; 193 | y-offset) yoff=$OPTARG;; 194 | session) session_name=$OPTARG;; 195 | number) num=$OPTARG;; 196 | pre-create-hook) pre_create=$OPTARG;; 197 | post-create-hook) post_create=$OPTARG;; 198 | pre-map-float-command) pre_float=$OPTARG;; 199 | post-map-float-command) post_float=$OPTARG;; 200 | pre-map-hook) pre_map=$OPTARG;; 201 | post-map-hook) post_map=$OPTARG;; 202 | pre-unmap-hook) pre_unmap=$OPTARG;; 203 | post-unmap-hook) post_unmap=$OPTARG;; 204 | decoration-fix) dec_fix=$OPTARG;; 205 | no-subtract-when-same) subtract_when_same=false;; 206 | is-floating) is_floating=$OPTARG;; 207 | program-flags) eval "program_flags=($OPTARG)";; 208 | auto-detect-wm) auto_detect_wm=true;; 209 | activate) always_activate=true;; 210 | monitor-aware) monitor_aware=true;; 211 | pointer-monitor-detection) pointer_monitor_detection=true;; 212 | monitor) monitor=$OPTARG;; 213 | wm) wm=$OPTARG 214 | user_set_wm=true;; 215 | class) class=$OPTARG;; 216 | name) name=$OPTARG;; 217 | clear) clearwid=true;; 218 | no-cancel) cancel_auto_show=false;; 219 | timeout) timeout=$OPTARG;; 220 | debug) debug=true;; 221 | remember) remember_geometry=true;; 222 | no-manage) xoff= 223 | yoff= 224 | width= 225 | height=;; 226 | help) print_help; exit;; 227 | *) error "Unknown option --$OPTION." \ 228 | "Use --help to see available flags.";; 229 | esac;; 230 | *) error "Unknown option -$OPTARG." \ 231 | "Use --help to see available flags.";; 232 | esac 233 | done 234 | shift "$((OPTIND-1))" 235 | program=$1 236 | 237 | if [[ ${#program_flags[@]} -eq 0 ]]; then 238 | program_flags=("${@:2}") 239 | fi 240 | 241 | if [[ -z $program ]]; then 242 | error "Program to run is required as a positional argument." \ 243 | "For help use --help or see the manpage." 244 | fi 245 | 246 | # check that the program is in PATH 247 | if [[ ! $program =~ ^(current|auto_hide|auto_show|toggle_auto_hide|hide_all|foreach)$ ]] && \ 248 | ! hash "$program" 2> /dev/null; then 249 | error "The program ($program) should be in PATH." 250 | fi 251 | 252 | # validate options that require number values 253 | if [[ ! $height$width$xoff$yoff =~ ^[0-9%-]*$ ]]; then 254 | error "The -h, -w, -x, and -y values must be numbers (or percentages)." 255 | fi 256 | if [[ -n $dec_fix ]] && [[ ! $dec_fix =~ ^-?[0-9]+x-?[0-9]+$ ]]; then 257 | error "The decoration fix value must have form 'num'x'num'." \ 258 | "The numbers can be negative or zero." 259 | fi 260 | 261 | # check that dependencies are installed 262 | if ! hash xprop xwininfo xdotool; then 263 | error "Xprop, xwininfo, and xdotool must all be installed." 264 | fi 265 | 266 | if ! hash gawk; then 267 | error "Gawk must be installed." 268 | fi 269 | 270 | if ! hash pgrep; then 271 | error "Pgrep must be installed." 272 | fi 273 | 274 | if [[ -n $monitor_aware ]] && ! hash xrandr 2> /dev/null; then 275 | error "Xrandr must be installed to use -m / --monitor-aware." 276 | fi 277 | 278 | if [[ -n $session_name ]] && ! hash tmux 2> /dev/null; then 279 | error "Tmux must be installed to use -s / --session." 280 | fi 281 | 282 | # non-user-settable global vars 283 | wid= 284 | 285 | # * Multiple Monitor Automatic Re-Sizing 286 | percent_of_total() { # percent total 287 | # use gawk to allow floating point percentages 288 | gawk "BEGIN {printf(\"%.0f\", 0.01*${1%\%}*$2)}" 289 | # echo $((${1%\%} * ${2} / 100)) 290 | } 291 | 292 | # acts on globals 293 | convert_geometry_to_pixels() { 294 | total_width=$1 295 | total_height=$2 296 | local minus_width minus_height minus_xoff minus_yoff 297 | if [[ $width =~ %$ ]]; then 298 | width=$(percent_of_total "$width" "$total_width") 299 | elif [[ $width =~ ^- ]]; then 300 | minus_width=${width#-} 301 | width=$((total_width-minus_width)) 302 | fi 303 | if [[ $height =~ %$ ]]; then 304 | height=$(percent_of_total "$height" "$total_height") 305 | elif [[ $height =~ ^- ]]; then 306 | minus_height=${height#-} 307 | height=$((total_height-minus_height)) 308 | fi 309 | if [[ $xoff =~ %$ ]]; then 310 | xoff=$(percent_of_total "$xoff" "$total_width") 311 | elif [[ $xoff =~ ^- ]]; then 312 | minus_xoff=${xoff#-} 313 | xoff=$((total_width-minus_xoff)) 314 | fi 315 | if [[ $yoff =~ %$ ]]; then 316 | yoff=$(percent_of_total "$yoff" "$total_height") 317 | elif [[ $yoff =~ ^- ]]; then 318 | minus_yoff=${yoff#-} 319 | yoff=$((total_height-minus_yoff)) 320 | fi 321 | } 322 | 323 | # meant to set variables for calling function given geometry: 324 | # - x_begin 325 | # - y_begin 326 | # - x_width 327 | # - x_height 328 | split_geometry() { # 329 | monitor_geo=$1 330 | # x_begin=$(echo "$monitor_geo" | gawk -F '+' '{print $2}') 331 | x_begin=${monitor_geo#*+} 332 | x_begin=${x_begin%+*} 333 | # y_begin=$(echo "$monitor_geo" | gawk -F '+' '{print $3}') 334 | y_begin=${monitor_geo##*+} 335 | # x_width=$(echo "$monitor_geo" | gawk -F 'x' '{print $1}') 336 | x_width=${monitor_geo%x*} 337 | # y_height=$(echo "$monitor_geo" | gawk -F 'x|+' '{print $2}') 338 | y_height=${monitor_geo#*x} 339 | y_height=${y_height%%+*} 340 | } 341 | 342 | # sets these variables for the calling function to use: 343 | # - x_begin 344 | # - y_begin 345 | # - x_width 346 | # - y_height 347 | populate_current_monitor_geometry() { 348 | # it is conceivable that a user may want to use -m but not -a, so 349 | # get the wm from within this function 350 | 351 | # get current monitor 352 | local current_monitor 353 | if [[ -n $monitor ]]; then 354 | current_monitor=$monitor 355 | elif [[ $wm == bspwm ]]; then 356 | current_monitor=$(bspc query --names --monitors --monitor) 357 | elif [[ $wm == i3 ]]; then 358 | # TODO use jq if installed 359 | # I'd rather not make jq a dependency 360 | current_monitor=$(i3-msg -t get_workspaces | sed 's/"num"/\n/g' | \ 361 | gawk -F ',' '/focused":true/ {sub(".*output",""); gsub("[:\"]",""); print $1}') 362 | elif [[ $wm != herbstluftwm ]]; then 363 | local current_x current_y monitors_info x_end y_end 364 | if ! $pointer_monitor_detection; then 365 | # determine current monitor using active window 366 | local wid wininfo 367 | wid=$(get_active_wid_or_empty) 368 | if [[ -z $wid ]]; then 369 | # will try again after remapping or creating the dropdown 370 | return 1 371 | fi 372 | 373 | wininfo=$(xwininfo -id "$wid") 374 | current_x=$(echo "$wininfo" | gawk '/Absolute.*X/ {print $4}') 375 | current_y=$(echo "$wininfo" | gawk '/Absolute.*Y/ {print $4}') 376 | else 377 | # shellcheck disable=SC2034 378 | local X Y SCREEN WINDOW 379 | # determine current monitor using pointer location 380 | eval "$(xdotool getmouselocation --shell)" 381 | current_x=X 382 | current_y=Y 383 | fi 384 | monitors_info=$(xrandr --current | gawk '/ connected/ {gsub("primary ",""); print}') 385 | while read -r monitor; do 386 | monitor_geo=$(echo "$monitor" | gawk '{print $3}') 387 | if [[ $monitor_geo =~ ^[0-9]+x[0-9]+\+[0-9]+\+[0-9]+$ ]]; then 388 | split_geometry "$monitor_geo" 389 | x_end=$((x_begin+x_width)) 390 | y_end=$((y_begin+y_height)) 391 | if [[ $current_x -ge $x_begin ]] && [[ $current_x -lt $x_end ]] && \ 392 | [[ $current_y -ge $y_begin ]] && [[ $current_y -lt $y_end ]]; then 393 | # current_monitor=$(echo "$monitor" | gawk '{print $1}') 394 | current_monitor=${monitor%% *} 395 | break 396 | fi 397 | fi 398 | done <<< "$monitors_info" 399 | fi 400 | 401 | local monitor_geo 402 | if [[ $wm == herbstluftwm ]]; then 403 | # fine if no monitor; will use current 404 | monitor_geo=$(herbstclient monitor_rect "$monitor") 405 | # not using read because behavior depends on bash version 406 | x_begin=${monitor_geo%% *} 407 | y_begin=${monitor_geo#* } 408 | y_begin=${y_begin%% *} 409 | x_width=${monitor_geo#* } 410 | x_width=${x_width#* } 411 | x_width=${x_width%% *} 412 | y_height=${monitor_geo##* } 413 | else 414 | monitor_geo=$(xrandr --current | \ 415 | gawk "/^$current_monitor/ {gsub(\"primary \",\"\"); print \$3}") 416 | split_geometry "$monitor_geo" 417 | fi 418 | } 419 | 420 | update_geometry_settings_for_monitor() { 421 | # 1. Correctly interpret width/height percentages when there exist multiple 422 | # monitors so an initially created dropdown is the correct size (xdotool 423 | # would create a dropdown the width of all screens for 100% width) 424 | # 2. Force resize the dropdown to the correct percentage of the current 425 | # monitor IF the monitor has changed since the last time the dropdown 426 | # was used 427 | 428 | local x_begin y_begin x_width y_height 429 | populate_current_monitor_geometry 430 | 431 | # convert w/h/x/y percentages/negatives to pixels 432 | convert_geometry_to_pixels "$x_width" "$y_height" 433 | 434 | # update x and y offsets, so that will appear on correct screen 435 | # (required for some WMs apparently, but not for others) 436 | [[ -n $xoff ]] && ((xoff+=x_begin)) 437 | [[ -n $yoff ]] && ((yoff+=y_begin)) 438 | } 439 | 440 | map_and_reset_geometry() { 441 | if [[ -n $width ]] && [[ -n $height ]] && [[ -n $xoff ]] \ 442 | && [[ -n $yoff ]]; then 443 | xdotool windowmap --sync "$wid" windowmove "$wid" "$xoff" "$yoff" \ 444 | windowsize "$wid" "$width" "$height" 2> /dev/null 445 | elif [[ -n $width ]] && [[ -n $height ]]; then 446 | xdotool windowmap --sync "$wid" windowsize "$wid" "$width" "$height" \ 447 | 2> /dev/null 448 | elif [[ -n $xoff ]] && [[ -n $yoff ]]; then 449 | xdotool windowmap --sync "$wid" windowmove "$wid" "$xoff" "$yoff" \ 450 | 2> /dev/null 451 | else 452 | xdotool windowmap --sync "$wid" 2> /dev/null 453 | fi 454 | # windowmap does not activate the window for all window managers; 455 | # windowactivate should be run separately after windowmap --sync or it can 456 | # activate a window on another desktop on some window managers (e.g. bspwm) 457 | xdotool windowactivate "$wid" 2> /dev/null 458 | } 459 | 460 | # * WM Detection and Hooks 461 | set_wm() { 462 | wm_wid=$(xprop -root -notype _NET_SUPPORTING_WM_CHECK) 463 | wm_wid=${wm_wid##* } 464 | if ! $user_set_wm && $auto_detect_wm; then 465 | # xfwm4 and fvwm at least will give two names (hence piping into head) 466 | wm=$(xprop -notype -id "$wm_wid" _NET_WM_NAME | head -n 1) 467 | wm=${wm##* } 468 | wm=${wm//\"/} 469 | debug "window manager: $wm" 470 | fi 471 | } 472 | 473 | decoration_settings() { 474 | if [[ -z $subtract_when_same ]]; then 475 | if $auto_detect_wm \ 476 | && [[ $wm =~ ^(Mutter|GNOME Shell|bspwm|i3|GoomwW)$ ]]; then 477 | subtract_when_same=false 478 | else 479 | subtract_when_same=true 480 | fi 481 | fi 482 | 483 | if [[ -z $dec_fix ]] && $auto_detect_wm; then 484 | # settings for stacking/floating wms where can't get right position 485 | # easily from xwininfo; take borders into account 486 | if [[ $wm == Blackbox ]]; then 487 | dec_fix="1x22" 488 | elif [[ $wm =~ ^(Mutter|GNOME Shell)$ ]]; then 489 | dec_fix="-10x-8" 490 | elif [[ $wm =~ ^(Mutter \(Muffin\))$ ]]; then 491 | dec_fix="-9x-8" 492 | elif [[ $wm == herbstluftwm ]]; then 493 | # alternatively could just not subtract when storing (though this 494 | # will also work if border changes after hiding but before showing) 495 | border_width=$(herbstclient get window_border_width) 496 | dec_fix="-${border_width}x-${border_width}" 497 | fi 498 | fi 499 | } 500 | 501 | set_class() { 502 | if [[ -z $class ]]; then 503 | if [[ $program =~ ^emacsclient ]]; then 504 | class=emacs 505 | elif [[ $program =~ ^google-chrome ]]; then 506 | class=google-chrome 507 | elif [[ $program == st ]]; then 508 | class=st-256color 509 | elif [[ $program == gnome-terminal ]]; then 510 | class=Gnome-terminal 511 | elif [[ $program =~ ^urxvt.* ]]; then 512 | class=urxvt 513 | elif [[ $program == xiatec ]]; then 514 | class=xiate 515 | elif [[ $program == alacritty ]]; then 516 | class=Alacritty 517 | elif [[ $program == wezterm ]]; then 518 | # wezterm is normally installed through flatpak 519 | class=org.wezfurlong.wezterm 520 | elif [[ $program == flatpak ]]; then 521 | # flatpak jails the application, the PID cannot be used to find 522 | # the window. A specific class has to be given by the user. 523 | error "--class is required with flatpak but was not given." 524 | elif [[ $program == brave ]]; then 525 | class=brave-browser 526 | elif ! [[ $program =~ ^(current|auto_show)$ ]]; then 527 | # NOTE: current/auto_show will use restore_class to set class to the 528 | # recorded class later 529 | class=$program 530 | fi 531 | fi 532 | } 533 | 534 | is_floating() { 535 | if [[ -n $is_floating ]]; then 536 | eval "$is_floating $1" 537 | elif [[ $wm =~ FLOATING_WMS_REGEXP ]]; then 538 | return 539 | elif $auto_detect_wm; then 540 | if [[ $wm == i3 ]]; then 541 | # TODO make sure this returns 1 on failure 542 | i3-msg -t get_tree | gawk 'gsub(/{"id"/, "\n{\"id\"")' | \ 543 | gawk '/focused":true.*floating":"user_on/ {print $1}' 544 | elif [[ $wm == bspwm ]]; then 545 | bspc query -T -n | grep '"state":"floating"' 546 | elif [[ $wm == herbstluftwm ]]; then 547 | herbstclient or , compare tags.focus.floating = on , compare clients.focus.floating = on 548 | fi 549 | else 550 | return 0 551 | fi 552 | } 553 | 554 | pre_float() { 555 | # TODO why is this an exception? 556 | lowercase_classes="firefox" 557 | if [[ $wm == bspwm ]]; then 558 | # newest (using "instance" names) 559 | if [[ $class =~ [A-Z] ]] || [[ $class =~ $lowercase_classes ]]; then 560 | bspc rule -a "$class" -o state=floating 561 | else 562 | bspc rule -a \*:"$class" -o state=floating 563 | fi 564 | elif [[ $wm == herbstluftwm ]]; then 565 | if [[ $class =~ [A-Z] ]] || [[ $class =~ $lowercase_classes ]]; then 566 | herbstclient rule once class="$class" floating=on 567 | else 568 | herbstclient rule once instance="$class" floating=on 569 | fi 570 | fi 571 | } 572 | 573 | post_float() { 574 | if [[ $wm == awesome ]]; then 575 | echo 'local awful = require("awful") ; awful.client.floating.set(c, true)' | \ 576 | awesome-client 577 | elif [[ $wm == i3 ]]; then 578 | i3-msg "[id=$wid] floating enable" > /dev/null 579 | fi 580 | } 581 | 582 | pre_create() { 583 | if [[ -n $pre_create ]]; then 584 | eval "$pre_create" 585 | fi 586 | } 587 | 588 | post_create() { 589 | if [[ -n $post_create ]]; then 590 | eval "$post_create" 591 | fi 592 | } 593 | 594 | pre_map() { # float 595 | float=${1:-true} 596 | if [[ $float != false ]]; then 597 | if [[ -n $pre_float ]]; then 598 | eval "$pre_float" 599 | elif $auto_detect_wm; then 600 | pre_float 601 | fi 602 | fi 603 | if [[ -n $pre_map ]]; then 604 | eval "$pre_map" 605 | fi 606 | } 607 | 608 | map_and_post_map() { # float 609 | # always reset geometry 610 | map_and_reset_geometry 611 | float=${1:-true} 612 | if [[ $float != false ]]; then 613 | if [[ -n $post_float ]]; then 614 | eval "$post_float" 615 | elif $auto_detect_wm; then 616 | post_float 617 | fi 618 | fi 619 | # need to set geometry again if wasn't previously floating 620 | map_and_reset_geometry 621 | if [[ -n $post_map ]]; then 622 | eval "$post_map" 623 | fi 624 | } 625 | 626 | pre_unmap() { 627 | if [[ -n $pre_unmap ]]; then 628 | eval "$pre_unmap" 629 | fi 630 | } 631 | 632 | post_unmap() { 633 | if [[ -n $post_unmap ]]; then 634 | eval "$post_unmap" 635 | fi 636 | } 637 | 638 | unmap() { 639 | if $remember_geometry; then 640 | store_geometry true 641 | fi 642 | pre_unmap 643 | xdotool windowunmap "$wid" 644 | post_unmap 645 | } 646 | 647 | # Old notes: 648 | # floating WMs that may move a window after remapping it 649 | # pekwm|Fluxbox|Blackbox|xfwm4|Metacity|FVWM|Sawfish|GoomwW|Mutter|GNOME Shell|Mutter \(Muffin\)|KWin|Metacity \(Marco\)|[Cc]ompiz|bspwm 650 | # floating WMs that may both move and resize a window after remapping it 651 | # Openbox 652 | 653 | # * General Helper Functions 654 | # use when there may not be a focused window 655 | get_active_wid_or_empty() { 656 | if [[ $wm == Openbox ]]; then 657 | # on empty openbox desktop, getactivewindow will return the last active 658 | # window, and getwindowfocus will return the wm window id 659 | local wid 660 | wid=$(xdotool getwindowfocus) 661 | if [[ "$(printf 0x%x "$wid")" == "$wm_wid" ]]; then 662 | echo -n "" 663 | else 664 | echo -n "$wid" 665 | fi 666 | else 667 | xdotool getactivewindow 668 | fi 669 | } 670 | 671 | get_tdrop_name() { 672 | if tdrop_name=$(xprop -id "$1" TDROP_NAME 2> /dev/null); then 673 | # remove to first quote then remove closing quote 674 | tdrop_name=${tdrop_name#*\"} 675 | echo -n "${tdrop_name%\"*}" 676 | else 677 | echo -n "" 678 | fi 679 | } 680 | 681 | # check that the window is actually the correct tdrop dropdown instead of a 682 | # newly created window that is reusing the window id of a previously closed 683 | # tdrop window 684 | check_tdrop_name() { # wid 685 | [[ "$(get_tdrop_name "$1")" == "tdrop $program$num" ]] 686 | } 687 | 688 | store_wid() { # wid 689 | wid=$1 690 | # give it a name to uniquely recognize the window (since window ids can be 691 | # reused later if the window is closed) 692 | xprop -format TDROP_NAME 8u -id "$wid" -set TDROP_NAME "tdrop $program$num" 693 | echo "$wid" > "$WID_DIR/$program$num" 694 | } 695 | 696 | get_class_name() { # wid 697 | local class 698 | class=$(xprop -id "$1" WM_CLASS 2> /dev/null) 699 | class=${class%\"} 700 | class=${class##*\"} 701 | echo -n "$class" 702 | } 703 | 704 | store_class() { 705 | get_class_name "$wid" > "$CLASS_DIR/$wid" 706 | } 707 | 708 | restore_class() { 709 | if [[ -z $class ]]; then 710 | class=$(< "$CLASS_DIR/$wid") 711 | fi 712 | } 713 | 714 | get_visibility() { 715 | xwininfo -id "$1" 2> /dev/null | gawk '/Map State/ {print $3}' 716 | } 717 | 718 | maybe_cancel_auto_show() { 719 | if $cancel_auto_show && \ 720 | [[ $1 == $(cat "$HIDE_DIR"/wid 2> /dev/null) ]]; then 721 | # shellcheck disable=SC2188 722 | > "$HIDE_DIR"/wid 723 | fi 724 | } 725 | 726 | get_geometry() { # wid 727 | # so that won't float a tiled window later when showing 728 | if is_floating "$1" &> /dev/null; then 729 | local wininfo x y rel_x rel_y width height 730 | wininfo=$(xwininfo -id "$1") 731 | x=$(echo "$wininfo" | gawk '/Absolute.*X/ {print $4}') 732 | y=$(echo "$wininfo" | gawk '/Absolute.*Y/ {print $4}') 733 | rel_x=$(echo "$wininfo" | gawk '/Relative.*X/ {print $4}') 734 | rel_y=$(echo "$wininfo" | gawk '/Relative.*Y/ {print $4}') 735 | if [[ $subtract_when_same != false ]]; then 736 | # behavior works for most WMs (at least floating ones) 737 | x=$((x-rel_x)) 738 | y=$((y-rel_y)) 739 | else 740 | # don't subtract when abs and rel values are the same 741 | # necessary for WMs like bspwm and i3 742 | if [[ $x -ne $rel_x ]]; then 743 | x=$((x-rel_x)) 744 | fi 745 | if [[ $y -ne $rel_y ]]; then 746 | y=$((y-rel_y)) 747 | fi 748 | fi 749 | width=$(xwininfo -id "$(xdotool getactivewindow)" | \ 750 | gawk '/Width/ {print $2}') 751 | height=$(xwininfo -id "$(xdotool getactivewindow)" | \ 752 | gawk '/Height/ {print $2}') 753 | 754 | if $monitor_aware; then 755 | local x_begin y_begin x_width y_height 756 | populate_current_monitor_geometry 757 | ((x-=x_begin)) 758 | ((y-=y_begin)) 759 | fi 760 | 761 | echo -n -e "xoff=$x\nyoff=$y\nwidth=$width\nheight=$height" 762 | else 763 | # window is not floating; don't bother saving geometry 764 | echo -n "false" 765 | fi 766 | } 767 | 768 | store_geometry() { 769 | get_geometry "$wid" > "$GEO_DIR/$wid" 770 | } 771 | 772 | # set global xoff, yoff, width, and height based on stored values 773 | restore_geometry() { 774 | local geo x_fix y_fix 775 | geo="$(cat "$GEO_DIR/$wid" 2> /dev/null)" 776 | if [[ $geo =~ $GEO_REGEXP ]]; then 777 | eval "$geo" 778 | fi 779 | if [[ -n $dec_fix ]]; then 780 | # x_fix=$(echo "$dec_fix" | gawk -F "x" '{print $1}') 781 | x_fix=${dec_fix%x*} 782 | # y_fix=$(echo "$dec_fix" | gawk -F "x" '{print $2}') 783 | y_fix=${dec_fix#*x} 784 | xoff=$((xoff-x_fix)) 785 | yoff=$((yoff-y_fix)) 786 | fi 787 | if $monitor_aware; then 788 | local x_begin y_begin x_width y_height 789 | populate_current_monitor_geometry 790 | ((xoff+=x_begin)) 791 | ((yoff+=y_begin)) 792 | fi 793 | } 794 | 795 | # * Dropdown Initialization 796 | # TODO ideally this function wouldn't be necessary and some external program 797 | # (something like xtoolwait) could be used to return the wid 798 | create_win_return_wid() { 799 | local blacklist program_command pid visible_wid wids wid program_wid 800 | # save blacklist all existing wids of program 801 | # change pid for programs where $! won't always work (e.g. one pid for all 802 | # windows) 803 | if [[ $program =~ ^(tilix|xfce4-terminal)$ ]]; then 804 | pid=$(pgrep -x "$program") 805 | elif [[ $program == urxvtc ]]; then 806 | blacklist=$(xdotool search --classname urxvtd) 807 | pid=$(pgrep -x urxvtd) 808 | elif [[ $program == wezterm ]]; then 809 | blacklist=$(xdotool search --classname wezterm) 810 | elif [[ $program == xiatec ]]; then 811 | pid=$(pgrep -x xiate) 812 | elif [[ $program == chromium ]]; then 813 | # this may work fine 814 | # pid=$(pgrep -xo chromium) 815 | pid=$(pgrep -xa chromium | gawk '!/--type/ {print $1}') 816 | elif [[ $program == chromium-browser ]]; then 817 | pid=$(pgrep -xa chromium-browse | gawk '!/--type/ {print $1}') 818 | elif [[ $program =~ ^google-chrome ]]; then 819 | pid=$(pgrep -xa chrome | gawk '!/--type/ {print $1}') 820 | elif [[ $program == firefox ]]; then 821 | blacklist=$(xdotool search --name firefox) 822 | elif [[ $program =~ ^emacsclient ]]; then 823 | blacklist=$(xdotool search --classname emacs) 824 | elif [[ $program =~ ^(strawberry|clementine)$ ]]; then 825 | pid=$(pgrep -xa $program | gawk '!/--type/ {print $1}') 826 | else 827 | blacklist=$(xdotool search --classname "$program") 828 | fi 829 | # need to redirect stdout or function won't return 830 | "$@" > "$MUTDROP_PATH/program-output" & 831 | if [[ -z $pid ]]; then 832 | # for normal programs 833 | # also for when one of the programs above hadn't already been started 834 | pid=$! 835 | fi 836 | visible_wid=false 837 | counter=0 838 | while : ; do 839 | if ((counter==0)); then 840 | debug "pid: $pid" 841 | fi 842 | if [[ $program == gnome-terminal ]]; then 843 | wids="" 844 | for pid in $(pgrep gnome-terminal); do 845 | wids+=$(xdotool search --pid "$pid") 846 | done 847 | elif [[ $program == discord ]]; then 848 | wids=$(xdotool search --classname discord) 849 | blacklist= 850 | elif [[ $program =~ ^(qutebrowser|brave|spotify|wezterm)$ ]]; then 851 | # can't rely on pid for these programs 852 | # - wezterm - can have multiple processes with multiple windows each 853 | # $! is not correct, and pgrep would need to be delayed; easier to 854 | # just search for new window ids 855 | # - spotify - pid gives extra incorrect wid (want main window) 856 | # - others - one pid, but can't use for getting wids with xdotool 857 | wids=$(xdotool search --classname "$program") 858 | elif [[ $program =~ ^emacsclient ]]; then 859 | wids=$(xdotool search --classname emacs) 860 | elif [[ $program == flatpak ]]; then 861 | wids=$(xdotool search --classname "$class") 862 | elif [[ $program == firefox ]]; then 863 | wids=$(xdotool search --name firefox) 864 | elif [[ $program == tabbed ]]; then 865 | wids=$(head -n 1 "$MUTDROP_PATH/program-output") 866 | elif [[ $program =~ ^(postman|todoist)$ ]]; then 867 | # postman has multiple wids for the correct pid 868 | # todoist also creates multiple wids 869 | # print only wids that are both are for the class and have the 870 | # browser-window role 871 | wids=$(comm -12 \ 872 | <(xdotool search --classname "$program" | sort) \ 873 | <(xdotool search --role 'browser-window' | sort)) 874 | elif [[ $program == strawberry ]]; then 875 | wids=$(xdotool search --all --pid "$pid" --name "Strawberry Music Player") 876 | else 877 | wids=$(xdotool search --pid "$pid") 878 | fi 879 | if [[ -n $wids ]]; then 880 | debug "blacklist: $blacklist" 881 | debug "wids: ${wids[*]}" 882 | while read -r wid; do 883 | if [[ ! $blacklist =~ (^|$'\n')$wid($|$'\n') ]] && \ 884 | [[ $(get_visibility "$wid") == IsViewable ]]; then 885 | visible_wid=true 886 | program_wid=$wid 887 | fi 888 | done <<< "$wids" 889 | fi 890 | if $visible_wid; then 891 | break 892 | fi 893 | ((counter=counter+1)) 894 | if [[ $counter -gt $((timeout * 100)) ]]; then 895 | error "Exceeded timeout of $timeout seconds waiting for program." 896 | fi 897 | sleep 0.01 898 | done 899 | # workaround for urxvt tabbed plugin using -embed 900 | if [[ $program =~ urxvt ]] && [[ -n $program_wid ]]; then 901 | maybe_program_wid=$(xprop -id "$program_wid" | \ 902 | gawk -F '"' '/-embed/ {print $6}') 903 | if [[ -n $maybe_program_wid ]]; then 904 | program_wid=$maybe_program_wid 905 | fi 906 | fi 907 | debug "picked wid: $program_wid" 908 | echo -n "$program_wid" 909 | } 910 | 911 | # clear all stored wids matching wid; necessary if wid no longer exists to 912 | # prevent two dropdowns from getting thes same wid 913 | invalidate_wid() { # wid 914 | shopt -s nullglob dotglob 915 | for f in {"$HIDE_DIR"/wid,"$WID_DIR"/*}; do 916 | if [[ -f $f ]] && [[ $(< "$f") == "$wid" ]]; then 917 | debug "Clearing stored wid from $f since wid $wid no longer exists" 918 | rm "$f" 919 | fi 920 | done 921 | for f in {"$CLASS_DIR","$GEO_DIR"}/*; do 922 | if [[ $(basename "$f") == "$wid" ]]; then 923 | debug "Removing $f since wid $wid no longer exists" 924 | rm "$f" 925 | fi 926 | done 927 | shopt -u nullglob dotglob 928 | } 929 | 930 | program_start() { 931 | local program_command tmux_command wid 932 | program_command=("$program") 933 | program_command+=("${program_flags[@]}") 934 | if [[ -n $session_name ]]; then 935 | session_name=$(printf "%q" "$session_name") 936 | tmux_command="tmux attach-session -dt $session_name 2> /dev/null || \ 937 | tmuxifier load-session $session_name 2> /dev/null || \ 938 | tmuxinator start $session_name 2> /dev/null || \ 939 | tmuxp load $session_name 2> /dev/null || \ 940 | tmux new-session -s $session_name" 941 | # note: st will work with or without the -e flag (like kitty) 942 | # note: regular console works with or without quotes, but trinity's 943 | # konsole only works without quotes 944 | if [[ $program =~ ^(urxvt|alacritty|xiatec|st|lxterminal|qterminal|cool-retro-term|lilyterm|konsole$) ]]; then 945 | program_command+=(-e bash -c "$tmux_command") 946 | elif [[ $program == kitty ]]; then 947 | program_command+=(bash -c "$tmux_command") 948 | elif [[ $program == wezterm || $class =~ ^(org\.wezfurlong\.)?wezterm$ ]]; then 949 | program_command+=(start --always-new-process -- bash -c "$tmux_command") 950 | elif [[ $program == gnome-terminal ]]; then 951 | program_command+=(-- bash -c "$tmux_command") 952 | else 953 | program_command+=(-e "bash -c '$tmux_command'") 954 | fi 955 | fi 956 | wid=$(create_win_return_wid "${program_command[@]}") 957 | if [[ -n $name ]]; then 958 | xdotool set_window --name "$name" "$wid" 959 | fi 960 | store_wid "$wid" 961 | echo -n "$wid" 962 | } 963 | 964 | current_create() { 965 | # turns active window into a dropdown 966 | local wid 967 | wid=$(xdotool getactivewindow) 968 | store_wid "$wid" 969 | store_class 970 | if [[ -n $name ]]; then 971 | xdotool set_window --name "$name" "$wid" 972 | fi 973 | echo -n "$wid" 974 | } 975 | 976 | wid_toggle() { 977 | # used for -m option; at first tdrop assumes that there is a focused window 978 | # on the current desktop; if there isn't (and the WM doesn't have some way 979 | # to query the current monitor), this will be set to false, and tdrop will 980 | # have to find out the current monitor info after opening the dropdown 981 | # (currently, using xwininfo to find the position of a window is the only 982 | # WM-independent way I know to find out what the current monitor is) 983 | local focused_window_exists 984 | focused_window_exists=true 985 | 986 | # deal with percentages/negatives when no -m 987 | if ! $monitor_aware; then 988 | local total_geo total_width total_height 989 | total_geo=$(xwininfo -root | gawk '/geometry/ {gsub("+.*",""); print $2}') 990 | # total_width=$(echo "$total_geo" | gawk -F 'x' '{print $1}') 991 | total_width=${total_geo%x*} 992 | # total_height=$(echo "$total_geo" | gawk -F 'x' '{print $2}') 993 | total_height=${total_geo#*x} 994 | convert_geometry_to_pixels "$total_width" "$total_height" 995 | fi 996 | # get saved window id if already created 997 | local exists visibility 998 | # cat to silence error 999 | wid=$(cat "$WID_DIR/$program$num" 2> /dev/null) 1000 | exists=true 1001 | if [[ -n $wid ]]; then 1002 | visibility=$(get_visibility "$wid") 1003 | # sometimes xwininfo will still report a window as existing, so an xprop 1004 | # check is required; check_tdrop_name ensures both that the window 1005 | # actually exists (if xwininfo is wrong) and that it is the correct 1006 | # window (by checking the TDROP_NAME window property) 1007 | if [[ -z $visibility ]] || ! check_tdrop_name "$wid"; then 1008 | # window no longer exists; clear all stored information about it 1009 | invalidate_wid "$wid" 1010 | exists=false 1011 | fi 1012 | else 1013 | exists=false 1014 | fi 1015 | if $exists; then 1016 | debug "Window exists, visibility: $visibility" 1017 | if [[ $program == current ]]; then 1018 | restore_class 1019 | fi 1020 | if [[ $visibility =~ ^(IsUnMapped|IsUnviewable)$ ]] \ 1021 | || { $always_activate \ 1022 | && [[ $(get_active_wid_or_empty) != "$wid" ]]; } 1023 | then 1024 | # visibility will be IsUnMapped on most WMs if the dropdown is open 1025 | # on another desktop; may also be IsUnviewable 1026 | xdotool set_desktop_for_window "$wid" "$(xdotool get_desktop)" 1027 | if [[ $(get_visibility "$wid") == IsUnMapped ]]; then 1028 | pre_map 1029 | fi 1030 | if $remember_geometry; then 1031 | restore_geometry 1032 | elif $monitor_aware; then 1033 | # update here if possible so this doesn't cause a delay 1034 | # between the window being remapped and resized 1035 | update_geometry_settings_for_monitor || \ 1036 | focused_window_exists=false 1037 | fi 1038 | map_and_post_map 1039 | # cancel auto-show for a window when manually remapping it 1040 | maybe_cancel_auto_show "$wid" 1041 | if ! $focused_window_exists; then 1042 | # need to use dropdown as active window to get monitor info 1043 | update_geometry_settings_for_monitor 1044 | # always resize/move; if monitor hasn't changed then it won't be 1045 | # necessary, but it won't cause problems either 1046 | map_and_reset_geometry 1047 | fi 1048 | else 1049 | unmap 1050 | fi 1051 | else 1052 | # necessary to deal with negative width or height 1053 | # if creating on an empty desktop and can't determine the monitor, 1054 | # must set temporary values for negative width and/or height 1055 | local original_width original_height 1056 | if $monitor_aware && ! update_geometry_settings_for_monitor; then 1057 | focused_window_exists=false 1058 | if [[ $width =~ ^- ]]; then 1059 | original_width=$width 1060 | width=100% 1061 | fi 1062 | if [[ $height =~ ^- ]]; then 1063 | original_height=$height 1064 | height=100% 1065 | fi 1066 | fi 1067 | # make it 1068 | pre_create 1069 | if [[ $program == current ]]; then 1070 | wid=$(current_create) 1071 | unmap 1072 | else 1073 | pre_map 1074 | wid=$(program_start) 1075 | map_and_post_map 1076 | # update window dimensions if necessary 1077 | if ! $focused_window_exists; then 1078 | width=${original_width:-$width} 1079 | height=${original_height:-$height} 1080 | update_geometry_settings_for_monitor 1081 | map_and_reset_geometry 1082 | fi 1083 | fi 1084 | post_create 1085 | fi 1086 | } 1087 | 1088 | # * Helper Functions for Auto Hiding/Showing 1089 | toggle_auto_hide() { 1090 | local no_hide 1091 | no_hide=$(cat "$NOAUTOHIDE_FILE" 2> /dev/null) 1092 | if [[ -z $no_hide ]]; then 1093 | echo "true" > "$NOAUTOHIDE_FILE" 1094 | else 1095 | # shellcheck disable=SC2188 1096 | > "$NOAUTOHIDE_FILE" 1097 | fi 1098 | } 1099 | 1100 | # * Auto Hiding/Showing 1101 | # TODO if considered useful, nesting auto_hide could be supported by having the 1102 | # users pass a key as an extra argument to use to store the wid (e.g. script 1103 | # that calls could use the time) 1104 | auto_hide() { 1105 | local no_hide 1106 | no_hide=$(cat "$NOAUTOHIDE_FILE" 2> /dev/null) 1107 | if [[ -z $no_hide ]]; then 1108 | wid=$(xdotool getactivewindow) 1109 | echo "$wid" > "$HIDE_DIR"/wid 1110 | store_class 1111 | store_geometry 1112 | unmap 1113 | fi 1114 | } 1115 | 1116 | auto_show() { 1117 | local no_hide 1118 | no_hide=$(cat "$MUTDROP_PATH"/auto_hidden/no_hide 2> /dev/null) 1119 | if [[ -z $no_hide ]]; then 1120 | local was_floating 1121 | wid=$(< "$HIDE_DIR"/wid) 1122 | restore_class "$wid" 1123 | restore_geometry "$wid" 1124 | was_floating=$(< "$GEO_DIR/$wid") 1125 | pre_map "$was_floating" 1126 | map_and_post_map "$was_floating" 1127 | fi 1128 | } 1129 | 1130 | # * Hide All 1131 | hide_all() { 1132 | shopt -s nullglob dotglob 1133 | local dropdowns 1134 | dropdowns=("$WID_DIR"/*) 1135 | for dropdown in "${dropdowns[@]}"; do 1136 | # cat to silence errors 1137 | wid=$(cat "$dropdown" 2> /dev/null) 1138 | unmap "$wid" 2> /dev/null 1139 | done 1140 | shopt -u nullglob dotglob 1141 | } 1142 | 1143 | # * Run Command For Each 1144 | foreach() { 1145 | shopt -s nullglob dotglob 1146 | local dropdowns wid 1147 | dropdowns=("$WID_DIR"/*) 1148 | for dropdown in "${dropdowns[@]}"; do 1149 | # cat to silence errors 1150 | wid=$(cat "$dropdown" 2> /dev/null) 1151 | eval "$1" 1152 | done 1153 | } 1154 | 1155 | # * Main 1156 | # ** Setup 1157 | set_wm 1158 | decoration_settings 1159 | set_class 1160 | 1161 | # ** Primary Action 1162 | if $clearwid; then 1163 | debug "command: clear wid for $program$num" 1164 | # shellcheck disable=SC2188 1165 | > "$WID_DIR/$program$num" 1166 | elif [[ $program == toggle_auto_hide ]]; then 1167 | debug "command: toggle auto hide" 1168 | toggle_auto_hide 1169 | elif [[ $program == auto_hide ]]; then 1170 | debug "command: auto hide" 1171 | auto_hide 1172 | elif [[ $program == auto_show ]]; then 1173 | debug "command: auto show" 1174 | auto_show 1175 | elif [[ $program == hide_all ]]; then 1176 | debug "command: hide all" 1177 | hide_all 1178 | elif [[ $program == foreach ]]; then 1179 | debug "command: foreach" 1180 | foreach "$2" 1181 | else 1182 | debug "command: toggle $program$num" 1183 | wid_toggle 1184 | fi 1185 | 1186 | # vim is dumb 1187 | # vim: set ft=sh noet: 1188 | -------------------------------------------------------------------------------- /tdrop.1: -------------------------------------------------------------------------------- 1 | .\" Man page for tdrop. 2 | .\" Please make an issue on the online repository if you find errors or typos. 3 | .TH TDROP 1 "11 February 2015" "tdrop 0.5.0" "tdrop man page" 4 | .SH NAME 5 | Tdrop - make dropdown terminals and windows 6 | .SH SYNOPSIS 7 | tdrop [\fIOPTIONS\fR] [program name or cmd] [program options ...] 8 | .SH DESCRIPTION 9 | Tdrop is used for hiding/unhiding programs to acheive quake/dropdown functionality. It can create a dropdown window if one does not already exist or turn the current window into a dropdown on the fly. It provides options to control the intial size and position of dropdowns, for example to leave panels visible or to deal with window borders. When used with a terminal, it provides an option to specify the name of a tmux session to automatically start. It also allows the user to specify arbitrary options/flags to be used when starting programs. It uses window IDs as opposed to classes, so it can be used with multiple windows of the same program. 10 | 11 | It also has the ability to automatically hide and automatically show dropdowns. For example, it can be used to automatically hide a terminal when opening something from it, e.g. an image viewer, video player, etc. Tdrop can then automatically bring back the terminal whenever the image view, video player, etc. is closed. See the Examples section for more information. 12 | .SH Commands 13 | Tdrop expects the name of a program or 'current' (to use the current window) as the last argument to create a dropdown (optionally followed by any flags to that program). Alternatively, it can take one of auto_show, auto_hide, toggle_auto_hide, hide_all, or foreach. If hide_all is given instead of a program name, tdrop will hide all visible dropdowns. 14 | 15 | Foreach will run a run a command for each dropdown. For example, you can do the equivalent of hide_all with the following: 16 | 17 | tdrop foreach 'unmap $wid' 18 | 19 | To hide floating dropdowns only on herbstluftwm: 20 | 21 | tdrop foreach 'herbstclient compare clients.$(printf 0x%x $wid).floating = on && unmap $wid' 22 | 23 | Tdrop's functionality (except potentially foreach) is not particularly useful called directly from the command line. Commands should either be bound to a key, used in shell functions/aliases, or used with a file opener (e.g. in the rifle.conf). 24 | 25 | .SH OPTIONS 26 | .br 27 | E.g. 28 | .br 29 | $ tdrop -y 15 termite 30 | .br 31 | $ tdrop --y-offset=15 termite 32 | .br 33 | $ tdrop --y-offset 15 termite 34 | .TP 35 | Note that all hook and command related options can make use of any tdrop variables (such as $width, $height, $xoff, $yoff, $class, $wid, etc.). Note that for some of the hooks, the window id is not guarunteed to be known (since the window may not have yet been created), so any scripts that make use of these should check if it is defined (pre-map and pre-float; wid will never be known for pre-create). 36 | .TP 37 | \fB-w WIDTH\fR, \fB --width=WIDTH\fR 38 | Specify a width for a created window as a number or percentage. A negative number is allowed (e.g. '-w -4') in which case the width will be that many pixels less than 100% of the screen size (or monitor size if '-m' is being used). This fixes the problem where 100% width may actually go over the screen due to window borders/decoration. The other other geometry options also accept negative values ('-h', '-x', and '-y'). If both the width and height are specified as empty/"", do not alter the window's size. (default: 100%) 39 | .TP 40 | \fB-h HEIGHT\fR, \fB --height=HEIGHT\fR 41 | Specify a height for a created window as a number or percentage. If both the width and height are specified as empty/"", do not alter the window's size. (default: 45%) 42 | .TP 43 | \fB-x OFFSET\fR, \fB --x-offset=OFFSET\fR 44 | Specify the x position for a created window as a number or percentage. If both the x and y offsets are specified as empty/"", do not alter the window's position. (default: 0) 45 | .TP 46 | \fB-y OFFSET\fR, \fB --y-offset=OFFSET\fR 47 | Specify the y position for a created window as a number or percentage. If both the x and y offsets are specified as empty/"", do not alter the window's position. (default: 1, see BUGS) 48 | .TP 49 | \fB-s NAME\fR, \fB --session=NAME\fR 50 | Specify a tmuxinator, tmuxifier, or tmux session name to start. An existing tmux session has highest precedence and will be connected to with '-d', detaching other attached clients. If a there is no tmuxinator/tmuxifier/etc. session of the given name, a normal tmux session with the name will be created. If this option is not given, tmux will not be used. Note that this option requires that the program be a supported terminal. (default: none) 51 | .TP 52 | \fB-n NUMBER\fR, \fB --number=NUMBER\fR 53 | Specify a number (or any extra text) to differentiate between dropdowns of the same program (this is only needed when using multiple dropdowns of the same program). This flag can also be used for creating multiple different dropdowns on the fly ('current'). Note that it is not necessary to use this to deal with multi-user systems as tdrop stores dropdown information separately for each user. (default: none) 54 | .TP 55 | \fB-c COMMAND\fR, \fB --pre-create-hook=COMMAND\fR 56 | Specify a command to execute before first creating or initializing a dropdown (before mapping a normal dropdown or before unmapping the 'current' window). This flag has no effect for the auto_(hide|show) commands. (default: none) 57 | .TP 58 | \fB-C\fR, \fB --post-create-hook=COMMAND\fR 59 | Specify a command to execute after first creating or initializing a dropdown (after mapping a normal dropdown or after unmapping the 'current' window). This flag has no effect for the auto_(hide|show) commands. (default: none) 60 | .TP 61 | \fB-p COMMAND\fR, \fB --pre-map-hook=COMMAND\fR 62 | Specify a command to execute before showing/mapping a dropdown. Note that this will run when showing a dropdown for the first time even when --pre-create-hook is used. (default: none) 63 | .TP 64 | \fB-P COMMAND\fR, \fB --post-map-hook=COMMAND\fR 65 | Specify a command to execute after showing/mapping a dropdown. Note that this will run when hiding a dropdown for the first time even when --post-create-hook is used. (default: none) 66 | .TP 67 | \fB-u COMMAND\fR, \fB --pre-unmap-hook=COMMAND\fR 68 | Specify a command to execute before hiding/unmapping a dropdown. (default: none) 69 | .TP 70 | \fB-U COMMAND\fR, \fB --post-unmap-hook=COMMAND\fR 71 | Specify a command to execute after hiding/unmapping a dropdown. (default: none) 72 | .TP 73 | \fB-l COMMAND\fR, \fB --pre-map-float-command=COMMAND\fR 74 | Specify a command execute before showing/mapping a dropdown in order to float the dropdown (e.g. a bspwm oneshot rule). This may be useful if you don't want to float all windows of a given program and tdrop doesn't automatically support this for your window manager with the -a flag. This will override any default floating command when used with -a. 75 | .TP 76 | \fB-L COMMAND\fR, \fB --post-map-float-command=COMMAND\fR 77 | Specify a command execute after showing/mapping a dropdown in order to float the dropdown. This may be useful if you don't want to float all windows of a given program and tdrop doesn't automatically support this for your window manager with the -a flag. This can be used if your window manager does not support floating rules at all; for example, it can be used to fake a key combination (e.g. using xdotool) that will float the current window. This will override any default floating command when used with -a. 78 | .TP 79 | \fB-d DEC_SIZE\fR, \fB --decoration-fix=DEC_SIZE\fR 80 | Specify a window decoration/border size in the form x to be taken into account when saving window position. This should not be necessary for most window managers and is only used with 'auto_show', e.g. 'tdrop -d 1x22 auto_show' for blackbox. (default: none) 81 | .TP 82 | \fB-S\fR, \fB --no-subtract-when-same\fR 83 | This option is a more complicated companion to -d that is also unlikely to be needed. This is used to determine how to calculate the X and Y position of a window using xwininfo when 'auto_hide' is used (if the absolute and relative X or Y values are reported as the same, this option determines the behavior). If you are sure you have the decoration size correct, but windows are still being restored in an incorrect position with 'auto_show', you may want to try using this. Takes no argument. (default: false) 84 | .TP 85 | \fB-i\fR, \fB --is-floating\fR 86 | Specify a command that will determine whether the current window is floating ($wid can be used in the command instead). Only used for the auto_hide command. This will be used to save whether the current window is floating or not. When restoring the window, if there is a float command and the window was previously floating, it will be floated. (default: none) 87 | .TP 88 | \fB-f\fR, \fB --program-flags\fR 89 | NOTE: Using this flag is deprecated; it may be removed in the future. Instead, specify program flags after the program (e.g. "tdrop kitty --name foo"). 90 | 91 | Specify flags/options that the terminal or program should be called with. For example, to set the title of the terminal, something like 'tdrop -f "--title mytitle" ' can be used. 92 | 93 | Caution: If there is a tmux session specified (with -s), the option to execute a program (usually -e for terminal programs) is implicitly added by tdrop! (default: none) 94 | .TP 95 | \fB-a\fR, \fB --auto-detect-wm\fR 96 | If there are available settings for the detected window manager for the -l, -L, -d, and/or -i options, automatically set them. Takes no argument. Manually specified settings take precedence. This can be used both for dropdowns and the auto_(hide|show) commands. Takes no argument. (default: false) 97 | .TP 98 | \fB-m\fR, \fB --monitor-aware\fR 99 | This option only applies for dropdowns (not auto-hiding and auto-showing). Specify that geometry values should be relative to the current monitor. For example, if the width is a percentage or negative value, the pixel width will be calculated as a percentage of the current monitor's width (instead of the combined width of all monitors). If the monitor changes, this option will cause a dropdown to be resized to fit the given percentages. Note that this option assumes xrandr is being used and requires xrandr to work. (default: false) 100 | .TP 101 | \fB-t\fR, \fB --pointer-monitor-detection\fR 102 | Use mouse pointer location for detecting which monitor is the current one so terminal will be displayed on it. Without this option, the monitor with currently active window is considered the current one. This option is only effective if -m / --monitor-aware option is enabled. 103 | .TP 104 | \fB-A\fR, \fB --activate\fR 105 | Always activate/show the dropdown if it is not currently focused. Only hide the dropdown if it is currently focused. 106 | .TP 107 | \fB--monitor=NAME\fR 108 | Specify the monitor to base geometry calculations on when using -m / --monitor-aware instead of detecting the monitor by the actively focused window or mouse pointer. 109 | .TP 110 | \fB --wm=NAME\fR 111 | Specify the window manager name (which determines the default settings when -a is specified). This may be useful if you've change the name of your window manager using wmname as this will prevent tdrop from correctly detecting the real window manager name. This could also potentially be useful if the all the default -a settings for another window manager work with the current one (e.g. if using a similar but differently named fork of some window manager). (default: automatically detected) 112 | .TP 113 | \fB --class=NAME\fR 114 | Providing this option lets tdrop know what the class (or classname) of the window is (it does not actually set the class for a window). This is used for window managers like bspwm that use the class for floating rules. For some commonly used programs, tdrop will already use the correct class. This option is useful when the program name and class are not the same and there is not already a default mapping between the two. (default: the program name or a known substitution) 115 | 116 | Both the class and classname of a window can be obtained using xprop (see WM_CLASS). As for the difference, generally the class starts with an uppercase letter and the classname starts with a lowercase letter. The xprop output may only list one for some programs (e.g. urxvt only has "urxvt"). Currently this option is only useful for bspwm, and it does not matter whether the class or classname (which bspwm calls an instance name) is provided, so the user does not really need to worry about the distinction. 117 | .TP 118 | \fB --name=NAME\fR 119 | This option only applies for dropdowns (not auto-hiding and auto-showing). Set a new name for the dropdown window (see _NET_WM_NAME and WM_NAME in xprop output). This option may be useful if you want to add specific rules just for dropdowns with a program like compton by giving them a common title. (default: none) 120 | .TP 121 | \fB --clear\fR 122 | Used to clear a saved window id for the given program or 'current' instead of creating a dropdown. Takes no argument. 123 | .TP 124 | \fB --no-cancel\fR 125 | Specifies that manually re-showing an auto-hidden window with tdrop should not cancel an auto_show. Takes no argument. See the examples. 126 | .TP 127 | \fB --timeout\fR 128 | Specifies the timeout in to wait for a window to appear when starting a program before giving up. This prevents a tdrop process from sticking around forever if a program fails to start. (default: 10) 129 | .TP 130 | \fB --debug\fR 131 | Print information for debugging to stdout and to /tmp/tdrop_/log. Takes no argument. (default: false) 132 | .TP 133 | \fB-r\fR, \fB --remember\fR 134 | Store window geometry when hiding and restore geometry when showing instead of using the specified -x, -y, -w, and -h arguments. If used in combination with -m, the x and y positions will be shifted relative to the current monitor when storing/restoring. For example, if you close the dropdown on the leftmost monitor where x is 0 and restore on a monitor to the right, tdrop will offset x so it shows up on the right monitor. This option takes no argument. (default: false) 135 | .TP 136 | \fB-N\fR, \fB --no-manage\fR 137 | This is shorthand for -x "", -y "", -w "", -h "" and is incompatible with them (do not set both). When specified, tdrop will not enforce any geometry on the dropdown. This can be useful in combination with --remember if you want to ignore the default geometry values and not have tdrop alter the window geometry when first creating the dropdown. Takes no argument. (default: false) 138 | .TP 139 | \fB --help\fR 140 | Print basic help information. Takes no argument. 141 | 142 | .SH EXAMPLES 143 | .SS Making Dropdowns 144 | Use a key binding program such as sxhkd to bind keys to these commands. 145 | 146 | The simplest example to make a dropdown for an xterm: 147 | .br 148 | $ tdrop xterm 149 | 150 | When using a tiling window manager like bspwm, dropdowns like guake will by default be tiled instead of floated. One can create a rule to float every instance of guake or another dropdown. However, one may not want to float every instance of a terminal used with tdrop. Tdrop allows the user to run their own commands at various points during execution, for example before mapping the window: 151 | .br 152 | $ tdrop -p "bspc rule -a xterm -o floating=on" xterm 153 | 154 | Tdrop also provides tested settings for certain window managers. One can use the '-a' flag if settings exist for the current window manager. For example, if bspwm is the window manager, the following command is the same as the above command and will work for whatever terminal/program is specified and will also work with 'tdrop auto_show'. For a list of window managers with tested settings see the readme or the script itself. 155 | .br 156 | $ tdrop -a xterm 157 | 158 | Tdrop supports controlling the initial size and placement of a terminal. The border of a window may need to be taken into an account. For example, I use a border size of 2, so I use 4 less than my screen size. I also use a y-offset of 14 so that the dropdown doesn't hide my panel: 159 | .br 160 | $ tdrop -a -w 1362 -y 14 xterm 161 | 162 | Tdrop can also create a tmux session if it does not exist: 163 | .br 164 | $ tdrop -a -w 1362 -y 14 -s dropdown xterm 165 | 166 | Tdrop allows for having multiple dropdowns of the same type: 167 | .br 168 | $ tdrop xterm 169 | .br 170 | $ tdrop -n 1 xterm 171 | .br 172 | $ tdrop -n 2 xterm 173 | .br 174 | ... 175 | 176 | Tdrop works with normal windows (with some potential visual annoyance, see BUGS): 177 | .br 178 | $ tdrop zathura 179 | .br 180 | # the current window 181 | .br 182 | $ tdrop current 183 | 184 | Once a window is turned into a dropdown, the key bound to 'tdrop ... current' will continue to toggle that window until it is closed. Then the key can be used to create a new dropdown. '-n' can also be used to have multiple 'current' keys. If an active window is accidentally turned into a dropdown, it can be cleared: 185 | .br 186 | $ tdrop --clear current 187 | .br 188 | # clear a specific number 189 | .br 190 | $ tdrop -n 1 --clear current 191 | 192 | .SS Auto-hiding/showing 193 | These example will work even for non-dropdown terminals. 194 | 195 | Tdrop provides the functionality to get programs/terminals out of the way when opening other programs. For example, when opening an image viewer from a normal floating dropdown, the dropdown will be over the image viewer. This requires an extra hotkey press to hide the dropdown. If one wants to return to the dropdown after looking at images, the hotkey must be once again invoked. Tdrop allows for this process to be automated. 196 | 197 | For example, this could be added to a shell's config/startup file: 198 | .br 199 | hide_on_open() { tdrop -a auto_hide; "$@" && tdrop -a auto_show } 200 | 201 | To use it in an alias when writing a commit message in an graphical $EDITOR started from a terminal: 202 | .br 203 | alias gc='hide_on_open git commit' 204 | 205 | This will hide the terminal window when opening the commit editor and then reshow the terminal once the editor is closed. It should also maintain the window's position and size when showing it. If the window moves down and to the right every time it is auto-hidden and then shown again, the user may need to specify a -d value. Alternatively, if one already exists for the user's window manager, -a can be used to automatically set it. The -l and -L options are also used with auto_show and can be set automatically with -a if default settings exist for the current window manager. 206 | 207 | Note that for tiling window managers that support 'tdrop -a auto_show', reshowing a window will always float the window (even if it was orignally tiled) if -i is not specified. To prevent this, also use 'tdrop -a auto_hide' if your window manager is supported. Otherwise, -i must be manually specified with auto_hide. 208 | 209 | This functionality might lead to some unwanted "re-shows" of dropdown. Consider a situation in which one opens an image viewer from a dropdown and leaves it open for a while, resuming normal use of the dropdown. When the image viewer is closed, the dropdown appears, unwanted. Tdrop is smart about this and won't "re-show" a dropdown if it has been manually toggled since an auto-hide. If you don't want this check to happen, use '--no-cancel' in your dropdown key binding. 210 | 211 | Auto-hiding functionality is particularly nice to use with a file opener like rifle: 212 | .br 213 | mime ^image, has sxiv, X, flag f = tdrop auto_hide ; sxiv -a -- "$@" && tdrop -a auto_show 214 | 215 | .SH BUGS 216 | If -y is set to 0, a window may be subsequently moved to the middle when showing/mapping it with xdotool. This may have to do with the window border. 217 | 218 | .SH AUTHOR 219 | Fox Kiester 220 | .br 221 | Source: https://github.com/noctuid/tdrop 222 | 223 | .SH SEE ALSO 224 | xdotool(1), sxhkd(1), xprop(1), xwininfo(1), tmux(1) 225 | 226 | --------------------------------------------------------------------------------