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);
+}
+