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 <wofi.h>
19
20 #include <ctype.h>
21 #include <dlfcn.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <libgen.h>
25 #include <unistd.h>
26 #include <stdint.h>
27
28 #include <sys/stat.h>
29
30 #include <utils.h>
31 #include <config.h>
32 #include <utils_g.h>
33 #include <property_box.h>
34 #include <widget_builder.h>
35
36 #include <xdg-output-unstable-v1-client-protocol.h>
37 #include <wlr-layer-shell-unstable-v1-client-protocol.h>
38
39 #include <pango/pango.h>
40 #include <gdk/gdkwayland.h>
41
42 static const char* terminals[] = {"kitty", "termite", "alacritty", "gnome-terminal", "weston-terminal"};
43
44 enum matching_mode {
45 MATCHING_MODE_CONTAINS,
46 MATCHING_MODE_FUZZY
47 };
48
49 enum location {
50 LOCATION_CENTER,
51 LOCATION_TOP_LEFT,
52 LOCATION_TOP,
53 LOCATION_TOP_RIGHT,
54 LOCATION_RIGHT,
55 LOCATION_BOTTOM_RIGHT,
56 LOCATION_BOTTOM,
57 LOCATION_BOTTOM_LEFT,
58 LOCATION_LEFT
59 };
60
61 enum sort_order {
62 SORT_ORDER_DEFAULT,
63 SORT_ORDER_ALPHABETICAL
64 };
65
66 static uint64_t width, height;
67 static char* x, *y;
68 static struct zwlr_layer_shell_v1* shell = NULL;
69 static GtkWidget* window, *outer_box, *scroll, *entry, *inner_box, *previous_selection = NULL;
70 static gchar* filter = NULL;
71 static char* mode = NULL;
72 static bool allow_images, allow_markup;
73 static uint64_t image_size;
74 static char* cache_file = NULL;
75 static char* config_dir;
76 static bool mod_shift;
77 static bool mod_ctrl;
78 static char* terminal;
79 static GtkOrientation outer_orientation;
80 static bool exec_search;
81 static struct map* modes;
82 static enum matching_mode matching;
83 static bool insensitive;
84 static bool parse_search;
85 static GtkAlign content_halign;
86 static struct map* config;
87 static enum location location;
88 static bool no_actions;
89 static uint64_t columns;
90 static bool user_moved = false;
91 static uint32_t widget_count = 0;
92 static enum sort_order sort_order;
93 static int64_t max_height = 0;
94 static uint32_t lines, max_lines;
95 static int8_t line_wrap;
96 static int64_t ix, iy;
97 static uint8_t konami_cycle;
98 static bool is_konami = false;
99 static GDBusProxy* dbus = NULL;
100 static GdkRectangle resolution = {0};
101 static bool resize_expander = false;
102 static uint32_t line_count = 0;
103 static bool dynamic_lines;
104 static struct wl_list mode_list;
105 static pthread_t mode_thread;
106
107 static struct map* keys;
108
109 static struct wl_display* wl = NULL;
110 static struct wl_surface* wl_surface;
111 static struct wl_list outputs;
112 static struct zxdg_output_manager_v1* output_manager;
113 static struct zwlr_layer_surface_v1* wlr_surface;
114
115 struct output_node {
116 char* name;
117 struct wl_output* output;
118 int32_t width, height, x, y;
119 struct wl_list link;
120 };
121
122 struct key_entry {
123 char* mod;
124 void (*action)(void);
125 };
126
nop()127 static void nop() {}
128
add_interface(void * data,struct wl_registry * registry,uint32_t name,const char * interface,uint32_t version)129 static void add_interface(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
130 (void) data;
131 if(strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
132 shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version);
133 } else if(strcmp(interface, wl_output_interface.name) == 0) {
134 struct output_node* node = malloc(sizeof(struct output_node));
135 node->output = wl_registry_bind(registry, name, &wl_output_interface, version);
136 wl_list_insert(&outputs, &node->link);
137 } else if(strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
138 output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, version);
139 }
140 }
141
config_surface(void * data,struct zwlr_layer_surface_v1 * surface,uint32_t serial,uint32_t width,uint32_t height)142 static void config_surface(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial, uint32_t width, uint32_t height) {
143 (void) data;
144 (void) width;
145 (void) height;
146 zwlr_layer_surface_v1_ack_configure(surface, serial);
147 }
148
setup_surface(struct zwlr_layer_surface_v1 * surface)149 static void setup_surface(struct zwlr_layer_surface_v1* surface) {
150 zwlr_layer_surface_v1_set_size(surface, width, height);
151 zwlr_layer_surface_v1_set_keyboard_interactivity(surface, true);
152
153 if(location > 8) {
154 location -= 9;
155 }
156
157 if(x != NULL || y != NULL) {
158 if(location == LOCATION_CENTER) {
159 location = LOCATION_TOP_LEFT;
160 }
161
162 zwlr_layer_surface_v1_set_margin(surface, iy, -ix, -iy, ix);
163 }
164
165 if(location > 0) {
166 enum zwlr_layer_surface_v1_anchor anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
167
168 switch(location) {
169 case LOCATION_CENTER:
170 break;
171 case LOCATION_TOP_LEFT:
172 anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
173 break;
174 case LOCATION_TOP:
175 anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
176 break;
177 case LOCATION_TOP_RIGHT:
178 anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
179 break;
180 case LOCATION_LEFT:
181 anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
182 break;
183 case LOCATION_RIGHT:
184 anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
185 break;
186 case LOCATION_BOTTOM_LEFT:
187 anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
188 break;
189 case LOCATION_BOTTOM:
190 anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
191 break;
192 case LOCATION_BOTTOM_RIGHT:
193 anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
194 break;
195 }
196 zwlr_layer_surface_v1_set_anchor(surface, anchor);
197 }
198 }
199
do_search(gpointer data)200 static gboolean do_search(gpointer data) {
201 (void) data;
202 const gchar* new_filter = gtk_entry_get_text(GTK_ENTRY(entry));
203 if(filter == NULL || strcmp(new_filter, filter) != 0) {
204 if(filter != NULL) {
205 free(filter);
206 }
207 filter = strdup(new_filter);
208 gtk_flow_box_invalidate_filter(GTK_FLOW_BOX(inner_box));
209 gtk_flow_box_invalidate_sort(GTK_FLOW_BOX(inner_box));
210 GtkFlowBoxChild* child = gtk_flow_box_get_child_at_index(GTK_FLOW_BOX(inner_box), 0);
211 if(child != NULL) {
212 gtk_flow_box_select_child(GTK_FLOW_BOX(inner_box), child);
213 }
214 }
215 return G_SOURCE_CONTINUE;
216 }
217
get_img_data(char * original,char * str,struct map * mode_map,bool first,char ** mode,char ** data)218 static void get_img_data(char* original, char* str, struct map* mode_map, bool first, char** mode, char** data) {
219 char* colon = strchr(str, ':');
220 if(colon == NULL) {
221 if(first) {
222 *mode = "text";
223 *data = str;
224 return;
225 } else {
226 *mode = NULL;
227 *data = NULL;
228 return;
229 }
230 }
231
232 *colon = 0;
233
234 if(map_contains(mode_map, str)) {
235 if(original != str) {
236 *(str - 1) = 0;
237 }
238 *mode = str;
239 *data = colon + 1;
240 } else if(first) {
241 *colon = ':';
242 *mode = "text";
243 *data = str;
244 } else {
245 *colon = ':';
246 get_img_data(original, colon + 1, mode_map, first, mode, data);
247 }
248 }
249
250 //This is hideous, why did I do this to myself
parse_images(WofiPropertyBox * box,const char * text,bool create_widgets)251 static char* parse_images(WofiPropertyBox* box, const char* text, bool create_widgets) {
252 char* ret = strdup("");
253 struct map* mode_map = map_init();
254 map_put(mode_map, "img", "true");
255 map_put(mode_map, "img-noscale", "true");
256 map_put(mode_map, "img-base64", "true");
257 map_put(mode_map, "img-base64-noscale", "true");
258 map_put(mode_map, "text", "true");
259
260 char* original = strdup(text);
261 char* mode1 = NULL;
262 char* mode2 = NULL;
263 char* data1 = NULL;
264 char* data2 = NULL;
265
266 get_img_data(original, original, mode_map, true, &mode2, &data2);
267
268 while(true) {
269 if(mode1 == NULL) {
270 mode1 = mode2;
271 data1 = data2;
272 if(data1 != NULL) {
273 get_img_data(original, data1, mode_map, false, &mode2, &data2);
274 } else {
275 break;
276 }
277 } else {
278 if(strcmp(mode1, "img") == 0 && create_widgets) {
279 GdkPixbuf* buf = gdk_pixbuf_new_from_file(data1, NULL);
280 if(buf == NULL) {
281 fprintf(stderr, "Image %s cannot be loaded\n", data1);
282 goto done;
283 }
284
285 buf = utils_g_resize_pixbuf(buf, image_size * wofi_get_window_scale(), GDK_INTERP_BILINEAR);
286
287 GtkWidget* img = gtk_image_new();
288 cairo_surface_t* surface = gdk_cairo_surface_create_from_pixbuf(buf, wofi_get_window_scale(), gtk_widget_get_window(img));
289 gtk_image_set_from_surface(GTK_IMAGE(img), surface);
290 cairo_surface_destroy(surface);
291 g_object_unref(buf);
292
293 gtk_widget_set_name(img, "img");
294 gtk_container_add(GTK_CONTAINER(box), img);
295 } else if(strcmp(mode1, "img-noscale") == 0 && create_widgets) {
296 GdkPixbuf* buf = gdk_pixbuf_new_from_file(data1, NULL);
297 if(buf == NULL) {
298 fprintf(stderr, "Image %s cannot be loaded\n", data1);
299 goto done;
300 }
301
302 GtkWidget* img = gtk_image_new();
303 cairo_surface_t* surface = gdk_cairo_surface_create_from_pixbuf(buf, wofi_get_window_scale(), gtk_widget_get_window(img));
304 gtk_image_set_from_surface(GTK_IMAGE(img), surface);
305 cairo_surface_destroy(surface);
306 g_object_unref(buf);
307
308 gtk_widget_set_name(img, "img");
309 gtk_container_add(GTK_CONTAINER(box), img);
310 } else if(strcmp(mode1, "img-base64") == 0 && create_widgets) {
311 GdkPixbuf* buf = utils_g_pixbuf_from_base64(data1);
312 if(buf == NULL) {
313 fprintf(stderr, "base64 image cannot be loaded\n");
314 goto done;
315 }
316
317 buf = utils_g_resize_pixbuf(buf, image_size, GDK_INTERP_BILINEAR);
318
319 GtkWidget* img = gtk_image_new();
320 cairo_surface_t* surface = gdk_cairo_surface_create_from_pixbuf(buf, wofi_get_window_scale(), gtk_widget_get_window(img));
321 gtk_image_set_from_surface(GTK_IMAGE(img), surface);
322 cairo_surface_destroy(surface);
323 g_object_unref(buf);
324
325 gtk_widget_set_name(img, "img");
326 gtk_container_add(GTK_CONTAINER(box), img);
327 } else if(strcmp(mode1, "img-base64-noscale") == 0 && create_widgets) {
328 GdkPixbuf* buf = utils_g_pixbuf_from_base64(data1);
329 if(buf == NULL) {
330 fprintf(stderr, "base64 image cannot be loaded\n");
331 goto done;
332 }
333 GtkWidget* img = gtk_image_new();
334 cairo_surface_t* surface = gdk_cairo_surface_create_from_pixbuf(buf, wofi_get_window_scale(), gtk_widget_get_window(img));
335 gtk_image_set_from_surface(GTK_IMAGE(img), surface);
336 cairo_surface_destroy(surface);
337 g_object_unref(buf);
338
339 gtk_widget_set_name(img, "img");
340 gtk_container_add(GTK_CONTAINER(box), img);
341 } else if(strcmp(mode1, "text") == 0) {
342 if(create_widgets) {
343 GtkWidget* label = gtk_label_new(data1);
344 gtk_widget_set_name(label, "text");
345 gtk_label_set_use_markup(GTK_LABEL(label), allow_markup);
346 gtk_label_set_xalign(GTK_LABEL(label), 0);
347 if(line_wrap >= 0) {
348 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
349 gtk_label_set_line_wrap_mode(GTK_LABEL(label), line_wrap);
350 }
351 gtk_container_add(GTK_CONTAINER(box), label);
352 } else {
353 char* tmp = ret;
354 ret = utils_concat(2, ret, data1);
355 free(tmp);
356 }
357 }
358 done:
359 mode1 = NULL;
360 }
361 }
362 free(original);
363 map_free(mode_map);
364
365 if(create_widgets) {
366 free(ret);
367 return NULL;
368 } else {
369 return ret;
370 }
371 }
372
wofi_parse_image_escapes(const char * text)373 char* wofi_parse_image_escapes(const char* text) {
374 return parse_images(NULL, text, false);
375 }
376
setup_label(char * mode,WofiPropertyBox * box)377 static void setup_label(char* mode, WofiPropertyBox* box) {
378 wofi_property_box_add_property(box, "mode", mode);
379 char index[11];
380 snprintf(index, sizeof(index), "%u", ++widget_count);
381 wofi_property_box_add_property(box, "index", index);
382
383 gtk_widget_set_name(GTK_WIDGET(box), "unselected");
384
385 GtkStyleContext* style = gtk_widget_get_style_context(GTK_WIDGET(box));
386 gtk_style_context_add_class(style, "entry");
387 }
388
create_label(char * mode,char * text,char * search_text,char * action)389 static GtkWidget* create_label(char* mode, char* text, char* search_text, char* action) {
390 GtkWidget* box = wofi_property_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
391
392 wofi_property_box_add_property(WOFI_PROPERTY_BOX(box), "action", action);
393
394 setup_label(mode, WOFI_PROPERTY_BOX(box));
395
396 if(allow_images) {
397 parse_images(WOFI_PROPERTY_BOX(box), text, true);
398 } else {
399 GtkWidget* label = gtk_label_new(text);
400 gtk_widget_set_name(label, "text");
401 gtk_label_set_use_markup(GTK_LABEL(label), allow_markup);
402 gtk_label_set_xalign(GTK_LABEL(label), 0);
403 if(line_wrap >= 0) {
404 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
405 gtk_label_set_line_wrap_mode(GTK_LABEL(label), line_wrap);
406 }
407 gtk_container_add(GTK_CONTAINER(box), label);
408 }
409 if(parse_search) {
410 search_text = strdup(search_text);
411 if(allow_images) {
412 char* tmp = search_text;
413 search_text = parse_images(WOFI_PROPERTY_BOX(box), search_text, false);
414 free(tmp);
415 }
416 if(allow_markup) {
417 char* out = NULL;
418 pango_parse_markup(search_text, -1, 0, NULL, &out, NULL, NULL);
419 free(search_text);
420 search_text = out;
421 }
422 }
423 wofi_property_box_add_property(WOFI_PROPERTY_BOX(box), "filter", search_text);
424 if(parse_search) {
425 free(search_text);
426 }
427 return box;
428 }
429
get_cache_path(const gchar * mode)430 static char* get_cache_path(const gchar* mode) {
431 if(cache_file != NULL) {
432 return strdup(cache_file);
433 }
434 char* cache_path = getenv("XDG_CACHE_HOME");
435 if(cache_path == NULL) {
436 cache_path = utils_concat(3, getenv("HOME"), "/.cache/wofi-", mode);
437 } else {
438 cache_path = utils_concat(3, cache_path, "/wofi-", mode);
439 }
440 return cache_path;
441 }
442
execute_action(const gchar * mode,const gchar * cmd)443 static void execute_action(const gchar* mode, const gchar* cmd) {
444 struct mode* mode_ptr = map_get(modes, mode);
445 mode_ptr->mode_exec(cmd);
446 }
447
activate_item(GtkFlowBox * flow_box,GtkFlowBoxChild * row,gpointer data)448 static void activate_item(GtkFlowBox* flow_box, GtkFlowBoxChild* row, gpointer data) {
449 (void) flow_box;
450 (void) data;
451 GtkWidget* box = gtk_bin_get_child(GTK_BIN(row));
452 bool primary_action = GTK_IS_EXPANDER(box);
453 if(primary_action) {
454 box = gtk_expander_get_label_widget(GTK_EXPANDER(box));
455 }
456 execute_action(wofi_property_box_get_property(WOFI_PROPERTY_BOX(box), "mode"), wofi_property_box_get_property(WOFI_PROPERTY_BOX(box), "action"));
457 }
458
expand(GtkExpander * expander,gpointer data)459 static void expand(GtkExpander* expander, gpointer data) {
460 (void) data;
461 GtkWidget* box = gtk_bin_get_child(GTK_BIN(expander));
462 resize_expander = !gtk_expander_get_expanded(expander);
463 gtk_widget_set_visible(box, resize_expander);
464 }
465
update_surface_size(void)466 static void update_surface_size(void) {
467 if(lines > 0) {
468 height = max_height * lines;
469 height += 5;
470 }
471 if(shell != NULL) {
472 zwlr_layer_surface_v1_set_size(wlr_surface, width, height);
473 wl_surface_commit(wl_surface);
474 wl_display_roundtrip(wl);
475 }
476
477 gtk_window_resize(GTK_WINDOW(window), width, height);
478 gtk_widget_set_size_request(scroll, width, height);
479 }
480
widget_allocate(GtkWidget * widget,GdkRectangle * allocation,gpointer data)481 static void widget_allocate(GtkWidget* widget, GdkRectangle* allocation, gpointer data) {
482 (void) data;
483 if(resize_expander) {
484 return;
485 }
486
487 if(max_height > 0) {
488 if(allocation->height > max_height) {
489 int64_t ratio = allocation->height / max_height;
490 int64_t mod = allocation->height % max_height;
491 if(mod >= max_height / 2) {
492 ++ratio;
493 }
494 if(ratio > 1) {
495 gtk_widget_set_size_request(widget, -1, max_height * ratio);
496 } else {
497 max_height = allocation->height;
498 }
499 } else {
500 gtk_widget_set_size_request(widget, -1, max_height);
501 }
502 } else {
503 max_height = allocation->height;
504 }
505 if(lines > 0) {
506 update_surface_size();
507 }
508 }
509
_insert_widget(gpointer data)510 static gboolean _insert_widget(gpointer data) {
511 struct mode* mode = data;
512 struct widget* node;
513 if(mode->mode_get_widget == NULL) {
514 return FALSE;
515 } else {
516 node = mode->mode_get_widget();
517 }
518 if(node == NULL) {
519 return FALSE;
520 }
521
522 GtkWidget* parent;
523 if(node->action_count > 1 && !no_actions) {
524 parent = gtk_expander_new("");
525 g_signal_connect(parent, "activate", G_CALLBACK(expand), NULL);
526 GtkWidget* box;
527 if(node->builder == NULL) {
528 box = create_label(node->mode, node->text[0], node->search_text, node->actions[0]);
529 } else {
530 box = GTK_WIDGET(node->builder->box);
531 setup_label(node->builder->mode->name, WOFI_PROPERTY_BOX(box));
532 }
533 gtk_expander_set_label_widget(GTK_EXPANDER(parent), box);
534
535 GtkWidget* exp_box = gtk_list_box_new();
536 gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(exp_box), FALSE);
537 g_signal_connect(exp_box, "row-activated", G_CALLBACK(activate_item), NULL);
538 gtk_container_add(GTK_CONTAINER(parent), exp_box);
539 for(size_t count = 1; count < node->action_count; ++count) {
540 if(node->builder == NULL) {
541 box = create_label(node->mode, node->text[count], node->search_text, node->actions[count]);
542 } else {
543 box = GTK_WIDGET(node->builder[count].box);
544 setup_label(node->builder->mode->name, WOFI_PROPERTY_BOX(box));
545 }
546
547 GtkWidget* row = gtk_list_box_row_new();
548 gtk_widget_set_name(row, "entry");
549
550 gtk_container_add(GTK_CONTAINER(row), box);
551 gtk_container_add(GTK_CONTAINER(exp_box), row);
552 }
553 } else {
554 if(node->builder == NULL) {
555 parent = create_label(node->mode, node->text[0], node->search_text, node->actions[0]);
556 } else {
557 parent = GTK_WIDGET(node->builder->box);
558 setup_label(node->builder->mode->name, WOFI_PROPERTY_BOX(parent));
559 }
560 }
561
562 gtk_widget_set_halign(parent, content_halign);
563 GtkWidget* child = gtk_flow_box_child_new();
564 gtk_widget_set_name(child, "entry");
565 g_signal_connect(child, "size-allocate", G_CALLBACK(widget_allocate), NULL);
566
567 gtk_container_add(GTK_CONTAINER(child), parent);
568 gtk_widget_show_all(child);
569 gtk_container_add(GTK_CONTAINER(inner_box), child);
570 ++line_count;
571
572 if(!user_moved) {
573 GtkFlowBoxChild* child = gtk_flow_box_get_child_at_index(GTK_FLOW_BOX(inner_box), 0);
574 gtk_flow_box_select_child(GTK_FLOW_BOX(inner_box), child);
575 gtk_widget_grab_focus(GTK_WIDGET(child));
576 }
577
578 if(GTK_IS_EXPANDER(parent)) {
579 GtkWidget* box = gtk_bin_get_child(GTK_BIN(parent));
580 gtk_widget_set_visible(box, FALSE);
581 }
582
583 if(node->builder != NULL) {
584 wofi_widget_builder_free(node->builder);
585 } else {
586 free(node->mode);
587 for(size_t count = 0; count < node->action_count; ++count) {
588 free(node->text[count]);
589 }
590 free(node->text);
591 free(node->search_text);
592 for(size_t count = 0; count < node->action_count; ++count) {
593 free(node->actions[count]);
594 }
595 free(node->actions);
596 free(node);
597 }
598 return TRUE;
599 }
600
insert_all_widgets(gpointer data)601 static gboolean insert_all_widgets(gpointer data) {
602 pthread_join(mode_thread, NULL);
603 struct wl_list* modes = data;
604 if(modes->prev == modes) {
605 return FALSE;
606 } else {
607 struct mode* mode = wl_container_of(modes->prev, mode, link);
608 if(!_insert_widget(mode)) {
609 wl_list_remove(&mode->link);
610 }
611 return TRUE;
612 }
613 }
614
escape_lf(const char * cmd)615 static char* escape_lf(const char* cmd) {
616 size_t len = strlen(cmd);
617 char* buffer = calloc(1, (len + 1) * 2);
618
619 size_t buf_count = 0;
620 for(size_t count = 0; count < len; ++count) {
621 char chr = cmd[count];
622 if(chr == '\n') {
623 buffer[buf_count++] = '\\';
624 buffer[buf_count++] = 'n';
625 } else if(chr == '\\') {
626 buffer[buf_count++] = '\\';
627 buffer[buf_count++] = '\\';
628 } else {
629 buffer[buf_count++] = chr;
630 }
631 }
632 return buffer;
633 }
634
remove_escapes(const char * cmd)635 static char* remove_escapes(const char* cmd) {
636 size_t len = strlen(cmd);
637 char* buffer = calloc(1, len + 1);
638
639 size_t buf_count = 0;
640 for(size_t count = 0; count < len; ++count) {
641 char chr = cmd[count];
642 if(chr == '\\') {
643 chr = cmd[++count];
644 if(chr == 'n') {
645 buffer[buf_count++] = '\n';
646 } else if(chr == '\\') {
647 buffer[buf_count++] = '\\';
648 }
649 } else {
650 buffer[buf_count++] = chr;
651 }
652 }
653 return buffer;
654 }
655
wofi_write_cache(struct mode * mode,const char * _cmd)656 void wofi_write_cache(struct mode* mode, const char* _cmd) {
657 char* cmd = escape_lf(_cmd);
658
659 char* cache_path = get_cache_path(mode->name);
660
661 char* tmp_dir = strdup(cache_path);
662 utils_mkdir(dirname(tmp_dir), S_IRWXU | S_IRGRP | S_IXGRP);
663 free(tmp_dir);
664
665 struct wl_list lines;
666 wl_list_init(&lines);
667 bool inc_count = false;
668 if(access(cache_path, R_OK) == 0) {
669 FILE* file = fopen(cache_path, "r");
670 char* line = NULL;
671 size_t size = 0;
672 while(getline(&line, &size, file) != -1) {
673 char* space = strchr(line, ' ');
674 char* lf = strchr(line, '\n');
675 if(lf != NULL) {
676 *lf = 0;
677 }
678 if(space != NULL && strcmp(cmd, space + 1) == 0) {
679 struct cache_line* node = malloc(sizeof(struct cache_line));
680 uint64_t count = strtol(line, NULL, 10) + 1;
681 char num[6];
682 snprintf(num, 5, "%" PRIu64, count);
683 node->line = utils_concat(4, num, " ", cmd, "\n");
684 inc_count = true;
685 wl_list_insert(&lines, &node->link);
686 }
687 }
688 free(line);
689 line = NULL;
690 size = 0;
691 fseek(file, 0, SEEK_SET);
692 while(getline(&line, &size, file) != -1) {
693 char* space = strchr(line, ' ');
694 char* nl = strchr(line, '\n');
695 if(nl != NULL) {
696 *nl = 0;
697 }
698 if(space == NULL || strcmp(cmd, space + 1) != 0) {
699 struct cache_line* node = malloc(sizeof(struct cache_line));
700 node->line = utils_concat(2, line, "\n");
701 wl_list_insert(&lines, &node->link);
702 }
703 }
704 free(line);
705 fclose(file);
706 }
707 char* tmp_path = strdup(cache_path);
708 char* dir = dirname(tmp_path);
709
710 if(access(dir, W_OK) == 0) {
711 if(!inc_count) {
712 struct cache_line* node = malloc(sizeof(struct cache_line));
713 node->line = utils_concat(3, "1 ", cmd, "\n");
714 wl_list_insert(&lines, &node->link);
715 }
716 FILE* file = fopen(cache_path, "w");
717 struct cache_line* node, *tmp;
718 wl_list_for_each_safe(node, tmp, &lines, link) {
719 fwrite(node->line, 1, strlen(node->line), file);
720 free(node->line);
721 wl_list_remove(&node->link);
722 free(node);
723 }
724
725 fclose(file);
726 }
727 free(cache_path);
728 free(tmp_path);
729 free(cmd);
730 }
731
wofi_remove_cache(struct mode * mode,const char * _cmd)732 void wofi_remove_cache(struct mode* mode, const char* _cmd) {
733 char* cmd = escape_lf(_cmd);
734
735 char* cache_path = get_cache_path(mode->name);
736 if(access(cache_path, R_OK | W_OK) == 0) {
737 struct wl_list lines;
738 wl_list_init(&lines);
739
740 FILE* file = fopen(cache_path, "r");
741 char* line = NULL;
742 size_t size = 0;
743 while(getline(&line, &size, file) != -1) {
744 char* space = strchr(line, ' ');
745 char* lf = strchr(line, '\n');
746 if(lf != NULL) {
747 *lf = 0;
748 }
749 if(space == NULL || strcmp(cmd, space + 1) != 0) {
750 struct cache_line* node = malloc(sizeof(struct cache_line));
751 node->line = utils_concat(2, line, "\n");
752 wl_list_insert(&lines, &node->link);
753 }
754 }
755 free(line);
756 fclose(file);
757
758 file = fopen(cache_path, "w");
759 struct cache_line* node, *tmp;
760 wl_list_for_each_safe(node, tmp, &lines, link) {
761 fwrite(node->line, 1, strlen(node->line), file);
762 free(node->line);
763 wl_list_remove(&node->link);
764 free(node);
765 }
766 fclose(file);
767 }
768 free(cache_path);
769 free(cmd);
770 }
771
wofi_read_cache(struct mode * mode)772 struct wl_list* wofi_read_cache(struct mode* mode) {
773 char* cache_path = get_cache_path(mode->name);
774 struct wl_list* cache = malloc(sizeof(struct wl_list));
775 wl_list_init(cache);
776 struct wl_list lines;
777 wl_list_init(&lines);
778 if(access(cache_path, R_OK) == 0) {
779 FILE* file = fopen(cache_path, "r");
780 char* line = NULL;
781 size_t size = 0;
782 while(getline(&line, &size, file) != -1) {
783 struct cache_line* node = malloc(sizeof(struct cache_line));
784 char* lf = strchr(line, '\n');
785 if(lf != NULL) {
786 *lf = 0;
787 }
788 node->line = remove_escapes(line);
789 wl_list_insert(&lines, &node->link);
790 }
791 free(line);
792 fclose(file);
793 }
794 while(!wl_list_empty(&lines)) {
795 uint64_t smallest = UINT64_MAX;
796 struct cache_line* node, *smallest_node = NULL;
797 wl_list_for_each(node, &lines, link) {
798 uint64_t num = strtol(node->line, NULL, 10);
799 if(num < smallest) {
800 smallest = num;
801 smallest_node = node;
802 }
803 }
804 char* tmp = strdup(strchr(smallest_node->line, ' ') + 1);
805 free(smallest_node->line);
806 smallest_node->line = tmp;
807 wl_list_remove(&smallest_node->link);
808 wl_list_insert(cache, &smallest_node->link);
809 }
810 free(cache_path);
811 return cache;
812 }
813
wofi_create_widget(struct mode * mode,char * text[],char * search_text,char * actions[],size_t action_count)814 struct widget* wofi_create_widget(struct mode* mode, char* text[], char* search_text, char* actions[], size_t action_count) {
815 struct widget* widget = calloc(1, sizeof(struct widget));
816 widget->mode = strdup(mode->name);
817 widget->text = malloc(action_count * sizeof(char*));
818 for(size_t count = 0; count < action_count; ++count) {
819 widget->text[count] = strdup(text[count]);
820 }
821 widget->search_text = strdup(search_text);
822 widget->action_count = action_count;
823 widget->actions = malloc(action_count * sizeof(char*));
824 for(size_t count = 0; count < action_count; ++count) {
825 widget->actions[count] = strdup(actions[count]);
826 }
827 return widget;
828 }
829
wofi_insert_widgets(struct mode * mode)830 void wofi_insert_widgets(struct mode* mode) {
831 gdk_threads_add_idle(_insert_widget, mode);
832 }
833
wofi_get_dso_path(struct mode * mode)834 char* wofi_get_dso_path(struct mode* mode) {
835 return mode->dso;
836 }
837
wofi_allow_images(void)838 bool wofi_allow_images(void) {
839 return allow_images;
840 }
841
wofi_allow_markup(void)842 bool wofi_allow_markup(void) {
843 return allow_markup;
844 }
845
wofi_get_image_size(void)846 uint64_t wofi_get_image_size(void) {
847 return image_size;
848 }
849
wofi_get_window_scale(void)850 uint64_t wofi_get_window_scale(void) {
851 return gdk_window_get_scale_factor(gtk_widget_get_window(window));
852 }
853
wofi_mod_shift(void)854 bool wofi_mod_shift(void) {
855 return mod_shift;
856 }
857
wofi_mod_control(void)858 bool wofi_mod_control(void) {
859 return mod_ctrl;
860 }
861
wofi_term_run(const char * cmd)862 void wofi_term_run(const char* cmd) {
863 if(terminal != NULL) {
864 execlp(terminal, terminal, "-e", cmd, NULL);
865 }
866 size_t term_count = sizeof(terminals) / sizeof(char*);
867 for(size_t count = 0; count < term_count; ++count) {
868 execlp(terminals[count], terminals[count], "-e", cmd, NULL);
869 }
870 fprintf(stderr, "No terminal emulator found please set term in config or use --term\n");
871 exit(1);
872 }
873
flag_box(GtkBox * box,GtkStateFlags flags)874 static void flag_box(GtkBox* box, GtkStateFlags flags) {
875 GList* selected_children = gtk_container_get_children(GTK_CONTAINER(box));
876 for(GList* list = selected_children; list != NULL; list = list->next) {
877 GtkWidget* child = list->data;
878 gtk_widget_set_state_flags(child, flags, TRUE);
879 }
880 g_list_free(selected_children);
881 }
882
select_item(GtkFlowBox * flow_box,gpointer data)883 static void select_item(GtkFlowBox* flow_box, gpointer data) {
884 (void) data;
885 if(previous_selection != NULL) {
886 gtk_widget_set_name(previous_selection, "unselected");
887 flag_box(GTK_BOX(previous_selection), GTK_STATE_FLAG_NORMAL);
888 }
889 GList* selected_children = gtk_flow_box_get_selected_children(flow_box);
890 GtkWidget* box = gtk_bin_get_child(GTK_BIN(selected_children->data));
891 g_list_free(selected_children);
892 if(GTK_IS_EXPANDER(box)) {
893 box = gtk_expander_get_label_widget(GTK_EXPANDER(box));
894 }
895 flag_box(GTK_BOX(box), GTK_STATE_FLAG_SELECTED);
896 gtk_widget_set_name(box, "selected");
897 previous_selection = box;
898 }
899
activate_search(GtkEntry * entry,gpointer data)900 static void activate_search(GtkEntry* entry, gpointer data) {
901 (void) data;
902 GtkFlowBoxChild* child = gtk_flow_box_get_child_at_index(GTK_FLOW_BOX(inner_box), 0);
903 gboolean is_visible = gtk_widget_get_visible(GTK_WIDGET(child));
904 if(mode != NULL && (exec_search || child == NULL || !is_visible)) {
905 execute_action(mode, gtk_entry_get_text(entry));
906 } else if(child != NULL) {
907 GtkWidget* box = gtk_bin_get_child(GTK_BIN(child));
908 bool primary_action = GTK_IS_EXPANDER(box);
909 if(primary_action) {
910 box = gtk_expander_get_label_widget(GTK_EXPANDER(box));
911 }
912 execute_action(wofi_property_box_get_property(WOFI_PROPERTY_BOX(box), "mode"), wofi_property_box_get_property(WOFI_PROPERTY_BOX(box), "action"));
913 }
914 }
915
filter_proxy(GtkFlowBoxChild * row)916 static gboolean filter_proxy(GtkFlowBoxChild* row) {
917 GtkWidget* box = gtk_bin_get_child(GTK_BIN(row));
918 if(GTK_IS_EXPANDER(box)) {
919 box = gtk_expander_get_label_widget(GTK_EXPANDER(box));
920 }
921 const gchar* text = wofi_property_box_get_property(WOFI_PROPERTY_BOX(box), "filter");
922 if(filter == NULL || strcmp(filter, "") == 0) {
923 return TRUE;
924 }
925 if(text == NULL) {
926 return FALSE;
927 }
928 if(insensitive) {
929 return strcasestr(text, filter) != NULL;
930 } else {
931 return strstr(text, filter) != NULL;
932 }
933 }
934
do_filter(GtkFlowBoxChild * row,gpointer data)935 static gboolean do_filter(GtkFlowBoxChild* row, gpointer data) {
936 (void) data;
937 gboolean ret = filter_proxy(row);
938
939 if(gtk_widget_get_visible(GTK_WIDGET(row)) == !ret && dynamic_lines) {
940 if(ret) {
941 ++line_count;
942 } else {
943 --line_count;
944 }
945
946 if(line_count < max_lines) {
947 lines = line_count;
948 update_surface_size();
949 } else {
950 lines = max_lines;
951 update_surface_size();
952 }
953 }
954
955 gtk_widget_set_visible(GTK_WIDGET(row), ret);
956
957 return ret;
958 }
959
fuzzy_sort(const gchar * text1,const gchar * text2)960 static gint fuzzy_sort(const gchar* text1, const gchar* text2) {
961 char* _filter = strdup(filter);
962 size_t len = strlen(_filter);
963
964 char* t1 = strdup(text1);
965 size_t t1l = strlen(t1);
966
967 char* t2 = strdup(text2);
968 size_t t2l = strlen(t2);
969
970 if(insensitive) {
971 for(size_t count = 0; count < len; ++count) {
972 char chr = _filter[count];
973 if(isalpha(chr)) {
974 _filter[count] = tolower(chr);
975 }
976 }
977 for(size_t count = 0; count < t1l; ++count) {
978 char chr = t1[count];
979 if(isalpha(chr)) {
980 t1[count] = tolower(chr);
981 }
982 }
983 for(size_t count = 0; count < t2l; ++count) {
984 char chr = t2[count];
985 if(isalpha(chr)) {
986 t2[count] = tolower(chr);
987 }
988 }
989 }
990
991 size_t dist1 = utils_distance(t1, _filter);
992 size_t dist2 = utils_distance(t2, _filter);
993 free(_filter);
994 free(t1);
995 free(t2);
996 return dist1 - dist2;
997 }
998
contains_sort(const gchar * text1,const gchar * text2)999 static gint contains_sort(const gchar* text1, const gchar* text2) {
1000 char* str1, *str2;
1001
1002 if(insensitive) {
1003 str1 = strcasestr(text1, filter);
1004 str2 = strcasestr(text2, filter);
1005 } else {
1006 str1 = strstr(text1, filter);
1007 str2 = strstr(text2, filter);
1008 }
1009 bool tx1 = str1 == text1;
1010 bool tx2 = str2 == text2;
1011 bool txc1 = str1 != NULL;
1012 bool txc2 = str2 != NULL;
1013
1014 if(tx1 && tx2) {
1015 return 0;
1016 } else if(tx1) {
1017 return -1;
1018 } else if(tx2) {
1019 return 1;
1020 } else if(txc1 && txc2) {
1021 return 0;
1022 } else if(txc1) {
1023 return -1;
1024 } else if(txc2) {
1025 return 1;
1026 } else {
1027 return 0;
1028 }
1029 }
1030
do_sort(GtkFlowBoxChild * child1,GtkFlowBoxChild * child2,gpointer data)1031 static gint do_sort(GtkFlowBoxChild* child1, GtkFlowBoxChild* child2, gpointer data) {
1032 (void) data;
1033 gtk_flow_box_get_child_at_index(GTK_FLOW_BOX(inner_box), 0);
1034
1035 GtkWidget* box1 = gtk_bin_get_child(GTK_BIN(child1));
1036 GtkWidget* box2 = gtk_bin_get_child(GTK_BIN(child2));
1037 if(GTK_IS_EXPANDER(box1)) {
1038 box1 = gtk_expander_get_label_widget(GTK_EXPANDER(box1));
1039 }
1040 if(GTK_IS_EXPANDER(box2)) {
1041 box2 = gtk_expander_get_label_widget(GTK_EXPANDER(box2));
1042 }
1043
1044 const gchar* text1 = wofi_property_box_get_property(WOFI_PROPERTY_BOX(box1), "filter");
1045 const gchar* text2 = wofi_property_box_get_property(WOFI_PROPERTY_BOX(box2), "filter");
1046 uint64_t index1 = strtol(wofi_property_box_get_property(WOFI_PROPERTY_BOX(box1), "index"), NULL, 10);
1047 uint64_t index2 = strtol(wofi_property_box_get_property(WOFI_PROPERTY_BOX(box2), "index"), NULL, 10);
1048
1049 if(text1 == NULL || text2 == NULL) {
1050 return index1 - index2;
1051 }
1052
1053 uint64_t fallback = 0;
1054 switch(sort_order) {
1055 case SORT_ORDER_DEFAULT:
1056 fallback = index1 - index2;
1057 break;
1058 case SORT_ORDER_ALPHABETICAL:
1059 if(insensitive) {
1060 fallback = strcasecmp(text1, text2);
1061 } else {
1062 fallback = strcmp(text1, text2);
1063 }
1064 break;
1065 }
1066
1067 if(filter == NULL || strcmp(filter, "") == 0) {
1068 return fallback;
1069 }
1070
1071 gint primary = 0;
1072 switch(matching) {
1073 case MATCHING_MODE_CONTAINS:
1074 primary = contains_sort(text1, text2);
1075 if(primary == 0) {
1076 return fallback;
1077 }
1078 return primary;
1079 case MATCHING_MODE_FUZZY:
1080 primary = fuzzy_sort(text1, text2);
1081 if(primary == 0) {
1082 return fallback;
1083 }
1084 return primary;
1085 default:
1086 return 0;
1087 }
1088 }
1089
select_first(void)1090 static void select_first(void) {
1091 GtkFlowBoxChild* child = gtk_flow_box_get_child_at_index(GTK_FLOW_BOX(inner_box), 1);
1092 gtk_widget_grab_focus(GTK_WIDGET(child));
1093 gtk_flow_box_select_child(GTK_FLOW_BOX(inner_box), GTK_FLOW_BOX_CHILD(child));
1094 }
1095
get_mask_from_keyval(guint keyval)1096 static GdkModifierType get_mask_from_keyval(guint keyval) {
1097 switch(keyval) {
1098 case GDK_KEY_Shift_L:
1099 case GDK_KEY_Shift_R:
1100 return GDK_SHIFT_MASK;
1101 case GDK_KEY_Control_L:
1102 case GDK_KEY_Control_R:
1103 return GDK_CONTROL_MASK;
1104 case GDK_KEY_Alt_L:
1105 case GDK_KEY_Alt_R:
1106 return GDK_MOD1_MASK;
1107 default:
1108 return 0;
1109 }
1110 }
1111
get_mask_from_name(char * name)1112 static GdkModifierType get_mask_from_name(char* name) {
1113 return get_mask_from_keyval(gdk_keyval_from_name(name));
1114 }
1115
move_up(void)1116 static void move_up(void) {
1117 user_moved = true;
1118 gtk_widget_child_focus(window, GTK_DIR_UP);
1119 }
1120
move_down(void)1121 static void move_down(void) {
1122 user_moved = true;
1123 if(outer_orientation == GTK_ORIENTATION_VERTICAL) {
1124 if(gtk_widget_has_focus(entry) || gtk_widget_has_focus(scroll)) {
1125 select_first();
1126 return;
1127 }
1128 }
1129 gtk_widget_child_focus(window, GTK_DIR_DOWN);
1130 }
1131
move_left(void)1132 static void move_left(void) {
1133 user_moved = true;
1134 gtk_widget_child_focus(window, GTK_DIR_LEFT);
1135 }
1136
move_right(void)1137 static void move_right(void) {
1138 user_moved = true;
1139 if(outer_orientation == GTK_ORIENTATION_HORIZONTAL) {
1140 if(gtk_widget_has_focus(entry) || gtk_widget_has_focus(scroll)) {
1141 select_first();
1142 return;
1143 }
1144 }
1145 gtk_widget_child_focus(window, GTK_DIR_RIGHT);
1146 }
1147
move_forward(void)1148 static void move_forward(void) {
1149 user_moved = true;
1150 if(gtk_widget_has_focus(entry) || gtk_widget_has_focus(scroll)) {
1151 select_first();
1152 return;
1153 }
1154 gtk_widget_child_focus(window, GTK_DIR_TAB_FORWARD);
1155 }
1156
move_backward(void)1157 static void move_backward(void) {
1158 user_moved = true;
1159 gtk_widget_child_focus(window, GTK_DIR_TAB_BACKWARD);
1160 }
1161
move_pgup(void)1162 static void move_pgup(void) {
1163 uint64_t lines = height / max_height;
1164 for(size_t count = 0; count < lines; ++count) {
1165 move_up();
1166 }
1167 }
1168
move_pgdn(void)1169 static void move_pgdn(void) {
1170 uint64_t lines = height / max_height;
1171 for(size_t count = 0; count < lines; ++count) {
1172 move_down();
1173 }
1174 }
1175
do_exit(void)1176 static void do_exit(void) {
1177 exit(1);
1178 }
1179
do_expand(void)1180 static void do_expand(void) {
1181 GList* children = gtk_flow_box_get_selected_children(GTK_FLOW_BOX(inner_box));
1182 if(children->data != NULL && gtk_widget_has_focus(children->data)) {
1183 GtkWidget* expander = gtk_bin_get_child(children->data);
1184 if(GTK_IS_EXPANDER(expander)) {
1185 g_signal_emit_by_name(expander, "activate");
1186 }
1187 }
1188 g_list_free(children);
1189 }
1190
do_hide_search(void)1191 static void do_hide_search(void) {
1192 gtk_widget_set_visible(entry, !gtk_widget_get_visible(entry));
1193 update_surface_size();
1194 }
1195
do_key_action(GdkEvent * event,char * mod,void (* action)(void))1196 static bool do_key_action(GdkEvent* event, char* mod, void (*action)(void)) {
1197 if(mod != NULL) {
1198 GdkModifierType mask = get_mask_from_name(mod);
1199 if((event->key.state & mask) == mask) {
1200 event->key.state &= ~mask;
1201 action();
1202 return true;
1203 }
1204 return false;
1205 } else {
1206 action();
1207 return true;
1208 }
1209 }
1210
has_mod(guint state)1211 static bool has_mod(guint state) {
1212 return (state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK || (state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK || (state & GDK_MOD1_MASK) == GDK_MOD1_MASK;
1213 }
1214
do_nyan(gpointer data)1215 static gboolean do_nyan(gpointer data) {
1216 (void) data;
1217 wofi_load_css(true);
1218 return G_SOURCE_CONTINUE;
1219 }
1220
get_konami_key(uint8_t cycle)1221 static guint get_konami_key(uint8_t cycle) {
1222 switch(cycle) {
1223 case 0:
1224 return GDK_KEY_Up;
1225 case 1:
1226 return GDK_KEY_Up;
1227 case 2:
1228 return GDK_KEY_Down;
1229 case 3:
1230 return GDK_KEY_Down;
1231 case 4:
1232 return GDK_KEY_Left;
1233 case 5:
1234 return GDK_KEY_Right;
1235 case 6:
1236 return GDK_KEY_Left;
1237 case 7:
1238 return GDK_KEY_Right;
1239 case 8:
1240 return GDK_KEY_b;
1241 case 9:
1242 return GDK_KEY_a;
1243 case 10:
1244 return GDK_KEY_Return;
1245 default:
1246 return GDK_KEY_VoidSymbol;
1247 }
1248 }
1249
key_press(GtkWidget * widget,GdkEvent * event,gpointer data)1250 static gboolean key_press(GtkWidget* widget, GdkEvent* event, gpointer data) {
1251 (void) widget;
1252 (void) data;
1253 gchar* name = gdk_keyval_name(event->key.keyval);
1254 bool printable = strlen(name) == 1 && isprint(name[0]) && !has_mod(event->key.state);
1255
1256 guint konami_key = get_konami_key(konami_cycle);
1257 if(event->key.keyval == konami_key) {
1258 if(konami_cycle == 10) {
1259 konami_cycle = 0;
1260 if(!is_konami) {
1261 is_konami = true;
1262 gdk_threads_add_timeout(500, do_nyan, NULL);
1263 }
1264 return TRUE;
1265 } else {
1266 ++konami_cycle;
1267 }
1268 } else {
1269 konami_cycle = 0;
1270 }
1271
1272 if(gtk_widget_has_focus(entry) && printable) {
1273 return FALSE;
1274 }
1275
1276
1277 bool key_success = true;
1278 struct key_entry* key_ent = map_get(keys, gdk_keyval_name(event->key.keyval));
1279
1280 if(key_ent != NULL && key_ent->action != NULL) {
1281 key_success = do_key_action(event, key_ent->mod, key_ent->action);
1282 } else if(key_ent != NULL) {
1283 mod_shift = (event->key.state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK;
1284 mod_ctrl = (event->key.state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK;
1285 if(mod_shift) {
1286 event->key.state &= ~GDK_SHIFT_MASK;
1287 }
1288 if(mod_ctrl) {
1289 event->key.state &= ~GDK_CONTROL_MASK;
1290 }
1291 if(gtk_widget_has_focus(scroll)) {
1292 gtk_entry_grab_focus_without_selecting(GTK_ENTRY(entry));
1293 }
1294 GList* children = gtk_flow_box_get_selected_children(GTK_FLOW_BOX(inner_box));
1295 if(gtk_widget_has_focus(entry)) {
1296 g_signal_emit_by_name(entry, "activate", entry, NULL);
1297 } else if(gtk_widget_has_focus(inner_box) || children->data != NULL) {
1298 gpointer obj = children->data;
1299
1300 if(obj != NULL) {
1301 GtkWidget* exp = gtk_bin_get_child(GTK_BIN(obj));
1302 if(GTK_IS_EXPANDER(exp)) {
1303 GtkWidget* box = gtk_bin_get_child(GTK_BIN(exp));
1304 GtkListBoxRow* row = gtk_list_box_get_selected_row(GTK_LIST_BOX(box));
1305 if(row != NULL) {
1306 obj = row;
1307 }
1308 }
1309 g_signal_emit_by_name(obj, "activate", obj, NULL);
1310 }
1311 }
1312 g_list_free(children);
1313 } else if(event->key.keyval == GDK_KEY_Shift_L || event->key.keyval == GDK_KEY_Shift_R) {
1314 } else if(event->key.keyval == GDK_KEY_Control_L || event->key.keyval == GDK_KEY_Control_R) {
1315 } else if(event->key.keyval == GDK_KEY_Alt_L || event->key.keyval == GDK_KEY_Alt_R) {
1316 } else {
1317 key_success = false;
1318 }
1319
1320 if(key_success) {
1321 return TRUE;
1322 }
1323 if(!gtk_widget_has_focus(entry)) {
1324 gtk_entry_grab_focus_without_selecting(GTK_ENTRY(entry));
1325 }
1326 return FALSE;
1327 }
1328
focus(GtkWidget * widget,GdkEvent * event,gpointer data)1329 static gboolean focus(GtkWidget* widget, GdkEvent* event, gpointer data) {
1330 (void) data;
1331 if(event->focus_change.in) {
1332 gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_FOCUSED, TRUE);
1333 } else {
1334 gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_NORMAL, TRUE);
1335 }
1336 return FALSE;
1337 }
1338
focus_entry(GtkWidget * widget,GdkEvent * event,gpointer data)1339 static gboolean focus_entry(GtkWidget* widget, GdkEvent* event, gpointer data) {
1340 (void) data;
1341 if(widget == entry && dbus != NULL) {
1342 GError* err = NULL;
1343 g_dbus_proxy_call_sync(dbus, "SetVisible", g_variant_new("(b)", event->focus_change.in), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, &err);
1344 if(err != NULL) {
1345 if(err->code != G_DBUS_ERROR_SERVICE_UNKNOWN) {
1346 fprintf(stderr, "Error while changing OSK state %s\n", err->message);
1347 }
1348 g_error_free(err);
1349 }
1350 }
1351 return FALSE;
1352 }
1353
get_plugin_proc(const char * prefix,const char * suffix)1354 static void* get_plugin_proc(const char* prefix, const char* suffix) {
1355 char* proc_name = utils_concat(3, "wofi_", prefix, suffix);
1356 void* proc = dlsym(RTLD_DEFAULT, proc_name);
1357 free(proc_name);
1358 return proc;
1359 }
1360
load_mode(char * _mode,char * name,struct mode * mode_ptr,struct map * props)1361 static void* load_mode(char* _mode, char* name, struct mode* mode_ptr, struct map* props) {
1362 char* dso = strstr(_mode, ".so");
1363
1364 mode_ptr->name = strdup(name);
1365
1366 void (*init)(struct mode* _mode, struct map* props);
1367 void (*load)(struct mode* _mode);
1368 const char** (*get_arg_names)(void);
1369 size_t (*get_arg_count)(void);
1370 bool (*no_entry)(void);
1371 if(dso == NULL) {
1372 mode_ptr->dso = NULL;
1373 init = get_plugin_proc(_mode, "_init");
1374 load = get_plugin_proc(_mode, "_load");
1375 get_arg_names = get_plugin_proc(_mode, "_get_arg_names");
1376 get_arg_count = get_plugin_proc(_mode, "_get_arg_count");
1377 mode_ptr->mode_exec = get_plugin_proc(_mode, "_exec");
1378 mode_ptr->mode_get_widget = get_plugin_proc(_mode, "_get_widget");
1379 no_entry = get_plugin_proc(_mode, "_no_entry");
1380 } else {
1381 char* plugins_dir = utils_concat(2, config_dir, "/plugins/");
1382 char* full_name = utils_concat(2, plugins_dir, _mode);
1383 mode_ptr->dso = strdup(full_name);
1384 void* plugin = dlopen(full_name, RTLD_LAZY | RTLD_LOCAL);
1385 free(full_name);
1386 free(plugins_dir);
1387 init = dlsym(plugin, "init");
1388 load = dlsym(plugin, "load");
1389 get_arg_names = dlsym(plugin, "get_arg_names");
1390 get_arg_count = dlsym(plugin, "get_arg_count");
1391 mode_ptr->mode_exec = dlsym(plugin, "exec");
1392 mode_ptr->mode_get_widget = dlsym(plugin, "get_widget");
1393 no_entry = dlsym(plugin, "no_entry");
1394 }
1395
1396 if(load != NULL) {
1397 load(mode_ptr);
1398 }
1399
1400 const char** arg_names = NULL;
1401 size_t arg_count = 0;
1402 if(get_arg_names != NULL && get_arg_count != NULL) {
1403 arg_names = get_arg_names();
1404 arg_count = get_arg_count();
1405 }
1406
1407 if(mode == NULL && no_entry != NULL && no_entry()) {
1408 mode = mode_ptr->name;
1409 }
1410
1411 for(size_t count = 0; count < arg_count; ++count) {
1412 const char* arg = arg_names[count];
1413 char* full_name = utils_concat(3, name, "-", arg);
1414 map_put(props, arg, config_get(config, full_name, NULL));
1415 free(full_name);
1416 }
1417 return init;
1418 }
1419
add_mode(char * _mode)1420 static struct mode* add_mode(char* _mode) {
1421 struct mode* mode_ptr = calloc(1, sizeof(struct mode));
1422 struct map* props = map_init();
1423 void (*init)(struct mode* _mode, struct map* props) = load_mode(_mode, _mode, mode_ptr, props);
1424
1425 if(init == NULL) {
1426 free(mode_ptr->name);
1427 free(mode_ptr->dso);
1428 free(mode_ptr);
1429 map_free(props);
1430
1431 mode_ptr = calloc(1, sizeof(struct mode));
1432 props = map_init();
1433
1434 char* name = utils_concat(3, "lib", _mode, ".so");
1435 init = load_mode(name, _mode, mode_ptr, props);
1436 free(name);
1437
1438 if(init == NULL) {
1439 free(mode_ptr->name);
1440 free(mode_ptr->dso);
1441 free(mode_ptr);
1442 map_free(props);
1443
1444 mode_ptr = calloc(1, sizeof(struct mode));
1445 props = map_init();
1446
1447 init = load_mode("external", _mode, mode_ptr, props);
1448
1449 map_put(props, "exec", _mode);
1450
1451 if(init == NULL) {
1452 fprintf(stderr, "I would love to show %s but Idk what it is\n", _mode);
1453 exit(1);
1454 }
1455 }
1456 }
1457 map_put_void(modes, _mode, mode_ptr);
1458 init(mode_ptr, props);
1459
1460 map_free(props);
1461 return mode_ptr;
1462 }
1463
start_mode_thread(void * data)1464 static void* start_mode_thread(void* data) {
1465 char* mode = data;
1466 if(strchr(mode, ',') != NULL) {
1467 char* save_ptr;
1468 char* str = strtok_r(mode, ",", &save_ptr);
1469 do {
1470 struct mode* mode_ptr = add_mode(str);
1471 wl_list_insert(&mode_list, &mode_ptr->link);
1472 } while((str = strtok_r(NULL, ",", &save_ptr)) != NULL);
1473 } else {
1474 struct mode* mode_ptr = add_mode(mode);
1475 wl_list_insert(&mode_list, &mode_ptr->link);
1476 }
1477 return NULL;
1478 }
1479
parse_mods(char * key,void (* action)(void))1480 static void parse_mods(char* key, void (*action)(void)) {
1481 char* tmp = strdup(key);
1482 char* save_ptr;
1483 char* str = strtok_r(tmp, ",", &save_ptr);
1484 do {
1485 if(str == NULL) {
1486 break;
1487 }
1488 char* hyphen = strchr(str, '-');
1489 char* mod;
1490 if(hyphen != NULL) {
1491 *hyphen = 0;
1492 guint key1 = gdk_keyval_from_name(str);
1493 guint key2 = gdk_keyval_from_name(hyphen + 1);
1494 if(get_mask_from_keyval(key1) != 0) {
1495 mod = str;
1496 str = hyphen + 1;
1497 } else if(get_mask_from_keyval(key2) != 0) {
1498 mod = hyphen + 1;
1499 } else {
1500 fprintf(stderr, "Neither %s nor %s is a modifier, this is not supported\n", str, hyphen + 1);
1501 mod = NULL;
1502 }
1503 } else {
1504 mod = NULL;
1505 }
1506 struct key_entry* entry = malloc(sizeof(struct key_entry));
1507 if(mod == NULL) {
1508 entry->mod = NULL;
1509 } else {
1510 entry->mod = strdup(mod);
1511 }
1512 entry->action = action;
1513 map_put_void(keys, str, entry);
1514 } while((str = strtok_r(NULL, ",", &save_ptr)) != NULL);
1515 free(tmp);
1516 }
1517
get_output_name(void * data,struct zxdg_output_v1 * output,const char * name)1518 static void get_output_name(void* data, struct zxdg_output_v1* output, const char* name) {
1519 (void) output;
1520 struct output_node* node = data;
1521 node->name = strdup(name);
1522 }
1523
get_output_res(void * data,struct zxdg_output_v1 * output,int32_t width,int32_t height)1524 static void get_output_res(void* data, struct zxdg_output_v1* output, int32_t width, int32_t height) {
1525 (void) output;
1526 struct output_node* node = data;
1527 node->width = width;
1528 node->height = height;
1529 }
1530
get_output_pos(void * data,struct zxdg_output_v1 * output,int32_t x,int32_t y)1531 static void get_output_pos(void* data, struct zxdg_output_v1* output, int32_t x, int32_t y) {
1532 (void) output;
1533 struct output_node* node = data;
1534 node->x = x;
1535 node->y = y;
1536 }
1537
do_percent_size(gpointer data)1538 static gboolean do_percent_size(gpointer data) {
1539 char** geo_str = data;
1540 bool width_percent = strchr(geo_str[0], '%') != NULL;
1541 bool height_percent = strchr(geo_str[1], '%') != NULL && lines == 0;
1542
1543 GdkMonitor* monitor = gdk_display_get_monitor_at_window(gdk_display_get_default(), gtk_widget_get_window(window));
1544
1545 GdkRectangle rect;
1546 gdk_monitor_get_geometry(monitor, &rect);
1547
1548 if(rect.width == resolution.width && rect.height == resolution.height) {
1549 return G_SOURCE_CONTINUE;
1550 }
1551
1552 resolution = rect;
1553
1554 if(width_percent) {
1555 uint64_t w_percent = strtol(geo_str[0], NULL, 10);
1556 width = (w_percent / 100.f) * rect.width;
1557 }
1558 if(height_percent) {
1559 uint64_t h_percent = strtol(geo_str[1], NULL, 10);
1560 height = (h_percent / 100.f) * rect.height;
1561 }
1562 update_surface_size();
1563 return G_SOURCE_CONTINUE;
1564 }
1565
wofi_init(struct map * _config)1566 void wofi_init(struct map* _config) {
1567 config = _config;
1568 char* width_str = config_get(config, "width", "50%");
1569 char* height_str = config_get(config, "height", "40%");
1570 width = strtol(width_str, NULL, 10);
1571 height = strtol(height_str, NULL, 10);
1572 x = map_get(config, "x");
1573 y = map_get(config, "y");
1574 bool normal_window = strcmp(config_get(config, "normal_window", "false"), "true") == 0;
1575 char* mode = map_get(config, "mode");
1576 GtkOrientation orientation = config_get_mnemonic(config, "orientation", "vertical", 2, "vertical", "horizontal");
1577 outer_orientation = config_get_mnemonic(config, "orientation", "vertical", 2, "horizontal", "vertical");
1578 GtkAlign halign = config_get_mnemonic(config, "halign", "fill", 4, "fill", "start", "end", "center");
1579 content_halign = config_get_mnemonic(config, "content_halign", "fill", 4, "fill", "start", "end", "center");
1580 char* default_valign = "start";
1581 if(outer_orientation == GTK_ORIENTATION_HORIZONTAL) {
1582 default_valign = "center";
1583 }
1584 GtkAlign valign = config_get_mnemonic(config, "valign", default_valign, 4, "fill", "start", "end", "center");
1585 char* prompt = config_get(config, "prompt", mode);
1586 uint64_t filter_rate = strtol(config_get(config, "filter_rate", "100"), NULL, 10);
1587 allow_images = strcmp(config_get(config, "allow_images", "false"), "true") == 0;
1588 allow_markup = strcmp(config_get(config, "allow_markup", "false"), "true") == 0;
1589 image_size = strtol(config_get(config, "image_size", "32"), NULL, 10);
1590 cache_file = map_get(config, "cache_file");
1591 config_dir = map_get(config, "config_dir");
1592 terminal = map_get(config, "term");
1593 char* password_char = map_get(config, "password_char");
1594 exec_search = strcmp(config_get(config, "exec_search", "false"), "true") == 0;
1595 bool hide_scroll = strcmp(config_get(config, "hide_scroll", "false"), "true") == 0;
1596 matching = config_get_mnemonic(config, "matching", "contains", 2, "contains", "fuzzy");
1597 insensitive = strcmp(config_get(config, "insensitive", "false"), "true") == 0;
1598 parse_search = strcmp(config_get(config, "parse_search", "false"), "true") == 0;
1599 location = config_get_mnemonic(config, "location", "center", 18,
1600 "center", "top_left", "top", "top_right", "right", "bottom_right", "bottom", "bottom_left", "left",
1601 "0", "1", "2", "3", "4", "5", "6", "7", "8");
1602 no_actions = strcmp(config_get(config, "no_actions", "false"), "true") == 0;
1603 lines = strtol(config_get(config, "lines", "0"), NULL, 10);
1604 max_lines = lines;
1605 columns = strtol(config_get(config, "columns", "1"), NULL, 10);
1606 sort_order = config_get_mnemonic(config, "sort_order", "default", 2, "default", "alphabetical");
1607 line_wrap = config_get_mnemonic(config, "line_wrap", "off", 4, "off", "word", "char", "word_char") - 1;
1608 bool global_coords = strcmp(config_get(config, "global_coords", "false"), "true") == 0;
1609 bool hide_search = strcmp(config_get(config, "hide_search", "false"), "true") == 0;
1610 char* search = map_get(config, "search");
1611 dynamic_lines = strcmp(config_get(config, "dynamic_lines", "false"), "true") == 0;
1612 char* monitor = map_get(config, "monitor");
1613 char* layer = config_get(config, "layer", "top");
1614
1615 keys = map_init_void();
1616
1617
1618
1619 char* key_up = config_get(config, "key_up", "Up");
1620 char* key_down = config_get(config, "key_down", "Down");
1621 char* key_left = config_get(config, "key_left", "Left");
1622 char* key_right = config_get(config, "key_right", "Right");
1623 char* key_forward = config_get(config, "key_forward", "Tab");
1624 char* key_backward = config_get(config, "key_backward", "ISO_Left_Tab");
1625 char* key_submit = config_get(config, "key_submit", "Return");
1626 char* key_exit = config_get(config, "key_exit", "Escape");
1627 char* key_pgup = config_get(config, "key_pgup", "Page_Up");
1628 char* key_pgdn = config_get(config, "key_pgdn", "Page_Down");
1629 char* key_expand = config_get(config, "key_expand", "");
1630 char* key_hide_search = config_get(config, "key_hide_search", "");
1631
1632 parse_mods(key_up, move_up);
1633 parse_mods(key_down, move_down);
1634 parse_mods(key_left, move_left);
1635 parse_mods(key_right, move_right);
1636 parse_mods(key_forward, move_forward);
1637 parse_mods(key_backward, move_backward);
1638 parse_mods(key_submit, NULL); //submit is a special case, when a NULL action is encountered submit is used instead
1639 parse_mods(key_exit, do_exit);
1640 parse_mods(key_pgup, move_pgup);
1641 parse_mods(key_pgdn, move_pgdn);
1642 parse_mods(key_expand, do_expand);
1643 parse_mods(key_hide_search, do_hide_search);
1644
1645 modes = map_init_void();
1646
1647 if(lines > 0) {
1648 height = 1;
1649 }
1650
1651 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1652 gtk_widget_realize(window);
1653 gtk_widget_set_name(window, "window");
1654 gtk_window_set_default_size(GTK_WINDOW(window), width, height);
1655 gtk_window_resize(GTK_WINDOW(window), width, height);
1656 gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
1657 gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
1658
1659 if(!normal_window) {
1660 GdkDisplay* disp = gdk_display_get_default();
1661 wl_list_init(&outputs);
1662 wl = gdk_wayland_display_get_wl_display(disp);
1663
1664 if(wl == NULL) {
1665 fprintf(stderr, "Failed to connect to wayland compositor\n");
1666 exit(1);
1667 }
1668
1669 struct wl_registry* registry = wl_display_get_registry(wl);
1670 struct wl_registry_listener listener = {
1671 .global = add_interface,
1672 .global_remove = nop
1673 };
1674 wl_registry_add_listener(registry, &listener, NULL);
1675 wl_display_roundtrip(wl);
1676
1677 if(shell == NULL) {
1678 fprintf(stderr, "Compositor does not support wlr_layer_shell protocol, switching to normal window mode\n");
1679 normal_window = true;
1680 goto normal_win;
1681 }
1682
1683 struct output_node* node;
1684 wl_list_for_each(node, &outputs, link) {
1685 struct zxdg_output_v1* xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, node->output);
1686 struct zxdg_output_v1_listener* xdg_listener = malloc(sizeof(struct zxdg_output_v1_listener));
1687 xdg_listener->description = nop;
1688 xdg_listener->done = nop;
1689 xdg_listener->logical_position = get_output_pos;
1690 xdg_listener->logical_size = get_output_res;
1691 xdg_listener->name = get_output_name;
1692 zxdg_output_v1_add_listener(xdg_output, xdg_listener, node);
1693 }
1694 wl_display_roundtrip(wl);
1695
1696 ix = x == NULL ? 0 : strtol(x, NULL, 10);
1697 iy = y == NULL ? 0 : strtol(y, NULL, 10);
1698
1699 struct wl_output* output = NULL;
1700 if(global_coords) {
1701 wl_list_for_each(node, &outputs, link) {
1702 if(ix >= node->x && ix <= node->width + node->x
1703 && iy >= node->y && iy <= node->height + node->y) {
1704 output = node->output;
1705 ix -= node->x;
1706 iy -= node->y;
1707 break;
1708 }
1709 }
1710 } else if(monitor != NULL) {
1711 wl_list_for_each(node, &outputs, link) {
1712 if(strcmp(monitor, node->name) == 0) {
1713 output = node->output;
1714 break;
1715 }
1716 }
1717 }
1718
1719 GdkWindow* gdk_win = gtk_widget_get_window(window);
1720
1721 gdk_wayland_window_set_use_custom_surface(gdk_win);
1722 wl_surface = gdk_wayland_window_get_wl_surface(gdk_win);
1723
1724 enum zwlr_layer_shell_v1_layer wlr_layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
1725
1726 if(strcmp(layer, "background") == 0) {
1727 wlr_layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND;
1728 } else if(strcmp(layer, "bottom") == 0) {
1729 wlr_layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
1730 } else if(strcmp(layer, "top") == 0) {
1731 wlr_layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
1732 } else if(strcmp(layer, "overlay") == 0) {
1733 wlr_layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
1734 }
1735
1736 wlr_surface = zwlr_layer_shell_v1_get_layer_surface(shell, wl_surface, output, wlr_layer, "wofi");
1737 setup_surface(wlr_surface);
1738 struct zwlr_layer_surface_v1_listener* surface_listener = malloc(sizeof(struct zwlr_layer_surface_v1_listener));
1739 surface_listener->configure = config_surface;
1740 surface_listener->closed = nop;
1741 zwlr_layer_surface_v1_add_listener(wlr_surface, surface_listener, NULL);
1742 wl_surface_commit(wl_surface);
1743 wl_display_roundtrip(wl);
1744 }
1745
1746 normal_win:
1747
1748 outer_box = gtk_box_new(outer_orientation, 0);
1749 gtk_widget_set_name(outer_box, "outer-box");
1750 gtk_container_add(GTK_CONTAINER(window), outer_box);
1751 entry = gtk_search_entry_new();
1752
1753 gtk_widget_set_name(entry, "input");
1754 gtk_entry_set_placeholder_text(GTK_ENTRY(entry), prompt);
1755 gtk_container_add(GTK_CONTAINER(outer_box), entry);
1756 gtk_widget_set_child_visible(entry, !hide_search);
1757
1758 if(search != NULL) {
1759 gtk_entry_set_text(GTK_ENTRY(entry), search);
1760 }
1761
1762 if(password_char != NULL) {
1763 gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
1764 gtk_entry_set_invisible_char(GTK_ENTRY(entry), password_char[0]);
1765 }
1766
1767 scroll = gtk_scrolled_window_new(NULL, NULL);
1768 gtk_widget_set_name(scroll, "scroll");
1769 gtk_container_add(GTK_CONTAINER(outer_box), scroll);
1770 gtk_widget_set_size_request(scroll, width, height);
1771 if(hide_scroll) {
1772 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_EXTERNAL, GTK_POLICY_EXTERNAL);
1773 }
1774
1775 inner_box = gtk_flow_box_new();
1776 gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(inner_box), GTK_SELECTION_BROWSE);
1777 gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(inner_box), columns);
1778 gtk_orientable_set_orientation(GTK_ORIENTABLE(inner_box), orientation);
1779 gtk_widget_set_halign(inner_box, halign);
1780 gtk_widget_set_valign(inner_box, valign);
1781
1782 gtk_widget_set_name(inner_box, "inner-box");
1783 gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(inner_box), FALSE);
1784
1785 GtkWidget* wrapper_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1786 gtk_box_set_homogeneous(GTK_BOX(wrapper_box), TRUE);
1787 gtk_container_add(GTK_CONTAINER(wrapper_box), inner_box);
1788 gtk_container_add(GTK_CONTAINER(scroll), wrapper_box);
1789
1790 switch(matching) {
1791 case MATCHING_MODE_CONTAINS:
1792 gtk_flow_box_set_filter_func(GTK_FLOW_BOX(inner_box), do_filter, NULL, NULL);
1793 gtk_flow_box_set_sort_func(GTK_FLOW_BOX(inner_box), do_sort, NULL, NULL);
1794 break;
1795 case MATCHING_MODE_FUZZY:
1796 gtk_flow_box_set_sort_func(GTK_FLOW_BOX(inner_box), do_sort, NULL, NULL);
1797 break;
1798 }
1799
1800 g_signal_connect(inner_box, "child-activated", G_CALLBACK(activate_item), NULL);
1801 g_signal_connect(inner_box, "selected-children-changed", G_CALLBACK(select_item), NULL);
1802 g_signal_connect(entry, "activate", G_CALLBACK(activate_search), NULL);
1803 g_signal_connect(window, "key-press-event", G_CALLBACK(key_press), NULL);
1804 g_signal_connect(window, "focus-in-event", G_CALLBACK(focus), NULL);
1805 g_signal_connect(window, "focus-out-event", G_CALLBACK(focus), NULL);
1806 g_signal_connect(entry, "focus-in-event", G_CALLBACK(focus_entry), NULL);
1807 g_signal_connect(entry, "focus-out-event", G_CALLBACK(focus_entry), NULL);
1808 g_signal_connect(window, "destroy", G_CALLBACK(do_exit), NULL);
1809
1810 dbus = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, NULL, "sm.puri.OSK0", "/sm/puri/OSK0", "sm.puri.OSK0", NULL, NULL);
1811
1812 gdk_threads_add_timeout(filter_rate, do_search, NULL);
1813
1814
1815 bool width_percent = strchr(width_str, '%') != NULL;
1816 bool height_percent = strchr(height_str, '%') != NULL && lines == 0;
1817 if(width_percent || height_percent) {
1818 static char* geo_str[2];
1819 geo_str[0] = width_str;
1820 geo_str[1] = height_str;
1821 gdk_threads_add_timeout(50, do_percent_size, geo_str);
1822 do_percent_size(geo_str);
1823 }
1824
1825 wl_list_init(&mode_list);
1826
1827 pthread_create(&mode_thread, NULL, start_mode_thread, mode);
1828
1829 gdk_threads_add_idle(insert_all_widgets, &mode_list);
1830
1831 gtk_window_set_title(GTK_WINDOW(window), prompt);
1832 gtk_widget_show_all(window);
1833 }
1834