1 /*
2 * Copyright (C) 2019-2020 Scoopta
3 * This file is part of Wofi
4 * Wofi is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 Wofi is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with Wofi. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <stdio.h>
19 #include <stddef.h>
20 #include <stdlib.h>
21 #include <stdint.h>
22 #include <getopt.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <signal.h>
26
27 #include <map.h>
28 #include <wofi.h>
29 #include <utils.h>
30 #include <config.h>
31
32 #include <wayland-client.h>
33
34 #include <gtk/gtk.h>
35
36 static const char* nyan_colors[] = {"#FF0000", "#FFA500", "#FFFF00", "#00FF00", "#0000FF", "#FF00FF"};
37 static size_t nyan_color_l = sizeof(nyan_colors) / sizeof(char*);
38
39 static char* CONFIG_LOCATION;
40 static char* COLORS_LOCATION;
41 static struct map* config;
42 static char* config_path;
43 static char* stylesheet;
44 static char* color_path;
45 static uint8_t nyan_shift = 0;
46
47 struct option_node {
48 char* option;
49 struct wl_list link;
50 };
51
get_exec_name(char * path)52 static char* get_exec_name(char* path) {
53 char* slash = strrchr(path, '/');
54 uint64_t offset;
55 if(slash == NULL) {
56 offset = 0;
57 } else {
58 offset = (slash - path) + 1;
59 }
60 return path + offset;
61 }
62
print_usage(char ** argv)63 static void print_usage(char** argv) {
64 printf("%s [options]\n", get_exec_name(argv[0]));
65 printf("Options:\n");
66 printf("--help\t\t-h\tDisplays this help message\n");
67 printf("--fork\t\t-f\tForks the menu so you can close the terminal\n");
68 printf("--conf\t\t-c\tSelects a config file to use\n");
69 printf("--style\t\t-s\tSelects a stylesheet to use\n");
70 printf("--color\t\t-C\tSelects a colors file to use\n");
71 printf("--dmenu\t\t-d\tRuns in dmenu mode\n");
72 printf("--show\t\t-S\tSpecifies the mode to run in\n");
73 printf("--width\t\t-W\tSpecifies the surface width\n");
74 printf("--height\t-H\tSpecifies the surface height\n");
75 printf("--prompt\t-p\tPrompt to display\n");
76 printf("--xoffset\t-x\tThe x offset\n");
77 printf("--yoffset\t-y\tThe y offset\n");
78 printf("--normal-window\t-n\tRender to a normal window\n");
79 printf("--allow-images\t-I\tAllows images to be rendered\n");
80 printf("--allow-markup\t-m\tAllows pango markup\n");
81 printf("--cache-file\t-k\tSets the cache file to use\n");
82 printf("--term\t\t-t\tSpecifies the terminal to use when running in a term\n");
83 printf("--password\t-P\tRuns in password mode\n");
84 printf("--exec-search\t-e\tMakes enter always use the search contents not the first result\n");
85 printf("--hide-scroll\t-b\tHides the scroll bars\n");
86 printf("--matching\t-M\tSets the matching method, default is contains\n");
87 printf("--insensitive\t-i\tAllows case insensitive searching\n");
88 printf("--parse-search\t-q\tParses the search text removing image escapes and pango\n");
89 printf("--version\t-v\tPrints the version and then exits\n");
90 printf("--location\t-l\tSets the location\n");
91 printf("--no-actions\t-a\tDisables multiple actions for modes that support it\n");
92 printf("--define\t-D\tSets a config option\n");
93 printf("--lines\t\t-L\tSets the height in number of lines\n");
94 printf("--columns\t-w\tSets the number of columns to display\n");
95 printf("--sort-order\t-O\tSets the sort order\n");
96 printf("--gtk-dark\t-G\tUses the dark variant of the current GTK theme\n");
97 printf("--search\t-Q\tSearch for something immediately on open\n");
98 printf("--monitor\t-o\tSets the monitor to open on\n");
99 exit(0);
100 }
101
wofi_load_css(bool nyan)102 void wofi_load_css(bool nyan) {
103 if(access(stylesheet, R_OK) == 0) {
104 FILE* file = fopen(stylesheet, "r");
105 fseek(file, 0, SEEK_END);
106 ssize_t size = ftell(file);
107 fseek(file, 0, SEEK_SET);
108 char* data = malloc(size + 1);
109 fread(data, 1, size, file);
110 fclose(file);
111
112 data[size] = 0;
113 struct wl_list lines;
114 struct node {
115 char* line;
116 struct wl_list link;
117 };
118 wl_list_init(&lines);
119 if(nyan) {
120 for(ssize_t count = nyan_shift; count < 100 + nyan_shift; ++count) {
121 size_t i = count % nyan_color_l;
122 struct node* entry = malloc(sizeof(struct node));
123 entry->line = strdup(nyan_colors[i]);
124 wl_list_insert(&lines, &entry->link);
125 }
126 nyan_shift = (nyan_shift + 1) % nyan_color_l;
127 } else {
128 if(access(color_path, R_OK) == 0) {
129 file = fopen(color_path, "r");
130 char* line = NULL;
131 size_t line_size = 0;
132 ssize_t line_l = 0;
133 while((line_l = getline(&line, &line_size, file)) != -1) {
134 struct node* entry = malloc(sizeof(struct node));
135 line[line_l - 1] = 0;
136 entry->line = malloc(line_l + 1);
137 strcpy(entry->line, line);
138 wl_list_insert(&lines, &entry->link);
139 }
140 fclose(file);
141 free(line);
142 }
143 }
144
145 ssize_t count = wl_list_length(&lines) - 1;
146 if(count > 99) {
147 fprintf(stderr, "Woah there that's a lot of colors. Try having no more than 100, thanks\n");
148 exit(1);
149 }
150 struct node* node;
151 wl_list_for_each(node, &lines, link) {
152 //Do --wofi-color replace
153 const char* color = node->line;
154 const char* wofi_color = "--wofi-color";
155 char count_str[3];
156 snprintf(count_str, 3, "%zu", count--);
157 char* needle = utils_concat(2, wofi_color, count_str);
158 size_t color_len = strlen(color);
159 size_t needle_len = strlen(needle);
160 if(color_len > needle_len) {
161 free(needle);
162 fprintf(stderr, "What color format is this, try #FFFFFF, kthxbi\n");
163 continue;
164 }
165 char* replace = strstr(data, needle);
166 while(replace != NULL) {
167 memcpy(replace, color, color_len);
168 memset(replace + color_len, ' ', needle_len - color_len);
169 replace = strstr(data, needle);
170 }
171 free(needle);
172
173
174 //Do --wofi-rgb-color replace
175 if(color_len < 7) {
176 fprintf(stderr, "What color format is this, try #FFFFFF, kthxbi\n");
177 continue;
178 }
179 const char* wofi_rgb_color = "--wofi-rgb-color";
180 needle = utils_concat(2, wofi_rgb_color, count_str);
181 needle_len = strlen(needle);
182 replace = strstr(data, needle);
183 while(replace != NULL) {
184 char r[3];
185 char g[3];
186 char b[3];
187 memcpy(r, color + 1, 2);
188 memcpy(g, color + 3, 2);
189 memcpy(b, color + 5, 2);
190 r[2] = 0;
191 g[2] = 0;
192 b[2] = 0;
193 char rgb[14];
194 snprintf(rgb, 14, "%ld, %ld, %ld", strtol(r, NULL, 16), strtol(g, NULL, 16), strtol(b, NULL, 16));
195 size_t rgb_len = strlen(rgb);
196 memcpy(replace, rgb, rgb_len);
197 memset(replace + rgb_len, ' ', needle_len - rgb_len);
198 replace = strstr(data, needle);
199 }
200 free(needle);
201 }
202 GtkCssProvider* css = gtk_css_provider_new();
203 gtk_css_provider_load_from_data(css, data, strlen(data), NULL);
204 free(data);
205 struct node* tmp;
206 wl_list_for_each_safe(node, tmp, &lines, link) {
207 free(node->line);
208 wl_list_remove(&node->link);
209 free(node);
210 }
211 gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(css), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
212 }
213 }
214
sig(int32_t signum)215 static void sig(int32_t signum) {
216 switch(signum) {
217 case SIGTERM:
218 exit(1);
219 break;
220 }
221 }
222
main(int argc,char ** argv)223 int main(int argc, char** argv) {
224
225 const struct option opts[] = {
226 {
227 .name = "help",
228 .has_arg = no_argument,
229 .flag = NULL,
230 .val = 'h'
231 },
232 {
233 .name = "fork",
234 .has_arg = no_argument,
235 .flag = NULL,
236 .val = 'f'
237 },
238 {
239 .name = "conf",
240 .has_arg = required_argument,
241 .flag = NULL,
242 .val = 'c'
243 },
244 {
245 .name = "style",
246 .has_arg = required_argument,
247 .flag = NULL,
248 .val = 's'
249 },
250 {
251 .name = "color",
252 .has_arg = required_argument,
253 .flag = NULL,
254 .val = 'C'
255 },
256 {
257 .name = "dmenu",
258 .has_arg = no_argument,
259 .flag = NULL,
260 .val = 'd'
261 },
262 {
263 .name = "show",
264 .has_arg = required_argument,
265 .flag = NULL,
266 .val = 'S'
267 },
268 {
269 .name = "width",
270 .has_arg = required_argument,
271 .flag = NULL,
272 .val = 'W'
273 },
274 {
275 .name = "height",
276 .has_arg = required_argument,
277 .flag = NULL,
278 .val = 'H'
279 },
280 {
281 .name = "prompt",
282 .has_arg = required_argument,
283 .flag = NULL,
284 .val = 'p'
285 },
286 {
287 .name = "xoffset",
288 .has_arg = required_argument,
289 .flag = NULL,
290 .val = 'x'
291 },
292 {
293 .name = "yoffset",
294 .has_arg = required_argument,
295 .flag = NULL,
296 .val = 'y'
297 },
298 {
299 .name = "normal-window",
300 .has_arg = no_argument,
301 .flag = NULL,
302 .val = 'n'
303 },
304 {
305 .name = "allow-images",
306 .has_arg = no_argument,
307 .flag = NULL,
308 .val = 'I'
309 },
310 {
311 .name = "allow-markup",
312 .has_arg = no_argument,
313 .flag = NULL,
314 .val = 'm'
315 },
316 {
317 .name = "cache-file",
318 .has_arg = required_argument,
319 .flag = NULL,
320 .val = 'k'
321 },
322 {
323 .name = "term",
324 .has_arg = required_argument,
325 .flag = NULL,
326 .val = 't'
327 },
328 {
329 .name = "password",
330 .has_arg = optional_argument,
331 .flag = NULL,
332 .val = 'P'
333 },
334 {
335 .name = "exec-search",
336 .has_arg = no_argument,
337 .flag = NULL,
338 .val = 'e'
339 },
340 {
341 .name = "hide-scroll",
342 .has_arg = no_argument,
343 .flag = NULL,
344 .val = 'b'
345 },
346 {
347 .name = "matching",
348 .has_arg = required_argument,
349 .flag = NULL,
350 .val = 'M'
351 },
352 {
353 .name = "insensitive",
354 .has_arg = no_argument,
355 .flag = NULL,
356 .val = 'i'
357 },
358 {
359 .name = "parse-search",
360 .has_arg = no_argument,
361 .flag = NULL,
362 .val = 'q'
363 },
364 {
365 .name = "version",
366 .has_arg = no_argument,
367 .flag = NULL,
368 .val = 'v'
369 },
370 {
371 .name = "location",
372 .has_arg = required_argument,
373 .flag = NULL,
374 .val = 'l'
375 },
376 {
377 .name = "no-actions",
378 .has_arg = no_argument,
379 .flag = NULL,
380 .val = 'a'
381 },
382 {
383 .name = "define",
384 .has_arg = required_argument,
385 .flag = NULL,
386 .val = 'D'
387 },
388 {
389 .name = "lines",
390 .has_arg = required_argument,
391 .flag = NULL,
392 .val = 'L'
393 },
394 {
395 .name = "columns",
396 .has_arg = required_argument,
397 .flag = NULL,
398 .val = 'w'
399 },
400 {
401 .name = "sort-order",
402 .has_arg = required_argument,
403 .flag = NULL,
404 .val = 'O'
405 },
406 {
407 .name = "gtk-dark",
408 .has_arg = no_argument,
409 .flag = NULL,
410 .val = 'G'
411 },
412 {
413 .name = "search",
414 .has_arg = required_argument,
415 .flag = NULL,
416 .val = 'Q'
417 },
418 {
419 .name = "monitor",
420 .has_arg = required_argument,
421 .flag = NULL,
422 .val = 'o'
423 },
424 {
425 .name = NULL,
426 .has_arg = 0,
427 .flag = NULL,
428 .val = 0
429 }
430 };
431
432 const char* config_str = NULL;
433 char* style_str = NULL;
434 char* color_str = NULL;
435 char* mode = NULL;
436 char* prompt = NULL;
437 char* width = NULL;
438 char* height = NULL;
439 char* x = NULL;
440 char* y = NULL;
441 char* normal_window = NULL;
442 char* allow_images = NULL;
443 char* allow_markup = NULL;
444 char* cache_file = NULL;
445 char* terminal = NULL;
446 char* password_char = "false";
447 char* exec_search = NULL;
448 char* hide_scroll = NULL;
449 char* matching = NULL;
450 char* insensitive = NULL;
451 char* parse_search = NULL;
452 char* location = NULL;
453 char* no_actions = NULL;
454 char* lines = NULL;
455 char* columns = NULL;
456 char* sort_order = NULL;
457 char* gtk_dark = NULL;
458 char* search = NULL;
459 char* monitor = NULL;
460
461 struct wl_list options;
462 wl_list_init(&options);
463 struct option_node* node;
464
465 int opt;
466 while((opt = getopt_long(argc, argv, "hfc:s:C:dS:W:H:p:x:y:nImk:t:P::ebM:iqvl:aD:L:w:O:GQ:o:", opts, NULL)) != -1) {
467 switch(opt) {
468 case 'h':
469 print_usage(argv);
470 break;
471 case 'f':
472 if(fork() > 0) {
473 exit(0);
474 }
475 fclose(stdout);
476 fclose(stderr);
477 fclose(stdin);
478 break;
479 case 'c':
480 config_str = optarg;
481 break;
482 case 's':
483 style_str = optarg;
484 break;
485 case 'C':
486 color_str = optarg;
487 break;
488 case 'd':
489 mode = "dmenu";
490 break;
491 case 'S':
492 mode = optarg;
493 break;
494 case 'W':
495 width = optarg;
496 break;
497 case 'H':
498 height = optarg;
499 break;
500 case 'p':
501 prompt = optarg;
502 break;
503 case 'x':
504 x = optarg;
505 break;
506 case 'y':
507 y = optarg;
508 break;
509 case 'n':
510 normal_window = "true";
511 break;
512 case 'I':
513 allow_images = "true";
514 break;
515 case 'm':
516 allow_markup = "true";
517 break;
518 case 'k':
519 cache_file = optarg;
520 break;
521 case 't':
522 terminal = optarg;
523 break;
524 case 'P':
525 password_char = optarg;
526 break;
527 case 'e':
528 exec_search = "true";
529 break;
530 case 'b':
531 hide_scroll = "true";
532 break;
533 case 'M':
534 matching = optarg;
535 break;
536 case 'i':
537 insensitive = "true";
538 break;
539 case 'q':
540 parse_search = "true";
541 break;
542 case 'v':
543 printf(VERSION"\n");
544 exit(0);
545 break;
546 case 'l':
547 location = optarg;
548 break;
549 case 'a':
550 no_actions = "true";
551 break;
552 case 'D':
553 node = malloc(sizeof(struct option_node));
554 node->option = optarg;
555 wl_list_insert(&options, &node->link);
556 break;
557 case 'L':
558 lines = optarg;
559 break;
560 case 'w':
561 columns = optarg;
562 break;
563 case 'O':
564 sort_order = optarg;
565 break;
566 case 'G':
567 gtk_dark = "true";
568 break;
569 case 'Q':
570 search = optarg;
571 break;
572 case 'o':
573 monitor = optarg;
574 break;
575 }
576 }
577
578 const char* home_dir = getenv("HOME");
579 const char* xdg_conf = getenv("XDG_CONFIG_HOME");
580 if(xdg_conf == NULL) {
581 CONFIG_LOCATION = utils_concat(2, home_dir, "/.config/wofi");
582 } else {
583 CONFIG_LOCATION = utils_concat(2, xdg_conf, "/wofi");
584 }
585
586 const char* xdg_cache = getenv("XDG_CACHE_HOME");
587 if(xdg_cache == NULL) {
588 COLORS_LOCATION = utils_concat(2, home_dir, "/.cache/wal/colors");
589 } else {
590 COLORS_LOCATION = utils_concat(2, xdg_cache, "/wal/colors");
591 }
592
593 config = map_init();
594
595 //Check if --conf was specified
596 if(config_str == NULL) {
597 const char* config_f = "/config";
598 config_path = utils_concat(2, CONFIG_LOCATION, config_f);
599 } else {
600 config_path = strdup(config_str);
601 }
602 if(access(config_path, R_OK) == 0) {
603 config_load(config, config_path);
604 }
605 free(config_path);
606
607 if(style_str == NULL) {
608 style_str = map_get(config, "style");
609 }
610
611 //Check if --style was specified
612 if(style_str == NULL) {
613 style_str = map_get(config, "stylesheet");
614 if(style_str == NULL) {
615 const char* style_f = "/style.css";
616 stylesheet = utils_concat(2, CONFIG_LOCATION, style_f);
617 } else {
618 if(style_str[0] == '/') {
619 stylesheet = strdup(style_str);
620 } else {
621 stylesheet = utils_concat(3, CONFIG_LOCATION, "/", style_str);
622 }
623 }
624 } else {
625 stylesheet = strdup(style_str);
626 }
627
628 if(color_str == NULL) {
629 color_str = map_get(config, "color");
630 }
631
632 //Check if --color was specified
633 if(color_str == NULL) {
634 color_str = map_get(config, "colors");
635 if(color_str == NULL) {
636 color_path = strdup(COLORS_LOCATION);
637 } else {
638 if(color_str[0] == '/') {
639 color_path = strdup(color_str);
640 } else {
641 color_path = utils_concat(3, CONFIG_LOCATION, "/", color_str);
642 }
643 }
644 } else {
645 color_path = strdup(color_str);
646 }
647
648 //Check if --gtk-dark was specified
649 if(gtk_dark == NULL) {
650 gtk_dark = map_get(config, "gtk_dark");
651 }
652
653 free(COLORS_LOCATION);
654
655 struct option_node* tmp;
656 wl_list_for_each_safe(node, tmp, &options, link) {
657 config_put(config, node->option);
658 wl_list_remove(&node->link);
659 free(node);
660 }
661
662 if(map_get(config, "show") != NULL) {
663 map_put(config, "mode", map_get(config, "show"));
664 }
665
666 if(strcmp(get_exec_name(argv[0]), "dmenu") == 0) {
667 map_put(config, "mode", "dmenu");
668 cache_file = "/dev/null";
669 } else if(strcmp(get_exec_name(argv[0]), "wofi-askpass") == 0) {
670 map_put(config, "mode", "dmenu");
671 cache_file = "/dev/null";
672 password_char = "*";
673 prompt = "Password";
674 } else if(mode != NULL) {
675 map_put(config, "mode", mode);
676 } else if(map_get(config, "mode") == NULL) {
677 fprintf(stderr, "I need a mode, please give me a mode, that's what --show is for\n");
678 exit(1);
679 }
680
681 map_put(config, "config_dir", CONFIG_LOCATION);
682
683 if(width != NULL) {
684 map_put(config, "width", width);
685 }
686 if(height != NULL) {
687 map_put(config, "height", height);
688 }
689 if(prompt != NULL) {
690 map_put(config, "prompt", prompt);
691 }
692 if(map_get(config, "xoffset") != NULL) {
693 map_put(config, "x", map_get(config, "xoffset"));
694 }
695 if(x != NULL) {
696 map_put(config, "x", x);
697 }
698 if(map_get(config, "yoffset") != NULL) {
699 map_put(config, "y", map_get(config, "yoffset"));
700 }
701 if(y != NULL) {
702 map_put(config, "y", y);
703 }
704 if(normal_window != NULL) {
705 map_put(config, "normal_window", normal_window);
706 }
707 if(allow_images != NULL) {
708 map_put(config, "allow_images", allow_images);
709 }
710 if(allow_markup != NULL) {
711 map_put(config, "allow_markup", allow_markup);
712 }
713 if(cache_file != NULL) {
714 map_put(config, "cache_file", cache_file);
715 }
716 if(terminal != NULL) {
717 map_put(config, "term", terminal);
718 }
719 if(map_get(config, "password") != NULL) {
720 map_put(config, "password_char", map_get(config, "password"));
721 }
722 if(password_char == NULL || (password_char != NULL && strcmp(password_char, "false") != 0)) {
723 if(password_char == NULL) {
724 password_char = "*";
725 }
726 map_put(config, "password_char", password_char);
727 }
728 if(exec_search != NULL) {
729 map_put(config, "exec_search", exec_search);
730 }
731 if(hide_scroll != NULL) {
732 map_put(config, "hide_scroll", hide_scroll);
733 }
734 if(matching != NULL) {
735 map_put(config, "matching", matching);
736 }
737 if(insensitive != NULL) {
738 map_put(config, "insensitive", insensitive);
739 }
740 if(parse_search != NULL) {
741 map_put(config, "parse_search", parse_search);
742 }
743 if(location != NULL) {
744 map_put(config, "location", location);
745 }
746 if(no_actions != NULL) {
747 map_put(config, "no_actions", no_actions);
748 }
749 if(lines != NULL) {
750 map_put(config, "lines", lines);
751 }
752 if(columns != NULL) {
753 map_put(config, "columns", columns);
754 }
755 if(sort_order != NULL) {
756 map_put(config, "sort_order", sort_order);
757 }
758 if(search != NULL) {
759 map_put(config, "search", search);
760 }
761 if(monitor != NULL) {
762 map_put(config, "monitor", monitor);
763 }
764
765 struct sigaction sigact = {0};
766 sigact.sa_handler = sig;
767 sigaction(SIGTERM, &sigact, NULL);
768
769
770 gtk_init(&argc, &argv);
771
772 if(gtk_dark != NULL && strcmp(gtk_dark, "true") == 0) {
773 g_object_set(gtk_settings_get_default(),
774 "gtk-application-prefer-dark-theme", TRUE, NULL);
775 }
776 wofi_load_css(false);
777
778 wofi_init(config);
779 gtk_main();
780 return 0;
781 }
782