/**
* 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();
}