labwc/src/server.c

462 lines
14 KiB
C
Raw Normal View History

2021-09-24 21:45:48 +01:00
// SPDX-License-Identifier: GPL-2.0-only
2020-09-25 20:05:20 +01:00
#define _POSIX_C_SOURCE 200809L
2020-12-30 10:29:21 +00:00
#include "config.h"
2020-09-25 19:42:40 +01:00
#include <signal.h>
2020-12-01 04:15:37 +00:00
#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>
2022-02-28 22:30:36 +00:00
#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"
2020-11-01 22:22:15 +00:00
#include "config/rcxml.h"
#include "config/session.h"
#include "decorations.h"
2020-11-01 22:22:15 +00:00
#include "labwc.h"
#include "layers.h"
#include "menu/menu.h"
#include "regions.h"
2021-02-21 22:18:34 +00:00
#include "theme.h"
#include "view.h"
2022-06-15 02:02:50 +02:00
#include "workspaces.h"
#include "xwayland.h"
#define LAB_XDG_SHELL_VERSION (2)
2020-06-05 21:07:57 +01:00
static struct wlr_compositor *compositor;
2020-09-25 19:42:40 +01:00
static struct wl_event_source *sighup_source;
2020-12-21 18:56:46 +00:00
static struct wl_event_source *sigint_source;
static struct wl_event_source *sigterm_source;
2020-06-05 21:07:57 +01:00
2021-02-19 23:05:14 +00:00
static struct server *g_server;
static void
reload_config_and_theme(void)
2020-09-25 19:42:40 +01:00
{
rcxml_finish();
2020-09-25 19:42:40 +01:00
rcxml_read(NULL);
2021-02-21 22:03:14 +00:00
theme_finish(g_server->theme);
theme_init(g_server->theme, rc.theme_name);
2021-07-30 19:42:56 +01:00
struct view *view;
2022-11-03 19:58:21 +00:00
wl_list_for_each(view, &g_server->views, link) {
view_reload_ssd(view);
2021-07-30 19:42:56 +01:00
}
2022-02-19 02:05:38 +01:00
menu_reconfigure(g_server);
2021-10-10 12:03:18 -04:00
seat_reconfigure(g_server);
regions_reconfigure(g_server);
kde_server_decoration_update_default();
2020-09-25 19:42:40 +01:00
}
static int
2020-12-01 04:15:37 +00:00
handle_sighup(int signal, void *data)
2020-09-25 19:42:40 +01:00
{
session_environment_init(rc.config_dir);
2020-12-01 04:15:37 +00:00
reload_config_and_theme();
return 0;
}
2020-12-21 18:56:46 +00:00
static int
handle_sigterm(int signal, void *data)
{
struct wl_display *display = data;
wl_display_terminate(display);
return 0;
}
2021-08-25 19:59:49 +01:00
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)) {
2021-08-23 20:33:42 +01:00
seat_set_focus_layer(seat, NULL);
}
2021-08-25 19:59:49 +01:00
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 */
}
2021-08-25 19:59:49 +01:00
struct wlr_seat_client *previous_ptr_client =
seat->seat->pointer_state.focused_client;
2022-04-04 20:53:36 +01:00
if (previous_ptr_client && previous_ptr_client->client != active_client) {
wlr_seat_pointer_clear_focus(seat->seat);
}
}
2021-08-25 19:59:49 +01:00
static void
seat_disinhibit_input(struct seat *seat)
{
seat->active_client_while_inhibited = NULL;
2021-08-25 19:59:49 +01:00
/*
* 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);
}
2021-08-25 19:59:49 +01:00
static void
handle_input_inhibit(struct wl_listener *listener, void *data)
{
wlr_log(WLR_INFO, "activate input inhibit");
2021-08-25 19:59:49 +01:00
struct server *server =
wl_container_of(listener, server, input_inhibit_activate);
seat_inhibit_input(&server->seat, server->input_inhibit->active_client);
}
2021-08-25 19:59:49 +01:00
static void
handle_input_disinhibit(struct wl_listener *listener, void *data)
{
wlr_log(WLR_INFO, "deactivate input inhibit");
2021-08-25 19:59:49 +01:00
struct server *server =
wl_container_of(listener, server, input_inhibit_deactivate);
seat_disinhibit_input(&server->seat);
}
static void
2022-04-04 20:53:36 +01:00
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;
2023-01-31 03:35:13 +01:00
/* 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;
}
/*
* This message is intended to help users who are trying labwc on
* clean/minimalist systems without existing Desktop Environments (possibly
* through Virtual Managers) where polkit is missing or GPU drivers do not
* exist, in the hope that it will reduce the time required to get labwc running
* and prevent some troubleshooting steps.
*/
static const char helpful_seat_error_message[] =
"\n"
"Some friendly trouble-shooting help\n"
"===================================\n"
"\n"
"If a seat could not be created, this may be caused by lack of permission to the\n"
"seat, input and video groups. If you are using a systemd setup, try installing\n"
"polkit (sometimes called policykit-1). For other setups, search your OS/Distro's\n"
"documentation on how to use seatd, elogind or similar. This is likely to involve\n"
"manually adding users to groups.\n"
"\n"
"If the above does not work, try running with `WLR_RENDERER=pixman labwc` in\n"
"order to use the software rendering fallback\n";
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);
2020-09-25 19:42:40 +01:00
/* 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);
2020-12-21 18:56:46 +00:00
sigint_source = wl_event_loop_add_signal(
event_loop, SIGINT, handle_sigterm, server->wl_display);
2020-12-21 18:56:46 +00:00
sigterm_source = wl_event_loop_add_signal(
event_loop, SIGTERM, handle_sigterm, server->wl_display);
2022-06-15 02:02:50 +02:00
server->wl_event_loop = event_loop;
2020-09-25 19:42:40 +01:00
/*
2020-09-29 19:53:46 +01:00
* 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);
2020-09-29 19:53:46 +01:00
if (!server->backend) {
wlr_log(WLR_ERROR, "unable to create backend");
fprintf(stderr, helpful_seat_error_message);
exit(EXIT_FAILURE);
}
2021-11-26 19:27:50 +00:00
/*
* 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
2021-11-26 19:27:50 +00:00
* 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);
2021-11-26 19:27:50 +00:00
/*
* 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);
2022-06-15 02:02:50 +02:00
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.
*/
2020-06-05 21:07:57 +01:00
compositor =
wlr_compositor_create(server->wl_display, server->renderer);
2020-06-05 21:07:57 +01:00
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);
kde_server_decoration_init(server);
xdg_server_decoration_init(server);
Add xdg-activation protocol 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] https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/f6008ffff41f67e3c20bd8b3be8f216da6a4bb30)
2023-02-04 10:07:46 +01:00
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);
2022-02-28 22:30:36 +00:00
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);
2021-10-17 16:54:35 -04:00
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);
2021-08-25 19:59:49 +01:00
server->input_inhibit =
wlr_input_inhibit_manager_create(server->wl_display);
if (!server->input_inhibit) {
2021-08-25 19:59:49 +01:00
wlr_log(WLR_ERROR, "unable to create input inhibit manager");
exit(EXIT_FAILURE);
}
2021-08-25 19:59:49 +01:00
wl_signal_add(&server->input_inhibit->events.activate,
&server->input_inhibit_activate);
server->input_inhibit_activate.notify = handle_input_inhibit;
2021-08-25 19:59:49 +01:00
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);
2022-04-04 20:53:36 +01:00
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");
}
2022-03-06 04:46:11 +00:00
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);
2020-12-30 10:29:21 +00:00
#if HAVE_XWAYLAND
xwayland_server_init(server, compositor);
2020-12-30 10:29:21 +00:00
#endif
2021-02-19 23:05:14 +00:00
/* 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
*/
2020-09-29 19:53:46 +01:00
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)
{
2020-12-30 10:29:21 +00:00
#if HAVE_XWAYLAND
xwayland_server_finish(server);
2020-12-30 10:29:21 +00:00
#endif
2020-12-01 04:15:37 +00:00
if (sighup_source) {
wl_event_source_remove(sighup_source);
}
wl_display_destroy_clients(server->wl_display);
2020-10-22 19:54:30 +01:00
seat_finish(server);
2020-10-23 20:19:07 +01:00
wlr_output_layout_destroy(server->output_layout);
2020-10-22 19:54:30 +01:00
wl_display_destroy(server->wl_display);
2022-06-15 02:02:50 +02:00
/* TODO: clean up various scene_tree nodes */
workspaces_destroy(server);
}