mirror of
https://github.com/lbonn/rofi
synced 2024-11-22 03:43:09 +00:00
[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
This commit is contained in:
parent
9ec86220d5
commit
1e4ef36611
7 changed files with 798 additions and 4 deletions
|
@ -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
|
||||
|
|
45
include/modes/wayland-window.h
Normal file
45
include/modes/wayland-window.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* rofi
|
||||
*
|
||||
* MIT/X11 License
|
||||
* Copyright © 2013-2022 Qball Cow <qball@gmpclient.org>
|
||||
*
|
||||
* 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
270
protocols/wlr-foreign-toplevel-management-unstable-v1.xml
Normal file
270
protocols/wlr-foreign-toplevel-management-unstable-v1.xml
Normal file
|
@ -0,0 +1,270 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_foreign_toplevel_management_unstable_v1">
|
||||
<copyright>
|
||||
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.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_foreign_toplevel_manager_v1" version="3">
|
||||
<description summary="list and control opened apps">
|
||||
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
|
||||
</description>
|
||||
|
||||
<event name="toplevel">
|
||||
<description summary="a toplevel has been created">
|
||||
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.
|
||||
</description>
|
||||
<arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
|
||||
</event>
|
||||
|
||||
<request name="stop">
|
||||
<description summary="stop sending events">
|
||||
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.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="finished" type="destructor">
|
||||
<description summary="the compositor has finished with the toplevel manager">
|
||||
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.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_foreign_toplevel_handle_v1" version="3">
|
||||
<description summary="an opened toplevel">
|
||||
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.
|
||||
</description>
|
||||
|
||||
<event name="title">
|
||||
<description summary="title change">
|
||||
This event is emitted whenever the title of the toplevel changes.
|
||||
</description>
|
||||
<arg name="title" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="app_id">
|
||||
<description summary="app-id change">
|
||||
This event is emitted whenever the app-id of the toplevel changes.
|
||||
</description>
|
||||
<arg name="app_id" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="output_enter">
|
||||
<description summary="toplevel entered an output">
|
||||
This event is emitted whenever the toplevel becomes visible on
|
||||
the given output. A toplevel may be visible on multiple outputs.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="output_leave">
|
||||
<description summary="toplevel left an output">
|
||||
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.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<request name="set_maximized">
|
||||
<description summary="requests that the toplevel be maximized">
|
||||
Requests that the toplevel be maximized. If the maximized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="unset_maximized">
|
||||
<description summary="requests that the toplevel be unmaximized">
|
||||
Requests that the toplevel be unmaximized. If the maximized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="set_minimized">
|
||||
<description summary="requests that the toplevel be minimized">
|
||||
Requests that the toplevel be minimized. If the minimized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="unset_minimized">
|
||||
<description summary="requests that the toplevel be unminimized">
|
||||
Requests that the toplevel be unminimized. If the minimized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="activate">
|
||||
<description summary="activate the toplevel">
|
||||
Request that this toplevel be activated on the given seat.
|
||||
There is no guarantee the toplevel will be actually activated.
|
||||
</description>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
</request>
|
||||
|
||||
<enum name="state">
|
||||
<description summary="types of states on the toplevel">
|
||||
The different states that a toplevel can have. These have the same meaning
|
||||
as the states with the same names defined in xdg-toplevel
|
||||
</description>
|
||||
|
||||
<entry name="maximized" value="0" summary="the toplevel is maximized"/>
|
||||
<entry name="minimized" value="1" summary="the toplevel is minimized"/>
|
||||
<entry name="activated" value="2" summary="the toplevel is active"/>
|
||||
<entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/>
|
||||
</enum>
|
||||
|
||||
<event name="state">
|
||||
<description summary="the toplevel state changed">
|
||||
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.
|
||||
</description>
|
||||
|
||||
<arg name="state" type="array"/>
|
||||
</event>
|
||||
|
||||
<event name="done">
|
||||
<description summary="all information about the toplevel has been sent">
|
||||
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.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="close">
|
||||
<description summary="request that the toplevel be closed">
|
||||
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.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="set_rectangle">
|
||||
<description summary="the rectangle which represents the toplevel">
|
||||
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.
|
||||
</description>
|
||||
|
||||
<arg name="surface" type="object" interface="wl_surface"/>
|
||||
<arg name="x" type="int"/>
|
||||
<arg name="y" type="int"/>
|
||||
<arg name="width" type="int"/>
|
||||
<arg name="height" type="int"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_rectangle" value="0"
|
||||
summary="the provided rectangle is invalid"/>
|
||||
</enum>
|
||||
|
||||
<event name="closed">
|
||||
<description summary="this toplevel has been destroyed">
|
||||
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.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
|
||||
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.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<request name="set_fullscreen" since="2">
|
||||
<description summary="request that the toplevel be fullscreened">
|
||||
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.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||
</request>
|
||||
|
||||
<request name="unset_fullscreen" since="2">
|
||||
<description summary="request that the toplevel be unfullscreened">
|
||||
Requests that the toplevel be unfullscreened. If the fullscreen state
|
||||
actually changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
|
||||
<event name="parent" since="3">
|
||||
<description summary="parent change">
|
||||
This event is emitted whenever the parent of the toplevel changes.
|
||||
|
||||
No event is emitted when the parent handle is destroyed by the client.
|
||||
</description>
|
||||
<arg name="parent" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="true"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
469
source/modes/wayland-window.c
Normal file
469
source/modes/wayland-window.c
Normal file
|
@ -0,0 +1,469 @@
|
|||
/*
|
||||
* rofi
|
||||
*
|
||||
* MIT/X11 License
|
||||
* Copyright © 2013-2022 Qball Cow <qball@gmpclient.org>
|
||||
*
|
||||
* 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 <stdint.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#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
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue