rofi 2.1.0
wayland-window.c
Go to the documentation of this file.
1/*
2 * rofi
3 *
4 * MIT/X11 License
5 * Copyright © 2013-2022 Qball Cow <qball@gmpclient.org>
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining
8 * a copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sublicense, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 *
26 */
27
29#define G_LOG_DOMAIN "Modes.Window"
30
31#include "config.h"
32
33#ifdef WINDOW_MODE
34
35#include <stdint.h>
36
37#include <glib.h>
38#include <wayland-client.h>
39
40#include "display.h"
41#include "helper.h"
43#include "rofi.h"
44#include "settings.h"
45#include "wayland-internal.h"
46#include "widgets/textbox.h"
47
48#include "mode-private.h"
49#include "rofi-icon-fetcher.h"
50
51#include "ext-foreign-toplevel-list-v1-protocol.h"
52#include "wlr-foreign-toplevel-management-unstable-v1-protocol.h"
53
54#define EXT_FOREIGN_TOPLEVEL_VERSION 1
55#define WLR_FOREIGN_TOPLEVEL_VERSION 3
56
57enum WaylandWindowMatchingFields {
58 WW_MATCH_FIELD_TITLE = 1 << 0,
59 WW_MATCH_FIELD_APP_ID = 1 << 1,
60 WW_MATCH_FIELD_ALL = ~0,
61};
62
63typedef struct _WaylandWindowModePrivateData {
64 wayland_stuff *wayland;
65 struct wl_registry *registry;
66 struct ext_foreign_toplevel_list_v1 *list;
67 struct zwlr_foreign_toplevel_manager_v1 *manager;
68 GList *ext_toplevels; /* List of ExtForeignToplevelHandle */
69 GList *wlr_toplevels; /* List of WlrForeignToplevelHandle */
70
71 /* initial rendering complete, updates allowed */
72 gboolean visible;
73 int match_fields; /* bitmask of WaylandWindowMatchingFields */
74 glong title_len;
75 glong app_id_len;
76 GRegex *window_regex;
77} WaylandWindowModePrivateData;
78
79enum ForeignToplevelState {
80 TOPLEVEL_STATE_MAXIMIZED = 1
81 << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED,
82 TOPLEVEL_STATE_MINIMIZED = 1
83 << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED,
84 TOPLEVEL_STATE_ACTIVATED = 1
85 << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED,
86 TOPLEVEL_STATE_FULLSCREEN =
87 1 << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN,
88 TOPLEVEL_STATE_CLOSED = 1 << 4
89};
90
91typedef struct {
92 struct ext_foreign_toplevel_handle_v1 *handle;
93 WaylandWindowModePrivateData *view;
94
95 gchar *app_id;
96 gchar *identifier;
97 gboolean used;
98} ExtForeignToplevelHandle;
99
100typedef struct {
101 struct zwlr_foreign_toplevel_handle_v1 *handle;
102 WaylandWindowModePrivateData *view;
103
104 gchar *app_id;
105 glong app_id_len;
106 gchar *title;
107 glong title_len;
108 int state;
109 gchar *identifier; /* set from ext-foreign-toplevel data */
110
111 unsigned int cached_icon_uid;
112 unsigned int cached_icon_size;
113 guint cached_icon_scale;
114} WlrForeignToplevelHandle;
115
116static void ext_foreign_toplevel_handle_free(ExtForeignToplevelHandle *self) {
117 if (self->handle) {
118 ext_foreign_toplevel_handle_v1_destroy(self->handle);
119 self->handle = NULL;
120 }
121 g_free(self->app_id);
122 g_free(self->identifier);
123 g_free(self);
124}
125
126static void wlr_foreign_toplevel_handle_free(WlrForeignToplevelHandle *self) {
127 if (self->handle) {
128 zwlr_foreign_toplevel_handle_v1_destroy(self->handle);
129 self->handle = NULL;
130 }
131 g_free(self->title);
132 g_free(self->app_id);
133 /* identifier might not have been set, if ext-foreign-toplevel never happened */
134 if (self->identifier) {
135 g_free(self->identifier);
136 self->identifier = NULL;
137 }
138 g_free(self);
139}
140
141/* Set the `identifier` of the first wlr toplevel with matching class that
142 * doesn't yet have an `identifier` */
143static void wlr_toplevels_set_one_identifier(gpointer data, gpointer user_data) {
144 GList *wlr_toplevels = (GList *)user_data;
145 ExtForeignToplevelHandle *entry = (ExtForeignToplevelHandle *)data;
146
147 if (entry->used) {
148 // this ext toplevel has already been matched to a wlr toplevel
149 return;
150 }
151 do {
152 WlrForeignToplevelHandle *toplevel =
153 (WlrForeignToplevelHandle *)(wlr_toplevels->data);
154
155 if (toplevel->identifier) continue;
156 if (g_strcmp0(toplevel->app_id, entry->app_id) != 0) continue;
157 // found the first wlr toplevel with matching class and no existing identifier
158 g_debug("matched ext window %p to wlr window %p via app_id=%s, "
159 "assigning identifier=%s",
160 (void *)entry, (void *)toplevel, entry->app_id, entry->identifier);
161 toplevel->identifier = g_strdup(entry->identifier);
162 entry->used = TRUE;
163 break;
164 } while ((wlr_toplevels = g_list_next(wlr_toplevels)) != NULL);
165}
166
167static void toplevels_list_update_max_len(gpointer data, gpointer user_data) {
168 WaylandWindowModePrivateData *pd = (WaylandWindowModePrivateData *)user_data;
169 WlrForeignToplevelHandle *entry = (WlrForeignToplevelHandle *)data;
170
171 pd->title_len = MAX(entry->title_len, pd->title_len);
172 pd->app_id_len = MAX(entry->app_id_len, pd->app_id_len);
173}
174
175/* Update column alignment and schedule reload */
176static void wayland_window_update_toplevel(WlrForeignToplevelHandle *toplevel) {
177 WaylandWindowModePrivateData *pd = toplevel->view;
178
179 if (!pd->visible) {
180 /* initial fetch, just add the current item */
181 toplevels_list_update_max_len(toplevel, pd);
182 } else {
183 /* async update, recalculate from scratch */
184 pd->title_len = 0;
185 pd->app_id_len = 0;
186 g_list_foreach(pd->wlr_toplevels, toplevels_list_update_max_len, pd);
188 }
189}
190
191/* requests */
192
193static void wlr_foreign_toplevel_handle_activate(WlrForeignToplevelHandle *self,
194 struct wl_seat *seat) {
195 zwlr_foreign_toplevel_handle_v1_activate(self->handle, seat);
196}
197
198static void wlr_foreign_toplevel_handle_close(WlrForeignToplevelHandle *self) {
199 zwlr_foreign_toplevel_handle_v1_close(self->handle);
200}
201
202/* events (ext-foreign-toplevel-list-v1) */
203
204static void ext_foreign_toplevel_handle_done(
205 void *data, G_GNUC_UNUSED struct ext_foreign_toplevel_handle_v1 *handle) {
206 ExtForeignToplevelHandle *self = (ExtForeignToplevelHandle *)data;
207
208 g_debug("ext window %p id=%s identifier=%s", (void *)self,
209 self->app_id, self->identifier);
210}
211
212static void ext_foreign_toplevel_handle_closed(
213 void *data, G_GNUC_UNUSED struct ext_foreign_toplevel_handle_v1 *handle) {
214 ExtForeignToplevelHandle *self = (ExtForeignToplevelHandle *)data;
215
216 /* the handle is inert and will receive no further events */
217 self->view->ext_toplevels = g_list_remove(self->view->ext_toplevels, self);
218 ext_foreign_toplevel_handle_free(self);
219}
220
221static void ext_foreign_toplevel_handle_identifier(
222 void *data, G_GNUC_UNUSED struct ext_foreign_toplevel_handle_v1 *handle,
223 const char *identifier) {
224 ExtForeignToplevelHandle *self = (ExtForeignToplevelHandle *)data;
225 if (self->identifier) {
226 g_free(self->identifier);
227 }
228 self->identifier = g_strdup(identifier);
229}
230
231static void ext_foreign_toplevel_handle_title(
232 G_GNUC_UNUSED void *data,
233 G_GNUC_UNUSED struct ext_foreign_toplevel_handle_v1 *handle,
234 G_GNUC_UNUSED const char *title) {
235 /* ignore */
236}
237
238static void ext_foreign_toplevel_handle_app_id(
239 void *data, G_GNUC_UNUSED struct ext_foreign_toplevel_handle_v1 *handle,
240 const char *app_id) {
241 ExtForeignToplevelHandle *self = (ExtForeignToplevelHandle *)data;
242 if (self->app_id) {
243 g_free(self->app_id);
244 }
245 self->app_id = g_strdup(app_id);
246}
247
248static struct ext_foreign_toplevel_handle_v1_listener
249 ext_foreign_toplevel_handle_listener = {
250 .identifier = &ext_foreign_toplevel_handle_identifier,
251 .title = &ext_foreign_toplevel_handle_title,
252 .app_id = &ext_foreign_toplevel_handle_app_id,
253 .closed = &ext_foreign_toplevel_handle_closed,
254 .done = &ext_foreign_toplevel_handle_done};
255
256static ExtForeignToplevelHandle *
257ext_foreign_toplevel_handle_new(struct ext_foreign_toplevel_handle_v1 *handle,
258 WaylandWindowModePrivateData *view) {
259 ExtForeignToplevelHandle *self =
260 (ExtForeignToplevelHandle *)g_malloc0(sizeof(ExtForeignToplevelHandle));
261
262 self->handle = handle;
263 self->view = view;
264 self->used = FALSE;
265 ext_foreign_toplevel_handle_v1_add_listener(
266 handle, &ext_foreign_toplevel_handle_listener, self);
267 return self;
268}
269
270static void ext_foreign_toplevel_list_toplevel(
271 void *data, G_GNUC_UNUSED struct ext_foreign_toplevel_list_v1 *list,
272 struct ext_foreign_toplevel_handle_v1 *toplevel) {
273 WaylandWindowModePrivateData *pd = (WaylandWindowModePrivateData *)data;
274
275 ExtForeignToplevelHandle *handle = ext_foreign_toplevel_handle_new(toplevel, pd);
276 pd->ext_toplevels = g_list_prepend(pd->ext_toplevels, handle);
277}
278
279static void ext_foreign_toplevel_list_finished(
280 G_GNUC_UNUSED void *data,
281 struct ext_foreign_toplevel_list_v1 *list) {
282 ext_foreign_toplevel_list_v1_destroy(list);
283}
284
285static struct ext_foreign_toplevel_list_v1_listener
286 ext_foreign_toplevel_list_listener = {
287 .toplevel = &ext_foreign_toplevel_list_toplevel,
288 .finished = &ext_foreign_toplevel_list_finished};
289
290/* events (wlr-foreign-toplevel-management-unstable-v1) */
291
292static void wlr_foreign_toplevel_handle_title(
293 void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle,
294 const char *title) {
295 WlrForeignToplevelHandle *self = (WlrForeignToplevelHandle *)data;
296 if (self->title) {
297 g_free(self->title);
298 }
299 self->title = g_strdup(title);
300 self->title_len = g_utf8_strlen(self->title, -1);
301}
302
303static void wlr_foreign_toplevel_handle_app_id(
304 void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle,
305 const char *app_id) {
306 WlrForeignToplevelHandle *self = (WlrForeignToplevelHandle *)data;
307 if (self->app_id) {
308 g_free(self->app_id);
309 }
310 self->app_id = g_strdup(app_id);
311 self->app_id_len = g_utf8_strlen(self->app_id, -1);
312}
313
314static void wlr_foreign_toplevel_handle_output_enter(
315 G_GNUC_UNUSED void *data,
316 G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle,
317 G_GNUC_UNUSED struct wl_output *output) {
318 /* ignore */
319}
320
321static void wlr_foreign_toplevel_handle_output_leave(
322 G_GNUC_UNUSED void *data,
323 G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle,
324 G_GNUC_UNUSED struct wl_output *output) {
325 /* ignore */
326}
327
328static void wlr_foreign_toplevel_handle_state(
329 void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle,
330 struct wl_array *value) {
331 WlrForeignToplevelHandle *self = (WlrForeignToplevelHandle *)data;
332 uint32_t *elem;
333
334 self->state = 0;
335 wl_array_for_each(elem, value) { self->state |= 1 << *elem; }
336}
337
338static void wlr_foreign_toplevel_handle_done(
339 void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle) {
340 WlrForeignToplevelHandle *self = (WlrForeignToplevelHandle *)data;
341
342 g_debug("wlr window %p id=%s title=%s state=%d", (void *)self,
343 self->app_id, self->title, self->state);
344
345 wayland_window_update_toplevel(self);
346}
347
348static void wlr_foreign_toplevel_handle_closed(
349 void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle) {
350 WlrForeignToplevelHandle *self = (WlrForeignToplevelHandle *)data;
351
352 /* the handle is inert and will receive no further events */
353 self->state = TOPLEVEL_STATE_CLOSED;
354 self->view->wlr_toplevels = g_list_remove(self->view->wlr_toplevels, self);
355 wayland_window_update_toplevel(self);
356 wlr_foreign_toplevel_handle_free(self);
357}
358
359static void wlr_foreign_toplevel_handle_parent(
360 G_GNUC_UNUSED void *data,
361 G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle,
362 G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *parent) {
363 /* ignore */
364}
365
366static struct zwlr_foreign_toplevel_handle_v1_listener
367 wlr_foreign_toplevel_handle_listener = {
368 .title = &wlr_foreign_toplevel_handle_title,
369 .app_id = &wlr_foreign_toplevel_handle_app_id,
370 .output_enter = &wlr_foreign_toplevel_handle_output_enter,
371 .output_leave = &wlr_foreign_toplevel_handle_output_leave,
372 .state = &wlr_foreign_toplevel_handle_state,
373 .done = &wlr_foreign_toplevel_handle_done,
374 .closed = &wlr_foreign_toplevel_handle_closed,
375 .parent = &wlr_foreign_toplevel_handle_parent};
376
377static WlrForeignToplevelHandle *
378wlr_foreign_toplevel_handle_new(struct zwlr_foreign_toplevel_handle_v1 *handle,
379 WaylandWindowModePrivateData *view) {
380 WlrForeignToplevelHandle *self =
381 (WlrForeignToplevelHandle *)g_malloc0(sizeof(WlrForeignToplevelHandle));
382
383 self->handle = handle;
384 self->view = view;
385 zwlr_foreign_toplevel_handle_v1_add_listener(
386 handle, &wlr_foreign_toplevel_handle_listener, self);
387 return self;
388}
389
390static void wlr_foreign_toplevel_manager_toplevel(
391 void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_manager_v1 *manager,
392 struct zwlr_foreign_toplevel_handle_v1 *toplevel) {
393 WaylandWindowModePrivateData *pd = (WaylandWindowModePrivateData *)data;
394
395 WlrForeignToplevelHandle *handle = wlr_foreign_toplevel_handle_new(toplevel, pd);
396 pd->wlr_toplevels = g_list_prepend(pd->wlr_toplevels, handle);
397}
398
399static void wlr_foreign_toplevel_manager_finished(
400 G_GNUC_UNUSED void *data,
401 struct zwlr_foreign_toplevel_manager_v1 *manager) {
402 zwlr_foreign_toplevel_manager_v1_destroy(manager);
403}
404
405static struct zwlr_foreign_toplevel_manager_v1_listener
406 wlr_foreign_toplevel_manager_listener = {
407 .toplevel = &wlr_foreign_toplevel_manager_toplevel,
408 .finished = &wlr_foreign_toplevel_manager_finished};
409
410static void handle_global(void *data, struct wl_registry *registry,
411 uint32_t name, const char *interface,
412 uint32_t version) {
413 WaylandWindowModePrivateData *pd = (WaylandWindowModePrivateData *)data;
414
415 if (g_strcmp0(interface, zwlr_foreign_toplevel_manager_v1_interface.name) ==
416 0) {
417
418 pd->manager = (struct zwlr_foreign_toplevel_manager_v1 *)wl_registry_bind(
419 registry, name, &zwlr_foreign_toplevel_manager_v1_interface,
420 MIN(version, WLR_FOREIGN_TOPLEVEL_VERSION));
421 }
422 else if (g_strcmp0(interface, ext_foreign_toplevel_list_v1_interface.name) ==
423 0) {
424 pd->list = (struct ext_foreign_toplevel_list_v1 *)wl_registry_bind(
425 registry, name, &ext_foreign_toplevel_list_v1_interface,
426 MIN(version, EXT_FOREIGN_TOPLEVEL_VERSION));
427 }
428}
429
430static void handle_global_remove(G_GNUC_UNUSED void *data,
431 G_GNUC_UNUSED struct wl_registry *registry,
432 G_GNUC_UNUSED uint32_t name) {}
433
434static struct wl_registry_listener registry_listener = {
435 .global = &handle_global, .global_remove = &handle_global_remove};
436
437static int wayland_window_mode_parse_fields(void) {
438 int result = 0;
439 char *savept = NULL;
440 // Make a copy, as strtok will modify it.
441 char *switcher_str = g_strdup(config.window_match_fields);
442
443 const char *const sep = ",#";
444 for (char *token = strtok_r(switcher_str, sep, &savept); token != NULL;
445 token = strtok_r(NULL, sep, &savept)) {
446 if (g_strcmp0(token, "all") == 0) {
447 result |= WW_MATCH_FIELD_ALL;
448
449 } else if (g_strcmp0(token, "title") == 0) {
450 result |= WW_MATCH_FIELD_TITLE;
451
452 } else if (g_strcmp0(token, "class") == 0 ||
453 g_strcmp0(token, "app-id") == 0) {
454 result |= WW_MATCH_FIELD_APP_ID;
455
456 } else {
457 g_warning("Unsupported window field name :%s. "
458 "Wayland window switcher supports only 'title' and 'app-id' "
459 "('class') fields",
460 token);
461 }
462 }
463 g_free(switcher_str);
464 return result;
465}
466
467static void get_wayland_window(Mode *sw) {
468 WaylandWindowModePrivateData *pd =
469 (WaylandWindowModePrivateData *)mode_get_private_data(sw);
470
471 pd->match_fields = wayland_window_mode_parse_fields();
472 pd->window_regex = g_regex_new("{[-\\w]+(:-?[0-9]+)?}", 0, 0, NULL);
473
474 pd->wayland = wayland;
475
476 pd->registry = wl_display_get_registry(wayland->display);
477 wl_registry_add_listener(pd->registry, &registry_listener, pd);
478 wl_display_roundtrip(wayland->display);
479
480 if (pd->manager == NULL) {
481 g_warning("Unable to initialize Window mode: Wayland compositor does not "
482 "support wlr-foreign-toplevel-management protocol");
483 return;
484 }
485
486 if (pd->list == NULL) {
487 g_warning("Window mode: Wayland compositor does not support "
488 "ext-foreign-toplevel-list protocol; window-command will not work");
489 /* don't need to abort, we'll check for empty identifiers when running window-command */
490 } else {
491 ext_foreign_toplevel_list_v1_add_listener(
492 pd->list, &ext_foreign_toplevel_list_listener, pd);
493 }
494
495 zwlr_foreign_toplevel_manager_v1_add_listener(
496 pd->manager, &wlr_foreign_toplevel_manager_listener, pd);
497 /* fetch initial set of windows */
498 wl_display_roundtrip(wayland->display);
499 pd->visible = TRUE;
500}
501
502static void wlr_toplevels_list_item_free(gpointer data,
503 G_GNUC_UNUSED gpointer user_data) {
504 wlr_foreign_toplevel_handle_free((WlrForeignToplevelHandle *)data);
505}
506
507static void wayland_window_private_free(WaylandWindowModePrivateData *pd) {
508 if (pd->wlr_toplevels) {
509 g_list_foreach(pd->wlr_toplevels, wlr_toplevels_list_item_free, NULL);
510 g_list_free(pd->wlr_toplevels);
511 pd->wlr_toplevels = NULL;
512 }
513
514 if (pd->registry) {
515 wl_registry_destroy(pd->registry);
516 pd->registry = NULL;
517 }
518
519 if (pd->manager) {
520 zwlr_foreign_toplevel_manager_v1_stop(pd->manager);
521 pd->manager = NULL;
522 wl_display_roundtrip(pd->wayland->display);
523 }
524
525 if (pd->window_regex) {
526 g_regex_unref(pd->window_regex);
527 }
528
529 g_free(pd);
530}
531
532static int wayland_window_mode_init(Mode *sw) {
536 if (mode_get_private_data(sw) == NULL) {
537 WaylandWindowModePrivateData *pd =
538 (WaylandWindowModePrivateData *)g_malloc0(
539 sizeof(WaylandWindowModePrivateData));
540 mode_set_private_data(sw, (void *)pd);
541
542 get_wayland_window(sw);
543 }
544 return TRUE;
545}
546
547static unsigned int wayland_window_mode_get_num_entries(const Mode *sw) {
548 const WaylandWindowModePrivateData *pd =
549 (const WaylandWindowModePrivateData *)mode_get_private_data(sw);
550
551 g_return_val_if_fail(pd != NULL, 0);
552
553 return g_list_length(pd->wlr_toplevels);
554}
555
556static inline int wayland_act_on_window(WlrForeignToplevelHandle *toplevel,
557 WaylandWindowModePrivateData *pd) {
558 // match up wlr to ext toplevels, and set their identifiers
559 if (toplevel->identifier == NULL) {
560 /* NOTE: This assumes toplevels of the same class are received in the same
561 * order via both the wlr and ext protocols! i.e. if we get the order
562 * [firefox, kitty1, kitty2] via wlr and [kitty1, firefox, kitty2] via ext,
563 * that's fine, because the two kitty instances are in the same order.
564 * However, getting [kitty2, firefox, kitty1] via ext would break our
565 * mapping of ext to wlr toplevels, so a window-command on kitty1 would
566 * target kitty2 instead.
567 */
568 g_list_foreach(pd->ext_toplevels, wlr_toplevels_set_one_identifier,
569 pd->wlr_toplevels);
570 }
571 if (toplevel->identifier == NULL) {
573 "Couldn't resolve {window} identifier for the selected window",
574 FALSE);
575 return FALSE;
576 }
577 g_debug("resolved selected window to identifier %s", toplevel->identifier);
578
579 int retv = TRUE;
580 char **args = NULL;
581 int argc = 0;
582
583 helper_parse_setup(config.window_command, &args, &argc, "{window}",
584 toplevel->identifier, (char *)0);
585
586 GError *error = NULL;
587 g_spawn_async(NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL,
588 &error);
589 if (error != NULL) {
590 char *msg = g_strdup_printf(
591 "Failed to execute action for window: '%s'\nError: '%s'",
592 toplevel->identifier, error->message);
593 rofi_view_error_dialog(msg, FALSE);
594 g_free(msg);
595 // print error.
596 g_error_free(error);
597 retv = FALSE;
598 }
599
600 // Free the args list.
601 g_strfreev(args);
602 return retv;
603}
604
605static ModeMode wayland_window_mode_result(Mode *sw, int mretv,
606 G_GNUC_UNUSED char **input,
607 unsigned int selected_line) {
608 ModeMode retv = MODE_EXIT;
609 WaylandWindowModePrivateData *pd =
610 (WaylandWindowModePrivateData *)mode_get_private_data(sw);
611
612 g_return_val_if_fail(pd != NULL, retv);
613
614 if (mretv & MENU_NEXT) {
615 retv = NEXT_DIALOG;
616 } else if (mretv & MENU_PREVIOUS) {
617 retv = PREVIOUS_DIALOG;
618 } else if (mretv & MENU_QUICK_SWITCH) {
619 retv = (ModeMode)(mretv & MENU_LOWER_MASK);
620 } else if ((mretv & MENU_OK)) {
622 WlrForeignToplevelHandle *toplevel =
623 (WlrForeignToplevelHandle *)g_list_nth_data(pd->wlr_toplevels, selected_line);
624 if (mretv & MENU_CUSTOM_ACTION) {
625 // Run custom command on the window
626 wayland_act_on_window(toplevel, pd);
627 } else {
628 // Activate the window normally
629 wlr_foreign_toplevel_handle_activate(
630 toplevel, pd->wayland->last_seat->seat);
631 wl_display_flush(pd->wayland->display);
632 }
633
634 } else if ((mretv & MENU_ENTRY_DELETE) == MENU_ENTRY_DELETE) {
635 WlrForeignToplevelHandle *toplevel =
636 (WlrForeignToplevelHandle *)g_list_nth_data(pd->wlr_toplevels, selected_line);
637 wlr_foreign_toplevel_handle_close(toplevel);
638 wl_display_flush(pd->wayland->display);
639 ThemeWidget *wid = rofi_config_find_widget(sw->name, NULL, TRUE);
640 Property *p =
641 rofi_theme_find_property(wid, P_BOOLEAN, "close-on-delete", TRUE);
642 if (p && p->type == P_BOOLEAN && p->value.b == FALSE) {
643 return RELOAD_DIALOG;
644 }
645
646 } else if ((mretv & MENU_CUSTOM_INPUT) && *input != NULL &&
647 *input[0] != '\0') {
648 GError *error = NULL;
649 gboolean run_in_term = ((mretv & MENU_CUSTOM_ACTION) == MENU_CUSTOM_ACTION);
650 gsize lf_cmd_size = 0;
651 gchar *lf_cmd = g_locale_from_utf8(*input, -1, NULL, &lf_cmd_size, &error);
652 if (error != NULL) {
653 g_warning("Failed to convert command to locale encoding: %s",
654 error->message);
655 g_error_free(error);
656 return RELOAD_DIALOG;
657 }
658
659 RofiHelperExecuteContext context = {.name = NULL};
660 if (!helper_execute_command(NULL, lf_cmd, run_in_term,
661 run_in_term ? &context : NULL)) {
662 retv = RELOAD_DIALOG;
663 }
664 g_free(lf_cmd);
665 } else if (mretv & MENU_CUSTOM_COMMAND) {
666 retv = (mretv & MENU_LOWER_MASK);
667 }
668
669 return retv;
670}
671
672static void wayland_window_mode_destroy(Mode *sw) {
673 WaylandWindowModePrivateData *pd =
674 (WaylandWindowModePrivateData *)mode_get_private_data(sw);
675
676 if (pd == NULL) {
677 return;
678 }
679
680 wayland_window_private_free(pd);
681 mode_set_private_data(sw, NULL);
682}
683
684static int wayland_window_token_match(const Mode *sw, rofi_int_matcher **tokens,
685 unsigned int index) {
686 WaylandWindowModePrivateData *pd =
687 (WaylandWindowModePrivateData *)mode_get_private_data(sw);
688 WlrForeignToplevelHandle *toplevel =
689 (WlrForeignToplevelHandle *)g_list_nth_data(pd->wlr_toplevels, index);
690
691 g_return_val_if_fail(toplevel != NULL, 0);
692
693 int match = TRUE;
694
695 if (tokens) {
696 for (int j = 0; match && tokens[j] != NULL; j++) {
697 int test = 0;
698 /* See comment in window.c;
699 * for each token we want to match at least one field.
700 */
701 rofi_int_matcher *ftokens[2] = {tokens[j], NULL};
702
703 if ((pd->match_fields & WW_MATCH_FIELD_TITLE) &&
704 toplevel->title != NULL && toplevel->title[0] != '\0') {
705 test = helper_token_match(ftokens, toplevel->title);
706 }
707
708 if (test == tokens[j]->invert &&
709 (pd->match_fields & WW_MATCH_FIELD_APP_ID) &&
710 toplevel->app_id != NULL && toplevel->app_id[0] != '\0') {
711 test = helper_token_match(ftokens, toplevel->app_id);
712 }
713
714 if (test == 0) {
715 match = 0;
716 }
717 }
718 }
719
720 return match;
721}
722
723static void helper_eval_add_str(GString *str, const char *input, int len,
724 int max_len, int nc) {
725 // g_utf8 does not work with NULL string.
726 const char *input_nn = input ? input : "";
727 // Both len and max_len are in characters, not bytes.
728 int spaces = 0;
729 if (len > 0) {
730 if (nc > len) {
731 int bl = g_utf8_offset_to_pointer(input_nn, len) - input_nn;
732 char *tmp = g_markup_escape_text(input_nn, bl);
733 g_string_append(str, tmp);
734 g_free(tmp);
735 } else {
736 spaces = len - nc;
737 char *tmp = g_markup_escape_text(input_nn, -1);
738 g_string_append(str, tmp);
739 g_free(tmp);
740 }
741 } else {
742 char *tmp = g_markup_escape_text(input_nn, -1);
743 g_string_append(str, tmp);
744 g_free(tmp);
745 if (len == 0) {
746 spaces = MAX(0, max_len - nc);
747 }
748 }
749 while (spaces--) {
750 g_string_append_c(str, ' ');
751 }
752}
753
754struct arg {
755 const WaylandWindowModePrivateData *pd;
756 WlrForeignToplevelHandle *toplevel;
757};
758
759static gboolean helper_eval_cb(const GMatchInfo *info, GString *str,
760 gpointer data) {
761 struct arg *d = (struct arg *)data;
762 gchar *match;
763 // Get the match
764 match = g_match_info_fetch(info, 0);
765 if (match != NULL) {
766 int l = 0;
767 if (match[2] == ':') {
768 l = (int)g_ascii_strtoll(&match[3], NULL, 10);
769 }
770 /* Most of the arguments are not supported on wayland */
771 switch (match[1]) {
772 case 't': /* title */
773 helper_eval_add_str(str, d->toplevel->title, l, d->pd->title_len,
774 d->toplevel->title_len);
775 break;
776 case 'a': /* app_id */
777 case 'c': /* class */
778 helper_eval_add_str(str, d->toplevel->app_id, l, d->pd->app_id_len,
779 d->toplevel->app_id_len);
780 break;
781 }
782
783 g_free(match);
784 }
785 return FALSE;
786}
787
788static char *_generate_display_string(const WaylandWindowModePrivateData *pd,
789 WlrForeignToplevelHandle *toplevel) {
790
791 struct arg d = {pd, toplevel};
792 char *res = g_regex_replace_eval(pd->window_regex, config.window_format, -1,
793 0, 0, helper_eval_cb, &d, NULL);
794 return g_strchomp(res);
795}
796
797static char *_get_display_value(const Mode *sw, unsigned int selected_line,
798 int *state, G_GNUC_UNUSED GList **attr_list,
799 int get_entry) {
800 WaylandWindowModePrivateData *pd =
801 (WaylandWindowModePrivateData *)mode_get_private_data(sw);
802
803 g_return_val_if_fail(pd != NULL, NULL);
804
805 WlrForeignToplevelHandle *toplevel =
806 (WlrForeignToplevelHandle *)g_list_nth_data(pd->wlr_toplevels, selected_line);
807
808 if (toplevel == NULL || toplevel->state & TOPLEVEL_STATE_CLOSED) {
809 return get_entry ? g_strdup("Window has vanished") : NULL;
810 }
811
812 /* This may not work because layer-surface holds focus */
813 if (toplevel->state & TOPLEVEL_STATE_ACTIVATED) {
814 *state |= ACTIVE;
815 }
816 *state |= MARKUP;
817
818 return get_entry ? _generate_display_string(pd, toplevel) : NULL;
819}
820
821static cairo_surface_t *_get_icon(const Mode *sw, unsigned int selected_line,
822 unsigned int height) {
823 WaylandWindowModePrivateData *pd =
824 (WaylandWindowModePrivateData *)mode_get_private_data(sw);
825 const guint scale = display_scale();
826
827 g_return_val_if_fail(pd != NULL, NULL);
828
829 WlrForeignToplevelHandle *toplevel =
830 (WlrForeignToplevelHandle *)g_list_nth_data(pd->wlr_toplevels, selected_line);
831
832 /* some apps don't have app_id (WM_CLASS). this is fine */
833 if (toplevel == NULL || toplevel->app_id == NULL ||
834 toplevel->app_id[0] == '\0') {
835 return NULL;
836 }
837
838 if (toplevel->cached_icon_uid > 0 && toplevel->cached_icon_size == height &&
839 toplevel->cached_icon_scale == scale) {
840 return rofi_icon_fetcher_get(toplevel->cached_icon_uid);
841 }
842
848 gchar *app_id_lower = g_utf8_strdown(toplevel->app_id, -1);
849 toplevel->cached_icon_size = height;
850 toplevel->cached_icon_scale = scale;
851 toplevel->cached_icon_uid = rofi_icon_fetcher_query(app_id_lower, height);
852 g_free(app_id_lower);
853
854 return rofi_icon_fetcher_get(toplevel->cached_icon_uid);
855}
856
857#include "mode-private.h"
858
859Mode wayland_window_mode = {.name = "window",
860 .cfg_name_key = "display-window",
861 ._init = wayland_window_mode_init,
862 ._destroy = wayland_window_mode_destroy,
863 ._get_num_entries =
864 wayland_window_mode_get_num_entries,
865 ._result = wayland_window_mode_result,
866 ._token_match = wayland_window_token_match,
867 ._get_display_value = _get_display_value,
868 ._get_icon = _get_icon,
869 ._get_completion = NULL,
870 ._preprocess_input = NULL,
871 ._get_message = NULL,
872 .private_data = NULL,
873 .free = NULL,
874 .type = MODE_TYPE_SWITCHER};
875
876#endif // WINDOW_MODE
guint display_scale(void)
Definition display.c:78
Property * rofi_theme_find_property(ThemeWidget *wid, PropertyType type, const char *property, gboolean exact)
Definition theme.c:745
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
Definition helper.c:1457
ThemeWidget * rofi_config_find_widget(const char *name, const char *state, gboolean exact)
Definition theme.c:782
int helper_parse_setup(char *string, char ***output, int *length,...)
Definition helper.c:102
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition helper.c:541
cairo_surface_t * rofi_icon_fetcher_get(const uint32_t uid)
uint32_t rofi_icon_fetcher_query(const char *name, const int size)
struct rofi_mode Mode
Definition mode.h:49
void * mode_get_private_data(const Mode *mode)
Definition mode.c:176
void mode_set_private_data(Mode *mode, void *pd)
Definition mode.c:181
ModeMode
Definition mode.h:54
@ MENU_CUSTOM_COMMAND
Definition mode.h:84
@ MENU_LOWER_MASK
Definition mode.h:92
@ MENU_PREVIOUS
Definition mode.h:86
@ MENU_QUICK_SWITCH
Definition mode.h:82
@ MENU_ENTRY_DELETE
Definition mode.h:80
@ MENU_NEXT
Definition mode.h:76
@ MENU_CUSTOM_ACTION
Definition mode.h:90
@ MENU_OK
Definition mode.h:72
@ MENU_CUSTOM_INPUT
Definition mode.h:78
@ MODE_EXIT
Definition mode.h:56
@ NEXT_DIALOG
Definition mode.h:58
@ RELOAD_DIALOG
Definition mode.h:60
@ PREVIOUS_DIALOG
Definition mode.h:62
@ ACTIVE
Definition textbox.h:109
@ MARKUP
Definition textbox.h:113
void rofi_view_hide(void)
Definition view.c:2219
void rofi_view_reload(void)
Definition view.c:2221
int rofi_view_error_dialog(const char *msg, int markup)
Definition view.c:1980
@ MODE_TYPE_SWITCHER
@ P_BOOLEAN
Definition rofi-types.h:18
struct rofi_int_matcher_t rofi_int_matcher
Settings config
PropertyValue value
Definition rofi-types.h:293
PropertyType type
Definition rofi-types.h:291
char * window_format
Definition settings.h:176
char * window_command
Definition settings.h:98
char * window_match_fields
Definition settings.h:100
char * name
struct wl_display * display
struct wl_registry * registry
wayland_stuff * wayland
Definition display.c:120