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