├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── common.mk ├── conf.mk ├── conf ├── 10-autoexec ├── 20-completion ├── 20-nobeep ├── 40-escape ├── 40-home_end ├── 40-recent ├── 40-setprompt ├── 40-sigint ├── 40-sudo ├── 90-hist ├── 99-clear └── shellexrc.in ├── doc ├── autoresize.txt └── man │ ├── Makefile │ ├── asciidoc.conf │ ├── man.mk │ └── shellex.man.in ├── format.mk ├── indent.sh ├── preload ├── .clang-format ├── main.c └── preload.mk ├── shellex.in ├── shellex.mk └── urxvt ├── .perltidyrc ├── shellex.in └── urxvt_shellex.mk /.gitignore: -------------------------------------------------------------------------------- 1 | preload/shellex_preload.so 2 | conf/shellexrc 3 | shellex 4 | urxvt/shellex 5 | urxvt/shellex.in.bak 6 | doc/man/shellex.1 7 | doc/man/shellex.man 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | script: 3 | - make 4 | - sudo make install 5 | - make format_perl 6 | - make format_c 7 | - git diff --stat --exit-code 8 | 9 | before_install: 10 | - sudo apt-get -qq build-dep shellex 11 | - sudo apt-get -qq install clang-format-3.8 12 | - sudo apt-get -qq install perltidy 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.3 - 2018-12-10 5 | ---------------- 6 | • Miscellaneous maintenance changes 7 | • Updated empty-line treatment 8 | • Changed xrandr parseing 9 | • Cleanup of unused perl modules 10 | • Bugfix: allow symlinks in user .shellex directories 11 | • Use configuration from `$XDG_CONFIG_HOME` 12 | • Enable compinit by default, add ctrl-x r for recent file completion 13 | 14 | The slight change in the default completion behaviour is very likely desirable. 15 | In the unlikely chance that a user does not appreciate the change, refer to the 16 | README about how to disable the snippet `20-completion`. 17 | 18 | 0.2 - 2016-12-27 19 | ---------------- 20 | • Added optional sudo configuration 21 | • Bugfix: flashing window with large completion lists 22 | • Moved config handling from perl to shell 23 | • Disable beeping 24 | • Improved history file handling 25 | • Avoid using temporary files for configuration parsing 26 | • Avoid using temporary files for command execution 27 | 28 | 0.1 - 2013-09-03 29 | ---------------- 30 | • History support (default) 31 | • Pass command-line to urxvt (support colors) 32 | • Bugfix: Correct pointer-based positioning 33 | • Bugfix: Correct focus-based positioning 34 | • Bugfix: Correctly predict terminal size, when adding strings 35 | 36 | 37 | 0.0 - 2013-08-31 38 | ---------------- 39 | • Initial release 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2016 Axel Wagner and contributors 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 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Axel Wagner nor the 15 | names of contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY Axel Wagner ''AS IS'' AND ANY 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL Axel Wagner BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TOPDIR=$(shell pwd) 2 | 3 | include $(TOPDIR)/common.mk 4 | 5 | ALL_TARGETS = 6 | INSTALL_TARGETS = 7 | CLEAN_TARGETS = 8 | DISTCLEAN_TARGETS = 9 | 10 | all: real-all 11 | 12 | include preload/preload.mk 13 | include shellex.mk 14 | include urxvt/urxvt_shellex.mk 15 | include conf.mk 16 | include format.mk 17 | include doc/man/man.mk 18 | 19 | real-all: $(ALL_TARGETS) 20 | 21 | install: $(INSTALL_TARGETS) 22 | 23 | clean: $(CLEAN_TARGETS) 24 | 25 | distclean: clean $(DISTCLEAN_TARGETS) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | shellex - Shell-based launcher 2 | ============================== 3 | 4 | `shellex` is supposed to be a dmenu-style launcher with a lot more features and 5 | a lot simpler design. It launches a shell (currently `zsh`) and shows it in a 6 | small terminal, wrapping every command with a little bit of extra magic 7 | (redirecting stdout, stderr, disowning and closing the shell) to get more 8 | typical launcher-behaviour. 9 | 10 | This gives you a simple launcher with tab-completion and other shell-features, 11 | configurable in shell. 12 | 13 | See this video for a short demonstration and comparison to another app starter: 14 | ![demo](http://virgilio.mib.infn.it/~seyfert/images/shellexdemo.gif) 15 | 16 | I tried to do this a few years back, then using C and implementing the 17 | terminal-operations myself. This turned out to be a very bad idea, it made the 18 | design overly complex and the state I left it in had regular segfaults and was far 19 | from working. After not much seem to happen in that direction, I decided to 20 | start again, this time using `urxvt` to do the terminal-part, which turned out to 21 | be really easy. 22 | 23 | While this had the label “early prototype”, it has worked in daily use quite 24 | reasonably. A few possible improvements were never apparently important enough 25 | to invest serious amounts of time and work into them. Still, contributions are 26 | welcome. 27 | 28 | Architecture 29 | ============ 30 | 31 | `shellex` has three parts: 32 | 33 | * [A small shell-script](shellex.in) that just starts a `urxvt` with some extra 34 | parameters 35 | * [An urxvt-extension](urxvt/shellex.in) that manages the terminal/displaying 36 | part. 37 | * [configfile](conf) that do all stuff relating to the functional behaviour 38 | 39 | (Planned) Features 40 | ================== 41 | 42 | Working: 43 | * Launching Applications (yay) 44 | * Commandline parameters 45 | * Basic Tab-completion 46 | * Starting on the right output (configurable, either the output containing the 47 | currently focused window or the output containing the mousepointer) 48 | * Dynamic resizing of the launcher-window e.g. for multiple lines of 49 | suggestions for tab-completions (see [doc/autoresize.txt](doc/autoresize.txt)) 50 | 51 | Planned, but not implemented: 52 | * Buffering/showing some output, for errors etc. We have to think about some 53 | magic way to determine, whether output is helpful or the launcher should be 54 | hidden immediately 55 | * dmenu-like completion, typing part of a command still completing (maybe zsh 56 | has something to do that? possibly [fzf](https://github.com/junegunn/fzf)) 57 | * [.desktop-file integration](https://github.com/pseyfert/shellex-desktop) 58 | * Your ticket here 59 | 60 | 61 | Installation 62 | ============ 63 | 64 | There are packages in 65 | 66 | * [debian](http://packages.debian.org/search?keywords=shellex&searchon=names&suite=all§ion=all&sourceid=mozilla-search) 67 | * Arch Linux: [Arch User Repository](https://aur.archlinux.org/packages/?SeB=n&K=shellex) ([latest Release](https://aur.archlinux.org/packages/shellex/) and [git-Repository](https://aur.archlinux.org/packages/shellex-git/)) 68 | * Gentoo: there is an [Overlay](https://github.com/proxypoke/gentoo-overlay) which contains shellex 69 | 70 | If you are on one of these distributions, we encourage you to install `shellex` 71 | via your package manager. 72 | 73 | Else, or if you want to help developing, just do 74 | 75 | ```sh 76 | $ git clone git://github.com/Merovius/shellex.git 77 | $ cd shellex 78 | $ make 79 | $ make install 80 | ``` 81 | 82 | for installing in `$(HOME)/.local` 83 | 84 | ```sh 85 | $ git clone git://github.com/Merovius/shellex.git 86 | $ cd shellex 87 | $ PREFIX=$HOME/.local make 88 | $ PREFIX=$HOME/.local make install 89 | ``` 90 | 91 | Usage 92 | ===== 93 | 94 | After installing `shellex` you probably want to bind it to a shortcut - most 95 | likely Alt+F2. How to do this depends on your desktop environment and/or window 96 | manager. 97 | 98 | ## xbindkeys 99 | 100 | If you're running [xbindkeys](http://www.nongnu.org/xbindkeys/xbindkeys.html), 101 | the entry for your `~/.xbindkeysrc` file might look like: 102 | 103 | ``` 104 | "shellex" 105 | alt + c:68 106 | ``` 107 | 108 | ## i3 109 | 110 | [i3](i3wm.org) shortcuts can be modified as described in the 111 | [official documentation](https://i3wm.org/docs/userguide.html). 112 | 113 | ``` 114 | bindsym Mod1+F2 exec shellex 115 | ``` 116 | 117 | Contributing 118 | ============ 119 | 120 | `shellex` is a very young project, it would highly profit from your help. The 121 | following is a not comprehensive list of highly appreciated ways to contribute: 122 | 123 | 1. Test it and make [tickets](https://github.com/Merovius/shellex/issues) for 124 | *any* problems you stumble upon or ideas you have to make it better. 125 | 2. Have a look at a [list of issues](https://github.com/Merovius/shellex/issues) 126 | and find one to fix. But please announce your intention to fix it, so that 127 | we can be sure that it will be merged and there is no duplication of effort. 128 | 3. Have a look at the [list of packaged dirstributions](https://github.com/Merovius/shellex#installation). 129 | If your favourite distribution is not on it, please package it. Please 130 | announce your intent to do so (in a ticket) and treat it as at least a 131 | mid-term commitment to maintain the package. 132 | 4. Customize `shellex` in self-contained, side-effect free config-snippets and 133 | add them - if you consider them useful to more then just yourself - in a 134 | pull-request to the conf-dir. 135 | 5. Contribute comments and documentation. Consider translating the manpage. 136 | Again, please announce your intention and again, if you translate to a 137 | language, that none of the core-developers speak (currently everything but 138 | English and German) please consider it to be at least a mid-term commitment 139 | to maintain the translation. 140 | 141 | 142 | Customizations 143 | ============== 144 | 145 | Users are invited to publish their customizations. Either as a contribution 146 | (see above) if these are changes that are a sensible default for all users, or 147 | in their own small packages which contain only the customizations. Especially, 148 | when the customization will be useful for many, but not all users. 149 | 150 | Existing customization projects are: 151 | 152 | * [pseyfert's customizations](https://github.com/pseyfert/shellex-customizations) 153 | * Your project here 154 | 155 | Configuration 156 | ============= 157 | 158 | Configuration of `shellex` has two parts: The first one are X-resources. 159 | Additionally to the urxvt-class, instances run by shellex will also look for 160 | the shellex-class. This makes it possible to customize the appearance of 161 | shellex without interfering with your usual terminals. 162 | 163 | There are also two additional resources defined by shellex: 164 | 165 | 166 | Resource | Values | Default | Description 167 | ----------- | -------------- | ------- | --- 168 | shellex.pos | pointer|focus | focus | If pointer, shellex shows the window on the window, the mousepointer is on, else it uses the output, where most of the currently focused window is (falling back to the pointer-method, if the root-window is focused). 169 | shellex.edge | bottom|top | top | On what screenedge to show shellex 170 | 171 | On start, `shellex` assembles a list of snippet basenames by looking at all of 172 | the paths listed below. For each snippet basename, `shellex` loads the first 173 | file it finds when looking through the paths in order: 174 | 175 | 1. `$XDG_CONFIG_HOME/.shellex`. Typically unset, defaulting to `$HOME/.config/shellex`. 176 | 2. `$HOME/.shellex` 177 | 3. `/etc/shellex` (shellex defaults, symlinks to `/usr/shellex/conf/`) 178 | 179 | To customize shellex, you can do the following things in 180 | `$XDG_CONFIG_HOME/.shellex` or `$HOME/.shellex/`: 181 | 182 | 1. Overwrite a default by creating a new snippet of the same name 183 | 2. Not include a default by creating a symlink to `/dev/null` of the same same 184 | 3. Include an example-snippet not used by default, by creating a symlink to `/usr/shellex/snippet` 185 | 4. Write you own snippets with a currently unused name 186 | 187 | To avoid naming-conflicts in the future, you should add a common suffix to all 188 | your own snippets. Snippets are run in ascending order. By choosing a number 189 | which sorts between/after the existing snippet(s) you can ensure it runs at the 190 | desired time. E.g. if your snippet beeps on errors, name it 15-errorbeep so that 191 | it sorts before 20-nobeep. 192 | 193 | Command-line 194 | ============ 195 | 196 | All command-line parameters given to `shellex` are passed directly to `urxvt`, 197 | so if you want to change colors or font, you can do it through the appropriate 198 | `urxvt`-parameters (or by using resources, for persistent configuration). For 199 | example, to get a dark grey background with a slightly yellow font you might 200 | start shellex with 201 | 202 | ```sh 203 | $ shellex -bg grey15 -fg linen 204 | ``` 205 | -------------------------------------------------------------------------------- /common.mk: -------------------------------------------------------------------------------- 1 | INSTALL=install 2 | SED=sed 3 | ifndef PREFIX 4 | PREFIX=/usr 5 | endif 6 | ifndef SYSCONFDIR 7 | ifeq ($(PREFIX),/usr) 8 | SYSCONFDIR=/etc 9 | else 10 | SYSCONFDIR=$(PREFIX)/etc 11 | endif 12 | endif 13 | LIBDIR ?= /lib 14 | 15 | SHELLEX_CFLAGS = -std=c99 16 | SHELLEX_CFLAGS += -Wall 17 | SHELLEX_CFLAGS += -Wunused-value 18 | 19 | sed_replace_vars := -e 's,@DESTDIR@,$(DESTDIR),g' \ 20 | -e 's,@PREFIX@,$(PREFIX),g' \ 21 | -e 's,@LIBDIR@,$(LIBDIR),g' \ 22 | -e 's,@SYSCONFDIR@,$(SYSCONFDIR),g' 23 | 24 | V ?= 0 25 | ifeq ($(V),0) 26 | # Don't print command lines which are run 27 | .SILENT: 28 | 29 | endif 30 | 31 | # always remake the following targets 32 | .PHONY: install clean dist distclean 33 | -------------------------------------------------------------------------------- /conf.mk: -------------------------------------------------------------------------------- 1 | INSTALL_TARGETS += install-conf install-rc 2 | CLEAN_TARGETS += clean-shellexrc 3 | ALL_TARGETS += conf/shellexrc 4 | 5 | default_confs := 10-autoexec 20-completion 20-nobeep 40-escape 40-home_end 40-setprompt 40-sigint 40-recent 90-hist 99-clear 6 | 7 | install-conf: 8 | echo "[INSTALL] $@" 9 | $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/conf 10 | for file in $(wildcard conf/[0-9][0-9]-*); \ 11 | do \ 12 | $(INSTALL) -m 0644 $${file} $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/conf/; \ 13 | done 14 | $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/shellex 15 | for link in $(default_confs); \ 16 | do \ 17 | [ -e $(DESTDIR)$(SYSCONFDIR)/shellex/$${link} ] || ln -s $(PREFIX)$(LIBDIR)/shellex/conf/$${link} $(DESTDIR)$(SYSCONFDIR)/shellex; \ 18 | done 19 | 20 | conf/shellexrc: conf/shellexrc.in 21 | echo "[SED] $@" 22 | $(SED) $(sed_replace_vars) $< > $@ 23 | 24 | install-rc: conf/shellexrc 25 | echo "[INSTALL] $@" 26 | $(INSTALL) -m 0644 conf/shellexrc $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/shellexrc 27 | [ -e $(DESTDIR)$(SYSCONFDIR)/shellexrc ] || ln -s $(PREFIX)$(LIBDIR)/shellex/shellexrc $(DESTDIR)$(SYSCONFDIR)/shellexrc 28 | 29 | clean-shellexrc: 30 | echo "[CLEAN] conf/shellexrc" 31 | rm -f conf/shellexrc 32 | -------------------------------------------------------------------------------- /conf/10-autoexec: -------------------------------------------------------------------------------- 1 | # vim:ft=zsh 2 | # Make zsh automatically execute a command, when enter is hit 3 | # © 2013 Axel Wagner and contributors (see also: LICENSE) 4 | 5 | function shellex_preexec () { 6 | # In $1 the command-line is given 7 | # In $3 the command-line with expanded aliases and function-bodies is given 8 | 9 | # Execute the tempfile, then exit 10 | zsh -c $3 > /dev/null 2>&1 & disown 11 | 12 | exit 13 | } 14 | 15 | # We use preexec_functions, so that the user can decide to overwrite our choice 16 | # or ammend it by own functions 17 | preexec_functions=(shellex_preexec) 18 | 19 | # preexec doesn't get executed on empty lines, so employ zle to exit on empty lines 20 | # https://www.reddit.com/r/zsh/comments/s6t6d/is_there_an_alias_for_an_empty_line/ 21 | function empty-buffer-to-exit() { 22 | if [[ $#BUFFER == 0 ]]; then 23 | fc -P 24 | exit 25 | fi 26 | } 27 | # set special widget, see man zshzle 28 | zle -N zle-line-finish empty-buffer-to-exit 29 | -------------------------------------------------------------------------------- /conf/20-completion: -------------------------------------------------------------------------------- 1 | # enable tab completion system 2 | # © 2016 Paul Seyfert and contributors (see also: LICENSE) 3 | autoload -U compinit 4 | compinit -C 5 | -------------------------------------------------------------------------------- /conf/20-nobeep: -------------------------------------------------------------------------------- 1 | # disable beeping of the shellex shell. e.g. when tab-completing 2 | # © 2016 Paul Seyfert and contributors (see also: LICENSE) 3 | setopt NO_BEEP 4 | 5 | -------------------------------------------------------------------------------- /conf/40-escape: -------------------------------------------------------------------------------- 1 | # vim:ft=zsh 2 | # Make zsh exit on escape 3 | # © 2013 Axel Wagner and contributors (see also: LICENSE) 4 | function _shellex_exit { 5 | fc -P 6 | exit 7 | } 8 | zle -N _shellex_exit 9 | bindkey '^[' _shellex_exit 10 | 11 | -------------------------------------------------------------------------------- /conf/40-home_end: -------------------------------------------------------------------------------- 1 | # vim:ft=zsh 2 | # Make home and end buttons usable 3 | # © 2014 Axel Wagner and contributors (see also: LICENSE) 4 | 5 | bindkey '^[[7~' beginning-of-line 6 | bindkey '^[[8~' end-of-line 7 | bindkey '^[[3~' delete-char 8 | -------------------------------------------------------------------------------- /conf/40-recent: -------------------------------------------------------------------------------- 1 | # 'ctrl-x r' will complete the 12 last modified (mtime) files/directories 2 | # © 2018 Michael Stapelberg and contributors (see also: LICENSE) 3 | 4 | # only add this completion if compinit has been called 5 | if [[ -v _comps ]] 6 | then 7 | # 'ctrl-x r' will complete the 12 last modified (mtime) files/directories 8 | zle -C newest-files complete-word _generic 9 | bindkey '^Xr' newest-files 10 | zstyle ':completion:newest-files:*' completer _files 11 | zstyle ':completion:newest-files:*' file-patterns '*~.*(omN[1,12])' 12 | zstyle ':completion:newest-files:*' menu select yes 13 | zstyle ':completion:newest-files:*' sort false 14 | zstyle ':completion:newest-files:*' matcher-list 'b:=*' # important 15 | fi 16 | -------------------------------------------------------------------------------- /conf/40-setprompt: -------------------------------------------------------------------------------- 1 | # vim:ft=zsh 2 | # Set the prompt 3 | # © 2013 Axel Wagner and contributors (see also: LICENSE) 4 | PROMPT="shellex> " 5 | -------------------------------------------------------------------------------- /conf/40-sigint: -------------------------------------------------------------------------------- 1 | # vim:ft=zsh 2 | # Make zsh exit on ^C 3 | # © 2013 Axel Wagner and contributors (see also: LICENSE) 4 | trap exit SIGINT 5 | -------------------------------------------------------------------------------- /conf/40-sudo: -------------------------------------------------------------------------------- 1 | # vim:ft=zsh 2 | # use gksudo (or kdesudo if gksudo not exists) instead of sudo 3 | # © 2013 Johannes Visintini and contributors (see also: LICENSE) 4 | which kdesudo > /dev/null 2>&1 && alias sudo='kdesudo' 5 | which gksudo > /dev/null 2>&1 && alias sudo='gksudo' 6 | -------------------------------------------------------------------------------- /conf/90-hist: -------------------------------------------------------------------------------- 1 | # vim:ft=zsh 2 | # Avoid configuration sourcing in history 3 | # © 2013 Paul Seyfert and contributors (see also: LICENSE) 4 | 5 | # ensure history gets actually written (shell gets closed before history gets 6 | # written otherwise) 7 | setopt inc_append_history 8 | 9 | HISTSIZE=100 10 | SAVEHIST=100 11 | 12 | # Push current history to stack (make e.g. sourcing of configuration 13 | # inaccessible to user). The stack won't be poped. Set HISTFILE to ~/.shellex 14 | # and read history from there. 15 | fc -p ~/.shellex_history 16 | -------------------------------------------------------------------------------- /conf/99-clear: -------------------------------------------------------------------------------- 1 | # vim:ft=zsh 2 | # Clear the terminal-window at start 3 | # © 2013 Axel Wagner and contributors (see also: LICENSE) 4 | clear 5 | -------------------------------------------------------------------------------- /conf/shellexrc.in: -------------------------------------------------------------------------------- 1 | # vim:ft=zsh 2 | # Source all conf files 3 | # © 2016 Paul Seyfert and contributors (see also: LICENSE) 4 | 5 | # get array of all relevant files 6 | # http://stackoverflow.com/a/10981499 7 | # http://unix.stackexchange.com/a/26825 8 | # parentheses after * steer zsh's globbing 9 | # . means "only regular files" 10 | # -. means "regular files and symlinks pointing to regular files" 11 | # N means "empty list in case of no matches" 12 | thefiles=( 13 | @SYSCONFDIR@/shellex/* 14 | ${XDG_CONFIG_HOME:-$HOME/.config}/shellex/*(-.N) 15 | $HOME/.shellex/*(-.N) 16 | ) 17 | 18 | # get the basenames of all files and make unique list 19 | # http://stackoverflow.com/a/9516801 20 | uniquified=( $( for f in "${thefiles[@]}" ; do basename $f ; done | sort -u ) ) 21 | 22 | # source each file from the first of: 23 | # 1. $XDG_CONFIG_HOME/.shellex (typically $HOME/.config/shellex) 24 | # 2. $HOME/.shellex 25 | # 3. @SYSCONFDIR@/shellex 26 | for f in $uniquified 27 | do 28 | # -r checks if file exists and is readable 29 | if [[ -r ${XDG_CONFIG_HOME:-$HOME/.config}/shellex/$f ]] 30 | then 31 | source $HOME/.config/shellex/$f 32 | elif [[ -r $HOME/.shellex/$f ]] 33 | then 34 | source $HOME/.shellex/$f 35 | else 36 | source @SYSCONFDIR@/shellex/$f 37 | fi 38 | done 39 | -------------------------------------------------------------------------------- /doc/autoresize.txt: -------------------------------------------------------------------------------- 1 | The process of automatically resizing the window to match the shell-output is 2 | surprisingly complex. Normally the way the shell and terminal orchestrate 3 | themselves to do the output is the following: 4 | The terminal gets resized and does a TIOCSWINSZ ioctl on the pty-fd over which 5 | the two processes communicate, giving the new dimenions. This prompts the 6 | terminal to send the shell a SIGWINCH. The shell handles this by doing a 7 | TIOCGWINSZ ioctl on the pty which returns the data the terminal gave. 8 | 9 | zsh now uses this to determine, wether or not e.g. a tabcompletion-suggestion 10 | fits on the terminal and if not, handles it differently. This is a problem for 11 | shellex, because when is starting it's output, there is not enough space, for 12 | the tabcompletion, so even if we immediately resize the terminalwindow, it will 13 | be too late and the shell-output is screwed up. 14 | 15 | We rectify this, by injecting a custom ioctl-function into zsh via LD_PRELOAD, 16 | which rewrites all TIOCGWINSZ-requests to have a constant size. This fakes to 17 | the shell that there is more space available, then there actually is. The 18 | actual number of rows is calculated on start of the urxvt and put into a 19 | temporary file. The size is chosen a bit smaller than the screen, such that if 20 | zsh needs even more space for the tabcompletion than fits on the screen, the 21 | zsh handeling to deal with less space gets active. The filename is generated 22 | with mktemp before either zsh or urxvt start. The file gets unlinked by 23 | preload/main.c once a successful read happened. 24 | 25 | Shrinking after tab completions mostly works fine: Depending on the 26 | tabcompletion settings, shrinking breaks once one hit the maximum size limit. 27 | -------------------------------------------------------------------------------- /doc/man/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | $(MAKE) -C ../.. mans 3 | 4 | clean: 5 | $(MAKE) -C ../.. clean-mans 6 | 7 | .PHONY: all clean 8 | -------------------------------------------------------------------------------- /doc/man/asciidoc.conf: -------------------------------------------------------------------------------- 1 | ifdef::doctype-manpage[] 2 | ifdef::backend-docbook[] 3 | [header] 4 | template::[header-declarations] 5 | 6 | 7 | {mantitle} 8 | {manvolnum} 9 | shellex 10 | 0.3 11 | shellex Manual 12 | 13 | 14 | {manname} 15 | {manpurpose} 16 | 17 | endif::backend-docbook[] 18 | endif::doctype-manpage[] 19 | -------------------------------------------------------------------------------- /doc/man/man.mk: -------------------------------------------------------------------------------- 1 | DISTCLEAN_TARGETS += clean-mans 2 | 3 | A2X = a2x 4 | 5 | A2X_MAN_CALL = $(V_A2X)$(A2X) -f manpage --asciidoc-opts="-f doc/man/asciidoc.conf" $(A2X_FLAGS) $< 6 | 7 | MANS = \ 8 | doc/man/shellex.1 9 | 10 | mans: $(MANS) 11 | 12 | %.1: %.man doc/man/asciidoc.conf 13 | $(A2X_MAN_CALL) 14 | 15 | %.man: %.man.in 16 | $(SED) $(sed_replace_vars) $< > $@ 17 | 18 | clean-mans: 19 | for file in $(basename $(MANS)); \ 20 | do \ 21 | rm -f $${file}.1 $${file}.man; \ 22 | done 23 | -------------------------------------------------------------------------------- /doc/man/shellex.man.in: -------------------------------------------------------------------------------- 1 | shellex(1) 2 | ========== 3 | Paul Seyfert 4 | v0.3, July 2018 5 | 6 | == NAME 7 | 8 | shellex - shell-based launcher 9 | 10 | == SYNOPSIS 11 | 12 | *shellex* [...] 13 | 14 | == OPTIONS 15 | 16 | All command-line parameters (together with some shellex-specific) are passed on 17 | to urxvt. This means, you can you e.g. '-bg grey20' for a lighter background. 18 | Using it for more than just customizing the appearance (for example adding own 19 | extensions) might stop shellex from working, so be careful. 20 | 21 | See urxvt(1) for a full list of options. 22 | 23 | == DESCRIPTION 24 | 25 | *shellex* is a shell-based launcher with a lot more features and a lot simpler 26 | design. It launches a shell (currently zsh(1)) and shows it in a small 27 | terminal (currently urxvt(1)), wrapping every command with a little bit of 28 | extra magic (redirecting stdout, stderr, disowning and closing the shell) to 29 | get more typical launcher-behaviour. 30 | 31 | This gives you a simple launcher with tab-completion and other shell-features, 32 | configurable in shell. 33 | 34 | == RESOURCES 35 | 36 | *shellex* uses two X-Resources at the moment, to manipulate its behaviour: 37 | 38 | shellex.pos:: 39 | If pointer, shellex shows the window on the window, the mousepointer is on. If 40 | focus, it uses the output, where most of the currently focused window is. 41 | Defaults to focus. 42 | 43 | shellex.edge:: 44 | On what screen edge to show the launcher (top or bottom). Defaults to top. 45 | 46 | Additionally all resources defined by urxvt (with class 'shellex' instead of 47 | 'urxvt') can be used to customize the appearance or behaviour of the used 48 | terminal window. 49 | 50 | == CONFIGURATION 51 | 52 | *shellex* configuration snippets can be found in *@PREFIX@@LIBDIR@/shellex/*. 53 | 54 | On start, *shellex* assembles a list of snippet basenames by looking at all of 55 | the paths listed below. For each snippet basename, *shellex* loads the first 56 | file it finds when looking through the paths in order: 57 | 58 | 1. $XDG_CONFIG_HOME/.shellex. Typically unset, defaulting to $HOME/.config/shellex. 59 | 2. $HOME/.shellex 60 | 3. @SYSCONFDIR@/shellex (shellex defaults, symlinks to *@PREFIX@@LIBDIR@/shellex/conf/*) 61 | 62 | To customize shellex, you can do the following things in 63 | *$XDG_CONFIG_HOME/.shellex* or *$HOME/.shellex/*: 64 | 65 | 1. Overwrite a default by creating a new snippet of the same name 66 | 2. Not include a default by creating a symlink to */dev/null* of the same same 67 | 3. Include an example-snippet not used by default, by creating a symlink to *@PREFIX@@LIBDIR@/shellex/snippet* 68 | 4. Write you own snippets with a currently unused name 69 | 70 | To avoid naming-conflicts in the future, you should add a common suffix to all 71 | your own snippets. Snippets are run in ascending order. By choosing a number 72 | which sorts between/after the existing snippet(s) you can ensure it runs at the 73 | desired time. E.g. if your snippet beeps on errors, name it 15-errorbeep so that 74 | it sorts before 20-nobeep. 75 | 76 | == AUTHORS 77 | 78 | Axel Wagner and contributors 79 | -------------------------------------------------------------------------------- /format.mk: -------------------------------------------------------------------------------- 1 | format: format_c format_perl format_vim 2 | 3 | format_perl: urxvt/shellex.in 4 | echo "[FORMAT] $^" 5 | cd $(shell dirname $^) && perltidy $(shell basename $^) 6 | 7 | format_c: preload/main.c 8 | echo "[FORMAT] $^" 9 | clang-format -i $^ 10 | 11 | format_vim: 12 | echo "[FORMAT]" 13 | for shfile in $(wildcard conf/*) shellex.in; do echo "[FORMAT] $${shfile}"; ./indent.sh $${shfile}; done 14 | 15 | .PHONY: format format_c format_perl format_vim 16 | -------------------------------------------------------------------------------- /indent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # vim:ft=zsh 4 | # Helper script for CI to auto indent files 5 | # © 2017 Paul Seyfert and contributors (see also: LICENSE) 6 | 7 | if [ -f "$1" ]; then 8 | vim -f +"set softtabstop=2" +"set tabstop=2" +"set shiftwidth=2" +"set expandtab" +"gg=G" +":x" $1 9 | else 10 | echo "USAGE: $0 " 11 | echo "to autoindent file with vim" 12 | fi 13 | exit $? 14 | -------------------------------------------------------------------------------- /preload/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: WebKit 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: DontAlign 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: false 10 | AlignTrailingComments: false 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: true 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: All 36 | BreakBeforeBraces: Attach 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: true 39 | ColumnLimit: 0 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: false 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 49 | IncludeCategories: 50 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 51 | Priority: 2 52 | - Regex: '^(<|"(gtest|isl|json)/)' 53 | Priority: 3 54 | - Regex: '.*' 55 | Priority: 1 56 | IndentCaseLabels: false 57 | IndentWidth: 4 58 | IndentWrappedFunctionNames: false 59 | KeepEmptyLinesAtTheStartOfBlocks: true 60 | MacroBlockBegin: '' 61 | MacroBlockEnd: '' 62 | MaxEmptyLinesToKeep: 1 63 | NamespaceIndentation: Inner 64 | ObjCBlockIndentWidth: 4 65 | ObjCSpaceAfterProperty: true 66 | ObjCSpaceBeforeProtocolList: true 67 | PenaltyBreakBeforeFirstCallParameter: 19 68 | PenaltyBreakComment: 300 69 | PenaltyBreakFirstLessLess: 120 70 | PenaltyBreakString: 1000 71 | PenaltyExcessCharacter: 1000000 72 | PenaltyReturnTypeOnItsOwnLine: 60 73 | PointerAlignment: Right 74 | ReflowComments: true 75 | SortIncludes: true 76 | SpaceAfterCStyleCast: false 77 | SpaceBeforeAssignmentOperators: true 78 | SpaceBeforeParens: ControlStatements 79 | SpaceInEmptyParentheses: false 80 | SpacesBeforeTrailingComments: 1 81 | SpacesInAngles: false 82 | SpacesInContainerLiterals: true 83 | SpacesInCStyleCastParentheses: false 84 | SpacesInParentheses: false 85 | SpacesInSquareBrackets: false 86 | Standard: Cpp03 87 | TabWidth: 8 88 | UseTab: Never 89 | ... 90 | 91 | -------------------------------------------------------------------------------- /preload/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * shellex - shell based launcher 3 | * This is a small LD_PRELOAD library to work around some issues 4 | * © 2013 Axel Wagner and contributors (see also: LICENSE) 5 | */ 6 | #define _GNU_SOURCE 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /* We can not take this from , because it would define the 13 | * ioctl-function itself 14 | */ 15 | struct winsize { 16 | unsigned short ws_row; 17 | unsigned short ws_col; 18 | unsigned short ws_xpixel; 19 | unsigned short ws_ypixel; 20 | }; 21 | 22 | int ioctl(int d, int request, char *argp) { 23 | static int (*orig_ioctl)(int, int, char *); 24 | if (orig_ioctl == NULL) { 25 | orig_ioctl = dlsym(RTLD_NEXT, "ioctl"); 26 | } 27 | 28 | /* We only care for TIOCGWINSZ ioctls */ 29 | if (request != 0x5413) { 30 | return orig_ioctl(d, request, argp); 31 | } 32 | 33 | static int max_rows = -1; 34 | 35 | /* ioctl gets called once before the perl module had the time to determine 36 | * the right size! Leave max_rows negative to indicat that it still needs to 37 | * be read from the SHELLEX_SIZE_FILE */ 38 | 39 | if (max_rows < 0) { 40 | 41 | char *fname = getenv("SHELLEX_SIZE_FILE"); 42 | if (fname != NULL && fname[0] != '\0') { 43 | FILE *stream = fopen(fname, "r"); 44 | char str[5] = "-500"; 45 | if (stream != NULL) { 46 | char *ret = fgets(str, 5, stream); 47 | fclose(stream); 48 | if (ret != NULL) { 49 | /* this may be -500 */ 50 | max_rows = atoi(str); 51 | if (max_rows > 0) { 52 | unlink(fname); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | int retval = orig_ioctl(d, request, (char *)argp); 60 | struct winsize *ws = (struct winsize *)argp; 61 | 62 | /* max_rows is still negative at first invocation */ 63 | int fheight = ws->ws_ypixel / ws->ws_row; 64 | ws->ws_row = (max_rows > 0) ? max_rows : 25; 65 | ws->ws_ypixel = ws->ws_row * fheight; 66 | 67 | return retval; 68 | } 69 | -------------------------------------------------------------------------------- /preload/preload.mk: -------------------------------------------------------------------------------- 1 | ALL_TARGETS += preload/shellex_preload.so 2 | INSTALL_TARGETS += install-shellex_preload 3 | CLEAN_TARGETS += clean-shellex_preload 4 | 5 | SHELLEX_PRELOAD_LDFLAGS += -shared 6 | SHELLEX_PRELOAD_CFLAGS += -fPIC 7 | 8 | preload/shellex_preload.so: preload/main.c 9 | echo "[CC] $@" 10 | $(CC) $(SHELLEX_CPPFLAGS) $(CPPFLAGS) $(SHELLEX_CFLAGS) $(CFLAGS) $(SHELLEX_PRELOAD_CFLAGS) $(LDFLAGS) $(SHELLEX_LDFLAGS) $(SHELLEX_PRELOAD_LDFLAGS) -o $@ $< 11 | 12 | install-shellex_preload: preload/shellex_preload.so 13 | echo "[INSTALL] $<" 14 | $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex 15 | $(INSTALL) -m 0755 $< $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/ 16 | 17 | clean-shellex_preload: 18 | echo "[CLEAN] shellex_preload" 19 | rm -f preload/shellex_preload.so 20 | -------------------------------------------------------------------------------- /shellex.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # shellex - shell based launcher 3 | # This is a short shellscript to set some needed variables and defaults. 4 | # See shellex(1) for information on invocation. 5 | # © 2013 Axel Wagner and contributors (see also: LICENSE) 6 | 7 | SHELLEX_PRELOAD="@PREFIX@@LIBDIR@/shellex/shellex_preload.so" 8 | export SHELLEX_SIZE_FILE=$(mktemp -t shellex-size-XXXXXXXX) 9 | exec urxvt -perl-lib @PREFIX@@LIBDIR@/shellex/urxvt -pe shellex -override-redirect -name shellex $* -e env LD_PRELOAD=$SHELLEX_PRELOAD zsh -f 10 | -------------------------------------------------------------------------------- /shellex.mk: -------------------------------------------------------------------------------- 1 | ALL_TARGETS += shellex 2 | INSTALL_TARGETS += install-shellex 3 | CLEAN_TARGETS += clean-shellex 4 | 5 | shellex: shellex.in 6 | echo "[SED] $@" 7 | $(SED) $(sed_replace_vars) $< > $@ 8 | 9 | install-shellex: shellex 10 | echo "[INSTALL] $<" 11 | $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin 12 | $(INSTALL) -m 0755 shellex $(DESTDIR)$(PREFIX)/bin/ 13 | 14 | clean-shellex: 15 | echo "[CLEAN] shellex" 16 | rm -f shellex 17 | -------------------------------------------------------------------------------- /urxvt/.perltidyrc: -------------------------------------------------------------------------------- 1 | -pt=2 2 | -ce 3 | -l=91 4 | -b 5 | -------------------------------------------------------------------------------- /urxvt/shellex.in: -------------------------------------------------------------------------------- 1 | # vim:ft=perl 2 | #line 3 3 | # shellex - shell based launcher 4 | # This is the urxvt extension part of shellex. 5 | # © 2013 Axel Wagner and contributors (see also: LICENSE) 6 | use X11::Protocol; 7 | use POSIX qw|ceil|; 8 | use strict; 9 | 10 | # At the time of the original version of this function, the existing 11 | # Randr-modules on CPAN seem to work only barely, so instead we just parse the 12 | # output of xrandr --listactivemonitors. This is an uglyness, that should go 13 | # away some time in the future. 14 | sub get_outputs { 15 | my @outputs = (); 16 | for my $line (qx(xrandr --listactivemonitors)) { 17 | next if $line =~ /Monitors: /; 18 | 19 | # output looks like: 20 | #Monitors: 2 21 | # 0: +*LVDS-1 1366/277x768/156+0+0 LVDS-1 22 | # 1: +HDMI-2 1920/518x1200/324+1366+0 HDMI-2 23 | my ($w, $h, $x, $y) = ($line =~ /(\d+)\/\d+x(\d+)\/\d+\+(\d+)\+(\d+)/); 24 | print "found monitor with dimensions and position w=$w h=$h x=$x y=$y\n"; 25 | push @outputs, { w => $w, h => $h, x => $x, y => $y }; 26 | } 27 | return @outputs; 28 | } 29 | 30 | # This takes a list of outputs and looks up the one, the mouse pointer 31 | # currently is on. 32 | sub geometry_from_ptr { 33 | my ($self) = @_; 34 | 35 | my @outputs = get_outputs(); 36 | 37 | my $ptr = { $self->{X}->QueryPointer($self->DefaultRootWindow) }; 38 | 39 | for my $output (@outputs) { 40 | if ($output->{x} <= $ptr->{root_x} && $ptr->{root_x} < $output->{x} + $output->{w}) 41 | { 42 | $self->{x} = $output->{x}; 43 | if ($self->{bottom}) { 44 | 45 | # The real y-coordinate will change during execution, when the window grows 46 | $self->{y} = $output->{y} + $output->{h}; 47 | } else { 48 | $self->{y} = $output->{y}; 49 | } 50 | $self->{w} = $output->{w}; 51 | $self->{h} = $output->{h}; 52 | } 53 | } 54 | } 55 | 56 | # Helper, that take a list of numbers and return the max resp. min 57 | sub max { 58 | my $max = shift; 59 | while (my $n = shift) { 60 | $max = $n > $max ? $n : $max; 61 | } 62 | return $max; 63 | } 64 | 65 | sub min { 66 | my $min = shift; 67 | while (my $n = shift) { 68 | $min = $n < $min ? $n : $min; 69 | } 70 | return $min; 71 | } 72 | 73 | # This takes a list of outputs and looks up the one, that contains most of the 74 | # window having the input focus currently 75 | sub geometry_from_focus { 76 | my ($self) = @_; 77 | 78 | my @outputs = get_outputs(); 79 | 80 | # Look up the window that currently has the input focus 81 | my ($focus, $revert) = $self->{X}->GetInputFocus(); 82 | 83 | # If the root-window is focused, we fall back to using the pointer-position 84 | if ($focus == $self->DefaultRootWindow) { 85 | print "Fall back to getting shellex-position from pointer\n"; 86 | return $self->geometry_from_ptr(); 87 | } 88 | 89 | my $geom = { $self->{X}->GetGeometry($focus) }; 90 | my ($fw, $fh) = ($geom->{width}, $geom->{height}); 91 | 92 | print "Focus $focus (${fw}x${fh})\n"; 93 | 94 | # The (x,y) coordinates we get are relative to the parent not the 95 | # root-window. So we just translate the coordinates of the upper-left 96 | # corner into the coordinate-system of the root-window 97 | my (undef, undef, $fx, $fy) = 98 | $self->{X}->TranslateCoordinates($focus, $self->DefaultRootWindow, 0, 0); 99 | 100 | # Returns the area (in pixel²) of the intersection of two rectangles. 101 | # To understand how it works, best draw a picture. 102 | my $intersection = sub { 103 | my ($x, $y, $w, $h) = @_; 104 | my $dx; 105 | if ($x < $fx) { 106 | $dx = $x + $w - $fx; 107 | } else { 108 | $dx = $fx + $fw - $x; 109 | } 110 | $dx = max(0, min($dx, $fw, $w)); 111 | 112 | my $dy; 113 | if ($y < $fy) { 114 | $dy = $y + $h - $fy; 115 | } else { 116 | $dy = $fy + $fh - $y; 117 | } 118 | $dy = max(0, min($dy, $fh, $h)); 119 | 120 | return $dx * $dy; 121 | }; 122 | 123 | my $max_area = 0; 124 | for my $output (@outputs) { 125 | my $area = $intersection->($output->{x}, $output->{y}, $output->{w}, $output->{h}); 126 | if ($area >= $max_area) { 127 | $max_area = $area; 128 | $self->{x} = $output->{x}; 129 | if ($self->{bottom}) { 130 | 131 | # The real y-coordinate will change during execution, when the window grows 132 | $self->{y} = $output->{y} + $output->{h}; 133 | } else { 134 | $self->{y} = $output->{y}; 135 | } 136 | $self->{w} = $output->{w}; 137 | $self->{h} = $output->{h}; 138 | } 139 | } 140 | } 141 | 142 | # This hook is run when the extension is first initialized, before any windows 143 | # are created or mapped. There is not much work we can do here. 144 | sub on_init { 145 | my ($self) = @_; 146 | 147 | $self->{X} = X11::Protocol->new($self->display_id); 148 | 149 | # Some reasonably sane values in case all our methods to determine a 150 | # geometry fails. 151 | $self->{x} = 0; 152 | $self->{y} = 0; 153 | $self->{w} = 1024; 154 | $self->{h} = 768; 155 | 156 | (); 157 | } 158 | 159 | # This hook is run after the window is created, but before it is mapped, so 160 | # this is the place to set the geometry to what we want 161 | sub on_start { 162 | my ($self) = @_; 163 | 164 | # TODO: Remove compatibility code in future version 165 | if (defined $self->x_resource("%.edge") || defined $self->x_resource("%.pos")) { 166 | print 167 | "WARNING: URxvt.shellex.* resources are deprecated and will be removed in the future. Use shellex.*\n"; 168 | } 169 | 170 | if ($self->x_resource("edge") eq 'bottom' || $self->x_resource("%.edge") eq 'bottom') { 171 | print "position should be at the bottom\n"; 172 | $self->{bottom} = 1; 173 | $self->{y} = $self->{h}; 174 | } else { 175 | print "position should be at the top\n"; 176 | } 177 | 178 | if ($self->x_resource("pos") eq 'pointer' || $self->x_resource("%.pos") eq 'pointer') { 179 | print "Getting shellex-position from pointer\n"; 180 | $self->geometry_from_ptr(); 181 | } else { 182 | print "Getting shellex-position from focused window\n"; 183 | $self->geometry_from_focus(); 184 | } 185 | 186 | # This environment variable is used by the LD_PRELOAD ioctl-override to 187 | # determine the values to send to the shell 188 | # TODO revisit communication protocol (from file to pipe?) 189 | # TODO check if user defined their own SHELLEX_MAX_ROWS, which should be used like 190 | #$ENV{SHELLEX_MAX_ROWS} = $sane_max_rows < $ENV{SHELLEX_MAX_ROWS} ? $sane_max_rows : $ENV{SHELLEX_MAX_ROWS} ; 191 | # 192 | # shellex should leave part of the screen uncovered (10 lines), this assumes 193 | # that a screen will always be larger than 10 lines. 194 | my $sane_max_rows = int($self->{h} / $self->fheight) - 10; 195 | 196 | $ENV{SHELLEX_MAX_ROWS} = $sane_max_rows; 197 | my $filename = $ENV{SHELLEX_SIZE_FILE}; 198 | open(my $fh, '>', $filename); 199 | print $fh "$ENV{SHELLEX_MAX_ROWS}\n"; 200 | close $fh; 201 | print "wrote $sane_max_rows as max rows to $filename done\n"; 202 | 203 | $self->{border} = $self->x_resource('internalBorder'); 204 | 205 | # the compiled-in default if the resource is not set 206 | $self->{border} //= 2; 207 | 208 | $self->{border} += $self->x_resource('externalBorder'); 209 | 210 | $self->{row_height} = $self->fheight + $self->x_resource('lineSpace'); 211 | 212 | my $height = $self->{row_height} + 2 * $self->{border}; 213 | my $y = $self->{y}; 214 | 215 | # Our initial position is different, if we have to be at the bottom 216 | $y -= $height if $self->{bottom}; 217 | 218 | $self->XMoveResizeWindow($self->parent, $self->{x}, $y, $self->{w}, $height); 219 | 220 | print "loading config\n"; 221 | $self->tt_write($self->locale_encode("unset LD_PRELOAD\n")); 222 | $self->tt_write($self->locale_encode(". @SYSCONFDIR@/shellexrc\n")); 223 | 224 | (); 225 | } 226 | 227 | # This hook is run every time a line was changed. We do some resizing here, 228 | # because this catches most cases where we would want to shrink our window. 229 | sub on_line_update { 230 | my ($self, $row) = @_; 231 | print "line_update(row = $row)\n"; 232 | 233 | # Determine the last row, that is not empty. 234 | # TODO: Does this work as intended, if there is an empty line in the 235 | # middle? 236 | my $nrow = 0; 237 | for my $i ($self->top_row .. $self->nrow - 1) { 238 | if ($self->ROW_l($i) > 0) { 239 | $nrow++; 240 | } 241 | } 242 | $nrow = $nrow > $ENV{SHELLEX_MAX_ROWS} ? $ENV{SHELLEX_MAX_ROWS} : $nrow; 243 | $nrow = $nrow > 0 ? $nrow : 1; 244 | print "resizing to $nrow\n"; 245 | 246 | # If the window is supposed to be at the bottom, we have to move the 247 | # window up a little bit 248 | my $y = $self->{y}; 249 | if ($self->{bottom}) { 250 | $y -= 2 + $nrow * $self->fheight; 251 | } 252 | $self->cmd_parse("\e[8;$nrow;t\e[3;$self->{x};${y}t"); 253 | (); 254 | } 255 | 256 | # Predict the number of rows the terminal will have, after adding $string at 257 | # the current position 258 | sub predict_term_size { 259 | my ($self, $string) = @_; 260 | 261 | my ($row, $col) = $self->screen_cur(); 262 | my $i = $self->top_row; 263 | my $n = 0; 264 | 265 | # We iterate over all lines and accumulate the number of rows. If the 266 | # curser is not at the current line, we can just add its number of rows to 267 | # the total, else we test, if it grows when adding the string and add an 268 | # according number to the total 269 | while ($i < $self->nrow) { 270 | my $line = $self->line($i); 271 | $i += $line->end - $line->beg + 1; 272 | unless ($line->beg <= $row && $row <= $line->end) { 273 | $n += $line->end - $line->beg + 1; 274 | next; 275 | } 276 | 277 | my $len = ($row - $line->beg) * $self->ncol + $col; 278 | 279 | # Because there might be control-sequences in $string, affecting the 280 | # number of lines, we need to manually walk it 281 | for (my $j = 0 ; $j < length($string) ; $j++) { 282 | 283 | # Linebreaks mean the creating of a new line, finishing the old one 284 | if (substr($string, $j, 1) eq "\n") { 285 | $len = ($len == 0 ? 1 : $len); 286 | $n += ceil(($len * 1.0) / $self->ncol); 287 | $len = 0; 288 | next; 289 | } 290 | 291 | # Carriage-returns mean starting from the beginning. Though the new 292 | # len does not really have to be 0 (because the text is not 293 | # actually erased) it is a good enough estimate for now 294 | if (substr($string, $j, 1) eq "\r") { 295 | $len = 0; 296 | next; 297 | } 298 | 299 | # We just add one per other char. This actually might not work 300 | # correctly with wide-chars, but it is a good enough estimate for 301 | # now 302 | $len++; 303 | } 304 | $n += ceil(($len + 1.0) / $self->ncol); 305 | } 306 | print "Predicting term size: $n\n"; 307 | return $n; 308 | } 309 | 310 | # This hook is run every time before there is text output. We resize here, 311 | # immediately before new lines would be added, which would create scrolling 312 | sub on_add_lines { 313 | my ($self, $string) = @_; 314 | my $str = $string; 315 | $str =~ s/\n/\\n/g; 316 | $str =~ s/\r/\\r/g; 317 | print "add_lines(string = \"$str\")\n"; 318 | 319 | my $nrow = $self->predict_term_size($string); 320 | $nrow = $nrow > $ENV{SHELLEX_MAX_ROWS} ? $ENV{SHELLEX_MAX_ROWS} : $nrow; 321 | $nrow = $nrow > 0 ? $nrow : 1; 322 | print "resizing to $nrow\n"; 323 | 324 | # If the window is supposed to be at the bottom, we have to move the 325 | # window up a little bit 326 | my $y = $self->{y}; 327 | if ($self->{bottom}) { 328 | $y -= 2 + $nrow * $self->fheight; 329 | } 330 | $self->cmd_parse("\e[8;$nrow;t\e[3;$self->{x};${y}t"); 331 | (); 332 | } 333 | 334 | # Just for debugging 335 | sub on_size_change { 336 | my ($self, $nw, $nh) = @_; 337 | print "size_change($nw, $nh)\n"; 338 | (); 339 | } 340 | 341 | sub on_view_change { 342 | my ($self, $offset) = @_; 343 | print "view_change(offset = $offset)\n"; 344 | (); 345 | } 346 | 347 | sub on_scroll_back { 348 | my ($self, $lines, $saved) = @_; 349 | print "scroll_back(lines = $lines, saved = $saved)\n"; 350 | (); 351 | } 352 | 353 | sub on_x_event { 354 | my ($self, $event) = @_; 355 | 356 | if ($event->{type} == urxvt::EnterNotify) { 357 | $self->{X}->SetInputFocus($self->parent, 2, $self->{data}{event}{time}); 358 | $self->{X}->GetInputFocus(); 359 | } 360 | 361 | (); 362 | } 363 | 364 | # This hook is run directly after the window was mapped (= displayed on 365 | # screen). We grab the keyboard here. 366 | sub on_map_notify { 367 | my ($self, $ev) = @_; 368 | 369 | $self->{X}->SetInputFocus($self->parent, 2, $self->{data}{event}{time}); 370 | 371 | # We use GetInputFocus as a syncing-mechanism 372 | $self->{X}->GetInputFocus(); 373 | 374 | $self->vt_emask_add(urxvt::EnterWindowMask); 375 | (); 376 | } 377 | -------------------------------------------------------------------------------- /urxvt/urxvt_shellex.mk: -------------------------------------------------------------------------------- 1 | ALL_TARGETS += urxvt/shellex 2 | INSTALL_TARGETS += install-urxvt_shellex 3 | CLEAN_TARGETS += clean-urxvt_shellex 4 | 5 | urxvt/shellex: urxvt/shellex.in 6 | echo "[SED] $@" 7 | $(SED) $(sed_replace_vars) $< > $@ 8 | 9 | install-urxvt_shellex: urxvt/shellex 10 | echo "[INSTALL] $<" 11 | $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/urxvt 12 | $(INSTALL) -m 0644 urxvt/shellex $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/urxvt/ 13 | 14 | clean-urxvt_shellex: 15 | echo "[CLEAN] urxvt/shellex" 16 | rm -f urxvt/shellex 17 | --------------------------------------------------------------------------------