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