/** * 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 #include #include #include #include #include #include #include #include #include #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[&]:[]:\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(); }