mirror of
https://github.com/labwc/labwc.git
synced 2026-02-06 04:06:33 -05:00
This PR allows applications to activate themselves *if they provide
a valid xdg_activation token* (e.g. raise to the top and get keyboard
focus).
These tokens are given out by the xdg_activation protocol implemented
by wlroots and can be configured by the client requesting the token
in three ways:
- an "empty" token
(apparently used to tell the compositor about "urgency")
- seat / input serial attached
- surface attached
Wlroots makes sure that
- If the client attached the seat / input serial: those two are valid.
- If the client attached a surface: that it has keyboard focus at the
point where the request is finalized. There is a patch [1] pending
for backport to wlroots 0.16 that also allows valid tokens when the
supplied surface had cursor focus.
- a token is only valid for 30 seconds after being given out
The token can then be used by the client or given to other clients by
unspecified means (e.g. via environment variable or dbus) which then
may use the token on their own surface and request activation.
We only handle the actual request activation part:
- If the seat is set on the token we know wlroots validated seat and
input serial
- Thus, if no seat is set we deny the activation request so we don't
have windows suddenly popping up and taking over the keyboard focus
(focus stealing prevention)
- We should also check for the surface being set but we can't do that
with wlroots 0.16 as it will reset the surface to `NULL` when it is
destroyed (which is something that usually happens for
notifications). Once we move to wlroots 0.17.x we can add the
missing surface check because it provides a `new_token` signal.
We can use it to attach further details to the token which are then
verified later when we decide if we allow the activate request or
not.
With this PR in place the following setup should activate windows:
- launching an URL in foot should activate the target application if
it is already running, foot requests a proper token and then sets it
as `XDG_ACTIVATION_TOKEN` environment var before spawning `xdg-open`
- clicking on a `mako` notification with a `default` action defined
should request a proper token which is then given to the application
starting the notification and can thus be used to activate itself
This protocol is still very much in the process of being
implemented / finalized all over the place (e.g. GTK / QT / Firefox /
notification daemons, ..) but we should do our part and remove labwc
from the puzzle of potential issues causing this not to work.
[1] f6008ffff4)
456 lines
14 KiB
C
456 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#include "config.h"
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
#include <wlr/types/wlr_data_control_v1.h>
|
|
#include <wlr/types/wlr_export_dmabuf_v1.h>
|
|
#include <wlr/types/wlr_gamma_control_v1.h>
|
|
#include <wlr/types/wlr_input_inhibitor.h>
|
|
#include <wlr/types/wlr_presentation_time.h>
|
|
#include <wlr/types/wlr_primary_selection_v1.h>
|
|
#include <wlr/types/wlr_screencopy_v1.h>
|
|
#include <wlr/types/wlr_single_pixel_buffer_v1.h>
|
|
#include <wlr/types/wlr_viewporter.h>
|
|
#if HAVE_XWAYLAND
|
|
#include <wlr/xwayland.h>
|
|
#endif
|
|
#include "drm-lease-v1-protocol.h"
|
|
#include "config/rcxml.h"
|
|
#include "config/session.h"
|
|
#include "labwc.h"
|
|
#include "layers.h"
|
|
#include "menu/menu.h"
|
|
#include "regions.h"
|
|
#include "theme.h"
|
|
#include "view.h"
|
|
#include "workspaces.h"
|
|
#include "xwayland.h"
|
|
|
|
#define LAB_XDG_SHELL_VERSION (2)
|
|
|
|
static struct wlr_compositor *compositor;
|
|
static struct wl_event_source *sighup_source;
|
|
static struct wl_event_source *sigint_source;
|
|
static struct wl_event_source *sigterm_source;
|
|
|
|
static struct server *g_server;
|
|
|
|
static void
|
|
reload_config_and_theme(void)
|
|
{
|
|
rcxml_finish();
|
|
rcxml_read(NULL);
|
|
theme_finish(g_server->theme);
|
|
theme_init(g_server->theme, rc.theme_name);
|
|
|
|
struct view *view;
|
|
wl_list_for_each(view, &g_server->views, link) {
|
|
view_reload_ssd(view);
|
|
}
|
|
|
|
menu_reconfigure(g_server);
|
|
seat_reconfigure(g_server);
|
|
regions_reconfigure(g_server);
|
|
}
|
|
|
|
static int
|
|
handle_sighup(int signal, void *data)
|
|
{
|
|
session_environment_init(rc.config_dir);
|
|
reload_config_and_theme();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
handle_sigterm(int signal, void *data)
|
|
{
|
|
struct wl_display *display = data;
|
|
|
|
wl_display_terminate(display);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
seat_inhibit_input(struct seat *seat, struct wl_client *active_client)
|
|
{
|
|
seat->active_client_while_inhibited = active_client;
|
|
|
|
if (seat->focused_layer && active_client !=
|
|
wl_resource_get_client(seat->focused_layer->resource)) {
|
|
seat_set_focus_layer(seat, NULL);
|
|
}
|
|
struct wlr_surface *previous_kb_surface =
|
|
seat->seat->keyboard_state.focused_surface;
|
|
if (previous_kb_surface && active_client !=
|
|
wl_resource_get_client(previous_kb_surface->resource)) {
|
|
seat_focus_surface(seat, NULL); /* keyboard focus */
|
|
}
|
|
|
|
struct wlr_seat_client *previous_ptr_client =
|
|
seat->seat->pointer_state.focused_client;
|
|
if (previous_ptr_client && previous_ptr_client->client != active_client) {
|
|
wlr_seat_pointer_clear_focus(seat->seat);
|
|
}
|
|
}
|
|
|
|
static void
|
|
seat_disinhibit_input(struct seat *seat)
|
|
{
|
|
seat->active_client_while_inhibited = NULL;
|
|
|
|
/*
|
|
* Triggers a refocus of the topmost surface layer if necessary
|
|
* TODO: Make layer surface focus per-output based on cursor position
|
|
*/
|
|
output_update_all_usable_areas(seat->server, /*layout_changed*/ false);
|
|
}
|
|
|
|
static void
|
|
handle_input_inhibit(struct wl_listener *listener, void *data)
|
|
{
|
|
wlr_log(WLR_INFO, "activate input inhibit");
|
|
|
|
struct server *server =
|
|
wl_container_of(listener, server, input_inhibit_activate);
|
|
seat_inhibit_input(&server->seat, server->input_inhibit->active_client);
|
|
}
|
|
|
|
static void
|
|
handle_input_disinhibit(struct wl_listener *listener, void *data)
|
|
{
|
|
wlr_log(WLR_INFO, "deactivate input inhibit");
|
|
|
|
struct server *server =
|
|
wl_container_of(listener, server, input_inhibit_deactivate);
|
|
seat_disinhibit_input(&server->seat);
|
|
}
|
|
|
|
static void
|
|
handle_drm_lease_request(struct wl_listener *listener, void *data)
|
|
{
|
|
struct wlr_drm_lease_request_v1 *req = data;
|
|
struct wlr_drm_lease_v1 *lease = wlr_drm_lease_request_v1_grant(req);
|
|
if (!lease) {
|
|
wlr_log(WLR_ERROR, "Failed to grant lease request");
|
|
wlr_drm_lease_request_v1_reject(req);
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < req->n_connectors; ++i) {
|
|
struct output *output = req->connectors[i]->output->data;
|
|
if (!output) {
|
|
continue;
|
|
}
|
|
|
|
wlr_output_enable(output->wlr_output, false);
|
|
wlr_output_commit(output->wlr_output);
|
|
|
|
wlr_output_layout_remove(output->server->output_layout,
|
|
output->wlr_output);
|
|
output->scene_output = NULL;
|
|
|
|
output->leased = true;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
server_global_filter(const struct wl_client *client, const struct wl_global *global, void *data)
|
|
{
|
|
const struct wl_interface *iface = wl_global_get_interface(global);
|
|
struct server *server = (struct server *)data;
|
|
/* Silence unused var compiler warnings */
|
|
(void)iface; (void)server;
|
|
|
|
#if HAVE_XWAYLAND
|
|
struct wl_client *xwayland_client =
|
|
server->xwayland ? server->xwayland->server->client : NULL;
|
|
if (xwayland_client && client == xwayland_client) {
|
|
/*
|
|
* Filter out wp_drm_lease_device_v1 for now as it is resulting in
|
|
* issues with Xwayland applications lagging over time.
|
|
*
|
|
* https://github.com/labwc/labwc/issues/553
|
|
*/
|
|
if (!strcmp(iface->name, wp_drm_lease_device_v1_interface.name)) {
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
server_init(struct server *server)
|
|
{
|
|
server->wl_display = wl_display_create();
|
|
if (!server->wl_display) {
|
|
wlr_log(WLR_ERROR, "cannot allocate a wayland display");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
wl_display_set_global_filter(server->wl_display, server_global_filter, server);
|
|
|
|
/* Catch SIGHUP */
|
|
struct wl_event_loop *event_loop = NULL;
|
|
event_loop = wl_display_get_event_loop(server->wl_display);
|
|
sighup_source = wl_event_loop_add_signal(
|
|
event_loop, SIGHUP, handle_sighup, NULL);
|
|
sigint_source = wl_event_loop_add_signal(
|
|
event_loop, SIGINT, handle_sigterm, server->wl_display);
|
|
sigterm_source = wl_event_loop_add_signal(
|
|
event_loop, SIGTERM, handle_sigterm, server->wl_display);
|
|
server->wl_event_loop = event_loop;
|
|
|
|
/*
|
|
* The backend is a feature which abstracts the underlying input and
|
|
* output hardware. The autocreate option will choose the most suitable
|
|
* backend based on the current environment, such as opening an x11
|
|
* window if an x11 server is running.
|
|
*/
|
|
server->backend = wlr_backend_autocreate(server->wl_display);
|
|
if (!server->backend) {
|
|
wlr_log(WLR_ERROR, "unable to create backend");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The
|
|
* user can also specify a renderer using the WLR_RENDERER env var.
|
|
* The renderer is responsible for defining the various pixel formats it
|
|
* supports for shared memory, this configures that for clients.
|
|
*/
|
|
server->renderer = wlr_renderer_autocreate(server->backend);
|
|
if (!server->renderer) {
|
|
wlr_log(WLR_ERROR, "unable to create renderer");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
wlr_renderer_init_wl_display(server->renderer, server->wl_display);
|
|
|
|
/*
|
|
* Autocreates an allocator for us. The allocator is the bridge between
|
|
* the renderer and the backend. It handles the buffer creation,
|
|
* allowing wlroots to render onto the screen
|
|
*/
|
|
server->allocator = wlr_allocator_autocreate(
|
|
server->backend, server->renderer);
|
|
if (!server->allocator) {
|
|
wlr_log(WLR_ERROR, "unable to create allocator");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
wl_list_init(&server->views);
|
|
wl_list_init(&server->unmanaged_surfaces);
|
|
|
|
server->ssd_hover_state = ssd_hover_state_new();
|
|
|
|
server->scene = wlr_scene_create();
|
|
if (!server->scene) {
|
|
wlr_log(WLR_ERROR, "unable to create scene");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
server->view_tree = wlr_scene_tree_create(&server->scene->tree);
|
|
server->view_tree_always_on_top = wlr_scene_tree_create(&server->scene->tree);
|
|
#if HAVE_XWAYLAND
|
|
server->unmanaged_tree = wlr_scene_tree_create(&server->scene->tree);
|
|
#endif
|
|
server->menu_tree = wlr_scene_tree_create(&server->scene->tree);
|
|
|
|
workspaces_init(server);
|
|
|
|
output_init(server);
|
|
|
|
/*
|
|
* Create some hands-off wlroots interfaces. The compositor is
|
|
* necessary for clients to allocate surfaces and the data device
|
|
* manager handles the clipboard. Each of these wlroots interfaces has
|
|
* room for you to dig your fingers in and play with their behavior if
|
|
* you want.
|
|
*/
|
|
compositor =
|
|
wlr_compositor_create(server->wl_display, server->renderer);
|
|
if (!compositor) {
|
|
wlr_log(WLR_ERROR, "unable to create the wlroots compositor");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
wlr_subcompositor_create(server->wl_display);
|
|
|
|
struct wlr_data_device_manager *device_manager = NULL;
|
|
device_manager = wlr_data_device_manager_create(server->wl_display);
|
|
if (!device_manager) {
|
|
wlr_log(WLR_ERROR, "unable to create data device manager");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Empirically, primary selection doesn't work with Gtk apps unless the
|
|
* device manager is one of the earliest globals to be advertised. All
|
|
* credit to Wayfire for discovering this, though their symptoms
|
|
* (crash) are not the same as ours (silently does nothing). When adding
|
|
* more globals above this line it would be as well to check that
|
|
* middle-button paste still works with any Gtk app of your choice
|
|
*
|
|
* https://wayfire.org/2020/08/04/Wayfire-0-5.html
|
|
*/
|
|
wlr_primary_selection_v1_device_manager_create(server->wl_display);
|
|
|
|
seat_init(server);
|
|
|
|
/* Init xdg-shell */
|
|
server->xdg_shell = wlr_xdg_shell_create(server->wl_display,
|
|
LAB_XDG_SHELL_VERSION);
|
|
if (!server->xdg_shell) {
|
|
wlr_log(WLR_ERROR, "unable to create the XDG shell interface");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
server->new_xdg_surface.notify = xdg_surface_new;
|
|
wl_signal_add(&server->xdg_shell->events.new_surface,
|
|
&server->new_xdg_surface);
|
|
|
|
/* Disable CSD */
|
|
struct wlr_xdg_decoration_manager_v1 *xdg_deco_mgr = NULL;
|
|
xdg_deco_mgr = wlr_xdg_decoration_manager_v1_create(server->wl_display);
|
|
if (!xdg_deco_mgr) {
|
|
wlr_log(WLR_ERROR, "unable to create the XDG deco manager");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
wl_signal_add(&xdg_deco_mgr->events.new_toplevel_decoration,
|
|
&server->xdg_toplevel_decoration);
|
|
server->xdg_toplevel_decoration.notify = xdg_toplevel_decoration;
|
|
|
|
struct wlr_server_decoration_manager *deco_mgr = NULL;
|
|
deco_mgr = wlr_server_decoration_manager_create(server->wl_display);
|
|
if (!deco_mgr) {
|
|
wlr_log(WLR_ERROR, "unable to create the server deco manager");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
wlr_server_decoration_manager_set_default_mode(
|
|
deco_mgr, rc.xdg_shell_server_side_deco
|
|
? WLR_SERVER_DECORATION_MANAGER_MODE_SERVER
|
|
: WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT);
|
|
|
|
server->xdg_activation = wlr_xdg_activation_v1_create(server->wl_display);
|
|
if (!server->xdg_activation) {
|
|
wlr_log(WLR_ERROR, "unable to create xdg_activation interface");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
server->xdg_activation_request.notify = xdg_activation_handle_request;
|
|
wl_signal_add(&server->xdg_activation->events.request_activate,
|
|
&server->xdg_activation_request);
|
|
|
|
struct wlr_presentation *presentation =
|
|
wlr_presentation_create(server->wl_display, server->backend);
|
|
if (!presentation) {
|
|
wlr_log(WLR_ERROR, "unable to create presentation interface");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
wlr_scene_set_presentation(server->scene, presentation);
|
|
|
|
wlr_export_dmabuf_manager_v1_create(server->wl_display);
|
|
wlr_screencopy_manager_v1_create(server->wl_display);
|
|
wlr_data_control_manager_v1_create(server->wl_display);
|
|
wlr_gamma_control_manager_v1_create(server->wl_display);
|
|
wlr_viewporter_create(server->wl_display);
|
|
wlr_single_pixel_buffer_manager_v1_create(server->wl_display);
|
|
|
|
server->relative_pointer_manager = wlr_relative_pointer_manager_v1_create(
|
|
server->wl_display);
|
|
server->constraints = wlr_pointer_constraints_v1_create(
|
|
server->wl_display);
|
|
|
|
server->new_constraint.notify = create_constraint;
|
|
wl_signal_add(&server->constraints->events.new_constraint,
|
|
&server->new_constraint);
|
|
|
|
server->input_inhibit =
|
|
wlr_input_inhibit_manager_create(server->wl_display);
|
|
if (!server->input_inhibit) {
|
|
wlr_log(WLR_ERROR, "unable to create input inhibit manager");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
wl_signal_add(&server->input_inhibit->events.activate,
|
|
&server->input_inhibit_activate);
|
|
server->input_inhibit_activate.notify = handle_input_inhibit;
|
|
|
|
wl_signal_add(&server->input_inhibit->events.deactivate,
|
|
&server->input_inhibit_deactivate);
|
|
server->input_inhibit_deactivate.notify = handle_input_disinhibit;
|
|
|
|
server->foreign_toplevel_manager =
|
|
wlr_foreign_toplevel_manager_v1_create(server->wl_display);
|
|
|
|
server->drm_lease_manager = wlr_drm_lease_v1_manager_create(
|
|
server->wl_display, server->backend);
|
|
if (server->drm_lease_manager) {
|
|
server->drm_lease_request.notify = handle_drm_lease_request;
|
|
wl_signal_add(&server->drm_lease_manager->events.request,
|
|
&server->drm_lease_request);
|
|
} else {
|
|
wlr_log(WLR_DEBUG, "Failed to create wlr_drm_lease_device_v1");
|
|
wlr_log(WLR_INFO, "VR will not be available");
|
|
}
|
|
|
|
server->output_power_manager_v1 =
|
|
wlr_output_power_manager_v1_create(server->wl_display);
|
|
server->output_power_manager_set_mode.notify =
|
|
handle_output_power_manager_set_mode;
|
|
wl_signal_add(&server->output_power_manager_v1->events.set_mode,
|
|
&server->output_power_manager_set_mode);
|
|
|
|
layers_init(server);
|
|
|
|
#if HAVE_XWAYLAND
|
|
xwayland_server_init(server, compositor);
|
|
#endif
|
|
/* used when handling SIGHUP */
|
|
g_server = server;
|
|
}
|
|
|
|
void
|
|
server_start(struct server *server)
|
|
{
|
|
/* Add a Unix socket to the Wayland display. */
|
|
const char *socket = wl_display_add_socket_auto(server->wl_display);
|
|
if (!socket) {
|
|
wlr_log_errno(WLR_ERROR, "unable to open wayland socket");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Start the backend. This will enumerate outputs and inputs, become
|
|
* the DRM master, etc
|
|
*/
|
|
if (!wlr_backend_start(server->backend)) {
|
|
wlr_log(WLR_ERROR, "unable to start the wlroots backend");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (setenv("WAYLAND_DISPLAY", socket, true) < 0) {
|
|
wlr_log_errno(WLR_ERROR, "unable to set WAYLAND_DISPLAY");
|
|
} else {
|
|
wlr_log(WLR_DEBUG, "WAYLAND_DISPLAY=%s", socket);
|
|
}
|
|
}
|
|
|
|
void
|
|
server_finish(struct server *server)
|
|
{
|
|
#if HAVE_XWAYLAND
|
|
xwayland_server_finish(server);
|
|
#endif
|
|
if (sighup_source) {
|
|
wl_event_source_remove(sighup_source);
|
|
}
|
|
wl_display_destroy_clients(server->wl_display);
|
|
|
|
seat_finish(server);
|
|
wlr_output_layout_destroy(server->output_layout);
|
|
|
|
wl_display_destroy(server->wl_display);
|
|
|
|
/* TODO: clean up various scene_tree nodes */
|
|
workspaces_destroy(server);
|
|
}
|