summaryrefslogblamecommitdiffstats
path: root/xlnch.c
blob: c68aecf02b26c9f33bb09f8826e1ebfafae4142e (plain) (tree)






















                                                                                  
                  


                   
                     
                 
                    


                      
 
                 
 













                                

            
                 
      

                 

                   



                 






                                   
                                                           







                                                                  




                                                                  
 

                                                                    
 





                                                                             
                               
                                                     





                                                                      
                               
                                                            
                                                                         







                                                                            
   

                                   

                             



              


                                






                           


                            
 
                        
 







                                
                           
                 
 




                                          
 


                 
                                  
                                                                   




                                                                 

                                            
 
                                                         





















































                                                                                                                           

                                                               
                               
                                                                          











                                                                                     
                                                                     








                                                                             







                                                            
     


                                                            



             
                               


                
         
 




                                                                       
           
 







                                                                   
                                    





                                                           


                                                            
                                     

 






                                                                       
                         







                                                        
                                  









                                                         

                                                                








                                             







































                                                                                 
/**
 * 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

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;
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);

  const char * fontname = "-*-terminus-medium-*-*-*-16-*-*-*-*-*-*-*";
  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] = ":";

    if (strlen(line) == 0 ||
        line[0] == '#')
      return 0;

    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 = 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 run()
{
    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();

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

int main (int argc, char *argv[])
{
    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;
    }

    FILE *f;
    if (argc == 1 || !strcmp(argv[1], "-"))
        f = stdin;
    else {
        f = fopen(argv[1], "r");
        if (!f)
          die("Cannot open file %s", argv[1]);
    }

    int n = 1;
    char tmp[BUF_SIZE] = "";
    while (fgets(buf, BUF_SIZE, f)) {
        trim(buf);
        strcpy(tmp, buf);
        if (parse_line(buf)) {
          fprintf(stderr, "%s:%d: cannot parse ā€˜%sā€™\n",
                  argc == 1 || !strcmp(argv[1], "-") ? "stdin": argv[1], n, tmp);
        }
        else {
          w_height++;
        }
        n++;
    }

    if (f != stdin) fclose(f);

    return run();
}