launcher.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /*
  2. * launcher.c
  3. * Copyright © 2008, 2009 Martin Duquesnoy <xorg62@gmail.com>
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions are
  8. * met:
  9. *
  10. * * Redistributions of source code must retain the above copyright
  11. * notice, this list of conditions and the following disclaimer.
  12. * * Redistributions in binary form must reproduce the above
  13. * copyright notice, this list of conditions and the following disclaimer
  14. * in the documentation and/or other materials provided with the
  15. * distribution.
  16. * * Neither the name of the nor the names of its
  17. * contributors may be used to endorse or promote products derived from
  18. * this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. */
  32. /* conforming to glib use _GNU_SOURCE for asprintf declaration */
  33. #ifndef _GNU_SOURCE
  34. #define _GNU_SOURCE
  35. #endif
  36. #include "wmfs.h"
  37. static char *complete_on_command(char*, size_t);
  38. static char *complete_on_files(char*, size_t);
  39. void
  40. launcher_execute(Launcher launcher)
  41. {
  42. BarWindow *bw;
  43. Bool found;
  44. Bool lastwastab = False;
  45. Bool my_guitar_gently_wheeps = True;
  46. char tmp[32] = { 0 };
  47. char buf[512] = { 0 };
  48. char tmpbuf[512] = { 0 };
  49. char *complete;
  50. int pos = 0, x;
  51. int tabhits = 0;
  52. KeySym ks;
  53. XEvent ev;
  54. screen_get_sel();
  55. x = (infobar[selscreen].layout_button->geo.x
  56. + textw(tags[selscreen][seltag[selscreen]].layout.symbol) + PAD);
  57. XGrabKeyboard(dpy, ROOT, True, GrabModeAsync, GrabModeAsync, CurrentTime);
  58. bw = barwin_create(infobar[selscreen].bar->win, x, 1,
  59. infobar[selscreen].bar->geo.width - x - 1,
  60. infobar[selscreen].bar->geo.height - 2,
  61. infobar[selscreen].bar->bg,
  62. infobar[selscreen].bar->fg,
  63. False, False, conf.border.bar);
  64. barwin_map(bw);
  65. barwin_refresh_color(bw);
  66. /* First draw of the cursor */
  67. XSetForeground(dpy, gc, getcolor(infobar[selscreen].bar->fg));
  68. XDrawLine(dpy, bw->dr, gc, textw(launcher.prompt) + textw(" "),
  69. 2, textw(launcher.prompt) + textw(" "), INFOBARH - 4);
  70. barwin_refresh(bw);
  71. barwin_draw_text(bw, 1, FHINFOBAR - 1, launcher.prompt);
  72. while(my_guitar_gently_wheeps)
  73. {
  74. if(ev.type == KeyPress)
  75. {
  76. XLookupString(&ev.xkey, tmp, sizeof(tmp), &ks, 0);
  77. /* Check Ctrl-c / Ctrl-d */
  78. if(ev.xkey.state & ControlMask)
  79. if(ks == XK_c || ks == XK_d)
  80. ks = XK_Escape;
  81. /* Check if there is a keypad */
  82. if(IsKeypadKey(ks) && ks == XK_KP_Enter)
  83. ks = XK_Return;
  84. switch(ks)
  85. {
  86. case XK_Return:
  87. spawn("%s %s", launcher.command, buf);
  88. my_guitar_gently_wheeps = 0;
  89. break;
  90. case XK_Escape:
  91. my_guitar_gently_wheeps = 0;
  92. break;
  93. case XK_Tab:
  94. /*
  95. * completion
  96. * if there is not space in buffer we
  97. * complete the command using complete_on_command.
  98. * Else we try to complete on filename using
  99. * complete_on_files.
  100. */
  101. buf[pos] = '\0';
  102. if (lastwastab)
  103. tabhits++;
  104. else
  105. {
  106. tabhits = 1;
  107. strcpy(tmpbuf, buf);
  108. }
  109. if (pos)
  110. {
  111. if (strchr(tmpbuf, ' '))
  112. complete = complete_on_files(tmpbuf, tabhits);
  113. else
  114. complete = complete_on_command(tmpbuf, tabhits);
  115. if (complete)
  116. {
  117. strcpy(buf, tmpbuf);
  118. strncat(buf, complete, sizeof(buf));
  119. found = True;
  120. free(complete);
  121. }
  122. }
  123. lastwastab = True;
  124. /* start a new round of tabbing */
  125. if (!found)
  126. tabhits = 0;
  127. pos = strlen(buf);
  128. break;
  129. case XK_BackSpace:
  130. lastwastab = False;
  131. if(pos)
  132. buf[--pos] = 0;
  133. break;
  134. default:
  135. lastwastab = False;
  136. strncat(buf, tmp, sizeof(buf));
  137. ++pos;
  138. break;
  139. }
  140. barwin_refresh_color(bw);
  141. /* Update cursor position */
  142. XSetForeground(dpy, gc, getcolor(infobar[selscreen].bar->fg));
  143. XDrawLine(dpy, bw->dr, gc,
  144. 1 + textw(launcher.prompt) + textw(" ") + textw(buf), 2,
  145. 1 + textw(launcher.prompt) + textw(" ") + textw(buf), INFOBARH - 4);
  146. barwin_draw_text(bw, 1, FHINFOBAR - 1, launcher.prompt);
  147. barwin_draw_text(bw, 1 + textw(launcher.prompt) + textw(" "), FHINFOBAR - 1, buf);
  148. barwin_refresh(bw);
  149. }
  150. else
  151. getevent(ev);
  152. XNextEvent(dpy, &ev);
  153. }
  154. barwin_unmap(bw);
  155. barwin_delete(bw);
  156. infobar_draw(selscreen);
  157. XUngrabKeyboard(dpy, CurrentTime);
  158. return;
  159. }
  160. void
  161. uicb_launcher(uicb_t cmd)
  162. {
  163. int i;
  164. for(i = 0; i < conf.nlauncher; ++i)
  165. if(!strcmp(cmd, conf.launcher[i].name))
  166. launcher_execute(conf.launcher[i]);
  167. return;
  168. }
  169. /*
  170. * Just search command in PATH.
  171. * Return the characters to complete the command.
  172. */
  173. static char *
  174. complete_on_command(char *start, size_t hits)
  175. {
  176. char *path;
  177. char *dirname;
  178. char *ret = NULL;
  179. DIR *dir;
  180. struct dirent *content;
  181. size_t count = 0;
  182. if (!getenv("PATH") || !start || hits <= 0)
  183. return NULL;
  184. path = _strdup(getenv("PATH"));
  185. dirname = strtok(path, ":");
  186. /* recursively open PATH */
  187. while (dirname)
  188. {
  189. if ((dir = opendir(dirname)))
  190. {
  191. while ((content = readdir(dir)))
  192. if (!strncmp(content->d_name, start, strlen(start)) && ++count == hits)
  193. {
  194. ret = _strdup(content->d_name + strlen(start));
  195. break;
  196. }
  197. closedir(dir);
  198. }
  199. if (ret)
  200. break;
  201. dirname = strtok(NULL, ":");
  202. }
  203. free(path);
  204. return ret;
  205. }
  206. /*
  207. * Complete a filename or directory name.
  208. * works like complete_on_command.
  209. */
  210. static char *
  211. complete_on_files(char *start, size_t hits)
  212. {
  213. char *ret = NULL;
  214. char *p = NULL;
  215. char *dirname = NULL;
  216. char *path = NULL;
  217. char *filepath = NULL;
  218. DIR *dir = NULL;
  219. struct dirent *content = NULL;
  220. struct stat st;
  221. size_t count = 0;
  222. if (!start || hits <= 0 || !(p = strrchr(start, ' ')))
  223. return NULL;
  224. /*
  225. * Search the directory to open and set
  226. * the beginning of file to complete on pointer 'p'.
  227. */
  228. if (*(++p) == '\0' || !strrchr(p, '/'))
  229. path = _strdup(".");
  230. else
  231. {
  232. /* remplace ~ by $HOME in dirname */
  233. if (!strncmp(p, "~/", 2) && getenv("HOME"))
  234. asprintf(&dirname, "%s%s", getenv("HOME"), p+1);
  235. else
  236. dirname = _strdup(p);
  237. /* Set p to filename to be complete
  238. * and path the directory containing the file
  239. * /foooooo/baaaaaar/somethinglikethis<tab>
  240. * <---- path - ---><------- p ------>
  241. */
  242. p = strrchr(dirname, '/');
  243. if (p != dirname)
  244. {
  245. *(p++) = '\0';
  246. path = _strdup(dirname);
  247. }
  248. else
  249. {
  250. path = _strdup("/");
  251. p++;
  252. }
  253. }
  254. if ((dir = opendir(path)))
  255. {
  256. while ((content = readdir(dir)))
  257. {
  258. if (!strcmp(content->d_name, ".") || !strcmp(content->d_name, ".."))
  259. continue;
  260. if (!strncmp(content->d_name, p, strlen(p)) && ++count == hits)
  261. {
  262. /* If it's a directory append '/' to the completion */
  263. asprintf(&filepath, "%s/%s", path, content->d_name);
  264. if (filepath && stat(filepath, &st) != -1)
  265. {
  266. if (S_ISDIR(st.st_mode))
  267. asprintf(&ret, "%s/", content->d_name + strlen(p));
  268. else
  269. ret = _strdup(content->d_name + strlen(p));
  270. }
  271. else
  272. warn("%s", filepath);
  273. IFREE(filepath);
  274. break;
  275. }
  276. }
  277. closedir(dir);
  278. }
  279. IFREE(dirname);
  280. IFREE(path);
  281. return ret;
  282. }