shellex-0.3/0000755000175000017500000000000013403571164015222 5ustar coffeedrinkercoffeedrinkershellex-0.3/common.mk0000644000175000017500000000117313403562665017053 0ustar coffeedrinkercoffeedrinkerINSTALL=install SED=sed ifndef PREFIX PREFIX=/usr endif ifndef SYSCONFDIR ifeq ($(PREFIX),/usr) SYSCONFDIR=/etc else SYSCONFDIR=$(PREFIX)/etc endif endif LIBDIR ?= /lib SHELLEX_CFLAGS = -std=c99 SHELLEX_CFLAGS += -Wall SHELLEX_CFLAGS += -Wunused-value sed_replace_vars := -e 's,@DESTDIR@,$(DESTDIR),g' \ -e 's,@PREFIX@,$(PREFIX),g' \ -e 's,@LIBDIR@,$(LIBDIR),g' \ -e 's,@SYSCONFDIR@,$(SYSCONFDIR),g' V ?= 0 ifeq ($(V),0) # Don't print command lines which are run .SILENT: endif # always remake the following targets .PHONY: install clean dist distclean shellex-0.3/README.md0000644000175000017500000002000613403562665016505 0ustar coffeedrinkercoffeedrinkershellex - Shell-based launcher ============================== `shellex` is supposed to be a dmenu-style launcher with a lot more features and a lot simpler design. It launches a shell (currently `zsh`) and shows it in a small terminal, wrapping every command with a little bit of extra magic (redirecting stdout, stderr, disowning and closing the shell) to get more typical launcher-behaviour. This gives you a simple launcher with tab-completion and other shell-features, configurable in shell. See this video for a short demonstration and comparison to another app starter: ![demo](http://virgilio.mib.infn.it/~seyfert/images/shellexdemo.gif) I tried to do this a few years back, then using C and implementing the terminal-operations myself. This turned out to be a very bad idea, it made the design overly complex and the state I left it in had regular segfaults and was far from working. After not much seem to happen in that direction, I decided to start again, this time using `urxvt` to do the terminal-part, which turned out to be really easy. While this had the label “early prototype”, it has worked in daily use quite reasonably. A few possible improvements were never apparently important enough to invest serious amounts of time and work into them. Still, contributions are welcome. Architecture ============ `shellex` has three parts: * [A small shell-script](shellex.in) that just starts a `urxvt` with some extra parameters * [An urxvt-extension](urxvt/shellex.in) that manages the terminal/displaying part. * [configfile](conf) that do all stuff relating to the functional behaviour (Planned) Features ================== Working: * Launching Applications (yay) * Commandline parameters * Basic Tab-completion * Starting on the right output (configurable, either the output containing the currently focused window or the output containing the mousepointer) * Dynamic resizing of the launcher-window e.g. for multiple lines of suggestions for tab-completions (see [doc/autoresize.txt](doc/autoresize.txt)) Planned, but not implemented: * Buffering/showing some output, for errors etc. We have to think about some magic way to determine, whether output is helpful or the launcher should be hidden immediately * dmenu-like completion, typing part of a command still completing (maybe zsh has something to do that? possibly [fzf](https://github.com/junegunn/fzf)) * [.desktop-file integration](https://github.com/pseyfert/shellex-desktop) * Your ticket here Installation ============ There are packages in * [debian](http://packages.debian.org/search?keywords=shellex&searchon=names&suite=all§ion=all&sourceid=mozilla-search) * 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/)) * Gentoo: there is an [Overlay](https://github.com/proxypoke/gentoo-overlay) which contains shellex If you are on one of these distributions, we encourage you to install `shellex` via your package manager. Else, or if you want to help developing, just do ```sh $ git clone git://github.com/Merovius/shellex.git $ cd shellex $ make $ make install ``` for installing in `$(HOME)/.local` ```sh $ git clone git://github.com/Merovius/shellex.git $ cd shellex $ PREFIX=$HOME/.local make $ PREFIX=$HOME/.local make install ``` Usage ===== After installing `shellex` you probably want to bind it to a shortcut - most likely Alt+F2. How to do this depends on your desktop environment and/or window manager. ## xbindkeys If you're running [xbindkeys](http://www.nongnu.org/xbindkeys/xbindkeys.html), the entry for your `~/.xbindkeysrc` file might look like: ``` "shellex" alt + c:68 ``` ## i3 [i3](i3wm.org) shortcuts can be modified as described in the [official documentation](https://i3wm.org/docs/userguide.html). ``` bindsym Mod1+F2 exec shellex ``` Contributing ============ `shellex` is a very young project, it would highly profit from your help. The following is a not comprehensive list of highly appreciated ways to contribute: 1. Test it and make [tickets](https://github.com/Merovius/shellex/issues) for *any* problems you stumble upon or ideas you have to make it better. 2. Have a look at a [list of issues](https://github.com/Merovius/shellex/issues) and find one to fix. But please announce your intention to fix it, so that we can be sure that it will be merged and there is no duplication of effort. 3. Have a look at the [list of packaged dirstributions](https://github.com/Merovius/shellex#installation). If your favourite distribution is not on it, please package it. Please announce your intent to do so (in a ticket) and treat it as at least a mid-term commitment to maintain the package. 4. Customize `shellex` in self-contained, side-effect free config-snippets and add them - if you consider them useful to more then just yourself - in a pull-request to the conf-dir. 5. Contribute comments and documentation. Consider translating the manpage. Again, please announce your intention and again, if you translate to a language, that none of the core-developers speak (currently everything but English and German) please consider it to be at least a mid-term commitment to maintain the translation. Customizations ============== Users are invited to publish their customizations. Either as a contribution (see above) if these are changes that are a sensible default for all users, or in their own small packages which contain only the customizations. Especially, when the customization will be useful for many, but not all users. Existing customization projects are: * [pseyfert's customizations](https://github.com/pseyfert/shellex-customizations) * Your project here Configuration ============= Configuration of `shellex` has two parts: The first one are X-resources. Additionally to the urxvt-class, instances run by shellex will also look for the shellex-class. This makes it possible to customize the appearance of shellex without interfering with your usual terminals. There are also two additional resources defined by shellex: Resource | Values | Default | Description ----------- | -------------- | ------- | --- 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). shellex.edge | bottom|top | top | On what screenedge to show shellex On start, `shellex` assembles a list of snippet basenames by looking at all of the paths listed below. For each snippet basename, `shellex` loads the first file it finds when looking through the paths in order: 1. `$XDG_CONFIG_HOME/.shellex`. Typically unset, defaulting to `$HOME/.config/shellex`. 2. `$HOME/.shellex` 3. `/etc/shellex` (shellex defaults, symlinks to `/usr/shellex/conf/`) To customize shellex, you can do the following things in `$XDG_CONFIG_HOME/.shellex` or `$HOME/.shellex/`: 1. Overwrite a default by creating a new snippet of the same name 2. Not include a default by creating a symlink to `/dev/null` of the same same 3. Include an example-snippet not used by default, by creating a symlink to `/usr/shellex/snippet` 4. Write you own snippets with a currently unused name To avoid naming-conflicts in the future, you should add a common suffix to all your own snippets. Snippets are run in ascending order. By choosing a number which sorts between/after the existing snippet(s) you can ensure it runs at the desired time. E.g. if your snippet beeps on errors, name it 15-errorbeep so that it sorts before 20-nobeep. Command-line ============ All command-line parameters given to `shellex` are passed directly to `urxvt`, so if you want to change colors or font, you can do it through the appropriate `urxvt`-parameters (or by using resources, for persistent configuration). For example, to get a dark grey background with a slightly yellow font you might start shellex with ```sh $ shellex -bg grey15 -fg linen ``` shellex-0.3/shellex.in0000644000175000017500000000075513403562665017233 0ustar coffeedrinkercoffeedrinker#!/bin/sh # shellex - shell based launcher # This is a short shellscript to set some needed variables and defaults. # See shellex(1) for information on invocation. # © 2013 Axel Wagner and contributors (see also: LICENSE) SHELLEX_PRELOAD="@PREFIX@@LIBDIR@/shellex/shellex_preload.so" export SHELLEX_SIZE_FILE=$(mktemp -t shellex-size-XXXXXXXX) exec urxvt -perl-lib @PREFIX@@LIBDIR@/shellex/urxvt -pe shellex -override-redirect -name shellex $* -e env LD_PRELOAD=$SHELLEX_PRELOAD zsh -f shellex-0.3/format.mk0000644000175000017500000000061613403562665017054 0ustar coffeedrinkercoffeedrinkerformat: format_c format_perl format_vim format_perl: urxvt/shellex.in echo "[FORMAT] $^" cd $(shell dirname $^) && perltidy $(shell basename $^) format_c: preload/main.c echo "[FORMAT] $^" clang-format -i $^ format_vim: echo "[FORMAT]" for shfile in $(wildcard conf/*) shellex.in; do echo "[FORMAT] $${shfile}"; ./indent.sh $${shfile}; done .PHONY: format format_c format_perl format_vim shellex-0.3/preload/0000755000175000017500000000000013403562665016656 5ustar coffeedrinkercoffeedrinkershellex-0.3/preload/preload.mk0000644000175000017500000000127013403562665020635 0ustar coffeedrinkercoffeedrinkerALL_TARGETS += preload/shellex_preload.so INSTALL_TARGETS += install-shellex_preload CLEAN_TARGETS += clean-shellex_preload SHELLEX_PRELOAD_LDFLAGS += -shared SHELLEX_PRELOAD_CFLAGS += -fPIC preload/shellex_preload.so: preload/main.c echo "[CC] $@" $(CC) $(SHELLEX_CPPFLAGS) $(CPPFLAGS) $(SHELLEX_CFLAGS) $(CFLAGS) $(SHELLEX_PRELOAD_CFLAGS) $(LDFLAGS) $(SHELLEX_LDFLAGS) $(SHELLEX_PRELOAD_LDFLAGS) -o $@ $< install-shellex_preload: preload/shellex_preload.so echo "[INSTALL] $<" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex $(INSTALL) -m 0755 $< $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/ clean-shellex_preload: echo "[CLEAN] shellex_preload" rm -f preload/shellex_preload.so shellex-0.3/preload/.clang-format0000644000175000017500000000512613403562665021235 0ustar coffeedrinkercoffeedrinker--- Language: Cpp # BasedOnStyle: WebKit AccessModifierOffset: -4 AlignAfterOpenBracket: DontAlign AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: false AlignOperands: false AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false IndentBraces: false BreakBeforeBinaryOperators: All BreakBeforeBraces: Attach BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: true ColumnLimit: 0 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IndentCaseLabels: false IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: Inner ObjCBlockIndentWidth: 4 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp03 TabWidth: 8 UseTab: Never ... shellex-0.3/preload/main.c0000644000175000017500000000364413403562665017755 0ustar coffeedrinkercoffeedrinker/* * shellex - shell based launcher * This is a small LD_PRELOAD library to work around some issues * © 2013 Axel Wagner and contributors (see also: LICENSE) */ #define _GNU_SOURCE #include #include #include #include /* We can not take this from , because it would define the * ioctl-function itself */ struct winsize { unsigned short ws_row; unsigned short ws_col; unsigned short ws_xpixel; unsigned short ws_ypixel; }; int ioctl(int d, int request, char *argp) { static int (*orig_ioctl)(int, int, char *); if (orig_ioctl == NULL) { orig_ioctl = dlsym(RTLD_NEXT, "ioctl"); } /* We only care for TIOCGWINSZ ioctls */ if (request != 0x5413) { return orig_ioctl(d, request, argp); } static int max_rows = -1; /* ioctl gets called once before the perl module had the time to determine * the right size! Leave max_rows negative to indicat that it still needs to * be read from the SHELLEX_SIZE_FILE */ if (max_rows < 0) { char *fname = getenv("SHELLEX_SIZE_FILE"); if (fname != NULL && fname[0] != '\0') { FILE *stream = fopen(fname, "r"); char str[5] = "-500"; if (stream != NULL) { char *ret = fgets(str, 5, stream); fclose(stream); if (ret != NULL) { /* this may be -500 */ max_rows = atoi(str); if (max_rows > 0) { unlink(fname); } } } } } int retval = orig_ioctl(d, request, (char *)argp); struct winsize *ws = (struct winsize *)argp; /* max_rows is still negative at first invocation */ int fheight = ws->ws_ypixel / ws->ws_row; ws->ws_row = (max_rows > 0) ? max_rows : 25; ws->ws_ypixel = ws->ws_row * fheight; return retval; } shellex-0.3/conf.mk0000644000175000017500000000213213403562665016504 0ustar coffeedrinkercoffeedrinkerINSTALL_TARGETS += install-conf install-rc CLEAN_TARGETS += clean-shellexrc ALL_TARGETS += conf/shellexrc default_confs := 10-autoexec 20-completion 20-nobeep 40-escape 40-home_end 40-setprompt 40-sigint 40-recent 90-hist 99-clear install-conf: echo "[INSTALL] $@" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/conf for file in $(wildcard conf/[0-9][0-9]-*); \ do \ $(INSTALL) -m 0644 $${file} $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/conf/; \ done $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/shellex for link in $(default_confs); \ do \ [ -e $(DESTDIR)$(SYSCONFDIR)/shellex/$${link} ] || ln -s $(PREFIX)$(LIBDIR)/shellex/conf/$${link} $(DESTDIR)$(SYSCONFDIR)/shellex; \ done conf/shellexrc: conf/shellexrc.in echo "[SED] $@" $(SED) $(sed_replace_vars) $< > $@ install-rc: conf/shellexrc echo "[INSTALL] $@" $(INSTALL) -m 0644 conf/shellexrc $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/shellexrc [ -e $(DESTDIR)$(SYSCONFDIR)/shellexrc ] || ln -s $(PREFIX)$(LIBDIR)/shellex/shellexrc $(DESTDIR)$(SYSCONFDIR)/shellexrc clean-shellexrc: echo "[CLEAN] conf/shellexrc" rm -f conf/shellexrc shellex-0.3/urxvt/0000755000175000017500000000000013403562665016420 5ustar coffeedrinkercoffeedrinkershellex-0.3/urxvt/.perltidyrc0000644000175000017500000000002313403562665020575 0ustar coffeedrinkercoffeedrinker-pt=2 -ce -l=91 -b shellex-0.3/urxvt/shellex.in0000644000175000017500000002727513403562665020431 0ustar coffeedrinkercoffeedrinker# vim:ft=perl #line 3 # shellex - shell based launcher # This is the urxvt extension part of shellex. # © 2013 Axel Wagner and contributors (see also: LICENSE) use X11::Protocol; use POSIX qw|ceil|; use strict; # At the time of the original version of this function, the existing # Randr-modules on CPAN seem to work only barely, so instead we just parse the # output of xrandr --listactivemonitors. This is an uglyness, that should go # away some time in the future. sub get_outputs { my @outputs = (); for my $line (qx(xrandr --listactivemonitors)) { next if $line =~ /Monitors: /; # output looks like: #Monitors: 2 # 0: +*LVDS-1 1366/277x768/156+0+0 LVDS-1 # 1: +HDMI-2 1920/518x1200/324+1366+0 HDMI-2 my ($w, $h, $x, $y) = ($line =~ /(\d+)\/\d+x(\d+)\/\d+\+(\d+)\+(\d+)/); print "found monitor with dimensions and position w=$w h=$h x=$x y=$y\n"; push @outputs, { w => $w, h => $h, x => $x, y => $y }; } return @outputs; } # This takes a list of outputs and looks up the one, the mouse pointer # currently is on. sub geometry_from_ptr { my ($self) = @_; my @outputs = get_outputs(); my $ptr = { $self->{X}->QueryPointer($self->DefaultRootWindow) }; for my $output (@outputs) { if ($output->{x} <= $ptr->{root_x} && $ptr->{root_x} < $output->{x} + $output->{w}) { $self->{x} = $output->{x}; if ($self->{bottom}) { # The real y-coordinate will change during execution, when the window grows $self->{y} = $output->{y} + $output->{h}; } else { $self->{y} = $output->{y}; } $self->{w} = $output->{w}; $self->{h} = $output->{h}; } } } # Helper, that take a list of numbers and return the max resp. min sub max { my $max = shift; while (my $n = shift) { $max = $n > $max ? $n : $max; } return $max; } sub min { my $min = shift; while (my $n = shift) { $min = $n < $min ? $n : $min; } return $min; } # This takes a list of outputs and looks up the one, that contains most of the # window having the input focus currently sub geometry_from_focus { my ($self) = @_; my @outputs = get_outputs(); # Look up the window that currently has the input focus my ($focus, $revert) = $self->{X}->GetInputFocus(); # If the root-window is focused, we fall back to using the pointer-position if ($focus == $self->DefaultRootWindow) { print "Fall back to getting shellex-position from pointer\n"; return $self->geometry_from_ptr(); } my $geom = { $self->{X}->GetGeometry($focus) }; my ($fw, $fh) = ($geom->{width}, $geom->{height}); print "Focus $focus (${fw}x${fh})\n"; # The (x,y) coordinates we get are relative to the parent not the # root-window. So we just translate the coordinates of the upper-left # corner into the coordinate-system of the root-window my (undef, undef, $fx, $fy) = $self->{X}->TranslateCoordinates($focus, $self->DefaultRootWindow, 0, 0); # Returns the area (in pixel²) of the intersection of two rectangles. # To understand how it works, best draw a picture. my $intersection = sub { my ($x, $y, $w, $h) = @_; my $dx; if ($x < $fx) { $dx = $x + $w - $fx; } else { $dx = $fx + $fw - $x; } $dx = max(0, min($dx, $fw, $w)); my $dy; if ($y < $fy) { $dy = $y + $h - $fy; } else { $dy = $fy + $fh - $y; } $dy = max(0, min($dy, $fh, $h)); return $dx * $dy; }; my $max_area = 0; for my $output (@outputs) { my $area = $intersection->($output->{x}, $output->{y}, $output->{w}, $output->{h}); if ($area >= $max_area) { $max_area = $area; $self->{x} = $output->{x}; if ($self->{bottom}) { # The real y-coordinate will change during execution, when the window grows $self->{y} = $output->{y} + $output->{h}; } else { $self->{y} = $output->{y}; } $self->{w} = $output->{w}; $self->{h} = $output->{h}; } } } # This hook is run when the extension is first initialized, before any windows # are created or mapped. There is not much work we can do here. sub on_init { my ($self) = @_; $self->{X} = X11::Protocol->new($self->display_id); # Some reasonably sane values in case all our methods to determine a # geometry fails. $self->{x} = 0; $self->{y} = 0; $self->{w} = 1024; $self->{h} = 768; (); } # This hook is run after the window is created, but before it is mapped, so # this is the place to set the geometry to what we want sub on_start { my ($self) = @_; # TODO: Remove compatibility code in future version if (defined $self->x_resource("%.edge") || defined $self->x_resource("%.pos")) { print "WARNING: URxvt.shellex.* resources are deprecated and will be removed in the future. Use shellex.*\n"; } if ($self->x_resource("edge") eq 'bottom' || $self->x_resource("%.edge") eq 'bottom') { print "position should be at the bottom\n"; $self->{bottom} = 1; $self->{y} = $self->{h}; } else { print "position should be at the top\n"; } if ($self->x_resource("pos") eq 'pointer' || $self->x_resource("%.pos") eq 'pointer') { print "Getting shellex-position from pointer\n"; $self->geometry_from_ptr(); } else { print "Getting shellex-position from focused window\n"; $self->geometry_from_focus(); } # This environment variable is used by the LD_PRELOAD ioctl-override to # determine the values to send to the shell # TODO revisit communication protocol (from file to pipe?) # TODO check if user defined their own SHELLEX_MAX_ROWS, which should be used like #$ENV{SHELLEX_MAX_ROWS} = $sane_max_rows < $ENV{SHELLEX_MAX_ROWS} ? $sane_max_rows : $ENV{SHELLEX_MAX_ROWS} ; # # shellex should leave part of the screen uncovered (10 lines), this assumes # that a screen will always be larger than 10 lines. my $sane_max_rows = int($self->{h} / $self->fheight) - 10; $ENV{SHELLEX_MAX_ROWS} = $sane_max_rows; my $filename = $ENV{SHELLEX_SIZE_FILE}; open(my $fh, '>', $filename); print $fh "$ENV{SHELLEX_MAX_ROWS}\n"; close $fh; print "wrote $sane_max_rows as max rows to $filename done\n"; $self->{border} = $self->x_resource('internalBorder'); # the compiled-in default if the resource is not set $self->{border} //= 2; $self->{border} += $self->x_resource('externalBorder'); $self->{row_height} = $self->fheight + $self->x_resource('lineSpace'); my $height = $self->{row_height} + 2 * $self->{border}; my $y = $self->{y}; # Our initial position is different, if we have to be at the bottom $y -= $height if $self->{bottom}; $self->XMoveResizeWindow($self->parent, $self->{x}, $y, $self->{w}, $height); print "loading config\n"; $self->tt_write($self->locale_encode("unset LD_PRELOAD\n")); $self->tt_write($self->locale_encode(". @SYSCONFDIR@/shellexrc\n")); (); } # This hook is run every time a line was changed. We do some resizing here, # because this catches most cases where we would want to shrink our window. sub on_line_update { my ($self, $row) = @_; print "line_update(row = $row)\n"; # Determine the last row, that is not empty. # TODO: Does this work as intended, if there is an empty line in the # middle? my $nrow = 0; for my $i ($self->top_row .. $self->nrow - 1) { if ($self->ROW_l($i) > 0) { $nrow++; } } $nrow = $nrow > $ENV{SHELLEX_MAX_ROWS} ? $ENV{SHELLEX_MAX_ROWS} : $nrow; $nrow = $nrow > 0 ? $nrow : 1; print "resizing to $nrow\n"; # If the window is supposed to be at the bottom, we have to move the # window up a little bit my $y = $self->{y}; if ($self->{bottom}) { $y -= 2 + $nrow * $self->fheight; } $self->cmd_parse("\e[8;$nrow;t\e[3;$self->{x};${y}t"); (); } # Predict the number of rows the terminal will have, after adding $string at # the current position sub predict_term_size { my ($self, $string) = @_; my ($row, $col) = $self->screen_cur(); my $i = $self->top_row; my $n = 0; # We iterate over all lines and accumulate the number of rows. If the # curser is not at the current line, we can just add its number of rows to # the total, else we test, if it grows when adding the string and add an # according number to the total while ($i < $self->nrow) { my $line = $self->line($i); $i += $line->end - $line->beg + 1; unless ($line->beg <= $row && $row <= $line->end) { $n += $line->end - $line->beg + 1; next; } my $len = ($row - $line->beg) * $self->ncol + $col; # Because there might be control-sequences in $string, affecting the # number of lines, we need to manually walk it for (my $j = 0 ; $j < length($string) ; $j++) { # Linebreaks mean the creating of a new line, finishing the old one if (substr($string, $j, 1) eq "\n") { $len = ($len == 0 ? 1 : $len); $n += ceil(($len * 1.0) / $self->ncol); $len = 0; next; } # Carriage-returns mean starting from the beginning. Though the new # len does not really have to be 0 (because the text is not # actually erased) it is a good enough estimate for now if (substr($string, $j, 1) eq "\r") { $len = 0; next; } # We just add one per other char. This actually might not work # correctly with wide-chars, but it is a good enough estimate for # now $len++; } $n += ceil(($len + 1.0) / $self->ncol); } print "Predicting term size: $n\n"; return $n; } # This hook is run every time before there is text output. We resize here, # immediately before new lines would be added, which would create scrolling sub on_add_lines { my ($self, $string) = @_; my $str = $string; $str =~ s/\n/\\n/g; $str =~ s/\r/\\r/g; print "add_lines(string = \"$str\")\n"; my $nrow = $self->predict_term_size($string); $nrow = $nrow > $ENV{SHELLEX_MAX_ROWS} ? $ENV{SHELLEX_MAX_ROWS} : $nrow; $nrow = $nrow > 0 ? $nrow : 1; print "resizing to $nrow\n"; # If the window is supposed to be at the bottom, we have to move the # window up a little bit my $y = $self->{y}; if ($self->{bottom}) { $y -= 2 + $nrow * $self->fheight; } $self->cmd_parse("\e[8;$nrow;t\e[3;$self->{x};${y}t"); (); } # Just for debugging sub on_size_change { my ($self, $nw, $nh) = @_; print "size_change($nw, $nh)\n"; (); } sub on_view_change { my ($self, $offset) = @_; print "view_change(offset = $offset)\n"; (); } sub on_scroll_back { my ($self, $lines, $saved) = @_; print "scroll_back(lines = $lines, saved = $saved)\n"; (); } sub on_x_event { my ($self, $event) = @_; if ($event->{type} == urxvt::EnterNotify) { $self->{X}->SetInputFocus($self->parent, 2, $self->{data}{event}{time}); $self->{X}->GetInputFocus(); } (); } # This hook is run directly after the window was mapped (= displayed on # screen). We grab the keyboard here. sub on_map_notify { my ($self, $ev) = @_; $self->{X}->SetInputFocus($self->parent, 2, $self->{data}{event}{time}); # We use GetInputFocus as a syncing-mechanism $self->{X}->GetInputFocus(); $self->vt_emask_add(urxvt::EnterWindowMask); (); } shellex-0.3/urxvt/urxvt_shellex.mk0000644000175000017500000000072513403562665021671 0ustar coffeedrinkercoffeedrinkerALL_TARGETS += urxvt/shellex INSTALL_TARGETS += install-urxvt_shellex CLEAN_TARGETS += clean-urxvt_shellex urxvt/shellex: urxvt/shellex.in echo "[SED] $@" $(SED) $(sed_replace_vars) $< > $@ install-urxvt_shellex: urxvt/shellex echo "[INSTALL] $<" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/urxvt $(INSTALL) -m 0644 urxvt/shellex $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/urxvt/ clean-urxvt_shellex: echo "[CLEAN] urxvt/shellex" rm -f urxvt/shellex shellex-0.3/conf/0000755000175000017500000000000013403562665016155 5ustar coffeedrinkercoffeedrinkershellex-0.3/conf/40-home_end0000644000175000017500000000030613403562665020076 0ustar coffeedrinkercoffeedrinker# vim:ft=zsh # Make home and end buttons usable # © 2014 Axel Wagner and contributors (see also: LICENSE) bindkey '^[[7~' beginning-of-line bindkey '^[[8~' end-of-line bindkey '^[[3~' delete-char shellex-0.3/conf/40-sudo0000644000175000017500000000037213403562665017275 0ustar coffeedrinkercoffeedrinker# vim:ft=zsh # use gksudo (or kdesudo if gksudo not exists) instead of sudo # © 2013 Johannes Visintini and contributors (see also: LICENSE) which kdesudo > /dev/null 2>&1 && alias sudo='kdesudo' which gksudo > /dev/null 2>&1 && alias sudo='gksudo' shellex-0.3/conf/99-clear0000644000175000017500000000016313403562665017425 0ustar coffeedrinkercoffeedrinker# vim:ft=zsh # Clear the terminal-window at start # © 2013 Axel Wagner and contributors (see also: LICENSE) clear shellex-0.3/conf/40-escape0000644000175000017500000000030113403562665017553 0ustar coffeedrinkercoffeedrinker# vim:ft=zsh # Make zsh exit on escape # © 2013 Axel Wagner and contributors (see also: LICENSE) function _shellex_exit { fc -P exit } zle -N _shellex_exit bindkey '^[' _shellex_exit shellex-0.3/conf/10-autoexec0000644000175000017500000000155313403562665020137 0ustar coffeedrinkercoffeedrinker# vim:ft=zsh # Make zsh automatically execute a command, when enter is hit # © 2013 Axel Wagner and contributors (see also: LICENSE) function shellex_preexec () { # In $1 the command-line is given # In $3 the command-line with expanded aliases and function-bodies is given # Execute the tempfile, then exit zsh -c $3 > /dev/null 2>&1 & disown exit } # We use preexec_functions, so that the user can decide to overwrite our choice # or ammend it by own functions preexec_functions=(shellex_preexec) # preexec doesn't get executed on empty lines, so employ zle to exit on empty lines # https://www.reddit.com/r/zsh/comments/s6t6d/is_there_an_alias_for_an_empty_line/ function empty-buffer-to-exit() { if [[ $#BUFFER == 0 ]]; then fc -P exit fi } # set special widget, see man zshzle zle -N zle-line-finish empty-buffer-to-exit shellex-0.3/conf/40-recent0000644000175000017500000000123713403562665017604 0ustar coffeedrinkercoffeedrinker# 'ctrl-x r' will complete the 12 last modified (mtime) files/directories # © 2018 Michael Stapelberg and contributors (see also: LICENSE) # only add this completion if compinit has been called if [[ -v _comps ]] then # 'ctrl-x r' will complete the 12 last modified (mtime) files/directories zle -C newest-files complete-word _generic bindkey '^Xr' newest-files zstyle ':completion:newest-files:*' completer _files zstyle ':completion:newest-files:*' file-patterns '*~.*(omN[1,12])' zstyle ':completion:newest-files:*' menu select yes zstyle ':completion:newest-files:*' sort false zstyle ':completion:newest-files:*' matcher-list 'b:=*' # important fi shellex-0.3/conf/20-completion0000644000175000017500000000017413403562665020472 0ustar coffeedrinkercoffeedrinker# enable tab completion system # © 2016 Paul Seyfert and contributors (see also: LICENSE) autoload -U compinit compinit -C shellex-0.3/conf/90-hist0000644000175000017500000000073013403562665017275 0ustar coffeedrinkercoffeedrinker# vim:ft=zsh # Avoid configuration sourcing in history # © 2013 Paul Seyfert and contributors (see also: LICENSE) # ensure history gets actually written (shell gets closed before history gets # written otherwise) setopt inc_append_history HISTSIZE=100 SAVEHIST=100 # Push current history to stack (make e.g. sourcing of configuration # inaccessible to user). The stack won't be poped. Set HISTFILE to ~/.shellex # and read history from there. fc -p ~/.shellex_history shellex-0.3/conf/40-setprompt0000644000175000017500000000015413403562665020356 0ustar coffeedrinkercoffeedrinker# vim:ft=zsh # Set the prompt # © 2013 Axel Wagner and contributors (see also: LICENSE) PROMPT="shellex> " shellex-0.3/conf/40-sigint0000644000175000017500000000015713403562665017621 0ustar coffeedrinkercoffeedrinker# vim:ft=zsh # Make zsh exit on ^C # © 2013 Axel Wagner and contributors (see also: LICENSE) trap exit SIGINT shellex-0.3/conf/shellexrc.in0000644000175000017500000000214613403562665020501 0ustar coffeedrinkercoffeedrinker# vim:ft=zsh # Source all conf files # © 2016 Paul Seyfert and contributors (see also: LICENSE) # get array of all relevant files # http://stackoverflow.com/a/10981499 # http://unix.stackexchange.com/a/26825 # parentheses after * steer zsh's globbing # . means "only regular files" # -. means "regular files and symlinks pointing to regular files" # N means "empty list in case of no matches" thefiles=( @SYSCONFDIR@/shellex/* ${XDG_CONFIG_HOME:-$HOME/.config}/shellex/*(-.N) $HOME/.shellex/*(-.N) ) # get the basenames of all files and make unique list # http://stackoverflow.com/a/9516801 uniquified=( $( for f in "${thefiles[@]}" ; do basename $f ; done | sort -u ) ) # source each file from the first of: # 1. $XDG_CONFIG_HOME/.shellex (typically $HOME/.config/shellex) # 2. $HOME/.shellex # 3. @SYSCONFDIR@/shellex for f in $uniquified do # -r checks if file exists and is readable if [[ -r ${XDG_CONFIG_HOME:-$HOME/.config}/shellex/$f ]] then source $HOME/.config/shellex/$f elif [[ -r $HOME/.shellex/$f ]] then source $HOME/.shellex/$f else source @SYSCONFDIR@/shellex/$f fi done shellex-0.3/conf/20-nobeep0000644000175000017500000000021513403562665017565 0ustar coffeedrinkercoffeedrinker# disable beeping of the shellex shell. e.g. when tab-completing # © 2016 Paul Seyfert and contributors (see also: LICENSE) setopt NO_BEEP shellex-0.3/indent.sh0000755000175000017500000000051613403562665017052 0ustar coffeedrinkercoffeedrinker#!/bin/sh # vim:ft=zsh # Helper script for CI to auto indent files # © 2017 Paul Seyfert and contributors (see also: LICENSE) if [ -f "$1" ]; then vim -f +"set softtabstop=2" +"set tabstop=2" +"set shiftwidth=2" +"set expandtab" +"gg=G" +":x" $1 else echo "USAGE: $0 " echo "to autoindent file with vim" fi exit $? shellex-0.3/CHANGELOG.md0000644000175000017500000000233613403562665017045 0ustar coffeedrinkercoffeedrinkerChangelog ========= 0.3 - 2018-12-10 ---------------- • Miscellaneous maintenance changes • Updated empty-line treatment • Changed xrandr parseing • Cleanup of unused perl modules • Bugfix: allow symlinks in user .shellex directories • Use configuration from `$XDG_CONFIG_HOME` • Enable compinit by default, add ctrl-x r for recent file completion The slight change in the default completion behaviour is very likely desirable. In the unlikely chance that a user does not appreciate the change, refer to the README about how to disable the snippet `20-completion`. 0.2 - 2016-12-27 ---------------- • Added optional sudo configuration • Bugfix: flashing window with large completion lists • Moved config handling from perl to shell • Disable beeping • Improved history file handling • Avoid using temporary files for configuration parsing • Avoid using temporary files for command execution 0.1 - 2013-09-03 ---------------- • History support (default) • Pass command-line to urxvt (support colors) • Bugfix: Correct pointer-based positioning • Bugfix: Correct focus-based positioning • Bugfix: Correctly predict terminal size, when adding strings 0.0 - 2013-08-31 ---------------- • Initial release shellex-0.3/LICENSE0000644000175000017500000000270113403562665016235 0ustar coffeedrinkercoffeedrinkerCopyright © 2016 Axel Wagner and contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Axel Wagner nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY Axel Wagner ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Axel Wagner BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. shellex-0.3/doc/0000755000175000017500000000000013403562665015775 5ustar coffeedrinkercoffeedrinkershellex-0.3/doc/man/0000755000175000017500000000000013403562665016550 5ustar coffeedrinkercoffeedrinkershellex-0.3/doc/man/man.mk0000644000175000017500000000060413403562665017654 0ustar coffeedrinkercoffeedrinkerDISTCLEAN_TARGETS += clean-mans A2X = a2x A2X_MAN_CALL = $(V_A2X)$(A2X) -f manpage --asciidoc-opts="-f doc/man/asciidoc.conf" $(A2X_FLAGS) $< MANS = \ doc/man/shellex.1 mans: $(MANS) %.1: %.man doc/man/asciidoc.conf $(A2X_MAN_CALL) %.man: %.man.in $(SED) $(sed_replace_vars) $< > $@ clean-mans: for file in $(basename $(MANS)); \ do \ rm -f $${file}.1 $${file}.man; \ done shellex-0.3/doc/man/shellex.man.in0000644000175000017500000000541313403562665021321 0ustar coffeedrinkercoffeedrinkershellex(1) ========== Paul Seyfert v0.3, July 2018 == NAME shellex - shell-based launcher == SYNOPSIS *shellex* [...] == OPTIONS All command-line parameters (together with some shellex-specific) are passed on to urxvt. This means, you can you e.g. '-bg grey20' for a lighter background. Using it for more than just customizing the appearance (for example adding own extensions) might stop shellex from working, so be careful. See urxvt(1) for a full list of options. == DESCRIPTION *shellex* is a shell-based launcher with a lot more features and a lot simpler design. It launches a shell (currently zsh(1)) and shows it in a small terminal (currently urxvt(1)), wrapping every command with a little bit of extra magic (redirecting stdout, stderr, disowning and closing the shell) to get more typical launcher-behaviour. This gives you a simple launcher with tab-completion and other shell-features, configurable in shell. == RESOURCES *shellex* uses two X-Resources at the moment, to manipulate its behaviour: shellex.pos:: If pointer, shellex shows the window on the window, the mousepointer is on. If focus, it uses the output, where most of the currently focused window is. Defaults to focus. shellex.edge:: On what screen edge to show the launcher (top or bottom). Defaults to top. Additionally all resources defined by urxvt (with class 'shellex' instead of 'urxvt') can be used to customize the appearance or behaviour of the used terminal window. == CONFIGURATION *shellex* configuration snippets can be found in *@PREFIX@@LIBDIR@/shellex/*. On start, *shellex* assembles a list of snippet basenames by looking at all of the paths listed below. For each snippet basename, *shellex* loads the first file it finds when looking through the paths in order: 1. $XDG_CONFIG_HOME/.shellex. Typically unset, defaulting to $HOME/.config/shellex. 2. $HOME/.shellex 3. @SYSCONFDIR@/shellex (shellex defaults, symlinks to *@PREFIX@@LIBDIR@/shellex/conf/*) To customize shellex, you can do the following things in *$XDG_CONFIG_HOME/.shellex* or *$HOME/.shellex/*: 1. Overwrite a default by creating a new snippet of the same name 2. Not include a default by creating a symlink to */dev/null* of the same same 3. Include an example-snippet not used by default, by creating a symlink to *@PREFIX@@LIBDIR@/shellex/snippet* 4. Write you own snippets with a currently unused name To avoid naming-conflicts in the future, you should add a common suffix to all your own snippets. Snippets are run in ascending order. By choosing a number which sorts between/after the existing snippet(s) you can ensure it runs at the desired time. E.g. if your snippet beeps on errors, name it 15-errorbeep so that it sorts before 20-nobeep. == AUTHORS Axel Wagner and contributors shellex-0.3/doc/man/asciidoc.conf0000644000175000017500000000076613403562665021206 0ustar coffeedrinkercoffeedrinkerifdef::doctype-manpage[] ifdef::backend-docbook[] [header] template::[header-declarations] {mantitle} {manvolnum} shellex 0.3 shellex Manual {manname} {manpurpose} endif::backend-docbook[] endif::doctype-manpage[] shellex-0.3/doc/man/Makefile0000644000175000017500000000012413403562665020205 0ustar coffeedrinkercoffeedrinkerall: $(MAKE) -C ../.. mans clean: $(MAKE) -C ../.. clean-mans .PHONY: all clean shellex-0.3/doc/autoresize.txt0000644000175000017500000000320013403562665020723 0ustar coffeedrinkercoffeedrinkerThe process of automatically resizing the window to match the shell-output is surprisingly complex. Normally the way the shell and terminal orchestrate themselves to do the output is the following: The terminal gets resized and does a TIOCSWINSZ ioctl on the pty-fd over which the two processes communicate, giving the new dimenions. This prompts the terminal to send the shell a SIGWINCH. The shell handles this by doing a TIOCGWINSZ ioctl on the pty which returns the data the terminal gave. zsh now uses this to determine, wether or not e.g. a tabcompletion-suggestion fits on the terminal and if not, handles it differently. This is a problem for shellex, because when is starting it's output, there is not enough space, for the tabcompletion, so even if we immediately resize the terminalwindow, it will be too late and the shell-output is screwed up. We rectify this, by injecting a custom ioctl-function into zsh via LD_PRELOAD, which rewrites all TIOCGWINSZ-requests to have a constant size. This fakes to the shell that there is more space available, then there actually is. The actual number of rows is calculated on start of the urxvt and put into a temporary file. The size is chosen a bit smaller than the screen, such that if zsh needs even more space for the tabcompletion than fits on the screen, the zsh handeling to deal with less space gets active. The filename is generated with mktemp before either zsh or urxvt start. The file gets unlinked by preload/main.c once a successful read happened. Shrinking after tab completions mostly works fine: Depending on the tabcompletion settings, shrinking breaks once one hit the maximum size limit. shellex-0.3/shellex.mk0000644000175000017500000000055513403562665017232 0ustar coffeedrinkercoffeedrinkerALL_TARGETS += shellex INSTALL_TARGETS += install-shellex CLEAN_TARGETS += clean-shellex shellex: shellex.in echo "[SED] $@" $(SED) $(sed_replace_vars) $< > $@ install-shellex: shellex echo "[INSTALL] $<" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin $(INSTALL) -m 0755 shellex $(DESTDIR)$(PREFIX)/bin/ clean-shellex: echo "[CLEAN] shellex" rm -f shellex shellex-0.3/Makefile0000644000175000017500000000060313403562665016667 0ustar coffeedrinkercoffeedrinkerTOPDIR=$(shell pwd) include $(TOPDIR)/common.mk ALL_TARGETS = INSTALL_TARGETS = CLEAN_TARGETS = DISTCLEAN_TARGETS = all: real-all include preload/preload.mk include shellex.mk include urxvt/urxvt_shellex.mk include conf.mk include format.mk include doc/man/man.mk real-all: $(ALL_TARGETS) install: $(INSTALL_TARGETS) clean: $(CLEAN_TARGETS) distclean: clean $(DISTCLEAN_TARGETS)