wmfs/src/launcher.c

385 lines
12 KiB
C

/*
* launcher.c
* Copyright © 2008, 2009 Martin Duquesnoy <xorg62@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of the nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "wmfs.h"
/*
* Just search command in PATH.
* Return the characters to complete the command.
*/
static char *
complete_on_command(char *start, size_t hits)
{
char *path;
char *dirname;
char *ret = NULL;
DIR *dir;
struct dirent *content;
char **namelist = NULL;
int n = 0, i;
if (!getenv("PATH") || !start || hits <= 0)
return NULL;
path = xstrdup(getenv("PATH"));
dirname = strtok(path, ":");
/* recursively open PATH */
while (dirname != NULL)
{
if ((dir = opendir(dirname)))
{
while ((content = readdir(dir)))
{
if(strncmp(content->d_name, ".", 1))
{
if (!strncmp(content->d_name, start, strlen(start)))
{
namelist = xrealloc(namelist, ++n, sizeof(*namelist));
namelist[n-1] = xstrdup(content->d_name);
}
}
}
closedir(dir);
}
dirname = strtok(NULL, ":");
}
free(path);
if(n > 0)
{
qsort(namelist, n, sizeof(char *), qsort_string_compare);
ret = xstrdup(namelist[((hits > 0) ? hits - 1 : 0) % n] + strlen(start));
for(i = 0; i < n; i++)
free(namelist[i]);
free(namelist);
}
return ret;
}
/*
* Complete a filename or directory name.
* works like complete_on_command.
*/
static char *
complete_on_files(char *start, size_t hits)
{
char *ret = NULL;
char *p = NULL;
char *dirname = NULL;
char *path = NULL;
char *filepath = NULL;
DIR *dir = NULL;
struct dirent *content = NULL;
struct stat st;
size_t count = 0;
if (!start || hits <= 0 || !(p = strrchr(start, ' ')))
return NULL;
/*
* Search the directory to open and set
* the beginning of file to complete on pointer 'p'.
*/
if (*(++p) == '\0' || !strrchr(p, '/'))
path = xstrdup(".");
else
{
/* remplace ~ by $HOME in dirname */
if (!strncmp(p, "~/", 2) && getenv("HOME"))
xasprintf(&dirname, "%s%s", getenv("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, ".."))
continue;
if (!strncmp(content->d_name, p, strlen(p)) && ++count == hits)
{
/* If it's a directory append '/' to the completion */
xasprintf(&filepath, "%s/%s", path, content->d_name);
if (filepath && stat(filepath, &st) != -1)
{
if (S_ISDIR(st.st_mode))
xasprintf(&ret, "%s/", content->d_name + strlen(p));
else
ret = xstrdup(content->d_name + strlen(p));
}
else
warn("%s", filepath);
free(filepath);
break;
}
}
closedir(dir);
}
free(dirname);
free(path);
return ret;
}
static void
launcher_execute(Launcher *launcher)
{
BarWindow *bw;
bool found;
bool lastwastab = False;
bool my_guitar_gently_wheeps = True;
char tmp[32] = { 0 };
char buf[512] = { 0 };
char tmpbuf[512] = { 0 };
char *complete;
int i, pos = 0, histpos = 0, w, x = 0;
int tabhits = 0;
KeySym ks;
XEvent ev;
InfobarElem *e;
screen_get_sel();
STAILQ_FOREACH(e, &infobar[selscreen].elemhead, next)
if(x < (e->geo.x + e->geo.width))
x = e->geo.x + e->geo.width + PAD;
XGrabKeyboard(dpy, ROOT, True, GrabModeAsync, GrabModeAsync, CurrentTime);
w = (launcher->width ? launcher->width : infobar[selscreen].bar->geo.width - x - 1);
bw = barwin_create(infobar[selscreen].bar->win, x, 1, w,
/* infobar[selscreen].bar->geo.width - x - 1, */
infobar[selscreen].bar->geo.height - 2,
infobar[selscreen].bar->bg,
infobar[selscreen].bar->fg,
False, False, conf.border.bar);
barwin_map(bw);
barwin_refresh_color(bw);
/* First draw of the cursor */
XSetForeground(dpy, gc, getcolor(infobar[selscreen].bar->fg));
XDrawLine(dpy, bw->dr, gc,
1 + textw(launcher->prompt) + textw(" ") + textw(buf), 2,
1 + textw(launcher->prompt) + textw(" ") + textw(buf), INFOBARH - 4);
barwin_refresh(bw);
barwin_draw_text(bw, 1, FHINFOBAR - 1, launcher->prompt);
while(my_guitar_gently_wheeps)
{
if(ev.type == KeyPress)
{
XLookupString(&ev.xkey, tmp, sizeof(tmp), &ks, 0);
/* Check Ctrl-c / Ctrl-d */
if(ev.xkey.state & ControlMask)
{
if(ks == XK_c || ks == XK_d)
ks = XK_Escape;
else if(ks == XK_p)
ks = XK_Up;
else if(ks == XK_n)
ks = XK_Down;
}
/* Check if there is a keypad */
if(IsKeypadKey(ks) && ks == XK_KP_Enter)
ks = XK_Return;
switch(ks)
{
case XK_Up:
if(launcher->nhisto)
{
if(histpos >= (int)launcher->nhisto)
histpos = 0;
strncpy(buf, launcher->histo[launcher->nhisto - ++histpos], sizeof(buf));
pos = strlen(buf);
}
break;
case XK_Down:
if(launcher->nhisto && histpos > 0 && histpos < (int)launcher->nhisto)
{
strncpy(buf, launcher->histo[launcher->nhisto - --histpos], sizeof(buf));
pos = strlen(buf);
}
break;
case XK_Return:
spawn("%s %s", launcher->command, buf);
/* Histo */
if(launcher->nhisto + 1 > HISTOLEN)
{
for(i = launcher->nhisto - 1; i > 1; --i)
strncpy(launcher->histo[i], launcher->histo[i - 1], sizeof(launcher->histo[i]));
launcher->nhisto = 0;
}
/* Store in histo array */
strncpy(launcher->histo[launcher->nhisto++], buf, sizeof(buf));
my_guitar_gently_wheeps = 0;
break;
case XK_Escape:
my_guitar_gently_wheeps = 0;
break;
case XK_Tab:
/*
* completion
* if there is not space in buffer we
* complete the command using complete_on_command.
* Else we try to complete on filename using
* complete_on_files.
*/
buf[pos] = '\0';
if (lastwastab)
tabhits++;
else
{
tabhits = 1;
strncpy(tmpbuf, buf, sizeof(tmpbuf));
}
if (pos)
{
if (strchr(tmpbuf, ' '))
complete = complete_on_files(tmpbuf, tabhits);
else
complete = complete_on_command(tmpbuf, tabhits);
if (complete)
{
strncpy(buf, tmpbuf, sizeof(buf));
strncat(buf, complete, sizeof(buf));
found = True;
free(complete);
}
}
lastwastab = True;
/* start a new round of tabbing */
if (found == False)
tabhits = 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;
}
barwin_refresh_color(bw);
/* Update cursor position */
XSetForeground(dpy, gc, getcolor(infobar[selscreen].bar->fg));
XDrawLine(dpy, bw->dr, gc,
1 + textw(launcher->prompt) + textw(" ") + textw(buf), 2,
1 + textw(launcher->prompt) + textw(" ") + textw(buf), INFOBARH - 4);
barwin_draw_text(bw, 1, FHINFOBAR - 1, launcher->prompt);
barwin_draw_text(bw, 1 + textw(launcher->prompt) + textw(" "), FHINFOBAR - 1, buf);
barwin_refresh(bw);
}
else if(ev.type < nevent && ev.type > 0)
HANDLE_EVENT(&ev);
XNextEvent(dpy, &ev);
}
barwin_unmap(bw);
barwin_delete(bw);
infobar_draw(&infobar[selscreen]);
XUngrabKeyboard(dpy, CurrentTime);
return;
}
void
uicb_launcher(uicb_t cmd)
{
int i;
for(i = 0; i < conf.nlauncher; ++i)
if(!strcmp(cmd, conf.launcher[i].name))
launcher_execute(&conf.launcher[i]);
return;
}