wmfs/src/launcher.c
2012-02-21 12:47:54 +01:00

439 lines
11 KiB
C

/*
* wmfs2 by Martin Duquesnoy <xorg62@gmail.com> { for(i = 2011; i < 2111; ++i) ©(i); }
* For license, see COPYING.
*/
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <X11/Xutil.h>
#include "wmfs.h"
#include "event.h"
#include "util.h"
#include "infobar.h"
#include "config.h"
static int
qsort_string_compare(const void * a, const void * b)
{
return (strcmp(*(char **)a, *(char **)b));
}
static char **
complete_on_command(char *start)
{
struct dirent *content;
DIR *dir;
char **paths, *path, *p, **namelist = NULL;
int i, count;
if(!(path = getenv("PATH")) || !start)
return NULL;
/* split PATH into paths */
path = p = xstrdup(path);
for(count = 1, p = path; strchr(p, ':'); ++p, ++count);
paths = xcalloc(count, sizeof(*paths));
for(paths[0] = p = path, count = 1; (p = strchr(p, ':')); ++p, ++count)
{
paths[count] = p + 1;
*p = '\0';
}
paths[count] = NULL;
/* recursively open PATH */
for(i = count = 0; paths[i]; ++i)
{
if(!(dir = opendir(paths[i])))
continue;
while((content = readdir(dir)))
{
if(strncmp(content->d_name, ".", 1)
&& !strncmp(content->d_name, start, strlen(start)))
{
namelist = xrealloc(namelist, ++count, sizeof(*namelist));
namelist[count - 1] = xstrdup(content->d_name + strlen(start));
}
}
closedir(dir);
}
if(count)
{
qsort(namelist, count, sizeof(char *), qsort_string_compare);
namelist = xrealloc(namelist, ++count, sizeof(*namelist));
namelist[count - 1] = NULL;
}
free(paths);
free(path);
return namelist;
}
/*
* Complete a filename or directory name.
* works like complete_on_command.
*/
static char **
complete_on_files(char *start)
{
struct dirent *content = NULL;
struct stat st;
DIR *dir;
char *home, *path, *dirname = NULL;
char **namelist = NULL, *filepath, *p = start;
int count = 0;
/*
* Search the directory to open and set
* the beginning of file to complete on pointer 'p'.
*/
if(*p == '\0' || !strrchr(p, '/'))
path = xstrdup(".");
else
{
if(!(home = getenv("HOME")))
return NULL;
/* remplace ~ by $HOME in dirname */
if(!strncmp(p, "~/", 2) && home)
xasprintf(&dirname, "%s%s", home, p+1);
else
dirname = xstrdup(p);
/* Set p to filename to be complete
* and path the directory containing the file
* /foooooo/baaaaaar/somethinglikethis<tab>
* <---- path - ---><------- p ------>
*/
p = strrchr(dirname, '/');
if(p != dirname)
{
*(p++) = '\0';
path = xstrdup(dirname);
}
else
{
path = xstrdup("/");
p++;
}
}
if((dir = opendir(path)))
{
while((content = readdir(dir)))
{
if(!strcmp(content->d_name, ".")
|| !strcmp(content->d_name, "..")
|| strncmp(content->d_name, p, strlen(p)))
continue;
/* If it's a directory append '/' to the completion */
xasprintf(&filepath, "%s/%s", path, content->d_name);
if(filepath && stat(filepath, &st) != -1)
{
namelist = xrealloc(namelist, ++count, sizeof(*namelist));
if(S_ISDIR(st.st_mode))
xasprintf(&namelist[count - 1], "%s/", content->d_name + strlen(p));
else
namelist[count - 1] = xstrdup(content->d_name + strlen(p));
}
else
warnl("%s", filepath);
free(filepath);
}
closedir(dir);
}
if(count)
{
namelist = xrealloc(namelist, ++count, sizeof(*namelist));
namelist[count - 1] = NULL;
}
free(dirname);
free(path);
return namelist;
}
static void
complete_cache_free(struct launcher_ccache *cache)
{
int i;
/* release memory */
free(cache->start);
if(cache->namelist)
{
for(i = 0; cache->namelist[i]; i++)
free(cache->namelist[i]);
free(cache->namelist);
}
/* init */
cache->hits = 0;
cache->start = NULL;
cache->namelist = NULL;
}
static char *
complete(struct launcher_ccache *cache, char *start)
{
char *p = NULL, *comp = NULL;
if(!start || !cache)
return NULL;
if((p = strrchr(start, ' ')))
p++;
else
p = start;
if(cache->start && !strcmp(cache->start, start))
{
if(cache->namelist && !cache->namelist[cache->hits])
cache->hits = 0;
}
else
{
complete_cache_free(cache);
cache->start = xstrdup(start);
if(p == start)
cache->namelist = complete_on_command(p);
else
cache->namelist = complete_on_files(p);
}
if(cache->namelist && cache->namelist[cache->hits])
comp = cache->namelist[cache->hits];
return comp;
}
#define LAUNCHER_INIT_ELEM(width) \
SLIST_FOREACH(ib, &W->screen->infobars, next) \
{ \
TAILQ_FOREACH(e, &ib->elements, next) \
if(e->type == ElemLauncher) \
{ \
e->geo.w = width; \
e->data = data; \
} \
infobar_elem_reinit(ib); \
}
static void
launcher_process(struct launcher *l)
{
struct infobar *ib;
struct element *e;
struct launcher_ccache cache = {NULL, NULL, 0};
bool loop = true, found = false, lastwastab = false;
char tmpbuf[512] = { 0 }, buf[512] = { 0 };
char tmp[32] = { 0 };
char *p, *data, *arg, *end, *cmd = xstrdup(l->command);
int i, pos = 0, histpos = 0;
void (*func)(Uicb);
XEvent ev;
KeySym ks;
W->flags |= WMFS_LAUNCHER;
/* Prepare elements */
xasprintf(&data, "%s ", l->prompt);
LAUNCHER_INIT_ELEM(l->width);
XGrabKeyboard(W->dpy, W->root, true, GrabModeAsync, GrabModeAsync, CurrentTime);
while(loop)
{
XNextEvent(W->dpy, &ev);
if(ev.type != KeyPress)
{
EVENT_HANDLE(&ev);
continue;
}
/* Get pressed key */
XLookupString(&ev.xkey, tmp, sizeof(tmp), &ks, 0);
/* Check Ctrl-c / Ctrl-d */
if(ev.xkey.state & ControlMask)
{
switch(ks)
{
case XK_c:
case XK_d:
ks = XK_Escape;
break;
case XK_p:
ks = XK_Up;
break;
case XK_n:
ks = XK_Down;
break;
}
}
/* Check if there is a keypad */
if(IsKeypadKey(ks) && ks == XK_KP_Enter)
ks = XK_Return;
/* Manage pressed keys */
switch(ks)
{
case XK_Up:
if(l->nhisto)
{
if(histpos >= (int)l->nhisto)
histpos = 0;
strncpy(buf, l->histo[l->nhisto - ++histpos], sizeof(buf));
pos = strlen(buf);
}
break;
case XK_Down:
if(l->nhisto && histpos > 0 && histpos < (int)l->nhisto)
{
strncpy(buf, l->histo[l->nhisto - --histpos], sizeof(buf));
pos = strlen(buf);
}
break;
case XK_Return:
/* Get function name only, if cmds are added in command */
arg = NULL;
if((p = strchr(cmd, ' ')))
{
*p = '\0';
xasprintf(&arg, "%s %s", p + 1, buf);
}
if((func = uicb_name_func(cmd)))
{
if(arg)
{
func(arg);
free(arg);
}
else
func(buf);
}
/* Histo */
if(l->nhisto + 1 > HISTOLEN)
{
for(i = l->nhisto - 1; i > 1; --i)
strncpy(l->histo[i], l->histo[i - 1], sizeof(l->histo[i]));
l->nhisto = 0;
}
/* Store in histo array */
strncpy(l->histo[l->nhisto++], buf, sizeof(buf));
loop = false;
break;
case XK_Escape:
loop = false;
break;
/* Completion */
case XK_Tab:
buf[pos] = '\0';
if(lastwastab)
++cache.hits;
else
{
cache.hits = 0;
strncpy(tmpbuf, buf, sizeof(tmpbuf));
}
if(pos && (end = complete(&cache, tmpbuf)))
{
strncpy(buf, tmpbuf, sizeof(buf));
strncat(buf, end, sizeof(buf));
found = true;
}
lastwastab = true;
/* start a new round of tabbing */
if(!found)
cache.hits = 0;
pos = strlen(buf);
break;
case XK_BackSpace:
lastwastab = false;
if(pos)
buf[--pos] = '\0';
break;
default:
lastwastab = false;
strncat(buf, tmp, sizeof(tmp));
++pos;
break;
}
free(data);
xasprintf(&data, "%s %s", l->prompt, buf);
/* Update EVERY launcher element of the screen */
SLIST_FOREACH(ib, &W->screen->infobars, next)
{
TAILQ_FOREACH(e, &ib->elements, next)
{
if(e->type != ElemLauncher)
continue;
e->data = data;
e->func_update(e);
}
}
}
XUngrabKeyboard(W->dpy, CurrentTime);
complete_cache_free(&cache);
free(cmd);
free(data);
/* 'Close' launcher elements */
W->flags ^= WMFS_LAUNCHER;
data = NULL;
LAUNCHER_INIT_ELEM(1);
}
void
uicb_launcher(Uicb cmd)
{
struct launcher *l;
if(!cmd)
return;
SLIST_FOREACH(l, &W->h.launcher, next)
if(!strcmp(l->name, cmd))
{
launcher_process(l);
break;
}
}