/**
* 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 <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <sys/wait.h>
#include <wordexp.h>
#define MAX(A, B) ((A) > (B) ? (A) : (B))
Display *dis;
int screen;
Window win;
GC gc;
Colormap cm;
XColor red;
typedef struct {
unsigned int w, h;
Display *dis;
int screen;
Window root, win;
Drawable drawable;
GC gc;
// XftColor *scheme;
} Drw;
Drw drw;
int w_width = 0;
int w_height = 0;
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 get_curson_pos(int *x, int *y)
{
int win_x_return, win_y_return;
unsigned int mask_return;
Window root_return, child_return;
XQueryPointer(drw.dis, drw.root, &root_return, &child_return,
x, y, &win_x_return, &win_y_return, &mask_return);
}
void init()
{
XWindowAttributes wa;
XSetWindowAttributes swa;
drw.dis = XOpenDisplay(NULL);
drw.screen = DefaultScreen(drw.dis);
drw.root = RootWindow(drw.dis, drw.screen);
if (!XGetWindowAttributes(drw.dis, drw.root, &wa))
die("could not get embedding window attributes: 0x%lx", drw.root);
drw.w = wa.width;
drw.h = wa.height;
//drw.drawable = XCreatePixmap(drw.dis, drw.root, drw.w, drw.h, DefaultDepth(drw.dis, drw.screen));
drw.gc = XCreateGC(drw.dis, drw.root, 0, NULL);
XSetLineAttributes(drw.dis, drw.gc, 1, LineSolid, CapButt, JoinMiter);
swa.override_redirect = True;
swa.background_pixel = BlackPixel(drw.dis, drw.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;
drw.win = XCreateWindow(drw.dis, drw.root, x, y, width, height, 0,
CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
printf("Window pos: {%d, %d}", x, y);
fflush(stdout);
cm = DefaultColormap(drw.dis, drw.screen);
if (! XAllocNamedColor(drw.dis, cm, "red", &red, &red)) {
fprintf(stderr, "XAllocNamedColor - failed to allocated 'red' color.\n");
exit(1);
}
XMapRaised(drw.dis, drw.win);
XRaiseWindow(drw.dis, drw.win);
}
void init_x()
{
/* get the colors black and white (see section for details) */
unsigned long black,white;
/* use the information from the environment variable DISPLAY
to create the X connection:
*/
dis=XOpenDisplay((char *)0);
screen=DefaultScreen(dis);
black=BlackPixel(dis,screen);
white=WhitePixel(dis, screen);
/* once the display is initialized, create the window.
This window will be have be 200 pixels across and 300 down.
It will have the foreground white and background black
*/
win=XCreateSimpleWindow(dis,DefaultRootWindow(dis),0,0,
200, 300, 1, white, black);
/* here is where some properties of the window can be set.
The third and fourth items indicate the name which appears
at the top of the window and the name of the minimized window
respectively.
*/
XSetStandardProperties(dis,win,"xlnch","xlnch",None,NULL,0,NULL);
XClassHint *hint = XAllocClassHint();
if (hint) {
hint->res_name = "xlnch";
hint->res_class = "xlnch";
}
XSetClassHint(dis,win,hint);
XFree(hint);
/* this routine determines which types of input are allowed in
the input. see the appropriate section for details...
*/
XSelectInput(dis, win, ExposureMask|ButtonPressMask|KeyPressMask);
/* create the Graphics Context */
gc=XCreateGC(dis, win, 0,0);
/* here is another routine to set the foreground and background
colors _currently_ in use in the window.
*/
XSetBackground(dis,gc,black);
XSetForeground(dis,gc,white);
cm = DefaultColormap(dis, screen);
if (! XAllocNamedColor(dis, cm, "red", &red, &red)) {
fprintf(stderr, "XAllocNamedColor - failed to allocated 'red' color.\n");
exit(1);
}
/* Font font = XLoadFont(dis,"*mono*"); */
/* XSetFont(dis,gc,font); */
/* clear the window and bring it on top of the other windows */
XClearWindow(dis, win);
XMapRaised(dis, win);
}
void close_x()
{
XFreeGC(drw.dis, drw.gc);
XDestroyWindow(drw.dis,drw.win);
XCloseDisplay(drw.dis);
}
#define BUF_SIZE 2048
char buf[BUF_SIZE] = "";
struct lnch_t {
int exits;
char key;
char desc[512];
char cmd[1024];
};
struct lnch_t lnch[32];
unsigned int idx = 0;
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';
}
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(drw.dis, &focuswin, &revertwin);
if (focuswin == drw.win)
return;
XSetInputFocus(drw.dis, drw.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(drw.dis, drw.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(drw.dis,drw.gc,WhitePixel(drw.dis,drw.screen));
else
XSetForeground(drw.dis,drw.gc,red.pixel);
sprintf(text, "%c: %s", lnch[i].key, lnch[i].desc);
XDrawString(drw.dis,drw.win,drw.gc,x,y+=13, text, strlen(text));
}
}
void redraw()
{
XClearWindow(drw.dis, drw.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(drw.dis, &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(drw.dis, drw.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(0);
}
}
}
}
if (event.type==ButtonPress) {
/* tell where the mouse Button was Pressed */
int x=event.xbutton.x,
y=event.xbutton.y;
strcpy(text,">>xlnch<<");
XSetForeground(drw.dis,drw.gc,rand()%255);
XDrawString(drw.dis,drw.win,drw.gc,x,y, text, strlen(text));
}
if (event.type==FocusIn) {
if (event.xfocus.window != win) {
grabfocus();
}
}
}
}