You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

2693 lines
94 KiB

  1. // this code is not optimal in many ways, but works just nicely.
  2. // Licence: GNU GPL v3
  3. // Authors: Tomas Matejicek <www.slax.org>
  4. // Peter Munch-Ellingsen <www.peterme.net>
  5. const int VERSION_MAJOR = 3; // Major version, changes when breaking backwards compatability
  6. const int VERSION_MINOR = 2; // Minor version, changes when new functionality is added
  7. const int VERSION_PATCH = 12; // Patch version, changes when something is changed without changing deliberate functionality (eg. a bugfix or an optimisation)
  8. #define _GNU_SOURCE
  9. /* open and O_RDWR,O_CREAT */
  10. #include <fcntl.h>
  11. /* include X11 stuff */
  12. #include <X11/Xlib.h>
  13. /* include Imlib2 stuff */
  14. #include <Imlib2.h>
  15. /* sprintf include */
  16. #include <stdio.h>
  17. /* strcpy include */
  18. #include <string.h>
  19. /* exit include */
  20. #include <stdlib.h>
  21. /* exec include */
  22. #include <unistd.h>
  23. /* long options include*/
  24. #include <getopt.h>
  25. /* cursor */
  26. #include <X11/cursorfont.h>
  27. /* X utils */
  28. #include <X11/Xutil.h>
  29. #include <X11/Xatom.h>
  30. /* parse commandline arguments */
  31. #include <ctype.h>
  32. /* one instance */
  33. #include <sys/file.h>
  34. #include <sys/stat.h>
  35. /* check stdin */
  36. #include <sys/poll.h>
  37. #include <errno.h>
  38. /* some globals for our window & X display */
  39. Display *disp;
  40. Window win;
  41. XVisualInfo vinfo;
  42. XSetWindowAttributes attr;
  43. int x11_fd;
  44. struct pollfd eventfds[2];
  45. XIM im;
  46. XIC ic;
  47. int screen;
  48. int screen_width;
  49. int screen_height;
  50. // Let's define a linked list node:
  51. typedef struct node
  52. {
  53. char title[256];
  54. char icon[256];
  55. char cmd[512];
  56. int hovered;
  57. int clicked;
  58. int hidden;
  59. int x;
  60. int y;
  61. struct node * next;
  62. } node_t;
  63. typedef struct button
  64. {
  65. char icon_normal[256];
  66. char icon_highlight[256];
  67. char cmd[512];
  68. int hovered;
  69. int clicked;
  70. int x;
  71. int y;
  72. int w;
  73. int h;
  74. struct button * next;
  75. } button_t;
  76. typedef struct shortcut
  77. {
  78. char *key;
  79. node_t *entry;
  80. struct shortcut *next;
  81. } shortcut_t;
  82. typedef struct keynode {
  83. char key[255];
  84. struct keynode * next;
  85. } keynode_t;
  86. typedef struct color {
  87. int r, g, b, a;
  88. } color_t;
  89. typedef struct percentable {
  90. int percent;
  91. int value;
  92. } percentable_t;
  93. int entries_count = 0;
  94. node_t * entries = NULL;
  95. button_t * buttons = NULL;
  96. shortcut_t * shortcuts = NULL;
  97. keynode_t * cmdline = NULL;
  98. enum exit_code {
  99. OKAY,
  100. ESCAPE = 0x20,
  101. RIGHTCLICK,
  102. VOIDCLICK,
  103. FOCUSLOST,
  104. INTERNALCMD,
  105. LOCKERROR = 0x40,
  106. ALLOCERROR,
  107. FONTERROR,
  108. CONFIGERROR,
  109. WINERROR,
  110. LOCALEERROR,
  111. INPUTMERROR,
  112. INPUTCERROR,
  113. POLLERROR
  114. };
  115. static struct option long_options[] =
  116. {
  117. {"tc", required_argument, 0, 1009},
  118. {"textcolor", required_argument, 0, 1009},
  119. {"pc", required_argument, 0, 1010},
  120. {"promptcolor", required_argument, 0, 1010},
  121. {"backgroundcolor", required_argument, 0, 1011},
  122. {"bc", required_argument, 0, 1011},
  123. {"hc", required_argument, 0, 1012},
  124. {"highlightcolor", required_argument, 0, 1012},
  125. {"name", required_argument, 0, 1013},
  126. {"config", required_argument, 0, 1014},
  127. {"bgfill", no_argument, 0, 1015},
  128. {"focuslostterminate", no_argument, 0, 1016},
  129. {"borderratio", required_argument, 0, 1017},
  130. {"sideborderratio", required_argument, 0, 1018},
  131. {"scroll", no_argument, 0, 1019},
  132. {"iconvpadding", required_argument, 0, 1020},
  133. {"button", required_argument, 0, 'A'},
  134. {"textafter", no_argument, 0, 'a'},
  135. {"border", required_argument, 0, 'b'},
  136. {"sideborder", required_argument, 0, 'B'},
  137. {"center", no_argument, 0, 'C'},
  138. {"columns", required_argument, 0, 'c'},
  139. {"desktop", no_argument, 0, 'd'},
  140. {"hidemissing", no_argument, 0, 'e'},
  141. {"font", required_argument, 0, 'f'},
  142. {"promptfont", required_argument, 0, 'F'},
  143. {"background", required_argument, 0, 'g'},
  144. {"rootwindowbackground", no_argument, 0, 'G'},
  145. {"height", required_argument, 0, 'h'},
  146. {"help", no_argument, 0, 'H'},
  147. {"iconpadding", required_argument, 0, 'I'},
  148. {"input", required_argument, 0, 'i'},
  149. {"highlight", required_argument, 0, 'L'},
  150. {"leastmargin", required_argument, 0, 'l'},
  151. {"clearmemory", no_argument, 0, 'M'},
  152. {"multiple", no_argument, 0, 'm'},
  153. {"noprompt", no_argument, 0, 'n'},
  154. {"outputonly", no_argument, 0, 'o'},
  155. {"textotherside", no_argument, 0, 'O'},
  156. {"prompt", required_argument, 0, 'p'},
  157. {"promptspacing", required_argument, 0, 'P'},
  158. {"dontquit", no_argument, 0, 'q'},
  159. {"reverse", no_argument, 0, 'R'},
  160. {"rows", required_argument, 0, 'r'},
  161. {"iconsize", required_argument, 0, 's'},
  162. {"selectonly", no_argument, 0, 'S'},
  163. {"textpadding", required_argument, 0, 'T'},
  164. {"voidclickterminate", no_argument, 0, 't'},
  165. {"shortcuts", required_argument, 0, 'U'},
  166. {"upsidedown", no_argument, 0, 'u'},
  167. {"leastvmargin", required_argument, 0, 'V'},
  168. {"version", no_argument, 0, 'v'},
  169. {"width", required_argument, 0, 'w'},
  170. {"windowed", no_argument, 0, 'W'},
  171. {"paddingswap", no_argument, 0, 'X'},
  172. {"xposition", required_argument, 0, 'x'},
  173. {"yposition", required_argument, 0, 'y'},
  174. {0, 0, 0, 0}
  175. };
  176. int icon_size = 48;
  177. int ucolumns = 0;
  178. int columns;
  179. int urows = 0;
  180. int rows;
  181. int column_margin = 0;
  182. int row_margin = 0;
  183. int icon_padding = 10;
  184. int icon_v_padding = -1;
  185. int text_padding = 10;
  186. int border;
  187. int side_border = 0;
  188. int border_ratio = 50;
  189. int side_border_ratio = 50;
  190. int cell_width;
  191. int cell_height;
  192. int font_height;
  193. int prompt_font_height;
  194. int use_root_img = 0;
  195. char commandline[10024];
  196. char commandlinetext[10024];
  197. int prompt_x;
  198. int prompt_y;
  199. char * background_file = "";
  200. char * highlight_file = "";
  201. char * input_file = "";
  202. char * config_file = "";
  203. FILE * input_source = NULL;
  204. char * prompt = "";
  205. char * font_name = "";
  206. char * prompt_font_name = "";
  207. char * program_name;
  208. int bg_fill = 0;
  209. int no_prompt = 0;
  210. int prompt_spacing = 48;
  211. int windowed = 0;
  212. int multiple_instances = 0;
  213. int uposx = -1;
  214. int uposy = -1;
  215. int uwidth = 0;
  216. int uheight = 0;
  217. percentable_t uborder = { .percent = -1, .value = 0 };
  218. percentable_t uside_border = { .percent = -1, .value = 0 };
  219. int void_click_terminate = 0;
  220. int focus_lost_terminate = 0;
  221. int dont_quit = 0;
  222. int reverse = 0;
  223. int output_only = 0;
  224. int select_only = 0;
  225. int text_after = 0;
  226. int text_other_side = 0;
  227. int clear_memory = 0;
  228. int upside_down = 0;
  229. int padding_swap = 0;
  230. int least_margin = 0;
  231. int least_v_margin = -1;
  232. int hide_missing = 0;
  233. int center_icons = 0;
  234. int scroll = 0;
  235. int scrolled_past= 0;
  236. int hovered_entry = 0;
  237. color_t text_color = {.r = 255, .g = 255, .b = 255, .a = 255};
  238. color_t prompt_color = {.r = 255, .g = 255, .b = 255, .a = 255};
  239. color_t background_color = {.r = 46, .g = 52, .b = 64, .a = 255};
  240. int background_color_set = 0;
  241. color_t highlight_color = {.r = 255, .g = 255, .b = 255, .a = 50};
  242. #define MOUSE 1
  243. #define KEYBOARD 2
  244. int hoverset=MOUSE;
  245. int desktop_mode=0;
  246. int lock;
  247. /* areas to update */
  248. Imlib_Updates updates, current_update;
  249. /* our background image, rendered only once */
  250. Imlib_Image background = NULL;
  251. /* highlighting image (placed under icon on hover) */
  252. Imlib_Image highlight = NULL;
  253. /* image variable */
  254. Imlib_Image image = NULL;
  255. void calculate_percentage(int maxvalue, percentable_t* percentable)
  256. {
  257. if(percentable->percent != -1) {
  258. percentable->value = (maxvalue * percentable->percent) / 100;
  259. }
  260. }
  261. void recalc_cells()
  262. {
  263. int margined_cell_width, margined_cell_height;
  264. if (text_after){
  265. cell_width=icon_size+icon_padding*2;
  266. cell_height=icon_size+icon_v_padding*2;
  267. margined_cell_width=icon_size+icon_padding*2+least_margin;
  268. margined_cell_height=icon_size+icon_v_padding*2+least_v_margin;
  269. if(ucolumns == 0)
  270. ucolumns = 1;
  271. } else {
  272. cell_width=icon_size+icon_padding*2;
  273. cell_height=icon_size+icon_v_padding*2+font_height+text_padding;
  274. margined_cell_width=icon_size+icon_padding*2+least_margin;
  275. margined_cell_height=icon_size+icon_v_padding*2+font_height+text_padding+least_v_margin;
  276. }
  277. border = screen_width/10;
  278. if (uborder.value > 0) border = uborder.value;
  279. if (uborder.value == -1 && uborder.percent == -1){
  280. if (ucolumns == 0){
  281. border = 0;
  282. } else {
  283. side_border = (screen_width - (ucolumns * cell_width + (ucolumns - 1) * least_margin))/2;
  284. border = (screen_height - (no_prompt ? 0 : prompt_font_height + prompt_spacing) - (urows * cell_height + (urows - 1) * least_v_margin))/2;
  285. }
  286. }
  287. if (uside_border.value == 0 && uside_border.percent == -1) side_border = border;
  288. if (uside_border.value > 0) side_border = uside_border.value;
  289. if (uside_border.value == -1 && uside_border.percent == -1){
  290. if (ucolumns == 0){
  291. side_border = 0;
  292. } else {
  293. side_border = (screen_width - (ucolumns * cell_width + (ucolumns - 1) * least_margin))/2;
  294. }
  295. }
  296. int usable_width;
  297. int usable_height;
  298. // These do while loops should run at most three times, it's just to avoid copying code
  299. do {
  300. usable_width = screen_width-side_border*2;
  301. if (no_prompt) {
  302. usable_height = screen_height-border*2;
  303. } else {
  304. usable_height = screen_height-border*2-prompt_spacing-prompt_font_height;
  305. }
  306. // If the usable_width is too small, take some space away from the border
  307. if (usable_width < cell_width) {
  308. side_border = (screen_width - cell_width - 1)/2;
  309. } else if (usable_height < cell_height) {
  310. border = (screen_height - cell_height - prompt_spacing - prompt_font_height - 1)/2;
  311. }
  312. } while ((usable_width < cell_width && screen_width > cell_width) || (usable_height < cell_height && screen_height > cell_height));
  313. // If columns were not manually overriden, calculate the most it can possibly contain
  314. if (ucolumns == 0){
  315. columns = usable_width/margined_cell_width;
  316. } else{
  317. columns = ucolumns;
  318. }
  319. if (urows == 0){
  320. rows = usable_height/margined_cell_height;
  321. } else{
  322. rows = urows;
  323. }
  324. // If we don't have space for a single column or row, force it.
  325. if (columns == 0) {
  326. columns = 1;
  327. }
  328. if (rows == 0) {
  329. rows = 1;
  330. }
  331. if (text_after){
  332. cell_width = (usable_width - least_margin*(columns - 1))/columns;
  333. }
  334. // The space between the icon tiles to fill all the space
  335. if (columns == 1){
  336. column_margin = (usable_width - cell_width*columns);
  337. } else {
  338. column_margin = (usable_width - cell_width*columns)/(columns - 1);
  339. }
  340. if (rows == 1){
  341. row_margin = (usable_height - cell_height*rows);
  342. } else {
  343. row_margin = (usable_height - cell_height*rows)/(rows - 1);
  344. }
  345. // These are kept in case manual positioning is reintroduced
  346. prompt_x = (side_border * side_border_ratio) / 50;
  347. prompt_y = (border * border_ratio) / 50;
  348. /*
  349. if(uside_border == 0){
  350. side_border = border;
  351. }*/
  352. }
  353. void restack()
  354. {
  355. if (desktop_mode) XLowerWindow(disp,win);
  356. else if (!windowed) XRaiseWindow(disp,win);
  357. }
  358. char* strncpyutf8(char* dst, const char* src, size_t num)
  359. {
  360. if(num)
  361. {
  362. size_t sizeSrc = strlen(src); // number of bytes not including null
  363. while(sizeSrc>num)
  364. {
  365. const char* lastByte = src + sizeSrc; // initially \0 at end
  366. while(lastByte-- > src) // test previous chars
  367. if((*lastByte & 0xC0) != 0x80) // utf8 start byte found
  368. break;
  369. sizeSrc = lastByte-src;
  370. }
  371. memcpy(dst, src, sizeSrc);
  372. dst[sizeSrc] = '\0';
  373. }
  374. return dst;
  375. }
  376. void arrange_positions()
  377. {
  378. node_t * current = entries;
  379. int i = 0;
  380. int j = 0;
  381. while (current != NULL)
  382. {
  383. if (current->hidden)
  384. {
  385. current->x = 0;
  386. current->y = 0;
  387. }
  388. else
  389. {
  390. int entries_last_line = entries_count - j * columns;
  391. if (center_icons && entries_last_line < columns) {
  392. int width = entries_last_line * cell_width + (entries_last_line - 1) * column_margin;
  393. int usable_width = screen_width - side_border * 2;
  394. int margin = usable_width - width;
  395. current->x = (side_border * side_border_ratio) / 50 + margin / 2 + i * (cell_width + column_margin);
  396. } else {
  397. current->x = (side_border * side_border_ratio) / 50 + i * (cell_width+column_margin);
  398. }
  399. if (no_prompt) {
  400. current->y = (border * border_ratio) / 50 + (j - scrolled_past) * (cell_height+row_margin);
  401. } else {
  402. current->y = (border * border_ratio) / 50 + prompt_font_height + prompt_spacing + (j - scrolled_past) * (cell_height+row_margin);
  403. }
  404. if (upside_down) {
  405. current->y=screen_height - cell_height - current->y;
  406. }
  407. if (i == columns-1) {
  408. i = 0;
  409. j++;
  410. } else {
  411. i++;
  412. }
  413. }
  414. current = current->next;
  415. }
  416. }
  417. void push_key(char * key)
  418. {
  419. keynode_t * current = cmdline;
  420. if (current==NULL)
  421. {
  422. cmdline = malloc(sizeof(keynode_t));
  423. strcpy(cmdline->key,key);
  424. cmdline->next = NULL;
  425. return;
  426. }
  427. while (current->next != NULL) {
  428. current = current->next;
  429. }
  430. /* now we can add a new item */
  431. current->next = malloc(sizeof(keynode_t));
  432. strcpy(current->next->key,key);
  433. current->next->next = NULL;
  434. }
  435. void pop_key()
  436. {
  437. if (cmdline==NULL) return;
  438. // if there is only one item, remove it
  439. if (cmdline->next==NULL)
  440. {
  441. free(cmdline);
  442. cmdline=NULL;
  443. return;
  444. }
  445. keynode_t * current = cmdline;
  446. while (current->next->next != NULL) {
  447. current = current->next;
  448. }
  449. /* now we can remove last item */
  450. free(current->next);
  451. current->next=NULL;
  452. }
  453. void clear_entries(){
  454. node_t * current = entries;
  455. while (current != NULL) {
  456. node_t * last = current;
  457. if (clear_memory) {
  458. memset(last->title, 0, 256);
  459. memset(last->icon, 0, 256);
  460. memset(last->cmd, 0, 512);
  461. // Free the entire struct, except for it's next pointer
  462. memset(last, 0, sizeof(node_t) - sizeof(node_t *));
  463. }
  464. free(last);
  465. current = current->next;
  466. }
  467. entries = NULL;
  468. }
  469. void cleanup()
  470. {
  471. flock(lock, LOCK_UN | LOCK_NB);
  472. // destroy window, disconnect display, and exit
  473. XDestroyWindow(disp,win);
  474. XFlush(disp);
  475. XCloseDisplay(disp);
  476. if(input_source == stdin){
  477. int fd = fileno(stdin);
  478. int flags = fcntl(fd, F_GETFL, 0);
  479. flags &= ~O_NONBLOCK;
  480. fcntl(fd, F_SETFL, flags);
  481. fclose(input_source);
  482. }
  483. clear_entries();
  484. button_t * button = buttons;
  485. buttons = NULL;
  486. while (button != NULL) {
  487. button_t *last = button;
  488. button = button->next;
  489. free(last);
  490. }
  491. shortcut_t * shortcut = shortcuts;
  492. shortcuts = NULL;
  493. while (shortcut != NULL) {
  494. shortcut_t *last = shortcut;
  495. shortcut = shortcut->next;
  496. free(last->key);
  497. free(last);
  498. }
  499. }
  500. Imlib_Image load_image(char * icon) {
  501. Imlib_Load_Error load_error;
  502. Imlib_Image image = imlib_load_image_with_error_return(icon, &load_error);
  503. if(image) {
  504. imlib_context_set_image(image);
  505. imlib_free_image();
  506. } else {
  507. fprintf(stderr, "Could not load icon %s, Imlib failed with: ", icon);
  508. switch(load_error) {
  509. case IMLIB_LOAD_ERROR_NONE:
  510. fprintf(stderr, "IMLIB_LOAD_ERROR_NONE");
  511. break;
  512. case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST:
  513. fprintf(stderr, "IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST");
  514. break;
  515. case IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY:
  516. fprintf(stderr, "IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY");
  517. break;
  518. case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ:
  519. fprintf(stderr, "IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ");
  520. break;
  521. case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT:
  522. fprintf(stderr, "IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT");
  523. break;
  524. case IMLIB_LOAD_ERROR_PATH_TOO_LONG:
  525. fprintf(stderr, "IMLIB_LOAD_ERROR_PATH_TOO_LONG");
  526. break;
  527. case IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT:
  528. fprintf(stderr, "IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT");
  529. break;
  530. case IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY:
  531. fprintf(stderr, "IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY");
  532. break;
  533. case IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE:
  534. fprintf(stderr, "IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE");
  535. break;
  536. case IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS:
  537. fprintf(stderr, "IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS");
  538. break;
  539. case IMLIB_LOAD_ERROR_OUT_OF_MEMORY:
  540. fprintf(stderr, "IMLIB_LOAD_ERROR_OUT_OF_MEMORY");
  541. break;
  542. case IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS:
  543. fprintf(stderr, "IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS");
  544. break;
  545. case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE:
  546. fprintf(stderr, "IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE");
  547. break;
  548. case IMLIB_LOAD_ERROR_OUT_OF_DISK_SPACE:
  549. fprintf(stderr, "IMLIB_LOAD_ERROR_OUT_OF_DISK_SPACE");
  550. break;
  551. case IMLIB_LOAD_ERROR_UNKNOWN:
  552. fprintf(stderr, "IMLIB_LOAD_ERROR_UNKNOWN");
  553. break;
  554. }
  555. fprintf(stderr, "\n");
  556. /*
  557. cleanup();
  558. exit(1);*/
  559. }
  560. return image;
  561. }
  562. void push_entry(node_t * new_entry)//(char * title, char * icon, char * cmd, int x, int y)
  563. {
  564. node_t * current = entries;
  565. int hasicon = (strlen(new_entry->icon) != 0);
  566. /* Pre-load the image into the cache, this is done to check for error messages
  567. * If a user has more images then can be shown this might incur a performance hit */
  568. if (hasicon) {
  569. Imlib_Image image = load_image(new_entry->icon);
  570. if (image == NULL) {
  571. if (hide_missing) return;
  572. strcpy(new_entry->icon, "/usr/share/icons/hicolor/48x48/apps/xlunch_ghost.png");
  573. }
  574. }
  575. // Set default values for internal state
  576. new_entry->x=0;
  577. new_entry->y=0;
  578. new_entry->hidden=0;
  579. new_entry->hovered=0;
  580. new_entry->clicked=0;
  581. new_entry->next = NULL;
  582. shortcut_t *shortcut = shortcuts;
  583. while (shortcut != NULL) {
  584. if (shortcut->entry == NULL) {
  585. shortcut->entry = new_entry;
  586. break;
  587. }
  588. shortcut = shortcut->next;
  589. }
  590. // empty list, add first directly
  591. if (current==NULL)
  592. {
  593. entries = new_entry;
  594. entries->next = NULL;
  595. return;
  596. }
  597. // Otherwise determine position
  598. if(!reverse){
  599. // Go to end of list and add there
  600. while (current->next != NULL) {
  601. current = current->next;
  602. }
  603. current->next = new_entry;
  604. current->next->next = NULL;
  605. } else {
  606. // Add at head of list
  607. new_entry->next = entries;
  608. entries = new_entry;
  609. }
  610. entries_count++;
  611. }
  612. char *strtok_new(char * string, char const * delimiter){
  613. static char *source = NULL;
  614. char *p, *riturn = 0;
  615. if(string != NULL) source = string;
  616. if(source == NULL) return NULL;
  617. if((p = strpbrk (source, delimiter)) != NULL) {
  618. *p = 0;
  619. riturn = source;
  620. source = ++p;
  621. }
  622. return riturn;
  623. }
  624. char* concat(const char *s1, const char *s2)
  625. {
  626. char *result = malloc(strlen(s1)+strlen(s2)+1);//+1 for the zero-terminator
  627. if(result != 0) {
  628. strcpy(result, s1);
  629. strcat(result, s2);
  630. return result;
  631. }
  632. exit(ALLOCERROR);
  633. }
  634. FILE * determine_input_source(){
  635. FILE * fp;
  636. if(input_source == NULL) {
  637. char * homeconf = NULL;
  638. char * home = getenv("HOME");
  639. if (home!=NULL)
  640. {
  641. homeconf = concat(home,"/.config/xlunch/entries.dsv");
  642. }
  643. if (strlen(input_file)==0){
  644. fp = stdin;
  645. int flags;
  646. int fd = fileno(fp);
  647. flags = fcntl(fd, F_GETFL, 0);
  648. flags |= O_NONBLOCK;
  649. fcntl(fd, F_SETFL, flags);
  650. struct pollfd fds;
  651. fds.fd = 0; /* this is STDIN */
  652. fds.events = POLLIN;
  653. // Give poll a little timeout to make give the piping program some time
  654. if (poll(&fds, 1, 10) == 0){
  655. int flags = fcntl(fd, F_GETFL, 0);
  656. flags &= ~O_NONBLOCK;
  657. fcntl(fd, F_SETFL, flags);
  658. fclose(stdin);
  659. fp = fopen(homeconf, "rb");
  660. }
  661. } else {
  662. fp = fopen(input_file, "rb");
  663. }
  664. if (fp == NULL)
  665. {
  666. fprintf(stderr, "Error opening entries file from %s.\nReverting back to system entries list.\n", input_file);
  667. input_file = "/etc/xlunch/entries.dsv";
  668. fp = fopen(input_file, "rb");
  669. if (fp == NULL)
  670. {
  671. fprintf(stderr, "Error opening entries file %s\n", input_file);
  672. fprintf(stderr, "You may need to create it. Icon file format is following:\n");
  673. fprintf(stderr, "title;icon_path;command\n");
  674. fprintf(stderr, "title;icon_path;command\n");
  675. fprintf(stderr, "title;icon_path;command\n");
  676. }
  677. }
  678. free(homeconf);
  679. } else {
  680. fp = input_source;
  681. }
  682. return fp;
  683. }
  684. FILE * determine_config_source(){
  685. FILE * fp;
  686. if(strlen(config_file) == 0) {
  687. char * homeconf = NULL;
  688. char * home = getenv("HOME");
  689. if (home!=NULL)
  690. {
  691. homeconf = concat(home,"/.config/xlunch/xlunch.conf");
  692. }
  693. fp = fopen(homeconf, "rb");
  694. if(fp == NULL) {
  695. fp = fopen("/etc/xlunch/default.conf", "rb");
  696. }
  697. free(homeconf);
  698. } else {
  699. fp = fopen(config_file, "rb");
  700. }
  701. return fp;
  702. }
  703. int mouse_over_cell(node_t * cell, int index, int mouse_x, int mouse_y)
  704. {
  705. if (cell->hidden) return 0;
  706. if (index > scrolled_past*columns && index < scrolled_past*columns + rows*columns +1
  707. && mouse_x >= cell->x
  708. && mouse_x < cell->x+cell_width
  709. && mouse_y >= cell->y
  710. && mouse_y < cell->y+cell_height) return 1;
  711. else return 0;
  712. }
  713. int mouse_over_button(button_t * button, int mouse_x, int mouse_y)
  714. {
  715. int x = (button->x < 0 ? screen_width + button->x + 1 - button->w : button->x);
  716. int y = (button->y < 0 ? screen_height + button->y + 1 - button->h : button->y);
  717. if ( mouse_x >= x
  718. && mouse_x < x+button->w
  719. && mouse_y >= y
  720. && mouse_y < y+button->h) return 1;
  721. else return 0;
  722. }
  723. Pixmap GetRootPixmap(Display* display, Window *root)
  724. {
  725. Pixmap currentRootPixmap;
  726. Atom act_type;
  727. int act_format;
  728. unsigned long nitems, bytes_after;
  729. unsigned char *data = NULL;
  730. Atom _XROOTPMAP_ID;
  731. _XROOTPMAP_ID = XInternAtom(display, "_XROOTPMAP_ID", False);
  732. if (XGetWindowProperty(display, *root, _XROOTPMAP_ID, 0, 1, False,
  733. XA_PIXMAP, &act_type, &act_format, &nitems, &bytes_after,
  734. &data) == Success) {
  735. if (data) {
  736. currentRootPixmap = *((Pixmap *) data);
  737. XFree(data);
  738. }
  739. }
  740. return currentRootPixmap;
  741. }
  742. int get_root_image_to_imlib_data(DATA32 * direct)
  743. {
  744. int screen;
  745. Window root;
  746. Display* display;
  747. XWindowAttributes attrs;
  748. Pixmap bg;
  749. XImage* img;
  750. display = XOpenDisplay(NULL);
  751. if (display == NULL) return 0;
  752. screen = DefaultScreen(display);
  753. root = RootWindow(display, screen);
  754. XGetWindowAttributes(display, root, &attrs);
  755. bg = GetRootPixmap(display, &root);
  756. img = XGetImage(display, bg, 0, 0, attrs.width, attrs.height, ~0, ZPixmap);
  757. XFreePixmap(display, bg);
  758. if (!img) {
  759. XCloseDisplay(display);
  760. return 0;
  761. }
  762. unsigned long pixel;
  763. int x, y;
  764. for (y = 0; y < img->height; y++)
  765. {
  766. for (x = 0; x < img->width; x++)
  767. {
  768. pixel = XGetPixel(img,x,y);
  769. direct[y*img->width+x+0] = 0xffffffff&pixel;
  770. }
  771. }
  772. XDestroyImage(img);
  773. return 1;
  774. }
  775. void joincmdline()
  776. {
  777. strcpy(commandline,"");
  778. keynode_t * current = cmdline;
  779. while (current != NULL) {
  780. strcat(commandline,current->key);
  781. current = current->next;
  782. }
  783. }
  784. void joincmdlinetext()
  785. {
  786. if (no_prompt) return;
  787. if (strlen(prompt)==0) prompt="Run: ";
  788. strcpy(commandlinetext,prompt);
  789. keynode_t * current = cmdline;
  790. while (current != NULL) {
  791. strcat(commandlinetext,current->key);
  792. current = current->next;
  793. }
  794. strcat(commandlinetext,"_");
  795. }
  796. void set_scroll_level(int new_scroll) {
  797. if (scroll){
  798. if (new_scroll != scrolled_past) {
  799. scrolled_past = new_scroll;
  800. if (scrolled_past > (entries_count - 1)/columns - rows + 1) {
  801. scrolled_past = (entries_count - 1)/columns - rows + 1;
  802. }
  803. if (scrolled_past < 0) {
  804. scrolled_past = 0;
  805. }
  806. arrange_positions();
  807. updates = imlib_update_append_rect(updates, 0, 0, screen_width, screen_height);
  808. }
  809. }
  810. }
  811. void set_hover(int hovered, node_t * cell, int hover)
  812. {
  813. if (hover) hovered_entry = hovered;
  814. if (cell==NULL) return;
  815. if (cell->hidden) return;
  816. if (cell->hovered!=hover) updates = imlib_update_append_rect(updates, cell->x, cell->y, cell_width, cell_height);
  817. cell->hovered=hover;
  818. }
  819. void hover_entry(int entry){
  820. hoverset = KEYBOARD;
  821. int to_row = (entry+columns-1)/columns;
  822. int display_row = (hovered_entry-(scrolled_past*columns)+columns-1)/columns;
  823. if (entry>(columns*rows)+scrolled_past*columns || entry<=scrolled_past*columns) {
  824. set_scroll_level(to_row - display_row);
  825. }
  826. int j = 1;
  827. int max = scrolled_past*columns + columns*rows;
  828. max = (max > entries_count ? entries_count : max);
  829. int i = (entry < scrolled_past*columns + 1
  830. ? scrolled_past*columns + 1
  831. : (entry > max
  832. ? max
  833. : entry));
  834. node_t * current = entries;
  835. while (current != NULL) {
  836. if (j == i) set_hover(i, current, 1);
  837. else set_hover(i, current, 0);
  838. if (!current->hidden) j++;
  839. current = current->next;
  840. }
  841. }
  842. void filter_entries()
  843. {
  844. node_t * current = entries;
  845. entries_count = 0;
  846. while (current != NULL)
  847. {
  848. if (strlen(commandline)==0 || strcasestr(current->title,commandline)!=NULL || strcasestr(current->cmd,commandline)!=NULL){
  849. current->hidden=0;
  850. entries_count ++;
  851. } else {
  852. current->hidden=1;
  853. current->clicked=0;
  854. set_hover(0, current, 1);
  855. }
  856. current=current->next;
  857. }
  858. set_scroll_level(0);
  859. }
  860. int starts_with(const char *pre, const char *str)
  861. {
  862. size_t lenpre = strlen(pre),
  863. lenstr = strlen(str);
  864. return lenstr < lenpre ? 0 : strncmp(pre, str, lenpre) == 0;
  865. }
  866. void run_command(char * cmd_orig);
  867. void run_internal_command(char * cmd_orig) {
  868. int will_quit = 0;
  869. char * cmd = malloc(strlen(cmd_orig)+1);
  870. memcpy(cmd, cmd_orig, strlen(cmd_orig)+1);
  871. char *p = strtok( cmd, " ");
  872. if (strcmp(p, "scroll") == 0) {
  873. p = strtok( NULL, " ");
  874. if (strcmp(p, "top") == 0){
  875. set_scroll_level(0);
  876. } else if (strcmp(p, "bottom") == 0){
  877. set_scroll_level(entries_count);
  878. } else {
  879. if (p[0] == '+' || p[0] == '-')
  880. set_scroll_level(scrolled_past + atoi(p));
  881. else
  882. set_scroll_level(atoi(p));
  883. }
  884. } else if (strcmp(p, "hover") == 0) {
  885. if (hoverset != KEYBOARD) hovered_entry = 0;
  886. p = strtok( NULL, " ");
  887. int new_hover;
  888. if (strcmp(p, "start") == 0){
  889. new_hover = 1;
  890. } else if (strcmp(p, "end") == 0){
  891. new_hover = entries_count;
  892. } else {
  893. if (p[0] == '+' || p[0] == '-')
  894. new_hover = hovered_entry + atoi(p);
  895. else
  896. new_hover = atoi(p);
  897. }
  898. hover_entry(new_hover);
  899. } else if (strcmp(p, "exec") == 0) {
  900. int old_oo = output_only;
  901. output_only = 0;
  902. run_command(strtok(NULL, "\""));
  903. output_only = old_oo;
  904. } else if (strcmp(p, "print") == 0){
  905. printf("%s\n", strtok(NULL, "\""));
  906. } else if (strcmp(p, "quit") == 0) {
  907. will_quit = 1;
  908. }
  909. if (p != NULL) {
  910. p = strtok(NULL, "");
  911. if (p != NULL) {
  912. if (p[0] == ':') {
  913. run_internal_command(&p[1]);
  914. } else {
  915. fprintf(stderr, "Internal command \"%s\" supplied with extraneous parameters \"%s\"\n", cmd_orig, p);
  916. }
  917. }
  918. }
  919. free(cmd);
  920. if (will_quit) {
  921. cleanup();
  922. exit(INTERNALCMD);
  923. }
  924. }
  925. void reset_prompt()
  926. {
  927. while (cmdline != NULL) pop_key();
  928. joincmdline();
  929. joincmdlinetext();
  930. filter_entries();
  931. arrange_positions();
  932. node_t * current = entries;
  933. while (current != NULL) {
  934. set_hover(0, current, 0);
  935. current = current->next;
  936. }
  937. updates = imlib_update_append_rect(updates, 0, 0, screen_width, screen_height);
  938. }
  939. void run_command(char * cmd_orig)
  940. {
  941. char cmd[512];
  942. char *array[100] = {0};
  943. strcpy(cmd,cmd_orig);
  944. int isrecur = starts_with(":recur ", cmd_orig) || (strcmp(":recur", cmd_orig) == 0);
  945. if(isrecur) {
  946. // split arguments into pieces
  947. int i = 0;
  948. // If we recur (ie. start xlunch again) run the command that was used to run xlunch
  949. array[0] = program_name;
  950. // Blindly consume the first token which should be "recur"
  951. char *p = strtok (cmd, " ");
  952. p = strtok (NULL, " ");
  953. while (p != NULL)
  954. {
  955. array[++i]=p;
  956. p = strtok (NULL, " ");
  957. if (i>=99) break;
  958. }
  959. } else {
  960. if (cmd_orig[0] == ':') {
  961. run_internal_command((char *)(cmd_orig + sizeof(char)));
  962. return;
  963. }
  964. if(output_only){
  965. fprintf(stdout, "%s\n", cmd_orig);
  966. if(!dont_quit){
  967. cleanup();
  968. exit(OKAY);
  969. } else {
  970. reset_prompt();
  971. return;
  972. }
  973. }
  974. array[0] = "/bin/bash";
  975. array[1] = "-c";
  976. array[2] = cmd_orig;
  977. array[3] = NULL;
  978. }
  979. restack();
  980. if (dont_quit)
  981. {
  982. pid_t pid=fork();
  983. if (pid==0) // child process
  984. {
  985. if (!multiple_instances) close(lock);
  986. printf("Forking command: %s\n",cmd);
  987. int err;
  988. err = execvp(array[0],array);
  989. fprintf(stderr,"Error forking %s : %d\n",cmd,err);
  990. exit(OKAY);
  991. }
  992. else if (pid<0) // error forking
  993. {
  994. fprintf(stderr,"Error running %s\n",cmd);
  995. }
  996. else // parent process
  997. {
  998. reset_prompt();
  999. }
  1000. }
  1001. else
  1002. {
  1003. cleanup();
  1004. printf("Running command: %s\n",cmd);
  1005. int err;
  1006. err = execvp(array[0],array);
  1007. fprintf(stderr,"Error running %s : %d\n",cmd, err);
  1008. exit(OKAY);
  1009. }
  1010. }
  1011. int parse_entries()
  1012. {
  1013. int changed = 0; // if the list of elements have changed or not
  1014. int parsing = 0; // section currently being read
  1015. int position = 0; // position in the current entry
  1016. int leading_space = 0;
  1017. int line = 1; // line count for error messages
  1018. int readstatus;
  1019. struct pollfd fds;
  1020. fds.fd = fileno(input_source);
  1021. fds.events = POLLIN;
  1022. node_t * current_entry = NULL;
  1023. char internal_command[128];
  1024. while(poll(&fds, 1, 0)) {
  1025. char b;
  1026. readstatus = read(fds.fd, &b, 1);
  1027. if(readstatus <= 0){
  1028. break;
  1029. }
  1030. if(parsing == 0 && position == leading_space){
  1031. if(b != ':' && b != '#') {
  1032. current_entry = malloc(sizeof(node_t));
  1033. } else {
  1034. current_entry = NULL;
  1035. if(b == '#'){
  1036. parsing = -1;
  1037. }
  1038. }
  1039. }
  1040. if (current_entry == NULL) {
  1041. if (b == '\n') b = '\0';
  1042. if (position > leading_space && parsing != -1)
  1043. internal_command[position-leading_space-1] = b;
  1044. } else {
  1045. if(b == '\0') {
  1046. fprintf(stderr, "Null-byte encountered while reading entries at line %d. Aborting.\n", line);
  1047. }
  1048. // Check for semi-colons only for the first two elements to support bash commands with semi-colons in them
  1049. if(b == ';' && parsing != 2) {
  1050. b = '\0';
  1051. } else if (b == '\n') {
  1052. line++;
  1053. b = '\0';
  1054. if(parsing == 0){
  1055. clear_entries();
  1056. changed = 1;
  1057. continue;
  1058. }
  1059. }
  1060. if(b == ' ' && position <= leading_space) leading_space++;
  1061. if(b != ' ' && leading_space > 0) leading_space = -leading_space;
  1062. switch(parsing){
  1063. case 0:
  1064. if (leading_space <= 0)
  1065. current_entry->title[position+leading_space] = b;
  1066. break;
  1067. case 1:
  1068. current_entry->icon[position] = b;
  1069. break;
  1070. case 2:
  1071. current_entry->cmd[position] = b;
  1072. break;
  1073. }
  1074. }
  1075. position++;
  1076. if(b == '\0') {
  1077. position = 0;
  1078. leading_space = 0;
  1079. if(parsing == 2 || current_entry == NULL) {
  1080. if(current_entry != NULL) {
  1081. push_entry(current_entry);
  1082. changed = 1;
  1083. } else {
  1084. if (parsing != -1)
  1085. run_internal_command(internal_command);
  1086. }
  1087. parsing = 0;
  1088. } else {
  1089. parsing++;
  1090. }
  1091. }
  1092. if(current_entry != NULL) {
  1093. int maxlen = (parsing == 2 ? 511 : 255);
  1094. if(position == maxlen){
  1095. fprintf(stderr, "Entry too long on line %d, maximum length is %d characters!\n", line, maxlen);
  1096. break;
  1097. }
  1098. }
  1099. }
  1100. if(readstatus == 0){
  1101. if(parsing == 2){
  1102. current_entry->cmd[position]='\0';
  1103. push_entry(current_entry);
  1104. changed = 1;
  1105. }
  1106. close(fds.fd);
  1107. input_source = NULL;
  1108. }
  1109. if(changed) {
  1110. filter_entries();
  1111. arrange_positions();
  1112. }
  1113. return changed;
  1114. }
  1115. void parse_button(char *button_spec) {
  1116. int parsing = 0; // section currently being read
  1117. int position = 0; // position in the current entry
  1118. int i = 0;
  1119. button_t *new_button = malloc(sizeof(button_t));
  1120. char b = button_spec[0];
  1121. char x[256];
  1122. char y[256];
  1123. while (b != '\0') {
  1124. if ((b == ';' && parsing != 4) || (parsing == 2 && b == ',') ) {
  1125. b = '\0';
  1126. }
  1127. switch(parsing){
  1128. case 0:
  1129. new_button->icon_normal[position] = b;
  1130. break;
  1131. case 1:
  1132. new_button->icon_highlight[position] = b;
  1133. break;
  1134. case 2:
  1135. x[position] = b;
  1136. break;
  1137. case 3:
  1138. y[position] = b;
  1139. break;
  1140. case 4:
  1141. new_button->cmd[position] = b;
  1142. break;
  1143. }
  1144. position ++;
  1145. if(b == '\0') {
  1146. position = 0;
  1147. parsing++;
  1148. }
  1149. int maxlen = (parsing == 4 ? 511 : 255);
  1150. if(position == maxlen){
  1151. fprintf(stderr, "Entry too long, maximum length is %d characters!\n", maxlen);
  1152. break;
  1153. }
  1154. i++;
  1155. b = button_spec[i];
  1156. }
  1157. if (x[0] == '-') {
  1158. new_button->x = atoi(x)-1;
  1159. } else {
  1160. new_button->x = atoi(x);
  1161. }
  1162. if (y[0] == '-') {
  1163. new_button->y = atoi(y)-1;
  1164. } else {
  1165. new_button->y = atoi(y);
  1166. }
  1167. imlib_context_set_image(imlib_load_image(new_button->icon_normal));
  1168. new_button->w = imlib_image_get_width();
  1169. new_button->h = imlib_image_get_height();
  1170. imlib_free_image();
  1171. new_button->cmd[position] = '\0';
  1172. new_button->next = buttons;
  1173. buttons = new_button;
  1174. }
  1175. void set_clicked(node_t * cell, int clicked)
  1176. {
  1177. if (cell==NULL) return;
  1178. if (cell->hidden) return;
  1179. if (cell->clicked!=clicked) updates = imlib_update_append_rect(updates, cell->x, cell->y, cell_width, cell_height);
  1180. cell->clicked=clicked;
  1181. }
  1182. Imlib_Font load_default_font(){
  1183. // No font was specified, try to load some default options as a fallback
  1184. Imlib_Font font;
  1185. font = imlib_load_font("OpenSans-Regular/10");
  1186. if (!font) font = imlib_load_font("DejaVuSans/10");
  1187. return font;
  1188. }
  1189. int get_font_height(Imlib_Font font){
  1190. imlib_context_set_font(font);
  1191. // maximum font descent is relative to baseline (ie. negative)
  1192. int height = imlib_get_maximum_font_ascent() - imlib_get_maximum_font_descent();
  1193. imlib_free_font();
  1194. return height;
  1195. }
  1196. Imlib_Font load_font()
  1197. {
  1198. Imlib_Font font;
  1199. if (strlen(font_name) == 0){
  1200. font = load_default_font();
  1201. } else {
  1202. font = imlib_load_font(font_name);
  1203. }
  1204. if (font == NULL) {
  1205. fprintf(stderr, "Font %s could not be loaded! Please specify one with -f parameter\n", font_name);
  1206. exit(FONTERROR);
  1207. }
  1208. font_height = get_font_height(font);
  1209. return font;
  1210. }
  1211. Imlib_Font load_prompt_font()
  1212. {
  1213. Imlib_Font font;
  1214. if (strlen(prompt_font_name) == 0){
  1215. if (strlen(font_name) == 0){
  1216. font = load_default_font();
  1217. } else {
  1218. font = imlib_load_font(font_name);
  1219. }
  1220. } else {
  1221. font = imlib_load_font(prompt_font_name);
  1222. }
  1223. if (font == NULL) {
  1224. fprintf(stderr, "Prompt font %s could not be loaded! Please specify one with -F parameter\n", prompt_font_name);
  1225. exit(FONTERROR);
  1226. }
  1227. prompt_font_height = get_font_height(font);
  1228. return font;
  1229. }
  1230. // set background image for desktop, optionally copy it from root window,
  1231. // and set background for highlighting items
  1232. //
  1233. void update_background_images()
  1234. {
  1235. /* reset images if exist */
  1236. if (background)
  1237. {
  1238. imlib_context_set_image(background);
  1239. imlib_free_image();
  1240. }
  1241. if (highlight)
  1242. {
  1243. imlib_context_set_image(highlight);
  1244. imlib_free_image();
  1245. }
  1246. // load highlighting image from a file
  1247. if (strlen(highlight_file)>0)
  1248. highlight=imlib_load_image(highlight_file);
  1249. /* fill the window background */
  1250. background = imlib_create_image(screen_width, screen_height);
  1251. // When xlunch is launched and there is another full screen window 'background' was NULL
  1252. if(background) {
  1253. imlib_context_set_image(background);
  1254. imlib_image_set_has_alpha(1);
  1255. imlib_image_clear();
  1256. if (use_root_img) {
  1257. DATA32 * direct = imlib_image_get_data();
  1258. int ok = get_root_image_to_imlib_data(direct);
  1259. if (ok) {
  1260. imlib_image_put_back_data(direct);
  1261. imlib_context_set_color(background_color.r, background_color.g, background_color.b, background_color.a);
  1262. imlib_context_set_blend(1);
  1263. imlib_image_fill_rectangle(0,0, screen_width, screen_height);
  1264. imlib_context_set_blend(0);
  1265. }
  1266. } else{ // load background from file
  1267. if (strlen(background_file)>0) {
  1268. image = imlib_load_image(background_file);
  1269. if (image) {
  1270. imlib_context_set_image(image);
  1271. int w = imlib_image_get_width();
  1272. int h = imlib_image_get_height();
  1273. imlib_context_set_image(background);
  1274. imlib_context_set_color(background_color.r, background_color.g, background_color.b, background_color.a);
  1275. imlib_context_set_blend(1);
  1276. // Those are our source coordinates if we use just scale
  1277. // this would give the same result as feh --bg-scale
  1278. int imx=0, imy=0, imw=w, imh=h;
  1279. // But we do not want to use scale, rather we use fill to keep aspect ratio
  1280. // It gives the same result as feh --bg-fill
  1281. if (bg_fill) {
  1282. if ( (double) (w/h) < (double) (screen_width/screen_height) )
  1283. {
  1284. imw = (int) w;
  1285. imh = (int) (screen_height * w / screen_width);
  1286. imx = 0;
  1287. imy = (int) ((h - imh) / 2);
  1288. }
  1289. else
  1290. {
  1291. imw = (int) (screen_width * h / screen_height);
  1292. imh = (int) h;
  1293. imx = (int) ((w - imw) / 2);
  1294. imy = 0;
  1295. }
  1296. }
  1297. imlib_blend_image_onto_image(image, 1, imx, imy, imw, imh, 0, 0, screen_width, screen_height);
  1298. imlib_image_fill_rectangle(0,0, screen_width, screen_height);
  1299. imlib_context_set_blend(0);
  1300. imlib_context_set_image(image);
  1301. imlib_free_image();
  1302. }
  1303. } else {
  1304. imlib_context_set_color(background_color.r, background_color.g, background_color.b, background_color.a);
  1305. imlib_image_fill_rectangle(0,0, screen_width, screen_height);
  1306. }
  1307. }
  1308. }
  1309. }
  1310. void draw_text_with_shadow(int posx, int posy, char * text, color_t color) {
  1311. imlib_context_set_color(0, 0, 0, 30);
  1312. imlib_text_draw(posx +1, posy +1, text);
  1313. imlib_text_draw(posx +1, posy +2, text);
  1314. imlib_text_draw(posx +2, posy +2, text);
  1315. imlib_context_set_color(color.r, color.g, color.b, color.a);
  1316. imlib_text_draw(posx, posy, text);
  1317. }
  1318. void handle_option(int c, char *optarg);
  1319. void parse_config(FILE *input) {
  1320. int readstatus;
  1321. int position = 0;
  1322. int size = 0;
  1323. int eol = 0;
  1324. int fileline = 1;
  1325. char *optarg = NULL;
  1326. char matching[(sizeof(long_options)/sizeof(struct option)) - 1];
  1327. char *entries_word = "entries";
  1328. int matching_entries = 1;
  1329. int matched = '?';
  1330. int comment = 0;
  1331. memset(matching, 1, sizeof(long_options)/sizeof(struct option) - 1);
  1332. struct pollfd fds;
  1333. fds.fd = fileno(input);
  1334. fds.events = POLLIN;
  1335. node_t * current_entry;
  1336. while(poll(&fds, 1, 0)) {
  1337. char b;
  1338. readstatus = read(fds.fd, &b, 1);
  1339. if(readstatus <= 0){
  1340. break;
  1341. }
  1342. if((b == ':' && optarg == NULL) || b == '\n') {
  1343. if (b == '\n') eol = 1;
  1344. b = '\0';
  1345. }
  1346. if(b == '#' && optarg == NULL && position == 0) comment = 1;
  1347. if(comment == 1 && eol != 1) continue;
  1348. if(b == ' ' && position == 0) continue;
  1349. if(optarg == NULL) {
  1350. for(int i = 0; i < sizeof(long_options)/sizeof(struct option) - 1; i++) {
  1351. if (long_options[i].name[position] != b){
  1352. matching[i] = 0;
  1353. }
  1354. }
  1355. if (entries_word[position] != b && eol != 1) {
  1356. matching_entries = 0;
  1357. }
  1358. if(b == '\0') {
  1359. for(int i = 0; i < sizeof(long_options)/sizeof(struct option) - 1; i++) {
  1360. if (matching[i] == 1) {
  1361. optarg = malloc(1);
  1362. position = -1;
  1363. matched = long_options[i].val;
  1364. }
  1365. }
  1366. }
  1367. } else {
  1368. optarg = realloc(optarg, ++size);
  1369. optarg[position] = b;
  1370. }
  1371. position++;
  1372. if(eol == 1) {
  1373. if(position != 1) {
  1374. if(matching_entries){
  1375. input_source = input;
  1376. break;
  1377. } else {
  1378. if(matched == '?') {
  1379. fprintf(stderr, "Got unknown option in config file on line %d\n", fileline);
  1380. }
  1381. handle_option(matched, optarg);
  1382. }
  1383. }
  1384. position = 0;
  1385. size = 0;
  1386. eol = 0;
  1387. comment = 0;
  1388. matching_entries = 1;
  1389. memset(matching, 1, sizeof(long_options)/sizeof(struct option) - 1);
  1390. if(optarg != NULL){
  1391. //free(optarg);
  1392. optarg = NULL;
  1393. }
  1394. matched = '?';
  1395. fileline++;
  1396. }
  1397. }
  1398. if(position > 1) {
  1399. if(matching_entries){
  1400. input_source = input;
  1401. } else {
  1402. if (matched == '?') {
  1403. for(int i = 0; i < sizeof(long_options)/sizeof(struct option) - 1; i++) {
  1404. if (matching[i] == 1) {
  1405. matched = long_options[i].val;
  1406. break;
  1407. }
  1408. }
  1409. }
  1410. if(matched == '?') {
  1411. fprintf(stderr, "Got unknown option in config file on line %d\n", fileline);
  1412. }
  1413. handle_option(matched, optarg);
  1414. }
  1415. }
  1416. if(input_source != input)
  1417. close(fds.fd);
  1418. }
  1419. void handle_option(int c, char *optarg) {
  1420. switch (c) {
  1421. case 'v':
  1422. fprintf(stderr, "xlunch graphical program launcher, version %d.%d.%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
  1423. exit(OKAY);
  1424. case 'd':
  1425. desktop_mode = 1;
  1426. break;
  1427. case 'G':
  1428. use_root_img = 1;
  1429. if(background_color_set == 0){
  1430. background_color.r = 0;
  1431. background_color.g = 0;
  1432. background_color.b = 0;
  1433. background_color.a = 100;
  1434. }
  1435. break;
  1436. case 'n':
  1437. no_prompt = 1;
  1438. break;
  1439. case 'g':
  1440. background_file = optarg;
  1441. if(background_color_set == 0){
  1442. background_color.r = 0;
  1443. background_color.g = 0;
  1444. background_color.b = 0;
  1445. background_color.a = 100;
  1446. }
  1447. break;
  1448. case 'L':
  1449. highlight_file = optarg;
  1450. break;
  1451. case 'I':
  1452. icon_padding = atoi(optarg);
  1453. break;
  1454. case 1020:
  1455. icon_v_padding = atoi(optarg);
  1456. break;
  1457. case 'T':
  1458. text_padding = atoi(optarg);
  1459. break;
  1460. case 'c':
  1461. ucolumns = atoi(optarg);
  1462. break;
  1463. case 'r':
  1464. urows = atoi(optarg);
  1465. break;
  1466. case 'b':
  1467. if(strcmp(optarg, "auto") == 0){
  1468. uborder.value = -1;
  1469. } else {
  1470. if (optarg[strlen(optarg)-1] == '%') {
  1471. uborder.percent = atoi(optarg);
  1472. } else {
  1473. uborder.value = atoi(optarg);
  1474. }
  1475. }
  1476. break;
  1477. case 'B':
  1478. if(strcmp(optarg, "auto") == 0){
  1479. uside_border.value = -1;
  1480. } else {
  1481. if (optarg[strlen(optarg)-1] == '%') {
  1482. uside_border.percent = atoi(optarg);
  1483. } else {
  1484. uside_border.value = atoi(optarg);
  1485. }
  1486. }
  1487. break;
  1488. case 'P':
  1489. prompt_spacing = atoi(optarg);
  1490. break;
  1491. case 's':
  1492. icon_size = atoi(optarg);
  1493. break;
  1494. case 'i':
  1495. input_file = optarg;
  1496. break;
  1497. case 'W':
  1498. windowed = 1;
  1499. break;
  1500. case 'p':
  1501. prompt = optarg;
  1502. break;
  1503. case 'f':
  1504. font_name = optarg;
  1505. break;
  1506. case 'F':
  1507. prompt_font_name = optarg;
  1508. break;
  1509. case 'm':
  1510. multiple_instances = 1;
  1511. break;
  1512. case 't':
  1513. void_click_terminate = 1;
  1514. break;
  1515. case 'x':
  1516. uposx = atoi(optarg);
  1517. break;
  1518. case 'y':
  1519. uposy = atoi(optarg);
  1520. break;
  1521. case 'w':
  1522. uwidth = atoi(optarg);
  1523. break;
  1524. case 'h':
  1525. uheight = atoi(optarg);
  1526. break;
  1527. case 'o':
  1528. output_only = 1;
  1529. break;
  1530. case 'S':
  1531. select_only = 1;
  1532. break;
  1533. case 1009:
  1534. sscanf(optarg, "%02x%02x%02x%02x", &text_color.r, &text_color.g, &text_color.b, &text_color.a);
  1535. break;
  1536. case 1010:
  1537. sscanf(optarg, "%02x%02x%02x%02x", &prompt_color.r, &prompt_color.g, &prompt_color.b, &prompt_color.a);
  1538. break;
  1539. case 1011:
  1540. sscanf(optarg, "%02x%02x%02x%02x", &background_color.r, &background_color.g, &background_color.b, &background_color.a);
  1541. background_color_set = 1;
  1542. break;
  1543. case 1012:
  1544. sscanf(optarg, "%02x%02x%02x%02x", &highlight_color.r, &highlight_color.g, &highlight_color.b, &highlight_color.a);
  1545. break;
  1546. case 'a':
  1547. text_after = 1;
  1548. break;
  1549. case 1013:
  1550. program_name = optarg;
  1551. break;
  1552. case 'q':
  1553. dont_quit = 1;
  1554. break;
  1555. case 'R':
  1556. reverse = 1;
  1557. break;
  1558. case 'O':
  1559. text_other_side = 1;
  1560. break;
  1561. case 'M':
  1562. clear_memory = 1;
  1563. break;
  1564. case 'u':
  1565. upside_down = 1;
  1566. break;
  1567. case 'X':
  1568. padding_swap = 1;
  1569. break;
  1570. case 'l':
  1571. least_margin = atoi(optarg);
  1572. break;
  1573. case 'V':
  1574. least_v_margin = atoi(optarg);
  1575. break;
  1576. case 'C':
  1577. center_icons = 1;
  1578. break;
  1579. case 'e':
  1580. hide_missing = 1;
  1581. break;
  1582. case 'A':
  1583. parse_button(optarg);
  1584. break;
  1585. case 'U': ;
  1586. unsigned char lb = optarg[0];
  1587. int i = 0;
  1588. shortcut_t *current_shortcut = NULL;
  1589. while (lb != '\0') {
  1590. if ( current_shortcut == NULL ) {
  1591. current_shortcut = malloc(sizeof(shortcut_t));
  1592. } else {
  1593. current_shortcut->next = malloc(sizeof(shortcut_t));
  1594. current_shortcut = current_shortcut->next;
  1595. }
  1596. if ( shortcuts == NULL)
  1597. shortcuts = current_shortcut;
  1598. current_shortcut->entry = NULL;
  1599. current_shortcut->next = NULL;
  1600. if (( lb & 0x80 ) == 0 ) { // lead bit is zero, must be a single ascii
  1601. current_shortcut->key = malloc(sizeof(char));
  1602. memcpy(current_shortcut->key, &optarg[i], sizeof(char));
  1603. i+=1;
  1604. } else if (( lb & 0xE0 ) == 0xC0 ) { // 110x xxxx
  1605. current_shortcut->key = malloc(sizeof(char)*2);
  1606. memcpy(current_shortcut->key, &optarg[i], sizeof(char)*2);
  1607. i+=2;
  1608. } else if (( lb & 0xF0 ) == 0xE0 ) { // 1110 xxxx
  1609. current_shortcut->key = malloc(sizeof(char)*3);
  1610. memcpy(current_shortcut->key, &optarg[i], sizeof(char)*3);
  1611. i+=3;
  1612. } else if (( lb & 0xF8 ) == 0xF0 ) { // 1111 0xxx
  1613. current_shortcut->key = malloc(sizeof(char)*4);
  1614. memcpy(current_shortcut->key, &optarg[i], sizeof(char)*4);
  1615. i+=4;
  1616. } else {
  1617. printf( "Unrecognized lead byte in shortcut (%02x)\n", lb );
  1618. }
  1619. lb = optarg[i];
  1620. }
  1621. break;
  1622. case 1014:
  1623. config_file = optarg;
  1624. break;
  1625. case 1015:
  1626. bg_fill = 1;
  1627. break;
  1628. case 1016:
  1629. focus_lost_terminate = 1;
  1630. break;
  1631. case 1017:
  1632. border_ratio = atoi(optarg);
  1633. break;
  1634. case 1018:
  1635. side_border_ratio = atoi(optarg);
  1636. break;
  1637. case 1019:
  1638. scroll = 1;
  1639. break;
  1640. case '?':
  1641. fprintf(stderr, "See --help for usage documentation\n");
  1642. exit(CONFIGERROR);
  1643. break;
  1644. case 'H':
  1645. fprintf (stderr,"usage: xlunch [options]\n"
  1646. " xlunch is a program launcher/option selector similar to dmenu or rofi.\n"
  1647. " By default it launches in full-screen mode and terminates after a selection is made,\n"
  1648. " it is also possible to close xlunch by pressing Esc or the right mouse button.\n"
  1649. " Some options changes this behaviour, the most notable being the desktop mode switch:\n"
  1650. " -d, --desktop Desktop mode, always keep the launcher at\n"
  1651. " background(behind other windows), and ignore ESC\n"
  1652. " and right mouse click. Combined with --dontquit\n"
  1653. " xlunch never exits (behaviour of --desktop from\n"
  1654. " previous versions).\n"
  1655. " Functional options:\n"
  1656. " --config [file] Reads configuration options from a file. The\n"
  1657. " options are the same as the long options\n"
  1658. " specified here. Options that take a value must\n"
  1659. " have a colon (':') before it's option. It is\n"
  1660. " also possible to pass the entries along with the\n"
  1661. " configuration file by using \"entries:\"\n"
  1662. " followed by a newline and the regular contents\n"
  1663. " of an input file\n"
  1664. " -v, --version Returns the version of xlunch\n"
  1665. " -H, --help Shows this help message\n"
  1666. " --name POSIX-esque way to specify the first part of\n"
  1667. " WM_CLASS (default: environment variable \n"
  1668. " RESOURCE_NAME or argv[0])\n"
  1669. " -n, --noprompt Hides the prompt, only allowing selection\n"
  1670. " by icon\n"
  1671. " -o, --outputonly Do not run the selected entry, only output it\n"
  1672. " to stdout\n"
  1673. " -S, --selectonly Only allow an actual entry and not free-typed\n"
  1674. " commands. Nice for scripting.\n"
  1675. " -i, --input [file] File to read entries from, defaults to stdin\n"
  1676. " if data is available otherwise it reads from\n"
  1677. " $HOME/.config/xlunch/entries.dsv or\n"
  1678. " /etc/xlunch/entries.dsv\n"
  1679. " -m, --multiple Allow multiple instances running\n"
  1680. " -t, --voidclickterminate Clicking anywhere that's not an entry terminates\n"
  1681. " xlunch, practical for touch screens.\n"
  1682. " --focuslostterminate When the window loses focus xlunch will quit,\n"
  1683. " practical for menus\n"
  1684. " -q, --dontquit When an option is selected, don't close xlunch.\n"
  1685. " Combined with --desktop xlunch\n"
  1686. " never exits (behaviour of --desktop from\n"
  1687. " previous versions).\n"
  1688. " -R, --reverse All entries in xlunch as reversly ordered.\n"
  1689. " -W, --windowed Start in windowed mode\n"
  1690. " -M, --clearmemory Set the memory of each entry to null before\n"
  1691. " exiting. Used for passing sensitive information\n"
  1692. " through xlunch.\n"
  1693. " -U, --shortcuts [shortcuts] Sets shortcuts for the entries, 'shortcuts' is a\n"
  1694. " string of UTF-8 characters to use sequentially\n"
  1695. " for the entries provided.\n"
  1696. " -A, --button [button] Adds a button to the window. The argument\n"
  1697. " \"button\" is a semicolon-separated list on the\n"
  1698. " form \"<icon>;<highlight icon>;<x>,<y>;<command>\"\n"
  1699. " . If x or y is negative positioning is relative\n"
  1700. " to the other side of the screen.\n"
  1701. " --scroll Enable to make xlunch scroll when reaching end of\n"
  1702. " entries or when using scrollwheel or pgup pgdn\n\n"
  1703. " Multi monitor setup: xlunch cannot detect your output monitors, it sees your monitors\n"
  1704. " as a big single screen. You can customize this manually by setting windowed mode and\n"
  1705. " providing the top/left coordinates and width/height of your monitor screen which\n"
  1706. " effectively positions xlunch on the desired monitor. Use the following options:\n"
  1707. " -x, --xposition [i] The x coordinate of the launcher window\n"
  1708. " -y, --yposition [i] The y coordinate of the launcher window\n"
  1709. " -w, --width [i] The width of the launcher window\n"
  1710. " -h, --height [i] The height of the launcher window\n\n"
  1711. " Style options:\n"
  1712. " -p, --prompt [text] The prompt asking for input (default: \"Run: \")\n"
  1713. " -f, --font [name] Font name including size after slash (default:\n"
  1714. " \"OpenSans-Regular/10\" and \"DejaVuSans/10\")\n"
  1715. " -F, --promptfont [name] Font to use for the prompt\n"
  1716. " (default: same as --font)\n"
  1717. " -G, --rootwindowbackground Use root windows background image\n"
  1718. " -g, --background [file] Image to set as background (jpg/png)\n"
  1719. " --bgfill Makes the background keep aspect ratio\n"
  1720. " while stretching\n"
  1721. " -L, --highlight [file] Image set as highlighting under selected icon\n"
  1722. " (jpg/png)\n"
  1723. " -I, --iconpadding [i] Padding around icons (default: 10)\n"
  1724. " --iconvpadding [i] Vertical padding around icons (default: same as\n"
  1725. " iconpadding)\n"
  1726. " -T, --textpadding [i] Padding around entry titles (default: 10)\n"
  1727. " -c, --columns [i] Number of columns to show (without this the max\n"
  1728. " amount possible is used)\n"
  1729. " -r, --rows [i] Numbers of rows to show (without this the max\n"
  1730. " amount possible is used)\n"
  1731. " -b, --border [i] Size of the border around the icons and prompt\n"
  1732. " (default: 1/10th of screen width)\n"
  1733. " This can also be set to 'auto' in order to\n"
  1734. " automatically calculate a border taking into\n"
  1735. " account the margin settings and the\n"
  1736. " configured columns and rows. You can also specify\n"
  1737. " border in terms of percentage of screen width by\n"
  1738. " appending a %% sign to the value\n"
  1739. " -B, --sideborder [i] Size of the border on the sides, if this is used\n"
  1740. " --border will be only top and bottom. Similarily\n"
  1741. " this can be set to 'auto' or a percentage but\n"
  1742. " then only side borders are calculated\n"
  1743. " --borderratio [i] The ratio of the border to apply above the\n"
  1744. " content. 0 is no top border, only bottom. 100 is\n"
  1745. " only top border, no bottom\n"
  1746. " --sideborderratio [i] The ratio of the side border to apply to the\n"
  1747. " left of the content. 0 is no left border, only\n"
  1748. " right. 100 is only left border, no right\n"
  1749. " -C, --center Center entries when there are fewer entries on a\n"
  1750. " row than the maximum\n"
  1751. " -P, --promptspacing [i] Distance between the prompt and the icons\n"
  1752. " (default: 48)\n"
  1753. " -s, --iconsize [i] Size of the icons (default: 48)\n"
  1754. " -a, --textafter Draw the title to the right of the icon instead\n"
  1755. " of below, this option automatically sets\n"
  1756. " --columns to 1 but this can be overridden.\n"
  1757. " -O, --textotherside Draw the text on the other side of the icon from\n"
  1758. " where it is normally drawn.\n"
  1759. " -u, --upsidedown Draw the prompt on the bottom and have icons\n"
  1760. " sort from bottom to top.\n"
  1761. " -X, --paddingswap Icon padding and text padding swaps order\n"
  1762. " around text.\n"
  1763. " -l, --leastmargin [i] Adds a margin to the calculation of\n"
  1764. " application sizes.\n"
  1765. " -V, --leastvmargin [i] Adds a vertical margin to the calculation of\n"
  1766. " application sizes.\n"
  1767. " -e, --hidemissing Hide entries with missing or broken icon images\n"
  1768. " --tc, --textcolor [color] Color to use for the text on the format rrggbbaa\n"
  1769. " (default: ffffffff)\n"
  1770. " --pc, --promptcolor [color] Color to use for the prompt text\n"
  1771. " (default: ffffffff)\n"
  1772. " --bc, --backgroundcolor [color] Color to use for the background\n"
  1773. " (default: 2e3440ff) NOTE: transparent background\n"
  1774. " color requires a compositor\n"
  1775. " --hc, --highlightcolor [color] Color to use for the highlight box\n"
  1776. " (default: ffffff32)\n\n");
  1777. // Check if we came from the error block above or if this was a call with --help
  1778. if(c == '?'){
  1779. exit(CONFIGERROR);
  1780. } else {
  1781. exit(OKAY);
  1782. }
  1783. break;
  1784. }
  1785. }
  1786. void init(int argc, char **argv)
  1787. {
  1788. int c, option_index;
  1789. while ((c = getopt_long(argc, argv, "vdr:ng:L:b:B:s:i:p:f:mc:x:y:w:h:oatGHI:T:P:WF:SqROMuXeCl:V:U:A:", long_options, &option_index)) != -1) {
  1790. handle_option(c, optarg);
  1791. }
  1792. FILE *config_source = determine_config_source();
  1793. if(config_source != NULL){
  1794. parse_config(config_source);
  1795. fclose(config_source);
  1796. }
  1797. if (least_v_margin == -1) least_v_margin = least_margin;
  1798. if (icon_v_padding == -1) icon_v_padding = icon_padding;
  1799. /* connect to X */
  1800. disp = XOpenDisplay(NULL);
  1801. if (!disp)
  1802. {
  1803. fprintf(stderr,"Cannot connect to DISPLAY\n");
  1804. exit(WINERROR);
  1805. }
  1806. XMatchVisualInfo(disp, DefaultScreen(disp), 32, TrueColor, &vinfo);
  1807. attr.colormap = XCreateColormap(disp, DefaultRootWindow(disp), vinfo.visual, AllocNone);
  1808. attr.border_pixel = 0;
  1809. attr.background_pixel = 0;
  1810. /* get default visual , colormap etc. you could ask imlib2 for what it */
  1811. /* thinks is the best, but this example is intended to be simple */
  1812. screen = DefaultScreen(disp);
  1813. /* get/set screen size */
  1814. if (uwidth==0) screen_width=DisplayWidth(disp,screen);
  1815. else screen_width=uwidth;
  1816. if (uheight==0) screen_height=DisplayHeight(disp,screen);
  1817. else screen_height=uheight;
  1818. calculate_percentage(screen_height, &uborder);
  1819. calculate_percentage(screen_width, &uside_border);
  1820. }
  1821. void recheckHover(XEvent ev) {
  1822. node_t * current = entries;
  1823. int i = 1;
  1824. int j = 1;
  1825. int any_hovered = 0;
  1826. while (current != NULL)
  1827. {
  1828. if (mouse_over_cell(current, j, ev.xmotion.x, ev.xmotion.y)) {
  1829. set_hover(i, current, 1);
  1830. any_hovered = 1;
  1831. hoverset=MOUSE;
  1832. }
  1833. else {
  1834. set_hover(i, current,0);
  1835. set_clicked(current,0);
  1836. }
  1837. if(!current->hidden)
  1838. j++;
  1839. i++;
  1840. current = current->next;
  1841. }
  1842. if (any_hovered == 0) hovered_entry = 0;
  1843. button_t * button = buttons;
  1844. while (button != NULL) {
  1845. int x = (button->x < 0 ? screen_width + button->x + 1 - button->w : button->x);
  1846. int y = (button->y < 0 ? screen_height + button->y + 1 - button->h : button->y);
  1847. if (mouse_over_button(button, ev.xmotion.x, ev.xmotion.y)) {
  1848. if (button->hovered != 1) updates = imlib_update_append_rect(updates, x, y, button->w, button->h);
  1849. button->hovered = 1;
  1850. } else {
  1851. if (button->hovered != 0) updates = imlib_update_append_rect(updates, x, y, button->w, button->h);
  1852. button->hovered = 0;
  1853. button->clicked = 0;
  1854. }
  1855. button = button->next;
  1856. }
  1857. }
  1858. void handleButtonPress(XEvent ev) {
  1859. switch (ev.xbutton.button) {
  1860. case 3:
  1861. if (!desktop_mode) {
  1862. cleanup();
  1863. exit(RIGHTCLICK);
  1864. }
  1865. break;
  1866. case 4:
  1867. set_scroll_level(scrolled_past - 1);
  1868. recheckHover(ev);
  1869. break;
  1870. case 5:;
  1871. set_scroll_level(scrolled_past + 1);
  1872. recheckHover(ev);
  1873. break;
  1874. case 1:;
  1875. node_t * current = entries;
  1876. int voidclicked = 1;
  1877. int index = 1;
  1878. while (current != NULL)
  1879. {
  1880. if (mouse_over_cell(current, index, ev.xmotion.x, ev.xmotion.y)) {
  1881. set_clicked(current,1);
  1882. voidclicked = 0;
  1883. }
  1884. else set_clicked(current,0);
  1885. if(!current->hidden)
  1886. index++;
  1887. current = current->next;
  1888. }
  1889. button_t * button = buttons;
  1890. while (button != NULL) {
  1891. int x = (button->x < 0 ? screen_width + button->x + 1 - button->w : button->x);
  1892. int y = (button->y < 0 ? screen_height + button->y + 1 - button->h : button->y);
  1893. if (mouse_over_button(button, ev.xmotion.x, ev.xmotion.y)) {
  1894. if (button->clicked != 1) updates = imlib_update_append_rect(updates, x, y, button->w, button->h);
  1895. button->clicked = 1;
  1896. voidclicked = 0;
  1897. } else {
  1898. if (button->clicked != 0) updates = imlib_update_append_rect(updates, x, y, button->w, button->h);
  1899. button->clicked = 0;
  1900. }
  1901. button = button->next;
  1902. }
  1903. if (voidclicked && void_click_terminate) {
  1904. cleanup();
  1905. exit(VOIDCLICK);
  1906. }
  1907. break;
  1908. }
  1909. }
  1910. void handleButtonRelease(XEvent ev) {
  1911. node_t * current = entries;
  1912. int index = 1;
  1913. while (current != NULL)
  1914. {
  1915. if (mouse_over_cell(current, index, ev.xmotion.x, ev.xmotion.y)) if (current->clicked==1) run_command(current->cmd);
  1916. set_clicked(current, 0); // button release means all cells are not clicked
  1917. updates = imlib_update_append_rect(updates, current->x, current->y, icon_size, icon_size);
  1918. if(!current->hidden)
  1919. index++;
  1920. current = current->next;
  1921. }
  1922. button_t * button = buttons;
  1923. while (button != NULL)
  1924. {
  1925. if (mouse_over_button(button, ev.xmotion.x, ev.xmotion.y) && button->clicked == 1) run_command(button->cmd);
  1926. button->clicked = 0;
  1927. int x = (button->x < 0 ? screen_width + button->x + 1 - button->w : button->x);
  1928. int y = (button->y < 0 ? screen_height + button->y + 1 - button->h : button->y);
  1929. updates = imlib_update_append_rect(updates, x, y, button->w, button->h);
  1930. button = button->next;
  1931. }
  1932. }
  1933. void handleKeyPress(XEvent ev) {
  1934. // keyboard events
  1935. int count = 0;
  1936. KeySym keycode = 0;
  1937. Status status = 0;
  1938. char kbdbuf[20]= {0};
  1939. count = Xutf8LookupString(ic, (XKeyPressedEvent*)&ev, kbdbuf, 20, &keycode, &status);
  1940. if (keycode==XK_Escape && !desktop_mode)
  1941. {
  1942. cleanup();
  1943. exit(ESCAPE);
  1944. }
  1945. if (keycode==XK_Return || keycode==XK_KP_Enter)
  1946. {
  1947. // if we have an icon hovered, and the hover was caused by keyboard arrows, run the hovered icon
  1948. node_t * current = entries;
  1949. node_t * selected = NULL;
  1950. node_t * selected_one = NULL;
  1951. node_t * first = NULL;
  1952. int nb=0;
  1953. while (current != NULL)
  1954. {
  1955. if (!current->hidden)
  1956. {
  1957. nb++;
  1958. selected_one=current;
  1959. if (first == NULL) first = current;
  1960. if (current->hovered) selected = current;
  1961. }
  1962. current=current->next;
  1963. }
  1964. /* if only 1 app was filtered, consider it selected */
  1965. if (nb==1 && selected_one!=NULL) run_command(selected_one->cmd);
  1966. else if (hoverset==KEYBOARD && selected!=NULL) run_command(selected->cmd);
  1967. // else run the command entered by commandline, if the command prompt is used
  1968. else if (!no_prompt && !select_only) run_command(commandline);
  1969. // or if --selectonly is specified, run first program regardless of selected state
  1970. else if (select_only && first!=NULL) run_command(first->cmd);
  1971. }
  1972. if (keycode==XK_Tab || keycode==XK_Up || keycode==XK_Down || keycode==XK_Left || keycode==XK_Right
  1973. || keycode==XK_KP_Up || keycode==XK_KP_Down || keycode==XK_KP_Left || keycode==XK_KP_Right
  1974. || keycode==XK_Page_Down || keycode==XK_Page_Up || keycode==XK_Home || keycode==XK_End)
  1975. {
  1976. int i=0;
  1977. if (keycode==XK_KP_Left || keycode==XK_Left) i=-1;
  1978. if (keycode==XK_Up || keycode==XK_KP_Up) i=-columns;
  1979. if (keycode==XK_Down || keycode==XK_KP_Down) i=columns;
  1980. if (keycode==XK_Tab || keycode==XK_Right || keycode==XK_KP_Right) i=1;
  1981. if (keycode==XK_Page_Up) i=-columns*rows;
  1982. if (keycode==XK_Page_Down) i=columns*rows;
  1983. if (keycode==XK_End) i = entries_count;//(scroll ? scrolled_past*columns+n : n);
  1984. if (keycode==XK_Home) i = -entries_count;//(scroll ? scrolled_past*columns+1 : 1);
  1985. if (hovered_entry == 0) {
  1986. if (keycode != XK_End && keycode != XK_Page_Down) {
  1987. hovered_entry = 1-i;
  1988. } else {
  1989. hovered_entry = 1;
  1990. }
  1991. }
  1992. i = hovered_entry + i;
  1993. hover_entry(i);
  1994. return;
  1995. }
  1996. if (keycode==XK_Delete || keycode==XK_BackSpace)
  1997. pop_key();
  1998. else if (count>1 || (count==1 && kbdbuf[0]>=32)){ // ignore unprintable characterrs
  1999. if (!no_prompt) push_key(kbdbuf);
  2000. shortcut_t *shortcut = shortcuts;
  2001. while(shortcut != NULL) {
  2002. for(int i = 0; i < count; i++){
  2003. if (kbdbuf[i] != shortcut->key[i]) {
  2004. break;
  2005. } else if (i == count-1) {
  2006. run_command(shortcut->entry->cmd);
  2007. }
  2008. }
  2009. shortcut = shortcut->next;
  2010. }
  2011. }
  2012. joincmdline();
  2013. joincmdlinetext();
  2014. filter_entries();
  2015. arrange_positions();
  2016. // we used keyboard to type command. So unselect all icons.
  2017. node_t * current = entries;
  2018. int i = 1;
  2019. while (current != NULL)
  2020. {
  2021. set_hover(i, current,0);
  2022. set_clicked(current,0);
  2023. current = current->next;
  2024. i++;
  2025. }
  2026. updates = imlib_update_append_rect(updates, 0, 0, screen_width, screen_height);
  2027. }
  2028. void renderEntry(Imlib_Image buffer, char title[256], node_t * current, Cursor * c, int up_x, int up_y) {
  2029. int h = 0;
  2030. int w = 0;
  2031. if (current->hovered)
  2032. {
  2033. if (hoverset == MOUSE) *c = XCreateFontCursor(disp,XC_hand1);
  2034. if (highlight)
  2035. {
  2036. imlib_context_set_image(highlight);
  2037. w = imlib_image_get_width();
  2038. h = imlib_image_get_height();
  2039. imlib_context_set_image(buffer);
  2040. imlib_blend_image_onto_image(highlight, 1, 0, 0, w, h, current->x-up_x, current->y-up_y, cell_width, cell_height);
  2041. }
  2042. else
  2043. {
  2044. imlib_context_set_image(buffer);
  2045. imlib_context_set_color(highlight_color.r, highlight_color.g, highlight_color.b, highlight_color.a);
  2046. imlib_image_fill_rectangle(current->x-up_x, current->y-up_y, cell_width, cell_height);
  2047. }
  2048. }
  2049. if (strlen(current->icon) != 0) {
  2050. image=imlib_load_image(current->icon);
  2051. if (image)
  2052. {
  2053. imlib_context_set_image(image);
  2054. w = imlib_image_get_width();
  2055. h = imlib_image_get_height();
  2056. imlib_context_set_image(buffer);
  2057. int d;
  2058. if (current->clicked) d=2;
  2059. else d=0;
  2060. int x = current->x - up_x +
  2061. (text_other_side && text_after ? cell_width - icon_padding - icon_size : icon_padding)+d;
  2062. int y = current->y - up_y +(text_other_side && !text_after ? cell_height - icon_v_padding - icon_size : icon_v_padding)+d;
  2063. imlib_blend_image_onto_image(image, 1, 0, 0, w, h, x, y, icon_size-d*2, icon_size-d*2);
  2064. imlib_context_set_image(image);
  2065. imlib_free_image();
  2066. }
  2067. }
  2068. /* draw text under icon */
  2069. Imlib_Font font = load_font();
  2070. if (font)
  2071. {
  2072. imlib_context_set_image(buffer);
  2073. int text_w;
  2074. int text_h;
  2075. const size_t osz = strlen(current->title);
  2076. size_t sz = osz;
  2077. imlib_context_set_font(font);
  2078. do
  2079. {
  2080. strncpyutf8(title,current->title,sz);
  2081. if(sz != osz)
  2082. strcat(title,"..");
  2083. imlib_get_text_size(title, &text_w, &text_h);
  2084. sz--;
  2085. } while(text_w > cell_width-(text_after ? (icon_size != 0 ? icon_padding*2 : icon_padding) + icon_size + text_padding : 2*text_padding) && sz>0);
  2086. int d;
  2087. if (current->clicked==1) d=4;
  2088. else d=0;
  2089. if (text_after) {
  2090. draw_text_with_shadow(current->x - up_x + (text_other_side ? text_padding : (icon_size != 0 ? (padding_swap ? icon_padding + text_padding : icon_padding*2) : icon_padding) + icon_size), current->y - up_y + cell_height/2 - font_height/2, title, text_color);
  2091. } else {
  2092. draw_text_with_shadow(current->x - up_x + cell_width/2 - text_w/2, current->y - up_y + (text_other_side ? text_padding : (padding_swap ? icon_v_padding + text_padding : icon_v_padding*2) + icon_size), title, text_color);
  2093. }
  2094. /* free the font */
  2095. imlib_free_font();
  2096. }
  2097. }
  2098. /* the program... */
  2099. int main(int argc, char **argv){
  2100. /* events we get from X */
  2101. XEvent ev;
  2102. /* our virtual framebuffer image we draw into */
  2103. Imlib_Image buffer;
  2104. char title[255];
  2105. /* width and height values */
  2106. int w, h, x, y;
  2107. // set initial variables now
  2108. init(argc, argv);
  2109. if(program_name == NULL) {
  2110. program_name = getenv("RESOURCE_NAME");
  2111. if(program_name == NULL) {
  2112. program_name = argv[0];
  2113. }
  2114. }
  2115. joincmdlinetext();
  2116. // If an instance is already running, quit
  2117. if (!multiple_instances)
  2118. {
  2119. int oldmask = umask(0);
  2120. lock = open("/tmp/xlunch.lock", O_CREAT | O_RDWR, 0666);
  2121. umask(oldmask);
  2122. int rc = flock(lock, LOCK_EX | LOCK_NB);
  2123. if (rc) {
  2124. if (errno == EWOULDBLOCK) fprintf(stderr,"xlunch already running. You may want to consider --multiple\nIf this is an error, you may remove /tmp/xlunch.lock\n");
  2125. exit(LOCKERROR);
  2126. }
  2127. }
  2128. //win = XCreateSimpleWindow(disp, DefaultRootWindow(disp), uposx, uposy, screen_width, screen_height, 0, 0, 0);
  2129. win = XCreateWindow(disp, DefaultRootWindow(disp), (uposx == -1 ? 0 : uposx), (uposy == -1 ? 0 : uposy), screen_width, screen_height, 0, vinfo.depth, InputOutput, vinfo.visual, CWColormap | CWBorderPixel | CWBackPixel, &attr);
  2130. // absolute fullscreen mode by overide redirect
  2131. if (!windowed && desktop_mode)
  2132. {
  2133. unsigned long valuemask = CWOverrideRedirect;
  2134. XSetWindowAttributes attributes;
  2135. attributes.override_redirect=True;
  2136. XChangeWindowAttributes(disp,win,valuemask,&attributes);
  2137. }
  2138. /* add the ttf fonts dir to our font path */
  2139. char* homedir;
  2140. if((homedir = getenv("HOME")) != NULL) {
  2141. imlib_add_path_to_font_path(concat(homedir,"/.local/share/fonts"));
  2142. imlib_add_path_to_font_path(concat(homedir,"/.fonts"));
  2143. }
  2144. imlib_add_path_to_font_path("/usr/local/share/fonts");
  2145. imlib_add_path_to_font_path("/usr/share/fonts/truetype");
  2146. imlib_add_path_to_font_path("/usr/share/fonts/truetype/dejavu");
  2147. imlib_add_path_to_font_path("/usr/share/fonts/TTF");
  2148. /* set our cache to 2 Mb so it doesn't have to go hit the disk as long as */
  2149. /* the images we use use less than 2Mb of RAM (that is uncompressed) */
  2150. imlib_set_cache_size(2048 * screen_width);
  2151. /* set the font cache to 512Kb - again to avoid re-loading */
  2152. imlib_set_font_cache_size(512 * screen_width);
  2153. /* Preload fonts so that recalc can know their sizes */
  2154. load_font();
  2155. load_prompt_font();
  2156. recalc_cells();
  2157. /* set the maximum number of colors to allocate for 8bpp and less to 128 */
  2158. imlib_set_color_usage(128);
  2159. /* dither for depths < 24bpp */
  2160. imlib_context_set_dither(1);
  2161. /* set the display , visual, colormap and drawable we are using */
  2162. imlib_context_set_display(disp);
  2163. imlib_context_set_visual(vinfo.visual);
  2164. imlib_context_set_colormap(attr.colormap);
  2165. imlib_context_set_drawable(win);
  2166. update_background_images();
  2167. /* tell X what events we are interested in */
  2168. XSelectInput(disp, win, StructureNotifyMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | ExposureMask | KeyPressMask | KeyReleaseMask | KeymapStateMask | FocusChangeMask );
  2169. GC gc = XCreateGC(disp, win, 0, 0);
  2170. /* set our name */
  2171. XClassHint* classHint;
  2172. /* set the titlebar name */
  2173. XStoreName(disp, win, "xlunch: Graphical app launcher");
  2174. /* set the name and class hints for the window manager to use */
  2175. classHint = XAllocClassHint();
  2176. if (classHint) {
  2177. classHint->res_name = basename(program_name);
  2178. classHint->res_class = (windowed ? "xlunch-windowed" : (desktop_mode ? "xlunch-desktop" : "xlunch-fullscreen"));
  2179. }
  2180. XSetClassHint(disp, win, classHint);
  2181. XFree(classHint);
  2182. /* show the window */
  2183. XMapRaised(disp, win);
  2184. /* Force window reposition, can make effect only when windowed is enabled, depending on WM */
  2185. if (uposx != -1 || uposy != -1)
  2186. XMoveWindow(disp,win,uposx,uposy);
  2187. // prepare for keyboard UTF8 input
  2188. if (XSetLocaleModifiers("@im=none") == NULL) {
  2189. cleanup();
  2190. exit(LOCALEERROR);
  2191. }
  2192. im = XOpenIM(disp, NULL, NULL, NULL);
  2193. if (im == NULL) {
  2194. fputs("Could not open input method\n", stdout);
  2195. cleanup();
  2196. exit(INPUTMERROR);
  2197. }
  2198. ic = XCreateIC(im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, win, NULL);
  2199. if (ic == NULL) {
  2200. fprintf(stderr,"Could not open input context, without it we can't properly handle UTF8\n");
  2201. cleanup();
  2202. exit(INPUTCERROR);
  2203. }
  2204. XSetICFocus(ic);
  2205. // send to back or front, depending on settings
  2206. restack();
  2207. // parse entries file
  2208. input_source = determine_input_source();
  2209. if(input_source != NULL){
  2210. parse_entries();
  2211. }
  2212. // prepare message for window manager that we are requesting fullscreen
  2213. XClientMessageEvent msg = {
  2214. .type = ClientMessage,
  2215. .display = disp,
  2216. .window = win,
  2217. .message_type = XInternAtom(disp, "_NET_WM_STATE", True),
  2218. .format = 32,
  2219. .data = {
  2220. .l = {
  2221. 1 /* _NET_WM_STATE_ADD */,
  2222. XInternAtom(disp, "_NET_WM_STATE_FULLSCREEN", True),
  2223. None,
  2224. 0,
  2225. 1
  2226. }
  2227. }
  2228. };
  2229. // send the message
  2230. if (!windowed && !desktop_mode)
  2231. XSendEvent(disp, DefaultRootWindow(disp), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*)&msg);
  2232. // Get the FD of the X11 display
  2233. x11_fd = ConnectionNumber(disp);
  2234. eventfds[0].fd = x11_fd;
  2235. eventfds[0].events = POLLIN || POLLPRI || POLLOUT || POLLRDHUP;
  2236. if(input_source == stdin) {
  2237. eventfds[1].fd = 0; /* this is STDIN */
  2238. eventfds[1].events = POLLIN;
  2239. }
  2240. /* infinite event loop */
  2241. for (;;)
  2242. {
  2243. /* init our updates to empty */
  2244. updates = imlib_updates_init();
  2245. // Poll for events, while blocking until one becomes available
  2246. int poll_result;
  2247. if(!XPending(disp)){
  2248. poll_result = poll(eventfds, (input_source == stdin ? 2 : 1), -1);
  2249. } else {
  2250. poll_result = 1;
  2251. eventfds[0].revents = 1;
  2252. }
  2253. if(poll_result < 0){
  2254. // An error occured, abort
  2255. cleanup();
  2256. exit(POLLERROR);
  2257. } else {
  2258. if(input_source == stdin && eventfds[1].revents != 0){
  2259. int changed = parse_entries(input_source);
  2260. if(changed){
  2261. updates = imlib_update_append_rect(updates, 0, 0, screen_width, screen_height);
  2262. }
  2263. }
  2264. if(eventfds[0].revents != 0) {
  2265. /* while there are events form X - handle them */
  2266. while (XPending(disp))
  2267. {
  2268. XNextEvent(disp, &ev);
  2269. // allow through only UTF8 events
  2270. if (XFilterEvent(&ev, win)) continue;
  2271. switch (ev.type)
  2272. {
  2273. case Expose:
  2274. /* window rectangle was exposed - add it to the list of */
  2275. /* rectangles we need to re-render */
  2276. updates = imlib_update_append_rect(updates, ev.xexpose.x, ev.xexpose.y, ev.xexpose.width, ev.xexpose.height);
  2277. break;
  2278. case FocusIn:
  2279. restack();
  2280. break;
  2281. case FocusOut:
  2282. restack();
  2283. if (focus_lost_terminate) {
  2284. cleanup();
  2285. exit(FOCUSLOST);
  2286. }
  2287. break;
  2288. case ConfigureNotify:
  2289. if (screen_width!=ev.xconfigure.width || screen_height!=ev.xconfigure.height)
  2290. {
  2291. screen_width=ev.xconfigure.width;
  2292. screen_height=ev.xconfigure.height;
  2293. calculate_percentage(screen_height, &uborder);
  2294. calculate_percentage(screen_width, &uside_border);
  2295. if (!use_root_img) update_background_images();
  2296. recalc_cells();
  2297. arrange_positions();
  2298. updates = imlib_update_append_rect(updates, 0, 0, screen_width, screen_height);
  2299. }
  2300. break;
  2301. case ButtonPress:
  2302. handleButtonPress(ev);
  2303. break;
  2304. case ButtonRelease:
  2305. handleButtonRelease(ev);
  2306. break;
  2307. // refresh keyboard layout if changed
  2308. case KeymapNotify:
  2309. XRefreshKeyboardMapping(&ev.xmapping);
  2310. break;
  2311. case KeyPress:
  2312. handleKeyPress(ev);
  2313. break;
  2314. case KeyRelease:
  2315. break;
  2316. case EnterNotify:
  2317. case MotionNotify:
  2318. recheckHover(ev);
  2319. break;
  2320. default:
  2321. /* any other events - do nothing */
  2322. break;
  2323. }
  2324. }
  2325. }
  2326. /* no more events for now ? ok - idle time so lets draw stuff */
  2327. /* take all the little rectangles to redraw and merge them into */
  2328. /* something sane for rendering */
  2329. updates = imlib_updates_merge_for_rendering(updates, screen_width, screen_height);
  2330. for (current_update = updates; current_update; current_update = imlib_updates_get_next(current_update))
  2331. {
  2332. int up_x, up_y, up_w, up_h;
  2333. /* find out where the first update is */
  2334. imlib_updates_get_coordinates(current_update, &up_x, &up_y, &up_w, &up_h);
  2335. /* create our buffer image for rendering this update */
  2336. buffer = imlib_create_image(up_w, up_h);
  2337. /* we can blend stuff now */
  2338. imlib_context_set_blend(1);
  2339. imlib_context_set_image(buffer);
  2340. imlib_image_set_has_alpha(1);
  2341. imlib_image_clear();
  2342. /* blend background image onto the buffer */
  2343. imlib_blend_image_onto_image(background, 1, 0, 0, screen_width, screen_height, - up_x, - up_y, screen_width, screen_height);
  2344. node_t * current = entries;
  2345. int drawn = 0;
  2346. int seen = 0;
  2347. Cursor c = XCreateFontCursor(disp,XC_top_left_arrow);
  2348. while (current != NULL)
  2349. {
  2350. if (!current->hidden)
  2351. {
  2352. if (seen++ < scrolled_past * columns) {
  2353. current = current->next;
  2354. continue;
  2355. }
  2356. renderEntry(buffer, title, current, &c, up_x, up_y);
  2357. if (++drawn == columns*rows)
  2358. break;
  2359. }
  2360. current = current->next;
  2361. }
  2362. button_t *button = buttons;
  2363. while (button != NULL) {
  2364. if(button->hovered) c = XCreateFontCursor(disp,XC_hand1);
  2365. image = imlib_load_image(button->hovered ? (button->icon_highlight[0] == '\0' ? button->icon_normal : button->icon_highlight) : button->icon_normal);
  2366. if (image)
  2367. {
  2368. imlib_context_set_image(buffer);
  2369. int d;
  2370. if (button->clicked) d=2;
  2371. else d=0;
  2372. int x = (button->x < 0 ? screen_width + button->x + 1 - button->w : button->x);
  2373. int y = (button->y < 0 ? screen_height + button->y + 1 - button->h : button->y);
  2374. imlib_blend_image_onto_image(image, 1, 0, 0, button->w, button->h, x - up_x + d, y - up_y + d, button->w-d*2, button->h-d*2);
  2375. imlib_context_set_image(image);
  2376. imlib_free_image();
  2377. }
  2378. button = button->next;
  2379. }
  2380. XDefineCursor(disp,win,c);
  2381. /* set the buffer image as our current image */
  2382. imlib_context_set_image(buffer);
  2383. /* draw prompt */
  2384. if (!no_prompt) {
  2385. Imlib_Font font = load_prompt_font();
  2386. if (font)
  2387. {
  2388. imlib_context_set_font(font);
  2389. if(upside_down) {
  2390. draw_text_with_shadow(prompt_x - up_x, (screen_height - prompt_font_height) - (prompt_y - up_y), commandlinetext, prompt_color);
  2391. } else {
  2392. draw_text_with_shadow(prompt_x - up_x, prompt_y - up_y, commandlinetext, prompt_color);
  2393. }
  2394. /* free the font */
  2395. imlib_free_font();
  2396. }
  2397. }
  2398. /* don't blend the image onto the drawable - slower */
  2399. imlib_context_set_blend(0);
  2400. /* render the image at 0, 0 */
  2401. imlib_render_image_on_drawable(up_x, up_y);
  2402. /* don't need that temporary buffer image anymore */
  2403. imlib_free_image();
  2404. }
  2405. }
  2406. /* if we had updates - free them */
  2407. if (updates)
  2408. imlib_updates_free(updates);
  2409. /* loop again waiting for events */
  2410. }
  2411. return 0;
  2412. }