29#define G_LOG_DOMAIN "Modes.Window"
38#include <wayland-client.h>
51#include "wlr-foreign-toplevel-management-unstable-v1-protocol.h"
53#define WLR_FOREIGN_TOPLEVEL_VERSION 3
55enum WaylandWindowMatchingFields {
56 WW_MATCH_FIELD_TITLE = 1 << 0,
57 WW_MATCH_FIELD_APP_ID = 1 << 1,
58 WW_MATCH_FIELD_ALL = ~0,
61typedef struct _WaylandWindowModePrivateData {
63 struct wl_registry *registry;
64 struct zwlr_foreign_toplevel_manager_v1 *manager;
73} WaylandWindowModePrivateData;
75enum ForeignToplevelState {
76 TOPLEVEL_STATE_MAXIMIZED = 1
77 << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED,
78 TOPLEVEL_STATE_MINIMIZED = 1
79 << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED,
80 TOPLEVEL_STATE_ACTIVATED = 1
81 << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED,
82 TOPLEVEL_STATE_FULLSCREEN =
83 1 << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN,
84 TOPLEVEL_STATE_CLOSED = 1 << 4
88 struct zwlr_foreign_toplevel_handle_v1 *handle;
89 WaylandWindowModePrivateData *view;
97 unsigned int cached_icon_uid;
98 unsigned int cached_icon_size;
99 guint cached_icon_scale;
100} ForeignToplevelHandle;
102static void foreign_toplevel_handle_free(ForeignToplevelHandle *self) {
105 zwlr_foreign_toplevel_handle_v1_destroy(self->handle);
109 g_free(self->app_id);
113static void toplevels_list_update_max_len(gpointer data, gpointer user_data) {
114 WaylandWindowModePrivateData *pd = (WaylandWindowModePrivateData *)user_data;
115 ForeignToplevelHandle *entry = (ForeignToplevelHandle *)data;
117 pd->title_len = MAX(entry->title_len, pd->title_len);
118 pd->app_id_len = MAX(entry->app_id_len, pd->app_id_len);
122static void wayland_window_update_toplevel(ForeignToplevelHandle *toplevel) {
123 WaylandWindowModePrivateData *pd = toplevel->view;
127 toplevels_list_update_max_len(toplevel, pd);
132 g_list_foreach(pd->toplevels, toplevels_list_update_max_len, pd);
139static void foreign_toplevel_handle_activate(ForeignToplevelHandle *self,
140 struct wl_seat *seat) {
141 zwlr_foreign_toplevel_handle_v1_activate(self->handle, seat);
144static void foreign_toplevel_handle_close(ForeignToplevelHandle *self) {
145 zwlr_foreign_toplevel_handle_v1_close(self->handle);
150static void foreign_toplevel_handle_title(
151 void *data, G_GNUC_UNUSED
struct zwlr_foreign_toplevel_handle_v1 *handle,
153 ForeignToplevelHandle *self = (ForeignToplevelHandle *)data;
157 self->title = g_strdup(title);
158 self->title_len = g_utf8_strlen(self->title, -1);
161static void foreign_toplevel_handle_app_id(
162 void *data, G_GNUC_UNUSED
struct zwlr_foreign_toplevel_handle_v1 *handle,
163 const char *app_id) {
164 ForeignToplevelHandle *self = (ForeignToplevelHandle *)data;
166 g_free(self->app_id);
168 self->app_id = g_strdup(app_id);
169 self->app_id_len = g_utf8_strlen(self->app_id, -1);
172static void foreign_toplevel_handle_output_enter(
173 G_GNUC_UNUSED
void *data,
174 G_GNUC_UNUSED
struct zwlr_foreign_toplevel_handle_v1 *handle,
175 G_GNUC_UNUSED
struct wl_output *output) {
179static void foreign_toplevel_handle_output_leave(
180 G_GNUC_UNUSED
void *data,
181 G_GNUC_UNUSED
struct zwlr_foreign_toplevel_handle_v1 *handle,
182 G_GNUC_UNUSED
struct wl_output *output) {
186static void foreign_toplevel_handle_state(
187 void *data, G_GNUC_UNUSED
struct zwlr_foreign_toplevel_handle_v1 *handle,
188 struct wl_array *value) {
189 ForeignToplevelHandle *self = (ForeignToplevelHandle *)data;
193 wl_array_for_each(elem, value) { self->state |= 1 << *elem; }
196static void foreign_toplevel_handle_done(
197 void *data, G_GNUC_UNUSED
struct zwlr_foreign_toplevel_handle_v1 *handle) {
198 ForeignToplevelHandle *self = (ForeignToplevelHandle *)data;
200 g_debug(
"window %p id=%s title=%s state=%d\n", (
void *)self, self->app_id,
201 self->title, self->state);
203 wayland_window_update_toplevel(self);
206static void foreign_toplevel_handle_closed(
207 void *data, G_GNUC_UNUSED
struct zwlr_foreign_toplevel_handle_v1 *handle) {
208 ForeignToplevelHandle *self = (ForeignToplevelHandle *)data;
211 self->state = TOPLEVEL_STATE_CLOSED;
212 self->view->toplevels = g_list_remove(self->view->toplevels, self);
213 wayland_window_update_toplevel(self);
214 foreign_toplevel_handle_free(self);
217static void foreign_toplevel_handle_parent(
218 G_GNUC_UNUSED
void *data,
219 G_GNUC_UNUSED
struct zwlr_foreign_toplevel_handle_v1 *handle,
220 G_GNUC_UNUSED
struct zwlr_foreign_toplevel_handle_v1 *parent) {
224static struct zwlr_foreign_toplevel_handle_v1_listener
225 foreign_toplevel_handle_listener = {
226 .title = &foreign_toplevel_handle_title,
227 .app_id = &foreign_toplevel_handle_app_id,
228 .output_enter = &foreign_toplevel_handle_output_enter,
229 .output_leave = &foreign_toplevel_handle_output_leave,
230 .state = &foreign_toplevel_handle_state,
231 .done = &foreign_toplevel_handle_done,
232 .closed = &foreign_toplevel_handle_closed,
233 .parent = &foreign_toplevel_handle_parent};
235static ForeignToplevelHandle *
236foreign_toplevel_handle_new(
struct zwlr_foreign_toplevel_handle_v1 *handle,
237 WaylandWindowModePrivateData *view) {
238 ForeignToplevelHandle *self =
239 (ForeignToplevelHandle *)g_malloc0(
sizeof(ForeignToplevelHandle));
241 self->handle = handle;
243 zwlr_foreign_toplevel_handle_v1_add_listener(
244 handle, &foreign_toplevel_handle_listener, self);
248static void foreign_toplevel_manager_toplevel(
249 void *data, G_GNUC_UNUSED
struct zwlr_foreign_toplevel_manager_v1 *manager,
250 struct zwlr_foreign_toplevel_handle_v1 *toplevel) {
251 WaylandWindowModePrivateData *pd = (WaylandWindowModePrivateData *)data;
253 ForeignToplevelHandle *handle = foreign_toplevel_handle_new(toplevel, pd);
254 pd->toplevels = g_list_prepend(pd->toplevels, handle);
257static void foreign_toplevel_manager_finished(
258 G_GNUC_UNUSED
void *data,
259 struct zwlr_foreign_toplevel_manager_v1 *manager) {
260 zwlr_foreign_toplevel_manager_v1_destroy(manager);
263static struct zwlr_foreign_toplevel_manager_v1_listener
264 foreign_toplevel_manager_listener = {
265 .toplevel = &foreign_toplevel_manager_toplevel,
266 .finished = &foreign_toplevel_manager_finished};
268static void handle_global(
void *data,
struct wl_registry *registry,
269 uint32_t name,
const char *interface,
271 WaylandWindowModePrivateData *pd = (WaylandWindowModePrivateData *)data;
273 if (g_strcmp0(interface, zwlr_foreign_toplevel_manager_v1_interface.name) ==
276 pd->manager = (
struct zwlr_foreign_toplevel_manager_v1 *)wl_registry_bind(
277 registry, name, &zwlr_foreign_toplevel_manager_v1_interface,
278 MIN(version, WLR_FOREIGN_TOPLEVEL_VERSION));
282static void handle_global_remove(G_GNUC_UNUSED
void *data,
283 G_GNUC_UNUSED
struct wl_registry *registry,
284 G_GNUC_UNUSED uint32_t name) {}
286static struct wl_registry_listener registry_listener = {
287 .global = &handle_global, .global_remove = &handle_global_remove};
289static int wayland_window_mode_parse_fields(
void) {
293 char *switcher_str = g_strdup(
config.window_match_fields);
295 const char *
const sep =
",#";
296 for (
char *token = strtok_r(switcher_str, sep, &savept); token != NULL;
297 token = strtok_r(NULL, sep, &savept)) {
298 if (g_strcmp0(token,
"all") == 0) {
299 result |= WW_MATCH_FIELD_ALL;
301 }
else if (g_strcmp0(token,
"title") == 0) {
302 result |= WW_MATCH_FIELD_TITLE;
304 }
else if (g_strcmp0(token,
"class") == 0 ||
305 g_strcmp0(token,
"app-id") == 0) {
306 result |= WW_MATCH_FIELD_APP_ID;
309 g_warning(
"Unsupported window field name :%s. "
310 "Wayland window switcher supports only 'title' and 'app-id' "
315 g_free(switcher_str);
319static void get_wayland_window(
Mode *sw) {
320 WaylandWindowModePrivateData *pd =
323 pd->match_fields = wayland_window_mode_parse_fields();
324 pd->window_regex = g_regex_new(
"{[-\\w]+(:-?[0-9]+)?}", 0, 0, NULL);
328 pd->registry = wl_display_get_registry(
wayland->display);
329 wl_registry_add_listener(pd->registry, ®istry_listener, pd);
330 wl_display_roundtrip(
wayland->display);
332 if (pd->manager == NULL) {
333 g_warning(
"Unable to initialize Window mode: Wayland compositor does not "
334 "support wlr-foreign-toplevel-management protocol");
338 zwlr_foreign_toplevel_manager_v1_add_listener(
339 pd->manager, &foreign_toplevel_manager_listener, pd);
341 wl_display_roundtrip(
wayland->display);
345static void toplevels_list_item_free(gpointer data,
346 G_GNUC_UNUSED gpointer user_data) {
347 foreign_toplevel_handle_free((ForeignToplevelHandle *)data);
350static void wayland_window_private_free(WaylandWindowModePrivateData *pd) {
352 g_list_foreach(pd->toplevels, toplevels_list_item_free, NULL);
353 g_list_free(pd->toplevels);
354 pd->toplevels = NULL;
358 wl_registry_destroy(pd->registry);
363 zwlr_foreign_toplevel_manager_v1_stop(pd->manager);
365 wl_display_roundtrip(pd->wayland->display);
368 if (pd->window_regex) {
369 g_regex_unref(pd->window_regex);
375static int wayland_window_mode_init(
Mode *sw) {
380 WaylandWindowModePrivateData *pd =
381 (WaylandWindowModePrivateData *)g_malloc0(
382 sizeof(WaylandWindowModePrivateData));
385 get_wayland_window(sw);
390static unsigned int wayland_window_mode_get_num_entries(
const Mode *sw) {
391 const WaylandWindowModePrivateData *pd =
394 g_return_val_if_fail(pd != NULL, 0);
396 return g_list_length(pd->toplevels);
399static ModeMode wayland_window_mode_result(
Mode *sw,
int mretv,
400 G_GNUC_UNUSED
char **input,
401 unsigned int selected_line) {
403 WaylandWindowModePrivateData *pd =
406 g_return_val_if_fail(pd != NULL, retv);
414 }
else if ((mretv &
MENU_OK)) {
416 ForeignToplevelHandle *toplevel =
417 (ForeignToplevelHandle *)g_list_nth_data(pd->toplevels, selected_line);
418 foreign_toplevel_handle_activate(toplevel, pd->wayland->last_seat->seat);
419 wl_display_flush(pd->wayland->display);
422 ForeignToplevelHandle *toplevel =
423 (ForeignToplevelHandle *)g_list_nth_data(pd->toplevels, selected_line);
424 foreign_toplevel_handle_close(toplevel);
425 wl_display_flush(pd->wayland->display);
431static void wayland_window_mode_destroy(
Mode *sw) {
432 WaylandWindowModePrivateData *pd =
439 wayland_window_private_free(pd);
444 unsigned int index) {
445 WaylandWindowModePrivateData *pd =
447 ForeignToplevelHandle *toplevel =
448 (ForeignToplevelHandle *)g_list_nth_data(pd->toplevels, index);
450 g_return_val_if_fail(toplevel != NULL, 0);
455 for (
int j = 0; match && tokens[j] != NULL; j++) {
462 if ((pd->match_fields & WW_MATCH_FIELD_TITLE) &&
463 toplevel->title != NULL && toplevel->title[0] !=
'\0') {
467 if (test == tokens[j]->invert &&
468 (pd->match_fields & WW_MATCH_FIELD_APP_ID) &&
469 toplevel->app_id != NULL && toplevel->app_id[0] !=
'\0') {
482static void helper_eval_add_str(GString *str,
const char *input,
int len,
483 int max_len,
int nc) {
485 const char *input_nn = input ? input :
"";
490 int bl = g_utf8_offset_to_pointer(input_nn, len) - input_nn;
491 char *tmp = g_markup_escape_text(input_nn, bl);
492 g_string_append(str, tmp);
496 char *tmp = g_markup_escape_text(input_nn, -1);
497 g_string_append(str, tmp);
501 char *tmp = g_markup_escape_text(input_nn, -1);
502 g_string_append(str, tmp);
505 spaces = MAX(0, max_len - nc);
509 g_string_append_c(str,
' ');
514 const WaylandWindowModePrivateData *pd;
515 ForeignToplevelHandle *toplevel;
518static gboolean helper_eval_cb(
const GMatchInfo *info, GString *str,
520 struct arg *d = (
struct arg *)data;
523 match = g_match_info_fetch(info, 0);
526 if (match[2] ==
':') {
527 l = (int)g_ascii_strtoll(&match[3], NULL, 10);
532 helper_eval_add_str(str, d->toplevel->title, l, d->pd->title_len,
533 d->toplevel->title_len);
537 helper_eval_add_str(str, d->toplevel->app_id, l, d->pd->app_id_len,
538 d->toplevel->app_id_len);
547static char *_generate_display_string(
const WaylandWindowModePrivateData *pd,
548 ForeignToplevelHandle *toplevel) {
550 struct arg d = {pd, toplevel};
551 char *res = g_regex_replace_eval(pd->window_regex,
config.window_format, -1,
552 0, 0, helper_eval_cb, &d, NULL);
553 return g_strchomp(res);
557 int *state, G_GNUC_UNUSED GList **attr_list,
559 WaylandWindowModePrivateData *pd =
562 g_return_val_if_fail(pd != NULL, NULL);
564 ForeignToplevelHandle *toplevel =
565 (ForeignToplevelHandle *)g_list_nth_data(pd->toplevels, selected_line);
567 if (toplevel == NULL || toplevel->state & TOPLEVEL_STATE_CLOSED) {
568 return get_entry ? g_strdup(
"Window has vanished") : NULL;
572 if (toplevel->state & TOPLEVEL_STATE_ACTIVATED) {
577 return get_entry ? _generate_display_string(pd, toplevel) : NULL;
580static cairo_surface_t *
_get_icon(
const Mode *sw,
unsigned int selected_line,
581 unsigned int height) {
582 WaylandWindowModePrivateData *pd =
586 g_return_val_if_fail(pd != NULL, NULL);
588 ForeignToplevelHandle *toplevel =
589 (ForeignToplevelHandle *)g_list_nth_data(pd->toplevels, selected_line);
592 if (toplevel == NULL || toplevel->app_id == NULL ||
593 toplevel->app_id[0] ==
'\0') {
597 if (toplevel->cached_icon_uid > 0 && toplevel->cached_icon_size == height &&
598 toplevel->cached_icon_scale == scale) {
607 gchar *app_id_lower = g_utf8_strdown(toplevel->app_id, -1);
608 toplevel->cached_icon_size = height;
609 toplevel->cached_icon_scale = scale;
611 g_free(app_id_lower);
618Mode wayland_window_mode = {.name =
"window",
619 .cfg_name_key =
"display-window",
620 ._init = wayland_window_mode_init,
621 ._destroy = wayland_window_mode_destroy,
623 wayland_window_mode_get_num_entries,
624 ._result = wayland_window_mode_result,
625 ._token_match = wayland_window_token_match,
628 ._get_completion = NULL,
629 ._preprocess_input = NULL,
630 ._get_message = NULL,
631 .private_data = NULL,
guint display_scale(void)
static cairo_surface_t * _get_icon(const Mode *sw, unsigned int selected_line, unsigned int height)
static char * _get_display_value(const Mode *sw, unsigned int selected_line, G_GNUC_UNUSED int *state, G_GNUC_UNUSED GList **attr_list, int get_entry)
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
cairo_surface_t * rofi_icon_fetcher_get(const uint32_t uid)
uint32_t rofi_icon_fetcher_query(const char *name, const int size)
void * mode_get_private_data(const Mode *mode)
void mode_set_private_data(Mode *mode, void *pd)
void rofi_view_hide(void)
void rofi_view_reload(void)
struct rofi_int_matcher_t rofi_int_matcher