summaryrefslogblamecommitdiffstats
path: root/xlnch.c
blob: a5ffd62d5b270beefea4c0e89aed77ba88d8b156 (plain) (tree)


















































































































































































































































































































































































                                                                                                                         
/**
 * xlnch -- D e s c r i p t i o n
 * Copyright (C) 2019  Anastasis Grammenos
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

/* Basic X code from:
 * Brian Hammond 2/9/96. http://mech.math.msu.su/~nap/2/GWindow/xintro.html
 */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>
#include <wordexp.h>
#include <X11/Xlib.h>
#include <X11/Xos.h>
#include <X11/Xutil.h>

#include "util.h"

#define BUF_SIZE 2048
#define MAX_ENTRIES 32

#define FONTNAME "-*-terminus-medium-*-*-*-16-*-*-*-*-*-*-*"

char buf[BUF_SIZE] = "", cur[BUF_SIZE] = "";

struct lnch_t {
    int exits;
    char key;
    char desc[512];
    char cmd[1024];
} lnch[MAX_ENTRIES];

unsigned int idx = 0;

Colormap cm;
Display *display;
GC gc;
int screen;
Window root, win;
XColor red;
XFontStruct *font;

int w_width = 0;
int w_height = 0;

void get_curson_pos(int *x, int *y)
{
    int win_x_return, win_y_return;
    unsigned int mask_return;
    Window root_return, child_return;

    XQueryPointer(display, root, &root_return, &child_return,
		  x, y, &win_x_return, &win_y_return, &mask_return);
}

void init()
{
    XWindowAttributes wa;
    XSetWindowAttributes swa;

    display = XOpenDisplay(NULL);
    screen = DefaultScreen(display);
    root = RootWindow(display, screen);
    if (!XGetWindowAttributes(display, root, &wa))
	die("could not get embedding window attributes: 0x%lx", root);

    gc = XCreateGC(display, root, 0, NULL);
    XSetLineAttributes(display, gc, 1, LineSolid, CapButt, JoinMiter);

    cm = DefaultColormap(display, screen);
    if (!XAllocNamedColor(display, cm, "red", &red, &red)) {
	fprintf(stderr,
		"XAllocNamedColor - failed to allocated 'red' color.\n");
	exit(1);
    }

    swa.override_redirect = True;
    swa.background_pixel = BlackPixel(display, screen);
    swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;

    int x, y;
    get_curson_pos(&x, &y);

    int height = 16 + (9 + 3) * w_height - 3 + 16,
	width = 16 + w_width * 11;
    win = XCreateWindow(display, root, x, y, width, height, 0,
			CopyFromParent, CopyFromParent, CopyFromParent,
			CWOverrideRedirect | CWBackPixel | CWEventMask,
			&swa);

    font = XLoadQueryFont(display, FONTNAME);
    /* If the font could not be loaded, revert to the "fixed" font. */
    if (!font) {
	fprintf(stderr, "unable to load font %s: using fixed\n", FONTNAME);
	font = XLoadQueryFont(display, "fixed");
    }
    XSetFont(display, gc, font->fid);

    XMapRaised(display, win);
    XRaiseWindow(display, win);
}

void close_x()
{
    XFreeGC(display, gc);
    XDestroyWindow(display, win);
    XCloseDisplay(display);
}

int parse_line(char *line)
{
    char *token;
    char delim[2] = ":";

    /* Copy buf to cur for error reporting */
    strcpy(cur, buf);

    trim(buf);

    if (strlen(line) == 0 || line[0] == '#')
	return 2;

    lnch[idx].exits = 1;

    if (line[0] == '&') {
	lnch[idx].exits = 0;
	line++;
    }

    /* Get key entry */
    token = strtok(line, delim);

    if (strlen(token) != 1)
	return 1;

    lnch[idx].key = token[0];
    line += 2;			// pass the key, pass the `:`

    /* Get description */
    token = strtok(NULL, delim);

    if (!token)
	return 1;

    strcpy(lnch[idx].desc, token);
    line += (strlen(token) + 1);	// pass the decription, pass the `:`

    /* Get command */
    strcpy(lnch[idx].cmd, line);

    /* If description is epmty command becomes the descritpion */
    if (!strlen(lnch[idx].cmd))
	strcpy(lnch[idx].cmd, lnch[idx].desc);

    w_width = MAX((int) strlen(lnch[idx].desc), w_width);
    idx++;
    return 0;
}

int run_cmd(char *cmd)
{
    wordexp_t p;
    int rc = wordexp(cmd, &p, 0);
    switch (rc) {
    case WRDE_BADCHAR:
	printf
	    ("Illegal occurrence of newline or one of |, &, ;, <, >, (, ), {, }.\n");
	break;
    case WRDE_BADVAL:
	printf
	    ("An undefined shell variable was referenced, and the WRDE_UNDEF flag told us to consider this an error.\n");
	break;
    case WRDE_CMDSUB:
	printf
	    ("Command substitution requested, but the WRDE_NOCMD flag told us to consider this an error.\n");
	break;
    case WRDE_NOSPACE:
	printf("Out of memory.\n");
	break;
    case WRDE_SYNTAX:
	printf
	    ("Shell syntax error, such as unbalanced parentheses or unmatched quotes.\n");
	break;
    default:
	printf("Running:");
	for (size_t i = 0; i < p.we_wordc; i++) {
	    printf(" %s", p.we_wordv[i]);
	}
	printf("\n");

	/* double fork from i3wm's exec implementation */
	if (fork() == 0) {
	    setsid();
	    if (fork() == 0) {
		execvp(p.we_wordv[0], p.we_wordv);
		/* never reached */
	    }
	    _exit(EXIT_SUCCESS);
	}
	wait(0);
    }
    wordfree(&p);
    return rc;
}

void grabfocus()
{
    struct timespec ts = {.tv_sec = 0,.tv_nsec = 10000000 };
    Window focuswin;
    int i, revertwin;

    for (i = 0; i < 100; ++i) {
	XGetInputFocus(display, &focuswin, &revertwin);
	if (focuswin == win)
	    return;
	XSetInputFocus(display, win, RevertToParent, CurrentTime);
	nanosleep(&ts, NULL);
    }
    printf("cannot grab focus\n");
}

void grabkeyboard()
{
    struct timespec ts = {.tv_sec = 0,.tv_nsec = 1000000 };
    int i;

    /* try to grab keyboard, we may have to wait for another process to ungrab */
    for (i = 0; i < 1000; i++) {
	if (XGrabKeyboard(display, root, True, GrabModeAsync,
			  GrabModeAsync, CurrentTime) == GrabSuccess)
	    return;
	nanosleep(&ts, NULL);
    }
    printf("Cannot grab keyboard\n");
}

void draw_text()
{
    char text[2048];
    int x = 10, y = 2;
    for (unsigned int i = 0; i < idx; i++) {
	if (lnch[i].exits) {
	    XSetForeground(display, gc, WhitePixel(display, screen));
	} else {
	    XSetForeground(display, gc, red.pixel);
	}
	sprintf(text, "%c: %s", lnch[i].key, lnch[i].desc);
	XDrawString(display, win, gc, x, y += 13, text, strlen(text));
    }
}

int run()
{
    XEvent event;
    KeySym keysym;
    char text[255];
    int flag, rc;

    init();
    draw_text();

    grabkeyboard();
    grabfocus();

    while (1) {
	XNextEvent(display, &event);

	if (event.type == Expose && event.xexpose.count == 0) {
	    /* the window was exposed redraw it! */
	    XClearWindow(display, win);
	    draw_text();
	}

	/* Keep window in view */
	else if (event.type == VisibilityNotify &&
		 event.xvisibility.state != VisibilityUnobscured)
	    XRaiseWindow(display, win);

	else if (event.type == KeyPress) {
	    /* C-g and ESC quit */
	    if ((event.xkey.state == 4 && event.xkey.keycode == 42)	/* => C-g  */
		||event.xkey.keycode == 9) {	/* => ESC  */
		close_x();
		return 0;
	    }

	    XLookupString(&event.xkey, text, 255, &keysym, 0);
	    flag = 0;

	    for (unsigned int i = 0; i < idx; i++) {
		if (lnch[i].key == text[0]) {
		    rc = run_cmd(lnch[i].cmd);
		    flag = 1;
		    if (lnch[i].exits) {
			close_x();
			return rc;
		    }
		}
	    }

	    /* Q and q quit as well, as long as they don't match anything */
	    if (!flag && event.xkey.keycode == 24) {	/* Q or q */
		close_x();
		return 0;
	    }
	}

	else if (event.type == FocusIn) {
	    if (event.xfocus.window != win) {
		grabfocus();
	    }
	}
    }
}

int main(int argc, char *argv[])
{
    int rc, n = 1;
    FILE *f;

    if ((argc > 1) &&
	(!strcmp(argv[1], "--help") ||
	 !strcmp(argv[1], "-h") || !strcmp(argv[1], "-?"))) {
	printf("Usage:\n%s [xlnchrc]\n", argv[0]);
	printf("\nxlnchrc format:\n[&]<key>:[<description>]:<command>\n");
	return 0;
    }

    if (argc == 1 || !strcmp(argv[1], "-"))
	f = stdin;
    else {
	f = fopen(argv[1], "r");
	if (!f)
	    die("Cannot open file %s", argv[1]);
    }

    while (fgets(buf, BUF_SIZE, f)) {
	rc = parse_line(buf);
	if (rc == 1) {
	    fprintf(stderr, "%s:%d: cannot parse ā€˜%sā€™\n",
		    argc == 1
		    || !strcmp(argv[1], "-") ? "stdin" : argv[1], n, cur);
	} else if (rc == 0) {
	    w_height++;
	}
	n++;
    }

    if (f != stdin)
	fclose(f);

    return run();
}