commit 338b4a391a7991d938921284fe500afeceab1a1b
Author: Christoph Lohmann <20h@r-36.net>
Date: Sun, 27 Mar 2011 22:10:02 +0200
Initial commit.
Diffstat:
LICENSE | | | 22 | ++++++++++++++++++++++ |
Makefile | | | 60 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
README.md | | | 26 | ++++++++++++++++++++++++++ |
config.def.h | | | 7 | +++++++ |
config.h | | | 7 | +++++++ |
config.mk | | | 26 | ++++++++++++++++++++++++++ |
thingmenu.c | | | 555 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
7 files changed, 703 insertions(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,22 @@
+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.
+
diff --git a/Makefile b/Makefile
@@ -0,0 +1,60 @@
+# 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}"
+
+config.h: config.mk
+ @echo creating $@ from config.def.h
+ @cp config.def.h $@
+
+.c.o:
+ @echo CC $<
+ @${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.mk
+
+${NAME}: ${OBJ}
+ @echo CC -o $@
+ @${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+ @echo cleaning
+ @rm -f ${NAME} ${OBJ} ${NAME}-${VERSION}.tar.gz
+
+dist: clean
+ @echo creating dist tarball
+ @mkdir -p ${NAME}-${VERSION}
+ @cp -R LICENSE 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} ${DESTDIR}${PREFIX}/bin
+ @chmod 755 ${DESTDIR}${PREFIX}/bin/${NAME}
+
+uninstall:
+ @echo removing executable file from ${DESTDIR}${PREFIX}/bin
+ @rm -f ${DESTDIR}${PREFIX}/bin/${NAME}
+
+.PHONY: all options clean dist install uninstall
diff --git a/README.md b/README.md
@@ -0,0 +1,26 @@
+# Thingmenu - a simple X11 menu
+
+This application evolved out of the need to be able to run commands
+in a touchscreen environment.
+
+## Installation
+
+ % tar -xzvf thingmenu-*.tar.gz
+ % cd thingmenu
+ % make
+ % sudo PREFIX=/usr make install
+
+## Usage
+
+ # This will open a 300px wide menu, which is showing an
+ # entry "Reboot now". When being clicked this entry will run
+ # "reboot". After that the menu will not exit (-s).
+ % thingmenu -s -ww 300 -- "Reboot now:reboot"
+
+ # This will create a centered menu, which is aligned based
+ # on the length of the label texts. After the first clicked
+ # entry it will exit.
+ % thingmenu "Force reboot:reboot -f" "Shutdown:shutdown"
+
+Have fun!
+
diff --git a/config.def.h b/config.def.h
@@ -0,0 +1,7 @@
+static const Bool wmborder = True;
+static const char *font = "-*-*-medium-*-*-*-14-*-*-*-*-*-*-*";
+static const char *normbgcolor = "#222222";
+static const char *normfgcolor = "#cccccc";
+static const char *pressbgcolor = "#ffffff";
+static const char *pressfgcolor = "#555555";
+
diff --git a/config.h b/config.h
@@ -0,0 +1,7 @@
+static const Bool wmborder = True;
+static const char *font = "-*-*-medium-*-*-*-14-*-*-*-*-*-*-*";
+static const char *normbgcolor = "#222222";
+static const char *normfgcolor = "#cccccc";
+static const char *pressbgcolor = "#ffffff";
+static const char *pressfgcolor = "#555555";
+
diff --git a/config.mk b/config.mk
@@ -0,0 +1,26 @@
+# thingmenu metadata
+NAME = thingmenu
+VERSION = 0.2
+
+# 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/thingmenu.c b/thingmenu.c
@@ -0,0 +1,555 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+#include <unistd.h>
+#include <locale.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <X11/keysym.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xproto.h>
+#include <X11/extensions/XTest.h>
+
+/* macros */
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define LENGTH(x) (sizeof x / sizeof x[0])
+
+/* enums */
+enum { ColFG, ColBG, ColLast };
+enum { NetWMWindowType, NetLast };
+
+/* typedefs */
+typedef unsigned int uint;
+typedef unsigned long ulong;
+
+typedef struct {
+ ulong norm[ColLast];
+ ulong press[ColLast];
+ Drawable drawable;
+ GC gc;
+ struct {
+ int ascent;
+ int descent;
+ int height;
+ XFontSet set;
+ XFontStruct *xfont;
+ } font;
+} DC; /* draw context */
+
+typedef struct {
+ char *label;
+ char *cmd;
+ uint width;
+ int x, y, w, h;
+ Bool pressed;
+ Bool forceexit;
+} Entry;
+
+/* function declarations */
+static void buttonpress(XEvent *e);
+static void buttonrelease(XEvent *e);
+static void cleanup(void);
+static void configurenotify(XEvent *e);
+static void unmapnotify(XEvent *e);
+static void die(const char *errstr, ...);
+static void drawmenu(void);
+static void drawentry(Entry *e);
+static void expose(XEvent *e);
+static Entry *findentry(int x, int y);
+static ulong getcolor(const char *colstr);
+static void initfont(const char *fontstr);
+static void leavenotify(XEvent *e);
+static void press(Entry *e);
+static void run(void);
+static void setup(void);
+static int textnw(const char *text, uint len);
+static void unpress(void);
+static void updateentries(void);
+
+/* variables */
+static int screen;
+static void (*handler[LASTEvent]) (XEvent *) = {
+ [ButtonPress] = buttonpress,
+ [ButtonRelease] = buttonrelease,
+ [ConfigureNotify] = configurenotify,
+ [UnmapNotify] = unmapnotify,
+ [Expose] = expose,
+ [LeaveNotify] = leavenotify,
+};
+
+static Display *dpy;
+static DC dc;
+static Window root, win;
+static Bool running = True;
+static int ww = 0, wh = 0, wx = 0, wy = 0;
+static char *name = "thingmenu";
+
+Entry **entries = NULL;
+int nentries = 0;
+int oneshot = 1;
+
+/* configuration, allows nested code to access above variables */
+#include "config.h"
+
+void
+buttonpress(XEvent *e)
+{
+ XButtonPressedEvent *ev = &e->xbutton;
+ Entry *en;
+
+ if((en = findentry(ev->x, ev->y)))
+ press(en);
+}
+
+void
+buttonrelease(XEvent *e)
+{
+ XButtonPressedEvent *ev = &e->xbutton;
+ Entry *en;
+
+ if((en = findentry(ev->x, ev->y)))
+ unpress();
+}
+
+void
+cleanup(void)
+{
+ if(dc.font.set)
+ XFreeFontSet(dpy, dc.font.set);
+ else
+ XFreeFont(dpy, dc.font.xfont);
+ XFreePixmap(dpy, dc.drawable);
+ XFreeGC(dpy, dc.gc);
+ XDestroyWindow(dpy, win);
+ XSync(dpy, False);
+ XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
+}
+
+void
+configurenotify(XEvent *e)
+{
+ XConfigureEvent *ev = &e->xconfigure;
+
+ if(ev->window == win && (ev->width != ww || ev->height != wh)) {
+ ww = ev->width;
+ wh = ev->height;
+ XFreePixmap(dpy, dc.drawable);
+ dc.drawable = XCreatePixmap(dpy, root, ww, wh,
+ DefaultDepth(dpy, screen));
+ updateentries();
+ }
+}
+
+void
+die(const char *errstr, ...)
+{
+ va_list ap;
+
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+void
+drawmenu(void)
+{
+ int i;
+
+ for(i = 0; i < nentries; i++)
+ drawentry(entries[i]);
+ XSync(dpy, False);
+}
+
+void
+drawentry(Entry *e)
+{
+ int x, y, h, len;
+ XRectangle r = { e->x, e->y, e->w, e->h};
+ const char *l;
+ ulong *col;
+
+ if(e->pressed)
+ col = dc.press;
+ else
+ col = dc.norm;
+
+ XSetForeground(dpy, dc.gc, col[ColBG]);
+ XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
+ XSetForeground(dpy, dc.gc, dc.norm[ColFG]);
+ r.height -= 1;
+ r.width -= 1;
+ XDrawRectangles(dpy, dc.drawable, dc.gc, &r, 1);
+ XSetForeground(dpy, dc.gc, col[ColFG]);
+
+ l = e->label;
+ len = strlen(l);
+ h = dc.font.height;
+ y = e->y + (e->h / 2) - (h / 2) + dc.font.ascent;
+ x = e->x + (e->w / 2) - (textnw(l, len) / 2);
+ if(dc.font.set) {
+ XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, l,
+ len);
+ } else
+ XDrawString(dpy, dc.drawable, dc.gc, x, y, l, len);
+ XCopyArea(dpy, dc.drawable, win, dc.gc, e->x, e->y, e->w, e->h,
+ e->x, e->y);
+}
+
+void
+unmapnotify(XEvent *e)
+{
+ running = False;
+}
+
+void
+expose(XEvent *e)
+{
+ XExposeEvent *ev = &e->xexpose;
+
+ if(ev->count == 0 && (ev->window == win))
+ drawmenu();
+}
+
+Entry *
+findentry(int x, int y)
+{
+ int i;
+
+ for(i = 0; i < nentries; i++) {
+ if(x > entries[i]->x && x < entries[i]->x + entries[i]->w
+ && y > entries[i]->y
+ && y < entries[i]->y + entries[i]->h)
+ return entries[i];
+ }
+ return NULL;
+}
+
+ulong
+getcolor(const char *colstr)
+{
+ Colormap cmap = DefaultColormap(dpy, screen);
+ XColor color;
+
+ if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color))
+ die("error, cannot allocate color '%s'\n", colstr);
+ return color.pixel;
+}
+
+void
+initfont(const char *fontstr)
+{
+ char *def, **missing;
+ int i, n;
+
+ missing = NULL;
+ if(dc.font.set)
+ XFreeFontSet(dpy, dc.font.set);
+ dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
+ if(missing) {
+ while(n--)
+ fprintf(stderr, "svkbd: missing fontset: %s\n", missing[n]);
+ XFreeStringList(missing);
+ }
+ if(dc.font.set) {
+ XFontSetExtents *font_extents;
+ XFontStruct **xfonts;
+ char **font_names;
+ dc.font.ascent = dc.font.descent = 0;
+ font_extents = XExtentsOfFontSet(dc.font.set);
+ n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names);
+ for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) {
+ dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent);
+ dc.font.descent = MAX(dc.font.descent,(*xfonts)->descent);
+ xfonts++;
+ }
+ }
+ else {
+ if(dc.font.xfont)
+ XFreeFont(dpy, dc.font.xfont);
+ dc.font.xfont = NULL;
+ if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr))
+ && !(dc.font.xfont = XLoadQueryFont(dpy, "fixed")))
+ die("error, cannot load font: '%s'\n", fontstr);
+ dc.font.ascent = dc.font.xfont->ascent;
+ dc.font.descent = dc.font.xfont->descent;
+ }
+ dc.font.height = dc.font.ascent + dc.font.descent;
+}
+
+void
+leavenotify(XEvent *e)
+{
+ unpress();
+}
+
+void
+runentry(Entry *e)
+{
+ char *shell;
+
+ if (fork()) {
+ if (oneshot || e->forceexit) {
+ XDestroyWindow(dpy, win);
+ exit(0);
+ }
+ return;
+ }
+ if (fork())
+ exit(0);
+
+ shell = getenv("SHELL");
+ if (!shell)
+ shell = "/bin/sh";
+
+ execlp(shell, basename(shell), "-c", e->cmd, (char *)NULL);
+}
+
+void
+press(Entry *e)
+{
+ e->pressed = !e->pressed;
+
+ runentry(e);
+ drawentry(e);
+}
+
+void
+run(void)
+{
+ XEvent ev;
+
+ /* main event loop */
+ XSync(dpy, False);
+ while(running) {
+ XNextEvent(dpy, &ev);
+ if(handler[ev.type])
+ (handler[ev.type])(&ev); /* call handler */
+ }
+}
+
+void
+setup(void)
+{
+ XSetWindowAttributes wa;
+ XTextProperty str;
+ XSizeHints *sizeh;
+ XClassHint *ch;
+ int i, sh, sw, ls;
+
+ /* init screen */
+ screen = DefaultScreen(dpy);
+ root = RootWindow(dpy, screen);
+ sw = DisplayWidth(dpy, screen) - 1;
+ sh = DisplayHeight(dpy, screen) - 1;
+ initfont(font);
+
+ /* init atoms */
+
+ /* init appearance */
+
+ if (ww == 0) {
+ for (i = 0, ww = 0; i < nentries; i++) {
+ ls = textnw(entries[i]->label,
+ strlen(entries[i]->label));
+ if (ls > ww)
+ ww = ls;
+ }
+ ww *= 1.5;
+ }
+ if (wh == 0)
+ wh = (nentries + 2) * dc.font.height + 4;
+ if (wy == 0)
+ wy = (sh - wh) / 2;
+ if (wx == 0)
+ wx = (sw - ww) / 2;
+
+ dc.norm[ColBG] = getcolor(normbgcolor);
+ dc.norm[ColFG] = getcolor(normfgcolor);
+ dc.press[ColBG] = getcolor(pressbgcolor);
+ dc.press[ColFG] = getcolor(pressfgcolor);
+ dc.drawable = XCreatePixmap(dpy, root, ww, wh, DefaultDepth(dpy, screen));
+ dc.gc = XCreateGC(dpy, root, 0, 0);
+ if(!dc.font.set)
+ XSetFont(dpy, dc.gc, dc.font.xfont->fid);
+ for(i = 0; i < nentries; i++)
+ entries[i]->pressed = 0;
+
+ wa.override_redirect = !wmborder;
+ wa.border_pixel = dc.norm[ColFG];
+ wa.background_pixel = dc.norm[ColBG];
+ win = XCreateWindow(dpy, root, wx, wy, ww, wh, 0,
+ CopyFromParent, CopyFromParent, CopyFromParent,
+ CWOverrideRedirect | CWBorderPixel | CWBackingPixel, &wa);
+ XSelectInput(dpy, win, StructureNotifyMask|ButtonReleaseMask|
+ ButtonPressMask|ExposureMask|LeaveWindowMask);
+
+ sizeh = XAllocSizeHints();
+ sizeh->flags = PMaxSize | PMinSize;
+ sizeh->min_width = sizeh->max_width = ww;
+ sizeh->min_height = sizeh->max_height = wh;
+ XStringListToTextProperty(&name, 1, &str);
+ ch = XAllocClassHint();
+ ch->res_class = name;
+ ch->res_name = name;
+
+ XSetWMProperties(dpy, win, &str, &str, NULL, 0, sizeh, NULL,
+ ch);
+
+ XFree(ch);
+ XFree(str.value);
+ XFree(sizeh);
+
+ XMapRaised(dpy, win);
+ updateentries();
+ drawmenu();
+}
+
+int
+textnw(const char *text, uint len)
+{
+ XRectangle r;
+
+ if(dc.font.set) {
+ XmbTextExtents(dc.font.set, text, len, NULL, &r);
+ return r.width;
+ }
+ return XTextWidth(dc.font.xfont, text, len);
+}
+
+void
+unpress()
+{
+ int i;
+
+ for(i = 0; i < nentries; i++) {
+ if(entries[i]->pressed) {
+ entries[i]->pressed = 0;
+ drawentry(entries[i]);
+ }
+ }
+}
+
+void
+updateentries(void)
+{
+ int i, y = 0, h;
+
+ h = wh / nentries;
+ for(i = 0; i < nentries; i++) {
+ entries[i]->x = 0;
+ entries[i]->y = y;
+ entries[i]->w = ww;
+ entries[i]->h = h;
+ y += h;
+ }
+}
+
+void
+usage(char *argv0)
+{
+ fprintf(stderr, "usage: %s [-hs] [-wh height] [-ww width] "
+ "[-wx x position] [-wy y position] [--] "
+ "label:cmd ...\n", argv0);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *label, *cmd;
+ int i;
+
+ if (argc < 2)
+ usage(argv[0]);
+ i = 1;
+ for (; argv[i]; i++) {
+ if (argv[i][0] != '-')
+ break;
+
+ if (argv[i][1] == '-') {
+ i++;
+ break;
+ }
+
+ switch (argv[i][1]) {
+ case 'h':
+ usage(argv[0]);
+ case 's':
+ oneshot = 0;
+ break;
+ case 'w':
+ switch ((i >= argc - 1)? 0 : argv[i][2]) {
+ case 'h':
+ wh = atoi(argv[i+1]);
+ i++;
+ break;
+ case 'w':
+ ww = atoi(argv[i+1]);
+ i++;
+ break;
+ case 'x':
+ wx = atoi(argv[i+1]);
+ i++;
+ break;
+ case 'y':
+ wy = atoi(argv[i+1]);
+ i++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ for (; argv[i]; i++) {
+ sscanf(argv[i], "%1024m[^:]:%1024m[^\n]", &label, &cmd);
+ if (label == NULL || cmd == NULL) {
+ if (label == NULL)
+ free(label);
+ if (cmd == NULL)
+ free(cmd);
+ usage(argv[0]);
+ }
+ entries = realloc(entries, sizeof(entries[0])*(++nentries));
+ entries[nentries-1] = malloc(sizeof(*entries[0]));
+ bzero(entries[nentries-1], sizeof(*entries[0]));
+ entries[nentries-1]->label = label;
+ entries[nentries-1]->cmd = cmd;
+ }
+ if (nentries < 1)
+ usage(argv[0]);
+
+ entries = realloc(entries, sizeof(entries[0])*(++nentries));
+ entries[nentries-1] = malloc(sizeof(*entries[0]));
+ bzero(entries[nentries-1], sizeof(*entries[0]));
+ entries[nentries-1]->label = strdup("cancel");
+ entries[nentries-1]->cmd = "exit";
+ entries[nentries-1]->forceexit = True;
+
+ if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
+ fprintf(stderr, "warning: no locale support\n");
+ if(!(dpy = XOpenDisplay(0)))
+ die("thingmenu: cannot open display\n");
+
+ setup();
+ run();
+ cleanup();
+ XCloseDisplay(dpy);
+
+ for (i = 0; i < nentries; i++)
+ free(entries[i]);
+ free(entries);
+
+ return 0;
+}
+