thinglaunch

A simple command and password prompter for X11.
git clone git://r-36.net/thinglaunch
Log | Files | Refs | LICENSE

commit 606c0f24754b284bacbb8de22ef5d0c73f065e8f
Author: Christoph Lohmann <20h@r-36.net>
Date:   Sun, 27 Mar 2011 18:50:19 +0200

Initial commit of 2.0.

Diffstat:
LICENSE | 23+++++++++++++++++++++++
LICENSE.orig | 41+++++++++++++++++++++++++++++++++++++++++
Makefile | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
README.md | 36++++++++++++++++++++++++++++++++++++
config.h | 5+++++
config.mk | 26++++++++++++++++++++++++++
thinglaunch.c | 466+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 655 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,23 @@ +MIT/X Consortium License + +© 2011 Christoph Lohmann <20h@r-36.net> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +SEE LICENSE.orig FOR FURTHER APPLYING LICENSE TERMS. diff --git a/LICENSE.orig b/LICENSE.orig @@ -0,0 +1,41 @@ +/* This program is a quick little launcher program for X. + * + * You run the program, it grabs the display (hopefully nothing else has it + * grabbed), and you type what you want to run. The styling is minimalist. + * + * (c) 2003 Matt Johnston + * matt (at) ucc.asn.au + * + * Compile it with + * cc thinglaunch.c -o thinglaunch -lX11 -L/usr/X11R6/lib -lpthread + * (works for Linux and OSF/1 anyway, for static you might need -ldl, and you + * mightn't need -lpthread.) + * + * This program can be freely distributed in source or binary form, under a + * quite permissive license. See the bottom of this file for the full license. + * If that license is too restrictive, mail me and you can choose another one. + * + * $Id: thinglaunch.c,v 1.8 2004/09/20 14:27:48 matt Exp $ + */ +/* +Copyright (c) 2003 Matt Johnston +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ diff --git a/Makefile b/Makefile @@ -0,0 +1,58 @@ +# thinglaunch +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = ${NAME}.c + +OBJ = ${SRC:.c=.o} + +all: options ${NAME} + +options: + @echo ${NAME} build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: config.mk + +${NAME}: ${OBJ} + @echo CC -o $@ + @${CC} -o $@ ${OBJ} ${LDFLAGS} + @ln -s ${NAME} thingaskpass + +clean: + @echo cleaning + @rm -f ${NAME} thingaskpass ${OBJ} ${NAME}-${VERSION}.tar.gz + +dist: clean + @echo creating dist tarball + @mkdir -p ${NAME}-${VERSION} + @cp -R LICENSE LICENSE.orig Makefile README.md config.mk \ + ${SRC} *.h ${NAME}-${VERSION} + @tar -cf ${NAME}-${VERSION}.tar ${NAME}-${VERSION} + @gzip ${NAME}-${VERSION}.tar + @rm -rf ${NAME}-${VERSION} + +etc: + @echo installing etc files into ${DESTDIR}/etc/${NAME} + @mkdir -p ${DESTDIR}/etc/${NAME} + @cp -R etc/${NAME}/* ${DESTDIR}/etc/${NAME} + +install: all + @echo installing executable file to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f ${NAME} thingaskpass ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/${NAME} + +uninstall: + @echo removing executable file from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/${NAME} + @rm -f ${DESTDIR}${PREFIX}/bin/thingaskpass + +.PHONY: all options clean dist install uninstall diff --git a/README.md b/README.md @@ -0,0 +1,36 @@ +# Thinglaunch - a simple entry box for X11 + +The first intention, as done by the original creator Matt Johnston +<matt@ucc.asn.au>, was to launch simple commandlines. + +In 2011 the single file project was extended by features like Unicode +support, a prompt and an ssh-askpass compatibility layer. + +## Installation + + % tar -xzvf thinglaunch-*.tar.gz + % cd thinglaunch + % make + % sudo PREFIX=/usr make install + +This will create the executable »thinglaunch« and »thingaskpass« in +»/usr/bin«. Thinglaunch will ask for a command and execute it and +thingaskpass can be used as a SSH_ASKPASS parameter value, which will +be used by ssh-agent to gather the password for private keys. + +## Usage + + # Get some input string and print it to stdout. There will + # be the prompt prepended "to stdout> ". + % thinglaunch -o -p "to stdout> " + + # Ask for a command, which will be executed. During entering + # the command, the entered string will be replaced by asterisks. + % thinglaunch -s -p "secret cmd> " + + # This symlink predefines -s, -o and -p "secret> ". + % ln -s thinglaunch thingaskpass + % ./thingaskpass + +Have fun! + diff --git a/config.h b/config.h @@ -0,0 +1,5 @@ +static const char *font = "-*-*-medium-*-*-*-14-*-*-*-*-*-*-*"; +static const char *prompt = "exec> "; +static const char *normbgcolor = "#222222"; +static const char *normfgcolor = "#cccccc"; + diff --git a/config.mk b/config.mk @@ -0,0 +1,26 @@ +# thinglaunch metadata +NAME = thinglaunch +VERSION = 2.00 + +# Customize below to fit your system + +# paths +PREFIX ?= /usr +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# includes and libs +INCS = -I. -I/usr/include +LIBS = -L/usr/lib -L${X11LIB} -lc -lX11 + +# flags +CPPFLAGS = -DVERSION=\"${VERSION}\" +CFLAGS = -g -std=gnu99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} +LDFLAGS = -g ${LIBS} +#LDFLAGS = -s ${LIBS} + +# compiler and linker +CC = cc + diff --git a/thinglaunch.c b/thinglaunch.c @@ -0,0 +1,466 @@ +/* + * Copy me if you can. + * by 20h + * + * For now this is a slightly modified version of the original from + * Matt Johnston <matt@ucc.asn.au>. See LICENSE.orig for his messages. + */ +#include <unistd.h> +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/Xlocale.h> +#include <locale.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <strings.h> +#include <libgen.h> +#include <wchar.h> + +#include "config.h" + +unsigned long getcolor(const char *colstr); +void createwindow(void); +void setupgc(void); +void eventloop(void); +void grabhack(void); +void redraw(void); +void keypress(XKeyEvent *keyevent); +void execcmd(void); +void die(char *errstr, ...); + +Display *dpy; +GC gc; +GC rectgc; +XIM im; +XIC ic; +Window win; +XFontStruct *font_info; +XFontSet fontset; +int screen, issecret = 0, tostdout = 0; +unsigned long fgcol, bgcol; + +#define MAXCMD 255 +#define WINWIDTH 640 +#define WINHEIGHT 25 + +/* the actual commandline */ +wchar_t command[MAXCMD+1]; +wchar_t secret[MAXCMD+1]; +char cbuf[MAXCMD*4+1]; + +void +usage(char *argv0) +{ + fprintf(stderr, "usage: %s [-hos] [-p prompt]\n", argv0); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int i; + + if (strstr(argv[0], "thingaskpass")) { + issecret = 1; + tostdout = 1; + prompt = "secret> "; + } + if (argc > 1) { + for (i = 1; argv[i]; i++) { + if (argv[i][0] == '-') { + switch (argv[i][1]) { + case 'o': + tostdout = 1; + break; + case 's': + issecret = 1; + break; + case 'p': + if (!argv[i+1]) + usage(argv[0]); + prompt = argv[i+1]; + i++; + break; + default: + case 'h': + usage(argv[0]); + break; + } + } + } + } + + bzero(command, sizeof(command)); + bzero(secret, sizeof(secret)); + + createwindow(); + setupgc(); + grabhack(); + eventloop(); + + return 0; +} + +unsigned long +getcolor(const char *colstr) +{ + Colormap cmap = DefaultColormap(dpy, screen); + XColor color; + + if (!XAllocNamedColor(dpy, cmap, colstr, &color, &color)) + die("error, canno allocate color '%s'\n", colstr); + return color.pixel; +} + +/* + * Stolen from: + * http://menehune.opt.wfu.edu/Kokua/Irix_6.5.21_doc_cd/usr/share/\ + * Insight/library/SGI_bookshelves/SGI_Developer/books/XLib_PG/sgi_\ + * html/ch11.html#S2-1002-11-11 + */ +XIMStyle +choosebetterstyle(XIMStyle style1, XIMStyle style2) +{ + XIMStyle s,t; + XIMStyle preedit = XIMPreeditArea | XIMPreeditCallbacks | + XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone; + XIMStyle status = XIMStatusArea | XIMStatusCallbacks | + XIMStatusNothing | XIMStatusNone; + if (style1 == 0) return style2; + if (style2 == 0) return style1; + if ((style1 & (preedit | status)) == (style2 & (preedit | status))) + return style1; + s = style1 & preedit; + t = style2 & preedit; + if (s != t) { + if (s | t | XIMPreeditCallbacks) + return (s == XIMPreeditCallbacks)?style1:style2; + else if (s | t | XIMPreeditPosition) + return (s == XIMPreeditPosition)?style1:style2; + else if (s | t | XIMPreeditArea) + return (s == XIMPreeditArea)?style1:style2; + else if (s | t | XIMPreeditNothing) + return (s == XIMPreeditNothing)?style1:style2; + } + else { /* if preedit flags are the same, compare status flags */ + s = style1 & status; + t = style2 & status; + if (s | t | XIMStatusCallbacks) + return (s == XIMStatusCallbacks)?style1:style2; + else if (s | t | XIMStatusArea) + return (s == XIMStatusArea)?style1:style2; + else if (s | t | XIMStatusNothing) + return (s == XIMStatusNothing)?style1:style2; + } +} + +void +initim(void) +{ + XIMStyles *im_supported_styles; + XIMStyle app_supported_styles; + XIMStyle style; + XIMStyle best_style; + XVaNestedList list; + char **missing_charsets; + int num_missing_charsets = 0; + char *default_string; + int i; + + fontset = XCreateFontSet(dpy, font, &missing_charsets, + &num_missing_charsets, &default_string); + if (num_missing_charsets > 0) + XFreeStringList(missing_charsets); + + if (!(im = XOpenIM(dpy, NULL, NULL, NULL))) + die("Couldn't open input method.\n"); + + XGetIMValues(im, XNQueryInputStyle, &im_supported_styles, NULL); + app_supported_styles = XIMPreeditNone | XIMPreeditNothing \ + | XIMPreeditArea; + app_supported_styles |= XIMStatusNone | XIMStatusNothing \ + | XIMStatusArea; + + for(i = 0, best_style = 0; i < im_supported_styles->count_styles; + i++) { + style = im_supported_styles->supported_styles[i]; + if ((style & app_supported_styles) == style) + best_style = choosebetterstyle(style, best_style); + } + if (best_style == 0) + die("no common shared interaction style found.\n"); + XFree(im_supported_styles); + + list = XVaCreateNestedList(0, XNFontSet, fontset, NULL); + ic = XCreateIC(im, XNInputStyle, best_style, XNClientWindow, win, + XNPreeditAttributes, list, XNStatusAttributes, + list, NULL); + XFree(list); + if (ic == NULL) + die("Could not create input context.\n"); +} + +void +createwindow(void) +{ + char *display_name; + int display_width, display_height; + int top, left; + XSizeHints *win_size_hints; + XSetWindowAttributes attrib; + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fprintf(stderr, "warning: no locale support.\n"); + + display_name = getenv("DISPLAY"); + if (display_name == NULL) + die("DISPLAY not set"); + + dpy = XOpenDisplay(display_name); + if (dpy == NULL) + die("Couldn't connect to DISPLAY"); + + if (!XSetLocaleModifiers("")) + fprintf(stderr, "warning: could not set local modifiers.\n"); + + initim(); + + screen = DefaultScreen(dpy); + display_width = DisplayWidth(dpy, screen); + display_height = DisplayHeight(dpy, screen); + + top = (display_height/2 - WINHEIGHT/2); + left = (display_width/2 - WINWIDTH/2); + + bgcol = getcolor(normbgcolor); + fgcol = getcolor(normfgcolor); + + /*win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen), + left, top, WINWIDTH, WINHEIGHT, borderwidth, + bgcol, bgcol);*/ + + attrib.override_redirect= True; + win = XCreateWindow(dpy, RootWindow(dpy, screen), + left, top, WINWIDTH, WINHEIGHT, + 0, CopyFromParent,InputOutput,CopyFromParent, + CWOverrideRedirect,&attrib); + + /* set up the window hints etc */ + win_size_hints = XAllocSizeHints(); + if (!win_size_hints) + die("out of memory allocating hints"); + + win_size_hints->flags = PMaxSize | PMinSize; + win_size_hints->min_width = win_size_hints->max_width = WINWIDTH; + + win_size_hints->min_height = win_size_hints->max_height = WINHEIGHT; + XSetWMNormalHints(dpy, win, win_size_hints); + + XFree(win_size_hints); + + XMapWindow(dpy, win); +} + +void +setupgc(void) +{ + XGCValues values; + int valuemask = 0; + int line_width = 1; + int line_style = LineSolid; + int cap_style = CapButt; + int join_style = JoinBevel; + + gc = XCreateGC(dpy, win, valuemask, &values); + rectgc = XCreateGC(dpy, win, valuemask, &values); + XSetForeground(dpy, gc, fgcol); + XSetBackground(dpy, gc, bgcol); + + XSetForeground(dpy, rectgc, bgcol); + XSetBackground(dpy, rectgc, bgcol); + + XSetLineAttributes(dpy, gc, line_width, line_style, + cap_style, join_style); + + /* setup the font */ + font_info = XLoadQueryFont(dpy, font); + if (!font_info) + die("couldn't load font"); + + XSetFont(dpy, gc, font_info->fid); +} + +void +eventloop(void) +{ + XEvent e; + + redraw(); + + XSelectInput(dpy, win, ExposureMask | KeyPressMask); + + for (;;) { + XNextEvent(dpy, &e); + switch(e.type) { + case Expose: + redraw(); + break; + case KeyPress: + keypress(&e.xkey); + break; + default: + break; + } + + } +} + +/* this loop is required since pwm grabs the keyboard during the event loop */ +void +grabhack(void) +{ + int maxwait = 3000000; /* 3 seconds */ + int interval = 5000; /* 5 millisec */ + int i, x; + + redraw(); + + /* if it takes longer than maxwait, just die */ + for (i = 0; i < (maxwait / interval); i++) { + usleep(interval); + x = XGrabKeyboard(dpy, win, False, GrabModeAsync, + GrabModeAsync, CurrentTime); + if (x == 0) + return; + } + + die("Couldn't grab keyboard"); +} + +void +redraw(void) +{ + int font_height, textwidth, promptwidth, dir, ascent, descent; + XCharStruct cs; + XRectangle ink, logical; + + font_height = font_info->ascent + font_info->descent; + XTextExtents(font_info, prompt, strlen(prompt), &dir, &ascent, + &descent, &cs); + promptwidth = cs.width; + XwcTextExtents(fontset, command, wcslen(command), &ink, &logical); + textwidth = logical.width; + textwidth += promptwidth; + + XFillRectangle(dpy, win, rectgc, 0, 0, WINWIDTH, WINHEIGHT); + XDrawRectangle(dpy, win, gc, 0, 0, WINWIDTH-1, WINHEIGHT-1); + XDrawString(dpy, win, gc, 2, font_height+2, prompt, + strlen(prompt)); + XwcDrawString(dpy, win, fontset, gc, 4 + promptwidth, + font_height+2, command, wcslen(command)); + XDrawLine(dpy, win, gc, 4 + textwidth, font_height + 2, + 4 + textwidth + 10, font_height+2); + + XFlush(dpy); +} + +void +keypress(XKeyEvent *keyevent) +{ + KeySym key_symbol; + int len; + wchar_t buffer[3]; + + len = XwcLookupString(ic, keyevent, buffer, 3, &key_symbol, NULL); + buffer[len] = L'\0'; + + switch(key_symbol) { + case XK_Escape: + exit(0); + break; + case XK_BackSpace: + len = wcslen(command); + if (len > 0) { + command[len-1] = L'\0'; + if (issecret) + secret[len-1] = L'\0'; + } + break; + case XK_Return: + case XK_KP_Enter: + execcmd(); + break; + default: + if (key_symbol > 255) + break; + + len = wcslen(command); + if (len < MAXCMD) { + if (issecret) { + secret[len] = buffer[0]; + secret[len+1] = L'\0'; + command[len] = L'*'; + command[len+1] = L'\0'; + } else { + command[len] = buffer[0]; + command[len+1] = L'\0'; + } + } + break; + } + redraw(); +} + +void +execcmd(void) +{ + char *shell; + char *argv[4]; + + XDestroyWindow(dpy, win); + + bzero(cbuf, sizeof(cbuf)); + if (issecret) + wcstombs(cbuf, secret, sizeof(cbuf)-1); + else + wcstombs(cbuf, command, sizeof(cbuf)-1); + + if (tostdout) { + printf("%s\n", cbuf); + exit(0); + } + + if (fork()) + exit(0); + + shell = getenv("SHELL"); + if (!shell) + shell = "/bin/sh"; + + argv[0] = basename(shell); + argv[1] = "-c"; + argv[2] = cbuf; + argv[3] = NULL; + + execv(shell, argv); + die("aiee, after exec"); + +} + + +void +die(char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + + exit(1); +} +