/**
* 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 <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <sys/wait.h>
#include <wordexp.h>
#include "util.h"
#define BUF_SIZE 2048
#define MAX_ENTRIES 32
char buf[BUF_SIZE] = "";
struct lnch_t {
int exits;
char key;
char desc[512];
char cmd[1024];
};
struct lnch_t lnch[MAX_ENTRIES];
unsigned int idx = 0;
Colormap cm;
XColor red;
Display *display;
int screen;
Window root, win;
//Drawable drawable;
GC gc;
// XftColor *scheme;
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);
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*9;
win = XCreateWindow(display, root, x, y, width, height, 0,
CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
printf("Window pos: {%d, %d}", x, y);
fflush(stdout);
cm = DefaultColormap(display, screen);
if (! XAllocNamedColor(display, cm, "red", &red, &red)) {
fprintf(stderr, "XAllocNamedColor - failed to allocated 'red' color.\n");
exit(1);
}
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] = ":";
lnch[idx].exits = 1;
if (line[0] == '#')
return 0;
if (line[0] == '&') {
lnch[idx].exits = 0;
line++;
}
/* Get key entry */
token = strtok(line, delim);
if (strlen(token) != 1) {
printf("error on token %s", token) ;
return 1;
}
lnch[idx].key = token[0];
line+=2; // pass the key, pass the `:`
/* Get description */
token = strtok(NULL, delim);
if (!token) return 1;
w_width = MAX((int) strlen(token), w_width);
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].desc))
strcpy(lnch[idx].desc, line);
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 = 10;
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));
}
}
void redraw()
{
XClearWindow(display, win);
draw_text();
}
int main (int argc, char *argv[])
{
if (argc < 2) {
printf("Usage:\n%s xlnchrc\n", argv[0]);
printf("xlnchrc format:\n[&]<key>:<description>:<command>\n");
return -1;
}
FILE *f;
if (!strcmp(argv[1], "-"))
f = stdin;
else {
f = fopen(argv[1], "r");
/* printf("xlnchrc required\n"); */
/* return -1; */
}
while (fgets(buf, BUF_SIZE, f)) {
trim(buf);
parse_line(buf);
w_height++;
}
if (f != stdin) fclose(f);
XEvent event; /* the XEvent declaration !!! */
KeySym key; /* a dealie-bob to handle KeyPress Events */
char text[255]; /* a char buffer for KeyPress Events */
int rc;
init();
// system("sleep 100");
grabkeyboard();
grabfocus();
/* look for events forever... */
while(1) {
/* get the next event and stuff it into our event variable.
Note: only events we set the mask for are detected!
*/
XNextEvent(display, &event);
if (event.type==Expose && event.xexpose.count==0) {
/* the window was exposed redraw it! */
redraw();
}
/* Keep window in view */
if (event.type==VisibilityNotify &&
event.xvisibility.state != VisibilityUnobscured)
XRaiseWindow(display, win);
if (event.type==KeyPress &&
XLookupString(&event.xkey,text,255,&key,0)==1) {
/* use the XLookupString routine to convert the invent
KeyPress data into regular text. Weird but necessary...
*/
if (text[0]=='q') {
close_x();
exit(0);
}
printf("You pressed the %c key!\n",text[0]);
for (unsigned int i = 0; i < idx; i++) {
if (lnch[i].key == text[0]) {
rc = run_cmd(lnch[i].cmd);
if (lnch[i].exits) {
close_x();
exit(rc);
}
}
}
}
if (event.type==ButtonPress) {
/* tell where the mouse Button was Pressed */
int x=event.xbutton.x,
y=event.xbutton.y;
strcpy(text,">>xlnch<<");
XSetForeground(display,gc,rand()%255);
XDrawString(display,win,gc,x,y, text, strlen(text));
}
if (event.type==FocusIn) {
if (event.xfocus.window != win) {
grabfocus();
}
}
}
}