diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 28 | ||||
-rw-r--r-- | README.org | 6 | ||||
-rw-r--r-- | eye-xlnchrc | 5 | ||||
-rw-r--r-- | nude-xlnchrc | 5 | ||||
-rw-r--r-- | util.h | 44 | ||||
-rw-r--r-- | xlnch.c | 371 | ||||
-rwxr-xr-x | xlnchrc | 13 |
8 files changed, 474 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b61172c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +xlnch +*.test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..172219f --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +TARGET=xlnch +SRC=*.c +CC=gcc +CFLAGS=-D_GNU_SOURCE -std=c99 -pedantic +REL_FLAGS=-O3 +DBG_FLAGS=-Wall -g3 +LIBS=-l X11 + +make: $(SRC) + $(CC) $(REL_FLAGS) $(CFLAGS) -o $(TARGET) $^ $(LIBS) + +debug: $(SRC) + $(CC) $(DBG_FLAGS) -fsanitize=address $(CFLAGS) -o $(TARGET) $^ $(LIBS) + +noasan: $(SRC) + $(CC) $(DBG_FLAGS) $(CFLAGS) -o $(TARGET) $^ $(LIBS) + +.PHONY: clean + +clean: + rm -f *.o + rm -f $(TARGET) + +install: $(TARGET) + @cp -v $(TARGET) /usr/local/bin/$(TARGET) + +uninstall: + @rm -v /usr/local/bin/$(TARGET) diff --git a/README.org b/README.org new file mode 100644 index 0000000..e3cf70b --- /dev/null +++ b/README.org @@ -0,0 +1,6 @@ +* TODOs + + (xlnch -p mouse|center|NE|NW|SE|SW) p stands for =pos= + - spawn window in mouse position + - spawn window center + - spawn window on NE NW SE or SW corners diff --git a/eye-xlnchrc b/eye-xlnchrc new file mode 100644 index 0000000..b20589e --- /dev/null +++ b/eye-xlnchrc @@ -0,0 +1,5 @@ +t:term:urxvt-server-client -e sh -c 'ssh -p 6666 192.168.1.11 -lgramanas' +e:emacs:urxvt-server-client -e sh -c 'ssh -p 6666 192.168.1.11 -lgramanas emacsclient' +n:ncmpcpp:urxvt-server-client -e sh -c 'ssh -p 6666 192.168.1.11 -lgramanas ncmpcpp' +k:kpcli:urxvt-server-client -e sh -c 'ssh -p 6666 192.168.1.11 -lgramanas kpcli' +p:pavucontrol:urxvt-server-client -e sh -c 'ssh -p 6666 192.168.1.11 -lgramanas pavucontrol --server localhost' diff --git a/nude-xlnchrc b/nude-xlnchrc new file mode 100644 index 0000000..20eb72f --- /dev/null +++ b/nude-xlnchrc @@ -0,0 +1,5 @@ +p:prometheus:firefox https://mgmt01.one.nude.noris.de:8004/ +a:alertmanager:firefox https://mgmt01.one.nude.noris.de:8006/ +g:grafana:https://mgmt01.one.nude.noris.de:8005/ +j:jira:firefox https://jira.office.noris.de +k:kibana:firefox https://mgmt01.one.nude.noris.de:8028/ @@ -0,0 +1,44 @@ +#ifndef XLNCH_UTIL_H +#define XLNCH_UTIL_H + +#include <stdarg.h> + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) + +void die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} + +void trim(char * str) +{ + int i; + int begin = 0; + int end = strlen(str) - 1; + + while (isspace((unsigned char) str[begin])) + begin++; + + while (isspace((unsigned char) str[end]) && (end >= begin)) + end--; + + /* Shift all characters back to the start of the string array. */ + for (i = begin; i <= end; i++) + str[i - begin] = str[i]; + + str[i - begin] = '\0'; +} + +#endif /* XLNCH_UTIL_H */ @@ -0,0 +1,371 @@ +/** + * 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(); +} @@ -0,0 +1,13 @@ +#!./xlnch +&e:emacs:emacsg +f:firefox:firefox +g:EYE:urxvt-server-client -e sh -c 'ssh -p 6666 192.168.1.11 -lgramanas' +t:tmux:urxvt-server-client -e tmux +m:mu4e:emacsclient -c --eval "(mu4e~headers-jump-to-maildir '/INBOX)" +i:inkscape:inkscape +k:keepassx:keepassx +p:emelfm2:emelfm2 +P:pavucontrol:pavucontrol +o:popcorntime:popcorntime +d:discord:discord +D:discord:killall Discord |