pax_global_header00006660000000000000000000000064122113650150014506gustar00rootroot0000000000000052 comment=46f97210ac197ceed878658a38698e9d674c9b9f shellex-0.1/000077500000000000000000000000001221136501500130125ustar00rootroot00000000000000shellex-0.1/.gitignore000066400000000000000000000001271221136501500150020ustar00rootroot00000000000000preload/shellex_preload.so shellex urxvt/shellex doc/man/shellex.1 doc/man/shellex.man shellex-0.1/CHANGELOG.md000066400000000000000000000005301221136501500146210ustar00rootroot00000000000000Changelog ========= 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.1/LICENSE000066400000000000000000000023321221136501500140170ustar00rootroot00000000000000Copyright © 2013 Axel Wagner 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 shellex-0.1/Makefile000066400000000000000000000005611221136501500144540ustar00rootroot00000000000000TOPDIR=$(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 doc/man/man.mk real-all: $(ALL_TARGETS) install: $(INSTALL_TARGETS) clean: $(CLEAN_TARGETS) distclean: clean $(DISTCLEAN_TARGETS) shellex-0.1/README.md000066400000000000000000000140111221136501500142660ustar00rootroot00000000000000shellex - 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. 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. So, this is the early prototype. It is usable and should already work and be usefull, but much is not working yet. I hope this time I will continue the work for longer ;) 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 yet: * Buffering/showing some output, for errors etc. We have to think about some magic way to determine, wether output is helpfull or the launcher should be hidden immediately * dmenu-like completion, typing part of a command still completing (maybe zsh has sometething to do that?) * .desktop-file integration * 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/)) 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 checkout git://github.com/Merovius/shellex.git $ cd shellex $ make $ make install ``` 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. Configuration ============= Configuration of `shellex` has two parts: The first one are X-resources (which we will try to eliminate in the future): Resource | Values | Default | Description ----------------- | -------------- | ------- | --- URxvt.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). URxvt.shellex.edge | bottom|top | top | On what screenedge to show shellex The other are small shell-script-snippets. When starting, `shellex` will look into `$HOME/.shellex` and into `/etc/shellex`. It will then source all the snippets in either location. If there is an identically named file in both directories, the one in your home will be preferred. This makes for a pretty flexible configuration process: Usually there will be a lot of snippets in `/usr/lib/shellex/conf`, which should be self-contained and without a lot of side-effects. In `/etc/shellex` there then are some symlinks to those snippets, making up the default-configuration on this system, together with administrator-provided additional defaults. Whenever you don't want a snippet form `/etc/shellex` to be used, just create a symlink of the same name to `/dev/null` in `$HOME/.shellex`. If you want to create your own snippets, just put them in `$HOME/.shellex` under a name not used yet and it will be automatically sourced. Changing Appearence =================== 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. 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.1/common.mk000066400000000000000000000011731221136501500146350ustar00rootroot00000000000000INSTALL=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.1/conf.mk000066400000000000000000000010461221136501500142710ustar00rootroot00000000000000INSTALL_TARGETS += install-conf default_confs := 10-autoexec 40-escape 40-setprompt 40-sigint 90-hist 99-clear install-conf: $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)$(LIBDIR)/shellex/conf for file in $(wildcard conf/*); \ 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 shellex-0.1/conf/000077500000000000000000000000001221136501500137375ustar00rootroot00000000000000shellex-0.1/conf/10-autoexec000066400000000000000000000027001221136501500157140ustar00rootroot00000000000000# vim:ft=zsh # Make zsh automatically execute a command, when enter is hit # © 2013 Axel Wagner and contributors (see also: LICENSE) zmodload zsh/regex function shellex_preexec () { # In $1 the command-line is given # In $3 the command-line with expanded aliases and function-bodies is given # Don't do anything, if an empty command is given if [ "$1" -regex-match '^\s*$' ] then return 0 fi # We need to write the command to a temporary file to accomodate multiple # commands (see https://github.com/Merovius/shellex/issues/11). We let the # shell immediately remove the tempfile, so it's rather short-lived. file=`mktemp -t shellex_exec-XXXXXXXX` # manually write history to histfile # this is indeed more hack, the child zsh which executes $file should do this if [ -n "$HISTFILE" ] then # We write the short form of the command to history echo "$1" >> $HISTFILE fi # We write the expanded form of the command to the tempfile, because the # executet shell will not have our aliases or functions available echo "rm $file\n$3" > $file # prevent writing meaningless "zsh $file > /dev/null ...." to history unset HISTFILE # Execute the tempfile, then exit zsh $file > /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) shellex-0.1/conf/40-escape000066400000000000000000000002671221136501500153500ustar00rootroot00000000000000# vim:ft=zsh # Make zsh exit on escape # © 2013 Axel Wagner and contributors (see also: LICENSE) function _shellex_exit { exit } zle -N _shellex_exit bindkey '^[' _shellex_exit shellex-0.1/conf/40-setprompt000066400000000000000000000001541221136501500161400ustar00rootroot00000000000000# vim:ft=zsh # Set the prompt # © 2013 Axel Wagner and contributors (see also: LICENSE) PROMPT="shellex> " shellex-0.1/conf/40-sigint000066400000000000000000000001571221136501500154030ustar00rootroot00000000000000# vim:ft=zsh # Make zsh exit on ^C # © 2013 Axel Wagner and contributors (see also: LICENSE) trap exit SIGINT shellex-0.1/conf/90-hist000066400000000000000000000003461221136501500150620ustar00rootroot00000000000000# histfile enabling and forcing to read the history after setting it # © 2013 Paul Seyfert and contributors (see also: LICENSE) setopt sharehistory setopt appendhistory HISTSIZE=100 HISTFILE=~/.shellex_history SAVEHIST=100 fc -R shellex-0.1/conf/99-clear000066400000000000000000000001631221136501500152070ustar00rootroot00000000000000# vim:ft=zsh # Clear the terminal-window at start # © 2013 Axel Wagner and contributors (see also: LICENSE) clear shellex-0.1/doc/000077500000000000000000000000001221136501500135575ustar00rootroot00000000000000shellex-0.1/doc/autoresize.txt000066400000000000000000000027321221136501500165160ustar00rootroot00000000000000The 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 urxvt via LD_PRELOAD, which rewrites all TIOCSWINSZ-requests to have a constant size, thus faking 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 an environment-variable. With the current state, the window of shellex is able to grow automatically exactly one time. This is, because zsh is spamming the output with '\n', if we grow it always and we have yet to figure out why and how to stop that. Shrinking (i.e. when the tab-completion vanishes again) is not implemented yet. shellex-0.1/doc/man/000077500000000000000000000000001221136501500143325ustar00rootroot00000000000000shellex-0.1/doc/man/Makefile000066400000000000000000000001241221136501500157670ustar00rootroot00000000000000all: $(MAKE) -C ../.. mans clean: $(MAKE) -C ../.. clean-mans .PHONY: all clean shellex-0.1/doc/man/asciidoc.conf000066400000000000000000000007661221136501500167700ustar00rootroot00000000000000ifdef::doctype-manpage[] ifdef::backend-docbook[] [header] template::[header-declarations] {mantitle} {manvolnum} shellex 0.1 shellex Manual {manname} {manpurpose} endif::backend-docbook[] endif::doctype-manpage[] shellex-0.1/doc/man/man.mk000066400000000000000000000006041221136501500154360ustar00rootroot00000000000000DISTCLEAN_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.1/doc/man/shellex.man.in000066400000000000000000000043601221136501500171030ustar00rootroot00000000000000shellex(1) ========== Axel Wagner v0.1, August 2013 == 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 appearence (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 monent, to manipulate its behaviour: URxvt.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. URxvt.shellex.edge:: On what screen edge to show the launcher (top or bottom). Defaults to top. == CONFIGURATION *shellex* configuration snippets can be found in *@PREFIX@@LIBDIR@/shellex/*. On start, *shellex* looks into @SYSCONFDIR@/shellex for default-snippets to source (usually this will be symlinks to *@PREFIX@@LIBDIR@/shellex/*) as well as into *$HOME/.shellex/* for any user-configuration. If a file of the same name exists in both locations, it will only use the one in *$HOME/.shellex/*. To customize shellex, you can do the following things in *$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. == AUTHORS Axel Wagner and contributors shellex-0.1/preload/000077500000000000000000000000001221136501500144405ustar00rootroot00000000000000shellex-0.1/preload/main.c000066400000000000000000000023661221136501500155370ustar00rootroot00000000000000/* * 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 /* 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"); } static int max_rows = -1; if (max_rows < 0 ) { char *str = getenv("SHELLEX_MAX_ROWS"); if (str != NULL) { max_rows = atoi(str); } } // We only care for TIOCSWINSZ ioctls if (request != 0x5414) { return orig_ioctl(d, request, argp); } struct winsize ws = *((struct winsize *)argp); int fheight = ws.ws_ypixel / ws.ws_row; if (max_rows < 0) { ws.ws_row = 80; ws.ws_ypixel += 80 * fheight; } else { ws.ws_row = max_rows; ws.ws_ypixel += max_rows * fheight; } return orig_ioctl(d, request, (char *)&ws); } shellex-0.1/preload/preload.mk000066400000000000000000000012521221136501500164170ustar00rootroot00000000000000ALL_TARGETS += preload/shellex_preload.so INSTALL_TARGETS += install-shellex_preload CLEAN_TARGETS += clean-shellex_preload SHELLEX_CFLAGS=-fPIC SHELLEX_PRELOAD_LDFLAGS=-shared 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.1/shellex.in000066400000000000000000000006721221136501500150130ustar00rootroot00000000000000#!/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) export LD_PRELOAD="@PREFIX@@LIBDIR@/shellex/shellex_preload.so" exec urxvt -perl-lib @PREFIX@@LIBDIR@/shellex/urxvt -pe shellex -override-redirect -name shellex -bg grey15 -fg linen $* -e env -u LD_PRELOAD zsh -f shellex-0.1/shellex.mk000066400000000000000000000006431221136501500150120ustar00rootroot00000000000000ALL_TARGETS += shellex INSTALL_TARGETS += install-shellex CLEAN_TARGETS += clean-shellex SHELLEX_CFLAGS=-fPIC SHELLEX_PRELOAD_LDFLAGS=-shared 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.1/urxvt/000077500000000000000000000000001221136501500142025ustar00rootroot00000000000000shellex-0.1/urxvt/shellex.in000066400000000000000000000247311221136501500162050ustar00rootroot00000000000000# 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 File::Temp qw|tempfile|; use File::Basename qw|basename|; use POSIX qw|ceil|; use strict; # The existing Randr-modules on CPAN seem to work only barely, so instead we # just parse the output of xrandr -q. This is an uglyness, that should go away # some time in the feature. sub get_outputs { my @outputs = (); for my $line (qx(xrandr -q)) { next unless $line =~ /\sconnected/; my ($w, $h, $x, $y) = ($line =~ /(\d+)x(\d+)\+(\d+)\+(\d+)/); 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) { 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}; } } } sub slurp { open my $fh, '<', shift; local $/; <$fh>; } sub gen_conf { my ($cfg, $cfgname) = tempfile("/tmp/shellex-XXXXXXXX", UNLINK => 0); print $cfg "rm $cfgname\n"; my %fileset; map { $fileset{basename($_)} = 1 } <@SYSCONFDIR@/shellex/*>; map { $fileset{basename($_)} = 1 } <$ENV{HOME}/.shellex/*>; my @files = sort keys %fileset; for my $f (@files) { if (-e "$ENV{HOME}/.shellex/$f") { print $cfg slurp("$ENV{HOME}/.shellex/$f"); } else { print $cfg slurp("@SYSCONFDIR@/shellex/$f"); } } close($cfg); return $cfgname; } # 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) = @_; if ($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') { 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 $ENV{SHELLEX_MAX_HEIGHT} = int($self->{h} / $self->fheight); # Our initial position is different, if we have to be at the bottom if ($self->{bottom}) { $self->XMoveResizeWindow($self->parent, $self->{x}, $self->{y} - (2 + $self->fheight), $self->{w}, 2+$self->fheight); } else { $self->XMoveResizeWindow($self->parent, $self->{x}, $self->{y}, $self->{w}, 2+$self->fheight); } my $cfg = gen_conf(); $self->tt_write($self->locale_encode(". $cfg\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 > 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); } 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); 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.1/urxvt/urxvt_shellex.mk000066400000000000000000000007251221136501500174530ustar00rootroot00000000000000ALL_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