From a1d4169eb318f504f3a869ec429517de25eb5427 Mon Sep 17 00:00:00 2001 From: niallo Date: Tue, 26 Jun 2007 19:34:26 +0000 Subject: [PATCH] modify "exec" dialog so that it auto-completes based on executables in _PATH_DEFPATH add an "ssh-to" dialog which auto-completes based on contents of ~/.ssh/known_hosts (M-.) testing and eyeballing by Simon Kuhnle , todd@, pedro@ mk@ and David Cathcart ok todd@ --- calmwm.h | 5 +- conf.c | 1 + cwm.1 | 4 ++ kbfunc.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- search.c | 53 ++++++++++++++----- 5 files changed, 199 insertions(+), 18 deletions(-) diff --git a/calmwm.h b/calmwm.h index d8158ea..5bbd3c0 100644 --- a/calmwm.h +++ b/calmwm.h @@ -257,6 +257,7 @@ struct menu { char print[MENU_MAXENTRY + 1]; void *ctx; short lasthit; + short dummy; }; TAILQ_HEAD(menu_q, menu); @@ -427,6 +428,7 @@ void kbfunc_client_maximize(struct client_ctx *, void *); void kbfunc_client_vmaximize(struct client_ctx *, void *); void kbfunc_menu_search(struct client_ctx *, void *); void kbfunc_exec(struct client_ctx *, void *); +void kbfunc_ssh(struct client_ctx *, void *); void kbfunc_term(struct client_ctx *cc, void *arg); void kbfunc_lock(struct client_ctx *cc, void *arg); @@ -437,10 +439,11 @@ struct menu *search_start(struct menu_q *menuq, void (*match)(struct menu_q *, struct menu_q *, char *), void (*rank)(struct menu_q *, char *), void (*print)(struct menu *mi, int), - char *); + char *, int); void search_match_client(struct menu_q *, struct menu_q *, char *); void search_print_client(struct menu *mi, int list); void search_match_text(struct menu_q *, struct menu_q *, char *); +void search_match_exec(struct menu_q *, struct menu_q *, char *); void search_rank_text(struct menu_q *, char *); void group_init(void); diff --git a/conf.c b/conf.c index 011ed80..299d480 100644 --- a/conf.c +++ b/conf.c @@ -189,6 +189,7 @@ conf_setup(struct conf *c) conf_bindkey(c, kbfunc_lock, XK_Delete, ControlMask|Mod1Mask, 0, NULL); conf_bindkey(c, kbfunc_exec, XK_question, Mod1Mask, 0, NULL); + conf_bindkey(c, kbfunc_ssh, XK_period, Mod1Mask, 0, NULL); conf_bindkey(c, kbfunc_client_hide, XK_Return, Mod1Mask, KBFLAG_NEEDCLIENT, 0); conf_bindkey(c, kbfunc_client_lower, diff --git a/cwm.1 b/cwm.1 index d592ae6..6092c88 100644 --- a/cwm.1 +++ b/cwm.1 @@ -87,6 +87,10 @@ Toggle full-screen size of window. Toggle vertical maximization of window. .It Fa M-? Spawn \&"Exec program\&" dialog. +.It Fa M-. +Spawn \&"Ssh to\&" dialog. +This parses your $HOME/.ssh/known_hosts file to provide host auto-completion. +Ssh will be executed via the configured terminal emulator. .El .Pp The mouse bindings are also important, they are: diff --git a/kbfunc.c b/kbfunc.c index 99a7ee2..23a0d1c 100644 --- a/kbfunc.c +++ b/kbfunc.c @@ -7,9 +7,14 @@ * $Id$ */ +#include + #include "headers.h" #include "calmwm.h" +#define KNOWN_HOSTS ".ssh/known_hosts" +#define HASH_MARKER "|1|" + void kbfunc_client_lower(struct client_ctx *cc, void *arg) { @@ -41,7 +46,7 @@ kbfunc_client_search(struct client_ctx *scratch, void *arg) if ((mi = search_start(&menuq, search_match_client, NULL, - search_print_client, "window")) != NULL) { + search_print_client, "window", 0)) != NULL) { cc = (struct client_ctx *)mi->ctx; if (cc->flags & CLIENT_HIDDEN) client_unhide(cc); @@ -75,7 +80,7 @@ kbfunc_menu_search(struct client_ctx *scratch, void *arg) } if ((mi = search_start(&menuq, - search_match_text, NULL, NULL, "application")) != NULL) + search_match_text, NULL, NULL, "application", 0)) != NULL) u_spawn(((struct cmd *)mi->ctx)->image); while ((mi = TAILQ_FIRST(&menuq)) != NULL) { @@ -125,7 +130,150 @@ kbfunc_lock(struct client_ctx *cc, void *arg) void kbfunc_exec(struct client_ctx *scratch, void *arg) { - grab_exec(); + char **ap, *paths[256], *path, tpath[MAXPATHLEN]; + int l, i, j, ngroups; + gid_t mygroups[NGROUPS_MAX]; + uid_t ruid, euid, suid; + DIR *dirp; + struct dirent *dp; + struct stat sb; + struct menu_q menuq; + struct menu *mi; + + if (getgroups(0, mygroups) == -1) + err(1, "getgroups failure"); + if ((ngroups = getresuid(&ruid, &euid, &suid)) == -1) + err(1, "getresuid failure"); + + TAILQ_INIT(&menuq); + /* just use default path until we have config to set this */ + path = xstrdup(_PATH_DEFPATH); + for (ap = paths; ap < &paths[sizeof(paths) - 1] && + (*ap = strsep(&path, ":")) != NULL;) { + if (**ap != '\0') + ap++; + } + *ap = NULL; + for (i = 0; i < sizeof(paths) && paths[i] != NULL; i++) { + if ((dirp = opendir(paths[i])) == NULL) + continue; + + while ((dp = readdir(dirp)) != NULL) { + /* skip everything but regular files and symlinks */ + if (dp->d_type != DT_REG && dp->d_type != DT_LNK) + continue; + memset(tpath, '\0', sizeof(tpath)); + l = snprintf(tpath, sizeof(tpath), "%s/%s", paths[i], + dp->d_name); + /* check for truncation etc */ + if (l == -1 || l >= (int)sizeof(tpath)) + continue; + /* just ignore on stat failure */ + if (stat(tpath, &sb) == -1) + continue; + /* may we execute this file? */ + if (euid == sb.st_uid) + if (sb.st_mode & S_IXUSR) + goto executable; + else + continue; + for (j = 0; j < ngroups; j++) + if (mygroups[j] == sb.st_gid) + if (sb.st_mode & S_IXGRP) + goto executable; + else + continue; + if (sb.st_mode & S_IXOTH) + goto executable; + continue; + executable: + /* the thing in tpath, we may execute */ + XCALLOC(mi, struct menu); + strlcpy(mi->text, dp->d_name, sizeof(mi->text)); + TAILQ_INSERT_TAIL(&menuq, mi, entry); + } + (void) closedir(dirp); + } + + if ((mi = search_start(&menuq, + search_match_exec, NULL, NULL, "exec", 1)) != NULL) + u_spawn(mi->text); + + if (mi != NULL && mi->dummy) + xfree(mi); + while ((mi = TAILQ_FIRST(&menuq)) != NULL) { + TAILQ_REMOVE(&menuq, mi, entry); + xfree(mi); + } + xfree(path); +} + +void +kbfunc_ssh(struct client_ctx *scratch, void *arg) +{ + struct menu_q menuq; + struct menu *mi; + FILE *fp; + size_t len; + char *buf, *lbuf, *p, *home; + char hostbuf[MAXHOSTNAMELEN], filename[MAXPATHLEN], cmd[256]; + int l; + + if ((home = getenv("HOME")) == NULL) + return; + + l = snprintf(filename, sizeof(filename), "%s/%s", home, KNOWN_HOSTS); + if (l == -1 || l >= sizeof(filename)) + return; + + if ((fp = fopen(filename, "r")) == NULL) + return; + + TAILQ_INIT(&menuq); + lbuf = NULL; + while ((buf = fgetln(fp, &len))) { + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + else { + /* EOF without EOL, copy and add the NUL */ + lbuf = xmalloc(len + 1); + memcpy(lbuf, buf, len); + lbuf[len] = '\0'; + buf = lbuf; + } + /* skip hashed hosts */ + if (strncmp(buf, HASH_MARKER, strlen(HASH_MARKER)) == 0) + continue; + for (p = buf; *p != ',' && *p != ' ' && p != buf + len; p++) { + /* do nothing */ + } + /* ignore badness */ + if (p - buf + 1 > sizeof(hostbuf)) + continue; + (void) strlcpy(hostbuf, buf, p - buf + 1); + XCALLOC(mi, struct menu); + (void) strlcpy(mi->text, hostbuf, sizeof(mi->text)); + TAILQ_INSERT_TAIL(&menuq, mi, entry); + } + xfree(lbuf); + fclose(fp); + + + if ((mi = search_start(&menuq, + search_match_exec, NULL, NULL, "ssh", 1)) != NULL) { + conf_cmd_refresh(&Conf); + l = snprintf(cmd, sizeof(cmd), "%s -e ssh %s", Conf.termpath, + mi->text); + if (l != -1 && l < sizeof(cmd)) + u_spawn(cmd); + } + + if (mi != NULL && mi->dummy) + xfree(mi); + while ((mi = TAILQ_FIRST(&menuq)) != NULL) { + TAILQ_REMOVE(&menuq, mi, entry); + xfree(mi); + } } void diff --git a/search.c b/search.c index 28cd7f2..9a2500d 100644 --- a/search.c +++ b/search.c @@ -12,7 +12,7 @@ #define SearchMask (KeyPressMask|ExposureMask) -static int _strsubmatch(char *, char *); +static int _strsubmatch(char *, char *, int); void search_init(struct screen_ctx *sc) @@ -37,7 +37,7 @@ search_start(struct menu_q *menuq, void (*match)(struct menu_q *, struct menu_q *, char *), void (*rank)(struct menu_q *resultq, char *search), void (*print)(struct menu *mi, int print), - char *prompt) + char *prompt, int dummy) { struct screen_ctx *sc = screen_current(); int x, y, dx, dy, fontheight, @@ -47,7 +47,7 @@ search_start(struct menu_q *menuq, char dispstr[MENU_MAXENTRY*2 + 1]; char promptstr[MENU_MAXENTRY + 1]; Window focuswin; - struct menu *mi = NULL; + struct menu *mi = NULL, *dummy_mi = NULL; struct menu_q resultq; char chr; enum ctltype ctl; @@ -133,16 +133,22 @@ search_start(struct menu_q *menuq, case CTL_RETURN: /* This is just picking the match the * cursor is over. */ - if ((mi = TAILQ_FIRST(&resultq)) != NULL) + if ((mi = TAILQ_FIRST(&resultq)) != NULL) { goto found; - else - goto out; + } else if (dummy) { + dummy_mi = xmalloc(sizeof *dummy_mi); + (void) strlcpy(dummy_mi->text, + searchstr, sizeof(dummy_mi->text)); + dummy_mi->dummy = 1; + goto found; + } + goto out; case CTL_WIPE: searchstr[0] = '\0'; mutated = 1; break; case CTL_ALL: - list = !list; + list = !list; break; case CTL_ABORT: goto out; @@ -273,9 +279,12 @@ out: /* (if no match) */ xu_ptr_ungrab(); XSetInputFocus(X_Dpy, focuswin, focusrevert, CurrentTime); + found: XUnmapWindow(X_Dpy, sc->searchwin); + if (dummy && dummy_mi != NULL) + return (dummy_mi); return (mi); } @@ -307,7 +316,7 @@ search_match_client(struct menu_q *menuq, struct menu_q *resultq, char *search) struct client_ctx *cc = mi->ctx; /* First, try to match on labels. */ - if (cc->label != NULL && _strsubmatch(search, cc->label)) { + if (cc->label != NULL && _strsubmatch(search, cc->label, 0)) { cc->matchname = cc->label; tier = 0; } @@ -315,7 +324,7 @@ search_match_client(struct menu_q *menuq, struct menu_q *resultq, char *search) /* Then, on window names. */ if (tier < 0) { TAILQ_FOREACH_REVERSE(wn, &cc->nameq, winname_q, entry) - if (_strsubmatch(search, wn->name)) { + if (_strsubmatch(search, wn->name, 0)) { cc->matchname = wn->name; tier = 2; break; @@ -327,7 +336,7 @@ search_match_client(struct menu_q *menuq, struct menu_q *resultq, char *search) * name. */ - if (tier < 0 && _strsubmatch(search, cc->app_class)) { + if (tier < 0 && _strsubmatch(search, cc->app_class, 0)) { cc->matchname = cc->app_class; tier = 3; } @@ -417,7 +426,19 @@ search_match_text(struct menu_q *menuq, struct menu_q *resultq, char *search) TAILQ_INIT(resultq); TAILQ_FOREACH(mi, menuq, entry) - if (_strsubmatch(search, mi->text)) + if (_strsubmatch(search, mi->text, 0)) + TAILQ_INSERT_TAIL(resultq, mi, resultentry); +} + +void +search_match_exec(struct menu_q *menuq, struct menu_q *resultq, char *search) +{ + struct menu *mi; + + TAILQ_INIT(resultq); + + TAILQ_FOREACH(mi, menuq, entry) + if (_strsubmatch(search, mi->text, 1)) TAILQ_INSERT_TAIL(resultq, mi, resultentry); } @@ -428,10 +449,10 @@ search_rank_text(struct menu_q *resultq, char *search) } static int -_strsubmatch(char *sub, char *str) +_strsubmatch(char *sub, char *str, int zeroidx) { size_t len, sublen; - u_int n; + u_int n, flen; if (sub == NULL || str == NULL) return (0); @@ -442,7 +463,11 @@ _strsubmatch(char *sub, char *str) if (sublen > len) return (0); - for (n = 0; n <= len - sublen; n++) + if (!zeroidx) + flen = len - sublen; + else + flen = 0; + for (n = 0; n <= flen; n++) if (strncasecmp(sub, str + n, sublen) == 0) return (1);