From 1e4ef36611e0032086cbd04ac30ef6828cbe581f Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 31 Aug 2022 23:29:29 -0700 Subject: [PATCH] [Window] Initial support for wayland window switcher The code still lacks a lot of features of XCB Window mode, but at least following should work: - Activate and Delete (close) actions - matching by window title - live update - icon lookup by app_id --- include/modes/modes.h | 1 + include/modes/wayland-window.h | 45 ++ include/modes/window.h | 4 +- meson.build | 6 +- ...oreign-toplevel-management-unstable-v1.xml | 270 ++++++++++ source/modes/wayland-window.c | 469 ++++++++++++++++++ source/rofi.c | 7 + 7 files changed, 798 insertions(+), 4 deletions(-) create mode 100644 include/modes/wayland-window.h create mode 100644 protocols/wlr-foreign-toplevel-management-unstable-v1.xml create mode 100644 source/modes/wayland-window.c diff --git a/include/modes/modes.h b/include/modes/modes.h index 72b8b6ee..5e507123 100644 --- a/include/modes/modes.h +++ b/include/modes/modes.h @@ -43,5 +43,6 @@ #include "modes/run.h" #include "modes/script.h" #include "modes/ssh.h" +#include "modes/wayland-window.h" #include "modes/window.h" #endif // ROFI_MODES_MODES_H diff --git a/include/modes/wayland-window.h b/include/modes/wayland-window.h new file mode 100644 index 00000000..16dacaab --- /dev/null +++ b/include/modes/wayland-window.h @@ -0,0 +1,45 @@ +/* + * rofi + * + * MIT/X11 License + * Copyright © 2013-2022 Qball Cow + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef ROFI_MODE_WAYLAND_WINDOW_H +#define ROFI_MODE_WAYLAND_WINDOW_H + +#include "mode.h" + +/** + * @defgroup WINDOWMode Window + * @ingroup MODES + * + * @{ + */ +#if defined(WINDOW_MODE) && defined(ENABLE_WAYLAND) + +extern Mode wayland_window_mode; + +#endif +/** @}*/ +#endif // ROFI_MODE_WAYLAND_WINDOW_H diff --git a/include/modes/window.h b/include/modes/window.h index 58dff0df..8f8c9561 100644 --- a/include/modes/window.h +++ b/include/modes/window.h @@ -36,12 +36,12 @@ * * @{ */ -#ifdef WINDOW_MODE +#if defined(WINDOW_MODE) && defined(ENABLE_XCB) extern Mode window_mode; extern Mode window_mode_cd; void window_client_handle_signal(xcb_window_t win, gboolean create); -#endif // WINDOW_MODE +#endif // defined(WINDOW_MODE) && defined(ENABLE_XCB) /** @}*/ #endif // ROFI_MODE_WINDOW_H diff --git a/meson.build b/meson.build index 99fabd3d..b4ac2439 100644 --- a/meson.build +++ b/meson.build @@ -111,8 +111,7 @@ header_conf.set('GLIB_VERSION_MIN_REQUIRED', '(G_ENCODE_VERSION(@0@,@1@))'.forma header_conf.set('GLIB_VERSION_MAX_ALLOWED', '(G_ENCODE_VERSION(@0@,@1@))'.format(glib_min_major, glib_min_minor)) header_conf.set('ENABLE_DRUN', get_option('drun')) -# Window mode is not supported in the Wayland backend yet. -header_conf.set('WINDOW_MODE', get_option('window') and xcb_enabled) +header_conf.set('WINDOW_MODE', get_option('window')) header_conf.set('ENABLE_WAYLAND', wayland_enabled) header_conf.set('ENABLE_XCB', xcb_enabled) @@ -264,6 +263,7 @@ if wayland_enabled protocols = files( wayland_sys_protocols_dir + '/stable/xdg-shell/xdg-shell.xml', wayland_sys_protocols_dir + '/unstable/primary-selection/primary-selection-unstable-v1.xml', + 'protocols/wlr-foreign-toplevel-management-unstable-v1.xml', 'protocols/wlr-layer-shell-unstable-v1.xml', ) proto_srcs = [] @@ -286,8 +286,10 @@ if wayland_enabled rofi_sources += proto_srcs rofi_sources += proto_headers rofi_sources += files( + 'source/modes/wayland-window.c', 'source/wayland/view.c', 'source/wayland/display.c', + 'include/modes/wayland-window.h', 'include/wayland-internal.h', ) endif diff --git a/protocols/wlr-foreign-toplevel-management-unstable-v1.xml b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 00000000..44505bbb --- /dev/null +++ b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,270 @@ + + + + Copyright © 2018 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + + + + + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + + + + + + + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app-id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + + + + + + + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + + + + + + + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + + + + + + + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + + + + + + + + + + + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + + + + + + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + + + + + + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + + + + + + + + + + + + + + + + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + + + + + + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + + + + + + + + Requests that the toplevel be fullscreened on the given output. If the + fullscreen state and/or the outputs the toplevel is visible on actually + change, this will be indicated by the state and output_enter/leave + events. + + The output parameter is only a hint to the compositor. Also, if output + is NULL, the compositor should decide which output the toplevel will be + fullscreened on, if at all. + + + + + + + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + + + + + + + + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + + + + + diff --git a/source/modes/wayland-window.c b/source/modes/wayland-window.c new file mode 100644 index 00000000..b4646fb7 --- /dev/null +++ b/source/modes/wayland-window.c @@ -0,0 +1,469 @@ +/* + * rofi + * + * MIT/X11 License + * Copyright © 2013-2022 Qball Cow + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/** The log domain of this dialog. */ +#define G_LOG_DOMAIN "Modes.Window" + +#include "config.h" + +#ifdef WINDOW_MODE + +#include + +#include +#include + +#include "helper.h" +#include "modes/wayland-window.h" +#include "rofi.h" +#include "settings.h" +#include "wayland-internal.h" +#include "widgets/textbox.h" + +#include "mode-private.h" +#include "rofi-icon-fetcher.h" + +#include "wlr-foreign-toplevel-management-unstable-v1-protocol.h" + +#define WLR_FOREIGN_TOPLEVEL_VERSION 3 + +typedef struct _WaylandWindowModePrivateData { + wayland_stuff *wayland; + struct wl_registry *registry; + struct zwlr_foreign_toplevel_manager_v1 *manager; + GList *toplevels; /* List of ForeignToplevelHandle */ + + /* initial rendering complete, updates allowed */ + gboolean visible; +} WaylandWindowModePrivateData; + +enum ForeignToplevelState { + TOPLEVEL_STATE_MAXIMIZED = 1 + << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED, + TOPLEVEL_STATE_MINIMIZED = 1 + << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED, + TOPLEVEL_STATE_ACTIVATED = 1 + << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED, + TOPLEVEL_STATE_FULLSCREEN = + 1 << ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN, + TOPLEVEL_STATE_CLOSED = 1 << 4 +}; + +typedef struct { + struct zwlr_foreign_toplevel_handle_v1 *handle; + WaylandWindowModePrivateData *view; + gchar *app_id; + gchar *title; + int state; + + unsigned int cached_icon_uid; + unsigned int cached_icon_size; +} ForeignToplevelHandle; + +static void foreign_toplevel_handle_free(ForeignToplevelHandle *self) { + + if (self->handle) { + zwlr_foreign_toplevel_handle_v1_destroy(self->handle); + self->handle = NULL; + } + g_free(self->title); + g_free(self->app_id); + g_free(self); +} + +/* requests */ + +static void foreign_toplevel_handle_activate(ForeignToplevelHandle *self, + struct wl_seat *seat) { + zwlr_foreign_toplevel_handle_v1_activate(self->handle, seat); +} + +static void foreign_toplevel_handle_close(ForeignToplevelHandle *self) { + zwlr_foreign_toplevel_handle_v1_close(self->handle); +} + +/* events */ + +static void foreign_toplevel_handle_title( + void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle, + const char *title) { + ForeignToplevelHandle *self = (ForeignToplevelHandle *)data; + if (self->title) { + g_free(self->title); + } + self->title = g_strdup(title); +} + +static void foreign_toplevel_handle_app_id( + void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle, + const char *app_id) { + ForeignToplevelHandle *self = (ForeignToplevelHandle *)data; + if (self->app_id) { + g_free(self->app_id); + } + self->app_id = g_strdup(app_id); +} + +static void foreign_toplevel_handle_output_enter( + G_GNUC_UNUSED void *data, + G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle, + G_GNUC_UNUSED struct wl_output *output) { + /* ignore */ +} + +static void foreign_toplevel_handle_output_leave( + G_GNUC_UNUSED void *data, + G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle, + G_GNUC_UNUSED struct wl_output *output) { + /* ignore */ +} + +static void foreign_toplevel_handle_state( + void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle, + struct wl_array *value) { + ForeignToplevelHandle *self = (ForeignToplevelHandle *)data; + uint32_t *elem; + + self->state = 0; + wl_array_for_each(elem, value) { self->state |= 1 << *elem; } +} + +static void foreign_toplevel_handle_done( + void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle) { + ForeignToplevelHandle *self = (ForeignToplevelHandle *)data; + + g_debug("window %p id=%s title=%s state=%d\n", (void *)self, self->app_id, + self->title, self->state); + + if (self->view->visible) { + rofi_view_reload(); + } +} + +static void foreign_toplevel_handle_closed( + void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle) { + ForeignToplevelHandle *self = (ForeignToplevelHandle *)data; + + /* the handle is inert and will receive no further events */ + self->state = TOPLEVEL_STATE_CLOSED; + self->view->toplevels = g_list_remove(self->view->toplevels, self); + if (self->view->visible) { + rofi_view_reload(); + } + foreign_toplevel_handle_free(self); +} + +static void foreign_toplevel_handle_parent( + G_GNUC_UNUSED void *data, + G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *handle, + G_GNUC_UNUSED struct zwlr_foreign_toplevel_handle_v1 *parent) { + /* ignore */ +} + +static struct zwlr_foreign_toplevel_handle_v1_listener + foreign_toplevel_handle_listener = { + .title = &foreign_toplevel_handle_title, + .app_id = &foreign_toplevel_handle_app_id, + .output_enter = &foreign_toplevel_handle_output_enter, + .output_leave = &foreign_toplevel_handle_output_leave, + .state = &foreign_toplevel_handle_state, + .done = &foreign_toplevel_handle_done, + .closed = &foreign_toplevel_handle_closed, + .parent = &foreign_toplevel_handle_parent}; + +static ForeignToplevelHandle * +foreign_toplevel_handle_new(struct zwlr_foreign_toplevel_handle_v1 *handle, + WaylandWindowModePrivateData *view) { + ForeignToplevelHandle *self = + (ForeignToplevelHandle *)g_malloc0(sizeof(ForeignToplevelHandle)); + + self->handle = handle; + self->view = view; + zwlr_foreign_toplevel_handle_v1_add_listener( + handle, &foreign_toplevel_handle_listener, self); + return self; +} + +static void foreign_toplevel_manager_toplevel( + void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_manager_v1 *manager, + struct zwlr_foreign_toplevel_handle_v1 *toplevel) { + WaylandWindowModePrivateData *pd = (WaylandWindowModePrivateData *)data; + + ForeignToplevelHandle *handle = foreign_toplevel_handle_new(toplevel, pd); + pd->toplevels = g_list_prepend(pd->toplevels, handle); +} + +static void foreign_toplevel_manager_finished( + G_GNUC_UNUSED void *data, + struct zwlr_foreign_toplevel_manager_v1 *manager) { + zwlr_foreign_toplevel_manager_v1_destroy(manager); +} + +static struct zwlr_foreign_toplevel_manager_v1_listener + foreign_toplevel_manager_listener = { + .toplevel = &foreign_toplevel_manager_toplevel, + .finished = &foreign_toplevel_manager_finished}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, + uint32_t version) { + WaylandWindowModePrivateData *pd = (WaylandWindowModePrivateData *)data; + + if (g_strcmp0(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == + 0) { + + pd->manager = (struct zwlr_foreign_toplevel_manager_v1 *)wl_registry_bind( + registry, name, &zwlr_foreign_toplevel_manager_v1_interface, + MIN(version, WLR_FOREIGN_TOPLEVEL_VERSION)); + } +} + +static void handle_global_remove(G_GNUC_UNUSED void *data, + G_GNUC_UNUSED struct wl_registry *registry, + G_GNUC_UNUSED uint32_t name) {} + +static struct wl_registry_listener registry_listener = { + .global = &handle_global, .global_remove = &handle_global_remove}; + +static void get_wayland_window(Mode *sw) { + WaylandWindowModePrivateData *pd = + (WaylandWindowModePrivateData *)mode_get_private_data(sw); + + pd->wayland = wayland; + + pd->registry = wl_display_get_registry(wayland->display); + wl_registry_add_listener(pd->registry, ®istry_listener, pd); + wl_display_roundtrip(wayland->display); + + if (pd->manager == NULL) { + g_warning("Unable to initialize Window mode: Wayland compositor does not " + "support wlr-foreign-toplevel-management protocol"); + return; + } + + zwlr_foreign_toplevel_manager_v1_add_listener( + pd->manager, &foreign_toplevel_manager_listener, pd); + /* fetch initial set of windows */ + wl_display_roundtrip(wayland->display); + pd->visible = TRUE; +} + +static void toplevels_list_item_free(gpointer data, + G_GNUC_UNUSED gpointer user_data) { + foreign_toplevel_handle_free((ForeignToplevelHandle *)data); +} + +static void wayland_window_private_free(WaylandWindowModePrivateData *pd) { + if (pd->toplevels) { + g_list_foreach(pd->toplevels, toplevels_list_item_free, NULL); + g_list_free(pd->toplevels); + pd->toplevels = NULL; + } + + if (pd->registry) { + wl_registry_destroy(pd->registry); + pd->registry = NULL; + } + + if (pd->manager) { + zwlr_foreign_toplevel_manager_v1_stop(pd->manager); + pd->manager = NULL; + wl_display_roundtrip(pd->wayland->display); + } + + g_free(pd); +} + +static int wayland_window_mode_init(Mode *sw) { + /** + * Called on startup when enabled (in modi list) + */ + if (mode_get_private_data(sw) == NULL) { + WaylandWindowModePrivateData *pd = + (WaylandWindowModePrivateData *)g_malloc0( + sizeof(WaylandWindowModePrivateData)); + mode_set_private_data(sw, (void *)pd); + + get_wayland_window(sw); + } + return TRUE; +} + +static unsigned int wayland_window_mode_get_num_entries(const Mode *sw) { + const WaylandWindowModePrivateData *pd = + (const WaylandWindowModePrivateData *)mode_get_private_data(sw); + + g_return_val_if_fail(pd != NULL, 0); + + return g_list_length(pd->toplevels); +} + +static ModeMode wayland_window_mode_result(Mode *sw, int mretv, + G_GNUC_UNUSED char **input, + unsigned int selected_line) { + ModeMode retv = MODE_EXIT; + WaylandWindowModePrivateData *pd = + (WaylandWindowModePrivateData *)mode_get_private_data(sw); + + g_return_val_if_fail(pd != NULL, retv); + + if (mretv & MENU_NEXT) { + retv = NEXT_DIALOG; + } else if (mretv & MENU_PREVIOUS) { + retv = PREVIOUS_DIALOG; + } else if (mretv & MENU_QUICK_SWITCH) { + retv = (ModeMode)(mretv & MENU_LOWER_MASK); + } else if ((mretv & MENU_OK)) { + ForeignToplevelHandle *toplevel = + (ForeignToplevelHandle *)g_list_nth_data(pd->toplevels, selected_line); + foreign_toplevel_handle_activate(toplevel, pd->wayland->last_seat->seat); + wl_display_flush(pd->wayland->display); + + } else if ((mretv & MENU_ENTRY_DELETE) == MENU_ENTRY_DELETE) { + ForeignToplevelHandle *toplevel = + (ForeignToplevelHandle *)g_list_nth_data(pd->toplevels, selected_line); + foreign_toplevel_handle_close(toplevel); + wl_display_flush(pd->wayland->display); + } + + return retv; +} + +static void wayland_window_mode_destroy(Mode *sw) { + WaylandWindowModePrivateData *pd = + (WaylandWindowModePrivateData *)mode_get_private_data(sw); + + g_return_if_fail(pd != NULL); + + wayland_window_private_free(pd); + mode_set_private_data(sw, NULL); +} + +static int wayland_window_token_match(const Mode *sw, rofi_int_matcher **tokens, + unsigned int index) { + WaylandWindowModePrivateData *pd = + (WaylandWindowModePrivateData *)mode_get_private_data(sw); + + ForeignToplevelHandle *toplevel = + (ForeignToplevelHandle *)g_list_nth_data(pd->toplevels, index); + + g_return_val_if_fail(toplevel != NULL, 0); + + // Call default matching function. + return helper_token_match(tokens, toplevel->title); +} + +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) { + WaylandWindowModePrivateData *pd = + (WaylandWindowModePrivateData *)mode_get_private_data(sw); + + g_return_val_if_fail(pd != NULL, NULL); + + if (!get_entry) { + return NULL; + } + + ForeignToplevelHandle *toplevel = + (ForeignToplevelHandle *)g_list_nth_data(pd->toplevels, selected_line); + + if (toplevel == NULL || toplevel->title == NULL || + toplevel->state & TOPLEVEL_STATE_CLOSED) { + return g_strdup("n/a"); + } + + if (toplevel->state & TOPLEVEL_STATE_ACTIVATED) { + *state |= ACTIVE; + } + + return g_strdup(toplevel->title); +} + +static cairo_surface_t *_get_icon(const Mode *sw, unsigned int selected_line, + unsigned int height) { + WaylandWindowModePrivateData *pd = + (WaylandWindowModePrivateData *)mode_get_private_data(sw); + + g_return_val_if_fail(pd != NULL, NULL); + + ForeignToplevelHandle *toplevel = + (ForeignToplevelHandle *)g_list_nth_data(pd->toplevels, selected_line); + + /* some apps don't have app_id (WM_CLASS). this is fine */ + if (toplevel == NULL || toplevel->app_id == NULL || + toplevel->app_id[0] == '\0') { + return NULL; + } + + cairo_surface_t *icon = NULL; + gchar *transformed = NULL; + + if (toplevel->cached_icon_uid > 0 && toplevel->cached_icon_size == height) { + return rofi_icon_fetcher_get(toplevel->cached_icon_uid); + } + + /** lookup icon */ + toplevel->cached_icon_size = height; + toplevel->cached_icon_uid = rofi_icon_fetcher_query(toplevel->app_id, height); + icon = rofi_icon_fetcher_get(toplevel->cached_icon_uid); + if (icon) { + return icon; + } + + /** lookup icon by lowercase app_id */ + transformed = g_utf8_strdown(toplevel->app_id, strlen(toplevel->app_id)); + toplevel->cached_icon_uid = rofi_icon_fetcher_query(transformed, height); + icon = rofi_icon_fetcher_get(toplevel->cached_icon_uid); + g_free(transformed); + + /* TODO: find desktop file by app_id and get the Icon= value */ + + return icon; +} + +#include "mode-private.h" + +Mode wayland_window_mode = { + .name = "window", + .cfg_name_key = "display-window", + ._init = wayland_window_mode_init, + ._destroy = wayland_window_mode_destroy, + ._get_num_entries = wayland_window_mode_get_num_entries, + ._result = wayland_window_mode_result, + ._token_match = wayland_window_token_match, + ._get_display_value = _get_display_value, + ._get_icon = _get_icon, + ._get_completion = NULL, + ._preprocess_input = NULL, + ._get_message = NULL, + .private_data = NULL, + .free = NULL, +}; + +#endif // WINDOW_MODE diff --git a/source/rofi.c b/source/rofi.c index bd3cd302..83e8152b 100644 --- a/source/rofi.c +++ b/source/rofi.c @@ -582,11 +582,18 @@ static void rofi_collectmodes_dir(const char *base_dir) { */ static void rofi_collect_modes(void) { #ifdef WINDOW_MODE +#ifdef ENABLE_XCB if (config.backend == DISPLAY_XCB) { rofi_collectmodes_add(&window_mode); rofi_collectmodes_add(&window_mode_cd); } #endif +#ifdef ENABLE_WAYLAND + if (config.backend == DISPLAY_WAYLAND) { + rofi_collectmodes_add(&wayland_window_mode); + } +#endif +#endif // WINDOW_MODE rofi_collectmodes_add(&run_mode); rofi_collectmodes_add(&ssh_mode); #ifdef ENABLE_DRUN