1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <ctype.h>
5 #include <unistd.h>
6 #include <getopt.h>
7 #include <gtk/gtk.h>
8 #include "jsmn.h"
9 #ifdef LAYERSHELL
10 #include <gtk-layer-shell/gtk-layer-shell.h>
11 #endif
12 
13 #ifdef LAYERSHELL
14 static const int exclusive_level = -1;
15 #endif
16 static const int default_size = 100;
17 static const char *version = "1.1.1";
18 
19 typedef struct
20 {
21     char *label;
22     char *action;
23     char *text;
24     float yalign;
25     float xalign;
26     guint bind;
27     gboolean circular;
28 } button;
29 
30 static char *command = NULL;
31 static char *layout_path = NULL;
32 static char *css_path = NULL;
33 static button *buttons = NULL;
34 static GtkWidget *gtk_window = NULL;
35 
36 static int num_buttons = 0;
37 
38 static int buttons_per_row = 3;
39 static int margin[] = {230, 230, 230, 230};
40 static int space[] = {0, 0};
41 static gboolean protocol = TRUE;
42 
process_args(int argc,char * argv[])43 static gboolean process_args(int argc, char *argv[])
44 {
45     static struct option long_options[] =
46     {
47         {"help", no_argument, NULL, 'h'},
48         {"layout", required_argument, NULL, 'l'},
49         {"version", no_argument, NULL, 'v'},
50         {"css", required_argument, NULL, 'C'},
51         {"margin", required_argument, NULL, 'm'},
52         {"margin-top", required_argument, NULL, 'T'},
53         {"margin-bottom", required_argument, NULL, 'B'},
54         {"margin-left", required_argument, NULL, 'L'},
55         {"margin-right", required_argument, NULL, 'R'},
56         {"buttons-per-row", required_argument, NULL, 'b'},
57         {"column-spacing", required_argument, NULL, 'c'},
58         {"row-spacing", required_argument, NULL, 'r'},
59         {"protocol", required_argument, NULL, 'p'},
60         {0, 0, 0, 0}
61     };
62 
63     const char *help =
64         "Usage: wlogout [options] [command]\n"
65         "\n"
66         "   -h, --help                      Show help message and stop\n"
67         "   -l, --layout <layout>           Specify a layout file\n"
68         "   -v, --version                   Show version number and stop\n"
69         "   -C, --css <css>                 Specify a css file\n"
70         "   -b, --buttons-per-row <num>     Set the number of buttons per row\n"
71         "   -c  --column-spacing <space>    Set space between buttons columns\n"
72         "   -r  --row-spacing <space>       Set space between buttons rows\n"
73         "   -m, --margin <padding>          Set margin around buttons\n"
74         "   -L, --margin-left <padding>     Set margin for left of buttons\n"
75         "   -R, --margin-right <padding>    Set margin for right of buttons\n"
76         "   -T, --margin-top <padding>      Set margin for top of buttons\n"
77         "   -B, --margin-bottom <padding>   Set margin for bottom of buttons\n"
78         "   -p, --protocol <protocol>       Use layer-shell or xdg protocol\n";
79 
80     int c;
81     while (TRUE)
82     {
83         int option_index = 0;
84         c = getopt_long(argc, argv, "hl:vc:m:b:T:R:L:B:r:c:p:",
85                 long_options, &option_index);
86         if (c == -1)
87         {
88             break;
89         }
90         switch (c)
91         {
92             case 'm':
93                 margin[0] = atoi(optarg);
94                 margin[1] = atoi(optarg);
95                 margin[2] = atoi(optarg);
96                 margin[3] = atoi(optarg);
97                 break;
98             case 'L':
99                 margin[2] = atoi(optarg);
100                 break;
101             case 'T':
102                 margin[0] = atoi(optarg);
103                 break;
104             case 'B':
105                 margin[1] = atoi(optarg);
106                 break;
107             case 'R':
108                 margin[3] = atoi(optarg);
109                 break;
110             case 'c':
111                 space[1] = atoi(optarg);
112                 break;
113             case 'r':
114                 space[0] = atoi(optarg);
115                 break;
116             case 'h':
117                 g_print("%s\n", help);
118                 return TRUE;
119             case 'l':
120                 layout_path = g_strdup(optarg);
121                 break;
122             case 'v':
123                 g_print("%s\n", version);
124                 return TRUE;
125             case 'C':
126                 css_path = g_strdup(optarg);
127                 break;
128             case 'b':
129                 buttons_per_row = atoi(optarg);
130                 break;
131             case 'p':
132                 if (strcmp("layer-shell", optarg) == 0) {
133                     protocol = FALSE;
134                 }
135                 else if (strcmp("xdg", optarg) == 0) {
136                     protocol = TRUE;
137                 }
138                 else {
139                     g_print("%s is an invalid protocol\n", optarg);
140                     return TRUE;
141                 }
142                 break;
143         }
144     }
145     return FALSE;
146 }
147 
get_layout_path()148 static gboolean get_layout_path()
149 {
150     char *buf = malloc(default_size * sizeof(char));
151     if (!buf)
152     {
153         fprintf(stderr, "Failed to allocate memory\n");
154     }
155 
156     char *tmp = getenv("XDG_CONFIG_HOME");
157     char *config_path = g_strdup(tmp);
158     if (!config_path)
159     {
160         config_path = getenv("HOME");
161         int n = snprintf(buf, default_size, "%s/.config", config_path);
162         if (n != 0)
163         {
164             free(buf);
165             buf = malloc((default_size * sizeof(char)) + (sizeof(char) * n));
166             snprintf(buf, (default_size * sizeof(char)) + (sizeof(char) * n),
167                     "%s/.config", config_path);
168         }
169         config_path = g_strdup(buf);
170     }
171 
172     int n = snprintf(buf, default_size, "%s/wlogout/layout", config_path);
173     if (n != 0)
174     {
175         free(buf);
176         buf = malloc((default_size * sizeof(char)) + (sizeof(char) * n));
177         snprintf(buf, (default_size * sizeof(char)) + (sizeof(char) * n),
178                 "%s/wlogout/layout", config_path);
179     }
180     free(config_path);
181 
182     if (layout_path)
183     {
184         free(buf);
185         return FALSE;
186     }
187     else if (access(buf, F_OK) != -1)
188     {
189         layout_path = g_strdup(buf);
190         free(buf);
191         return FALSE;
192     }
193     else if (access("/etc/wlogout/layout", F_OK) != -1)
194     {
195         layout_path = "/etc/wlogout/layout";
196         free(buf);
197         return FALSE;
198     }
199     else if (access("/usr/local/etc/wlogout/layout", F_OK) != -1)
200     {
201         layout_path = "/usr/local/etc/wlogout/layout";
202         free(buf);
203         return FALSE;
204     }
205     else
206     {
207         free(buf);
208         return TRUE;
209     }
210 }
211 
get_css_path()212 static gboolean get_css_path()
213 {
214     char *buf = malloc(default_size * sizeof(char));
215     if (!buf)
216     {
217         fprintf(stderr, "Failed to allocate memory\n");
218     }
219 
220     char *tmp = getenv("XDG_CONFIG_HOME");
221     char *config_path = g_strdup(tmp);
222     if (!config_path)
223     {
224         config_path = getenv("HOME");
225         int n = snprintf(buf, default_size, "%s/.config", config_path);
226         if (n != 0)
227         {
228             free(buf);
229             buf = malloc((default_size * sizeof(char)) + (sizeof(char) * n));
230             snprintf(buf, (default_size * sizeof(char)) + (sizeof(char) * n),
231                     "%s/.config", config_path);
232         }
233         config_path = g_strdup(buf);
234     }
235 
236     int n = snprintf(buf, default_size, "%s/wlogout/style.css", config_path);
237     if (n != 0)
238     {
239         free(buf);
240         buf = malloc((default_size * sizeof(char)) + (sizeof(char) * n));
241         snprintf(buf, (default_size * sizeof(char)) + (sizeof(char) * n),
242                 "%s/wlogout/style.css", config_path);
243     }
244     free(config_path);
245 
246     if (css_path)
247     {
248         free(buf);
249         return FALSE;
250     }
251     else if (access(buf, F_OK) != -1)
252     {
253         css_path = g_strdup(buf);
254         free(buf);
255         return FALSE;
256     }
257     else if (access("/etc/wlogout/style.css", F_OK) != -1)
258     {
259         css_path = "/etc/wlogout/style.css";
260         free(buf);
261         return FALSE;
262     }
263     else if (access("/usr/local/etc/wlogout/style.css", F_OK) != -1)
264     {
265         css_path = "/usr/local/etc/wlogout/style.css";
266         free(buf);
267         return FALSE;
268     }
269     else
270     {
271         return TRUE;
272     }
273 }
274 
get_window()275 static GtkWidget *get_window()
276 {
277     GtkWindow *window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
278 
279     if (protocol) {
280         gtk_window_fullscreen(GTK_WINDOW (window));
281     }
282     else {
283         #ifdef LAYERSHELL
284         gtk_layer_init_for_window (window);
285         gtk_layer_set_layer (window, GTK_LAYER_SHELL_LAYER_OVERLAY);
286         gtk_layer_set_exclusive_zone (window, exclusive_level);
287         gtk_layer_set_keyboard_interactivity (window, TRUE);
288 
289         for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++) {
290             gtk_layer_set_anchor (window, i, TRUE);
291         }
292         #else
293         printf("wlogout was not compiled with layer shell support\n");
294         gtk_window_fullscreen(GTK_WINDOW (window));
295         #endif
296     }
297 
298     return GTK_WIDGET(window);
299 }
300 
get_substring(char * s,int start,int end,char * buf)301 static char *get_substring(char *s, int start, int end, char *buf)
302 {
303     memcpy(s, &buf[start], (end - start));
304     s[end - start] = '\0';
305     return s;
306 }
307 
get_buttons(FILE * json)308 static gboolean get_buttons(FILE *json)
309 {
310     fseek(json, 0L, SEEK_END);
311     int length = ftell(json);
312     rewind(json);
313 
314     char *buffer = malloc(length);
315     if (!buffer)
316     {
317         g_warning("Failed to allocate memory\n");
318         return TRUE;
319     }
320     fread(buffer, 1, length, json);
321 
322     jsmn_parser p;
323     jsmntok_t *tok = malloc(default_size * sizeof(jsmntok_t));
324     if (!tok)
325     {
326         g_warning("Failed to allocate memory\n");
327         return TRUE;
328     }
329     jsmn_init(&p);
330     int numtok, i = 1;
331     do
332     {
333         numtok = jsmn_parse(&p, buffer, length, tok, default_size * i);
334         if (numtok == JSMN_ERROR_NOMEM)
335         {
336             i++;
337             jsmntok_t *tmp = realloc(tok,
338                     ((default_size * i) * sizeof(jsmntok_t)));
339             if (!tmp)
340             {
341                 free(tok);
342                 return FALSE;
343             }
344             else if (tmp != tok)
345             {
346                 tok = tmp;
347             }
348         }
349         else
350         {
351             break;
352         }
353     } while (TRUE);
354 
355     if (numtok < 0)
356     {
357         free(tok);
358         g_warning("Failed to parse JSON data\n");
359         return TRUE;
360     }
361 
362     for (int i = 0; i < numtok; i++)
363     {
364         if (tok[i].type == JSMN_OBJECT)
365         {
366             num_buttons++;
367             buttons[num_buttons - 1].yalign = 0.9;
368             buttons[num_buttons - 1].xalign = 0.5;
369             buttons[num_buttons - 1].circular = FALSE;
370         }
371         else if (tok[i].type == JSMN_STRING)
372         {
373             int length = tok[i].end - tok[i].start;
374             char tmp[length + 1];
375             get_substring(tmp, tok[i].start, tok[i].end, buffer);
376             i++;
377             length = tok[i].end - tok[i].start;
378 
379             if (strcmp(tmp, "label") == 0)
380             {
381                 char buf[length + 1];
382                 get_substring(buf, tok[i].start, tok[i].end, buffer);
383                 buttons[num_buttons - 1].label = malloc(sizeof(char)
384                         * length + 1);
385                 strcpy(buttons[num_buttons - 1].label, buf);
386             }
387             else if (strcmp(tmp, "action") == 0)
388             {
389                 char buf[length + 1];
390                 get_substring(buf, tok[i].start, tok[i].end, buffer);
391                 buttons[num_buttons - 1].action = malloc(sizeof(char)
392                         * length + 1);
393                 strcpy(buttons[num_buttons - 1].action, buf);
394             }
395             else if (strcmp(tmp, "text") == 0)
396             {
397                 char buf[length + 1];
398                 get_substring(buf, tok[i].start, tok[i].end, buffer);
399                 buttons[num_buttons - 1].text = malloc(sizeof(char)
400                         * length + 1);
401                 strcpy(buttons[num_buttons - 1].text, buf);
402             }
403             else if (strcmp(tmp, "keybind") == 0)
404             {
405                 if (length != 1)
406                 {
407                     fprintf(stderr, "Invalid keybind\n");
408                 }
409                 else
410                 {
411                     buttons[num_buttons - 1].bind = buffer[tok[i].start];
412                 }
413             }
414             else if (strcmp(tmp, "height") == 0)
415             {
416                 if (tok[i].type != JSMN_PRIMITIVE || !isdigit(buffer[tok[i].start]))
417                 {
418                     fprintf(stderr, "Invalid height\n");
419                 }
420                 else
421                 {
422                     buttons[num_buttons - 1].yalign = buffer[tok[i].start];
423                 }
424             }
425             else if (strcmp(tmp, "width") == 0)
426             {
427                 if (tok[i].type != JSMN_PRIMITIVE || !isdigit(buffer[tok[i].start]))
428                 {
429                     fprintf(stderr, "Invalid width\n");
430                 }
431                 else
432                 {
433                     buttons[num_buttons - 1].xalign = buffer[tok[i].start];
434                 }
435             }
436             else if (strcmp(tmp, "circular") == 0)
437             {
438                 if (tok[i].type != JSMN_PRIMITIVE)
439                 {
440                     fprintf(stderr, "Invalid boolean\n");
441                 }
442                 else
443                 {
444                     if (buffer[tok[i].start] == 't') {
445                         buttons[num_buttons - 1].circular = TRUE;
446                     }
447                     else {
448                         buttons[num_buttons - 1].circular = FALSE;
449                     }
450                 }
451             }
452             else
453             {
454                 g_warning("Invalid key %s\n", tmp);
455                 return TRUE;
456             }
457         }
458         else
459         {
460             g_warning("Invalid JSON Data\n");
461             return TRUE;
462         }
463     }
464 
465     free(tok);
466     free(buffer);
467     fclose(json);
468 
469     return FALSE;
470 }
471 
execute(GtkWidget * widget,char * action)472 static void execute(GtkWidget *widget, char *action)
473 {
474     command = malloc(strlen(action) * sizeof(char) + 1);
475     strcpy(command, action);
476     gtk_widget_destroy(gtk_window);
477     gtk_main_quit();
478 }
479 
check_key(GtkWidget * widget,GdkEventKey * event,gpointer data)480 static gboolean check_key(GtkWidget *widget, GdkEventKey *event, gpointer data)
481 {
482     if (event->keyval == GDK_KEY_Escape) {
483         gtk_main_quit();
484         return TRUE;
485     }
486     for (int i = 0; i < num_buttons; i++)
487     {
488         if (buttons[i].bind == event->keyval)
489         {
490             execute(NULL, buttons[i].action);
491             return TRUE;
492         }
493     }
494     return FALSE;
495 }
496 
load_buttons(GtkWindow * window)497 static void load_buttons(GtkWindow *window)
498 {
499     GtkWidget *grid = gtk_grid_new();
500     gtk_container_add(GTK_CONTAINER (window), grid);
501 
502     gtk_grid_set_row_spacing(GTK_GRID(grid), space[0]);
503     gtk_grid_set_column_spacing(GTK_GRID(grid), space[1]);
504 
505     gtk_widget_set_margin_top(grid, margin[0]);
506     gtk_widget_set_margin_bottom(grid, margin[1]);
507     gtk_widget_set_margin_start(grid, margin[2]);
508     gtk_widget_set_margin_end(grid, margin[3]);
509 
510     int num_col = 0;
511     if ((num_buttons % buttons_per_row) == 0)
512     {
513         num_col = (num_buttons / buttons_per_row);
514     }
515     else
516     {
517         num_col = (num_buttons / buttons_per_row) + 1;
518     }
519 
520     GtkWidget *but[buttons_per_row][num_col];
521 
522     int count = 0;
523     for (int i = 0; i < buttons_per_row; i++)
524     {
525         for (int j = 0; j < num_col; j++)
526         {
527             but[i][j] = gtk_button_new_with_label(buttons[count].text);
528             gtk_widget_set_name(but[i][j], buttons[count].label);
529             gtk_label_set_yalign(GTK_LABEL(
530                         gtk_bin_get_child(GTK_BIN(but[i][j]))), buttons[count].yalign);
531             gtk_label_set_xalign(GTK_LABEL(
532                         gtk_bin_get_child(GTK_BIN(but[i][j]))), buttons[count].xalign);
533             if (buttons[count].circular) {
534                 gtk_style_context_add_class(
535                     gtk_widget_get_style_context(but[i][j]), "circular");
536             }
537             g_signal_connect(but[i][j], "clicked", G_CALLBACK(execute),
538                         buttons[count].action);
539             gtk_widget_set_hexpand(but[i][j], TRUE);
540             gtk_widget_set_vexpand(but[i][j], TRUE);
541             gtk_grid_attach(GTK_GRID(grid), but[i][j], i, j, 1, 1);
542             count++;
543         }
544     }
545 }
546 
load_css()547 static void load_css()
548 {
549     GtkCssProvider *css = gtk_css_provider_new();
550     GError *error = NULL;
551     gtk_css_provider_load_from_path(css, css_path, &error);
552     if (error)
553     {
554         g_warning("%s", error->message);
555         g_clear_error(&error);
556     }
557     gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
558             GTK_STYLE_PROVIDER(css), GTK_STYLE_PROVIDER_PRIORITY_USER);
559 }
560 
main(int argc,char * argv[])561 int main (int argc, char *argv[])
562 {
563     buttons = malloc(sizeof(button) * default_size);
564 
565     g_set_prgname("wlogout");
566     gtk_init(&argc, &argv);
567     if (process_args(argc, argv))
568     {
569         return 0;
570     }
571 
572     if (get_layout_path())
573     {
574         g_warning("Failed to find a layout\n");
575         return 1;
576     }
577 
578     if (get_css_path())
579     {
580         g_warning("Failed to find css file\n");
581     }
582 
583     FILE *inptr = fopen(layout_path, "r");
584     if (!inptr)
585     {
586         g_warning("Failed to open %s\n", layout_path);
587         return 2;
588     }
589     if (get_buttons(inptr))
590     {
591         fclose(inptr);
592         return 3;
593     }
594 
595     gtk_window = get_window();
596     g_signal_connect(gtk_window, "key_press_event", G_CALLBACK(check_key), NULL);
597 
598     load_buttons(GTK_WINDOW(gtk_window));
599     load_css();
600     gtk_widget_show_all(gtk_window);
601 
602     gtk_main();
603     system(command);
604 
605     for (int i = 0; i < num_buttons; i++)
606     {
607         free(buttons[i].label);
608         free(buttons[i].action);
609         free(buttons[i].text);
610     }
611     free(buttons);
612     free(command);
613 }
614