launcher.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. /*
  2. * wmfs2 by Martin Duquesnoy <xorg62@gmail.com> { for(i = 2011; i < 2111; ++i) ©(i); }
  3. * For license, see COPYING.
  4. */
  5. #include <string.h>
  6. #include <dirent.h>
  7. #include <sys/stat.h>
  8. #include <X11/Xutil.h>
  9. #include "wmfs.h"
  10. #include "event.h"
  11. #include "util.h"
  12. #include "infobar.h"
  13. #include "config.h"
  14. static int
  15. qsort_string_compare(const void * a, const void * b)
  16. {
  17. return (strcmp(*(char **)a, *(char **)b));
  18. }
  19. static char **
  20. complete_on_command(char *start)
  21. {
  22. struct dirent *content;
  23. DIR *dir;
  24. char **paths, *path, *p, **namelist = NULL;
  25. int i, count;
  26. if(!(path = getenv("PATH")) || !start)
  27. return NULL;
  28. /* split PATH into paths */
  29. path = p = xstrdup(path);
  30. for(count = 1, p = path; strchr(p, ':'); ++p, ++count);
  31. paths = xcalloc(count, sizeof(*paths));
  32. for(paths[0] = p = path, count = 1; (p = strchr(p, ':')); ++p, ++count)
  33. {
  34. paths[count] = p + 1;
  35. *p = '\0';
  36. }
  37. paths[count] = NULL;
  38. /* recursively open PATH */
  39. for(i = count = 0; paths[i]; ++i)
  40. {
  41. if(!(dir = opendir(paths[i])))
  42. continue;
  43. while((content = readdir(dir)))
  44. {
  45. if(strncmp(content->d_name, ".", 1)
  46. && !strncmp(content->d_name, start, strlen(start)))
  47. {
  48. namelist = xrealloc(namelist, ++count, sizeof(*namelist));
  49. namelist[count - 1] = xstrdup(content->d_name + strlen(start));
  50. }
  51. }
  52. closedir(dir);
  53. }
  54. if(count)
  55. {
  56. qsort(namelist, count, sizeof(char *), qsort_string_compare);
  57. namelist = xrealloc(namelist, ++count, sizeof(*namelist));
  58. namelist[count - 1] = NULL;
  59. }
  60. free(paths);
  61. free(path);
  62. return namelist;
  63. }
  64. /*
  65. * Complete a filename or directory name.
  66. * works like complete_on_command.
  67. */
  68. static char **
  69. complete_on_files(char *start)
  70. {
  71. struct dirent *content = NULL;
  72. struct stat st;
  73. DIR *dir;
  74. char *home, *path, *dirname = NULL;
  75. char **namelist = NULL, *filepath, *p = start;
  76. int count = 0;
  77. /*
  78. * Search the directory to open and set
  79. * the beginning of file to complete on pointer 'p'.
  80. */
  81. if(*p == '\0' || !strrchr(p, '/'))
  82. path = xstrdup(".");
  83. else
  84. {
  85. if(!(home = getenv("HOME")))
  86. return NULL;
  87. /* remplace ~ by $HOME in dirname */
  88. if(!strncmp(p, "~/", 2) && home)
  89. xasprintf(&dirname, "%s%s", home, p+1);
  90. else
  91. dirname = xstrdup(p);
  92. /* Set p to filename to be complete
  93. * and path the directory containing the file
  94. * /foooooo/baaaaaar/somethinglikethis<tab>
  95. * <---- path - ---><------- p ------>
  96. */
  97. p = strrchr(dirname, '/');
  98. if(p != dirname)
  99. {
  100. *(p++) = '\0';
  101. path = xstrdup(dirname);
  102. }
  103. else
  104. {
  105. path = xstrdup("/");
  106. p++;
  107. }
  108. }
  109. if((dir = opendir(path)))
  110. {
  111. while((content = readdir(dir)))
  112. {
  113. if(!strcmp(content->d_name, ".")
  114. || !strcmp(content->d_name, "..")
  115. || strncmp(content->d_name, p, strlen(p)))
  116. continue;
  117. /* If it's a directory append '/' to the completion */
  118. xasprintf(&filepath, "%s/%s", path, content->d_name);
  119. if(filepath && stat(filepath, &st) != -1)
  120. {
  121. namelist = xrealloc(namelist, ++count, sizeof(*namelist));
  122. if(S_ISDIR(st.st_mode))
  123. xasprintf(&namelist[count - 1], "%s/", content->d_name + strlen(p));
  124. else
  125. namelist[count - 1] = xstrdup(content->d_name + strlen(p));
  126. }
  127. else
  128. warnl("%s", filepath);
  129. free(filepath);
  130. }
  131. closedir(dir);
  132. }
  133. if(count)
  134. {
  135. namelist = xrealloc(namelist, ++count, sizeof(*namelist));
  136. namelist[count - 1] = NULL;
  137. }
  138. free(dirname);
  139. free(path);
  140. return namelist;
  141. }
  142. static void
  143. complete_cache_free(struct launcher_ccache *cache)
  144. {
  145. int i;
  146. /* release memory */
  147. free(cache->start);
  148. if(cache->namelist)
  149. {
  150. for(i = 0; cache->namelist[i]; i++)
  151. free(cache->namelist[i]);
  152. free(cache->namelist);
  153. }
  154. /* init */
  155. cache->hits = 0;
  156. cache->start = NULL;
  157. cache->namelist = NULL;
  158. }
  159. static char *
  160. complete(struct launcher_ccache *cache, char *start)
  161. {
  162. char *p = NULL, *comp = NULL;
  163. if(!start || !cache)
  164. return NULL;
  165. if((p = strrchr(start, ' ')))
  166. p++;
  167. else
  168. p = start;
  169. if(cache->start && !strcmp(cache->start, start))
  170. {
  171. if(cache->namelist && !cache->namelist[cache->hits])
  172. cache->hits = 0;
  173. }
  174. else
  175. {
  176. complete_cache_free(cache);
  177. cache->start = xstrdup(start);
  178. if(p == start)
  179. cache->namelist = complete_on_command(p);
  180. else
  181. cache->namelist = complete_on_files(p);
  182. }
  183. if(cache->namelist && cache->namelist[cache->hits])
  184. comp = cache->namelist[cache->hits];
  185. return comp;
  186. }
  187. #define LAUNCHER_INIT_ELEM(width) \
  188. SLIST_FOREACH(ib, &W->screen->infobars, next) \
  189. { \
  190. TAILQ_FOREACH(e, &ib->elements, next) \
  191. if(e->type == ElemLauncher) \
  192. { \
  193. e->geo.w = width; \
  194. e->data = data; \
  195. } \
  196. infobar_elem_reinit(ib); \
  197. }
  198. static void
  199. launcher_process(struct launcher *l)
  200. {
  201. struct infobar *ib;
  202. struct element *e;
  203. struct launcher_ccache cache = {NULL, NULL, 0};
  204. bool loop = true, found = false, lastwastab = false;
  205. char tmpbuf[512] = { 0 }, buf[512] = { 0 };
  206. char tmp[32] = { 0 };
  207. char *p, *data, *arg, *end, *cmd = xstrdup(l->command);
  208. int i, pos = 0, histpos = 0;
  209. void (*func)(Uicb);
  210. XEvent ev;
  211. KeySym ks;
  212. W->flags |= WMFS_LAUNCHER;
  213. /* Prepare elements */
  214. xasprintf(&data, "%s ", l->prompt);
  215. LAUNCHER_INIT_ELEM(l->width);
  216. XGrabKeyboard(W->dpy, W->root, true, GrabModeAsync, GrabModeAsync, CurrentTime);
  217. while(loop)
  218. {
  219. XNextEvent(W->dpy, &ev);
  220. if(ev.type != KeyPress)
  221. {
  222. EVENT_HANDLE(&ev);
  223. continue;
  224. }
  225. /* Get pressed key */
  226. XLookupString(&ev.xkey, tmp, sizeof(tmp), &ks, 0);
  227. /* Check Ctrl-c / Ctrl-d */
  228. if(ev.xkey.state & ControlMask)
  229. {
  230. switch(ks)
  231. {
  232. case XK_c:
  233. case XK_d:
  234. ks = XK_Escape;
  235. break;
  236. case XK_p:
  237. ks = XK_Up;
  238. break;
  239. case XK_n:
  240. ks = XK_Down;
  241. break;
  242. }
  243. }
  244. /* Check if there is a keypad */
  245. if(IsKeypadKey(ks) && ks == XK_KP_Enter)
  246. ks = XK_Return;
  247. /* Manage pressed keys */
  248. switch(ks)
  249. {
  250. case XK_Up:
  251. if(l->nhisto)
  252. {
  253. if(histpos >= (int)l->nhisto)
  254. histpos = 0;
  255. strncpy(buf, l->histo[l->nhisto - ++histpos], sizeof(buf));
  256. pos = strlen(buf);
  257. }
  258. break;
  259. case XK_Down:
  260. if(l->nhisto && histpos > 0 && histpos < (int)l->nhisto)
  261. {
  262. strncpy(buf, l->histo[l->nhisto - --histpos], sizeof(buf));
  263. pos = strlen(buf);
  264. }
  265. break;
  266. case XK_Return:
  267. /* Get function name only, if cmds are added in command */
  268. arg = NULL;
  269. if((p = strchr(cmd, ' ')))
  270. {
  271. *p = '\0';
  272. xasprintf(&arg, "%s %s", p + 1, buf);
  273. }
  274. if((func = uicb_name_func(cmd)))
  275. {
  276. if(arg)
  277. {
  278. func(arg);
  279. free(arg);
  280. }
  281. else
  282. func(buf);
  283. }
  284. /* Histo */
  285. if(l->nhisto + 1 > HISTOLEN)
  286. {
  287. for(i = l->nhisto - 1; i > 1; --i)
  288. strncpy(l->histo[i], l->histo[i - 1], sizeof(l->histo[i]));
  289. l->nhisto = 0;
  290. }
  291. /* Store in histo array */
  292. strncpy(l->histo[l->nhisto++], buf, sizeof(buf));
  293. loop = false;
  294. break;
  295. case XK_Escape:
  296. loop = false;
  297. break;
  298. /* Completion */
  299. case XK_Tab:
  300. buf[pos] = '\0';
  301. if(lastwastab)
  302. ++cache.hits;
  303. else
  304. {
  305. cache.hits = 0;
  306. strncpy(tmpbuf, buf, sizeof(tmpbuf));
  307. }
  308. if(pos && (end = complete(&cache, tmpbuf)))
  309. {
  310. strncpy(buf, tmpbuf, sizeof(buf));
  311. strncat(buf, end, sizeof(buf));
  312. found = true;
  313. }
  314. lastwastab = true;
  315. /* start a new round of tabbing */
  316. if(!found)
  317. cache.hits = 0;
  318. pos = strlen(buf);
  319. break;
  320. case XK_BackSpace:
  321. lastwastab = false;
  322. if(pos)
  323. buf[--pos] = '\0';
  324. break;
  325. default:
  326. lastwastab = false;
  327. strncat(buf, tmp, sizeof(tmp));
  328. ++pos;
  329. break;
  330. }
  331. free(data);
  332. xasprintf(&data, "%s %s", l->prompt, buf);
  333. /* Update EVERY launcher element of the screen */
  334. SLIST_FOREACH(ib, &W->screen->infobars, next)
  335. {
  336. TAILQ_FOREACH(e, &ib->elements, next)
  337. {
  338. if(e->type != ElemLauncher)
  339. continue;
  340. e->data = data;
  341. e->func_update(e);
  342. }
  343. }
  344. }
  345. XUngrabKeyboard(W->dpy, CurrentTime);
  346. complete_cache_free(&cache);
  347. free(cmd);
  348. free(data);
  349. /* 'Close' launcher elements */
  350. W->flags ^= WMFS_LAUNCHER;
  351. data = NULL;
  352. LAUNCHER_INIT_ELEM(1);
  353. }
  354. void
  355. uicb_launcher(Uicb cmd)
  356. {
  357. struct launcher *l;
  358. if(!cmd)
  359. return;
  360. SLIST_FOREACH(l, &W->h.launcher, next)
  361. if(!strcmp(l->name, cmd))
  362. {
  363. launcher_process(l);
  364. break;
  365. }
  366. }