cage/cage.c

635 lines
18 KiB
C
Raw Normal View History

2018-11-22 19:59:06 +01:00
/*
* Cage: A Wayland kiosk.
*
2020-01-26 11:56:59 +01:00
* Copyright (C) 2018-2020 Jente Hidskes
2018-11-22 19:59:06 +01:00
*
* See the LICENSE file accompanying this file.
*/
#define _POSIX_C_SOURCE 200112L
#include "config.h"
#include <fcntl.h>
#include <getopt.h>
2018-11-22 19:59:06 +01:00
#include <signal.h>
#include <stdio.h>
2018-11-22 19:59:06 +01:00
#include <stdlib.h>
2018-12-16 21:51:48 +01:00
#include <sys/wait.h>
2018-11-22 19:59:06 +01:00
#include <unistd.h>
#include <wayland-server-core.h>
2018-11-22 19:59:06 +01:00
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
2018-11-22 19:59:06 +01:00
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
2019-01-04 20:23:50 +01:00
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_export_dmabuf_v1.h>
#include <wlr/types/wlr_gamma_control_v1.h>
#include <wlr/types/wlr_idle_inhibit_v1.h>
#include <wlr/types/wlr_idle_notify_v1.h>
2019-01-04 20:23:50 +01:00
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_output_management_v1.h>
#include <wlr/types/wlr_presentation_time.h>
2024-02-24 10:28:31 +01:00
#include <wlr/types/wlr_primary_selection_v1.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_screencopy_v1.h>
#include <wlr/types/wlr_server_decoration.h>
2022-12-16 05:43:10 -05:00
#include <wlr/types/wlr_single_pixel_buffer_v1.h>
#include <wlr/types/wlr_subcompositor.h>
#include <wlr/types/wlr_viewporter.h>
#include <wlr/types/wlr_virtual_keyboard_v1.h>
#include <wlr/types/wlr_virtual_pointer_v1.h>
#if CAGE_HAS_XWAYLAND
#include <wlr/types/wlr_xcursor_manager.h>
#endif
#include <wlr/types/wlr_xdg_decoration_v1.h>
2019-08-16 17:04:28 +00:00
#include <wlr/types/wlr_xdg_output_v1.h>
2020-02-17 19:49:47 +01:00
#include <wlr/types/wlr_xdg_shell.h>
2018-11-22 19:59:06 +01:00
#include <wlr/util/log.h>
#if CAGE_HAS_XWAYLAND
#include <wlr/xwayland.h>
#endif
2018-11-22 19:59:06 +01:00
#include "idle_inhibit_v1.h"
#include "output.h"
#include "seat.h"
#include "server.h"
#include "view.h"
#include "xdg_shell.h"
#if CAGE_HAS_XWAYLAND
#include "xwayland.h"
#endif
2018-11-22 19:59:06 +01:00
void
server_terminate(struct cg_server *server)
{
// Workaround for https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/421
if (server->terminated) {
return;
}
wl_display_terminate(server->wl_display);
}
static void
handle_display_destroy(struct wl_listener *listener, void *data)
{
struct cg_server *server = wl_container_of(listener, server, display_destroy);
server->terminated = true;
}
static int
sigchld_handler(int fd, uint32_t mask, void *data)
{
struct cg_server *server = data;
/* Close Cage's read pipe. */
close(fd);
if (mask & WL_EVENT_HANGUP) {
wlr_log(WLR_DEBUG, "Child process closed normally");
} else if (mask & WL_EVENT_ERROR) {
wlr_log(WLR_DEBUG, "Connection closed by server");
}
server->return_app_code = true;
server_terminate(server);
return 0;
}
static bool
set_cloexec(int fd)
{
int flags = fcntl(fd, F_GETFD);
if (flags == -1) {
wlr_log(WLR_ERROR, "Unable to set the CLOEXEC flag: fnctl failed");
return false;
}
flags = flags | FD_CLOEXEC;
if (fcntl(fd, F_SETFD, flags) == -1) {
wlr_log(WLR_ERROR, "Unable to set the CLOEXEC flag: fnctl failed");
return false;
}
return true;
}
2018-12-16 21:51:48 +01:00
static bool
spawn_primary_client(struct cg_server *server, char *argv[], pid_t *pid_out, struct wl_event_source **sigchld_source)
2018-12-16 21:51:48 +01:00
{
int fd[2];
if (pipe(fd) != 0) {
wlr_log(WLR_ERROR, "Unable to create pipe");
return false;
}
2018-12-16 21:51:48 +01:00
pid_t pid = fork();
if (pid == 0) {
sigset_t set;
sigemptyset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);
/* Close read, we only need write in the primary client process. */
close(fd[0]);
2018-12-16 21:51:48 +01:00
execvp(argv[0], argv);
/* execvp() returns only on failure */
wlr_log_errno(WLR_ERROR, "Failed to spawn client");
2018-12-16 21:51:48 +01:00
_exit(1);
} else if (pid == -1) {
wlr_log_errno(WLR_ERROR, "Unable to fork");
return false;
}
/* Set this early so that if we fail, the client process will be cleaned up properly. */
*pid_out = pid;
if (!set_cloexec(fd[0]) || !set_cloexec(fd[1])) {
return false;
}
/* Close write, we only need read in Cage. */
close(fd[1]);
struct wl_event_loop *event_loop = wl_display_get_event_loop(server->wl_display);
uint32_t mask = WL_EVENT_HANGUP | WL_EVENT_ERROR;
*sigchld_source = wl_event_loop_add_fd(event_loop, fd[0], mask, sigchld_handler, server);
2018-12-16 21:51:48 +01:00
wlr_log(WLR_DEBUG, "Child process created with pid %d", pid);
return true;
}
static int
cleanup_primary_client(pid_t pid)
{
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
wlr_log(WLR_DEBUG, "Child exited normally with exit status %d", WEXITSTATUS(status));
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
/* Mimic Bash and other shells for the exit status */
wlr_log(WLR_DEBUG, "Child was terminated by a signal (%d)", WTERMSIG(status));
return 128 + WTERMSIG(status);
}
return 0;
}
static bool
drop_permissions(void)
{
if (getuid() == 0 || getgid() == 0) {
wlr_log(WLR_INFO, "Running as root user, this is dangerous");
return true;
}
if (getuid() != geteuid() || getgid() != getegid()) {
wlr_log(WLR_INFO, "setuid/setgid bit detected, dropping permissions");
// Set the gid and uid in the correct order.
if (setgid(getgid()) != 0 || setuid(getuid()) != 0) {
wlr_log(WLR_ERROR, "Unable to drop root, refusing to start");
return false;
}
}
if (setgid(0) != -1 || setuid(0) != -1) {
2020-02-17 19:49:47 +01:00
wlr_log(WLR_ERROR,
"Unable to drop root (we shouldn't be able to restore it after setuid), refusing to start");
return false;
}
return true;
}
static int
handle_signal(int signal, void *data)
2018-11-22 19:59:06 +01:00
{
struct cg_server *server = data;
switch (signal) {
case SIGINT:
/* Fallthrough */
case SIGTERM:
server_terminate(server);
return 0;
default:
return 0;
}
2018-11-22 19:59:06 +01:00
}
static void
usage(FILE *file, const char *cage)
{
2020-02-17 19:49:47 +01:00
fprintf(file,
"Usage: %s [OPTIONS] [--] [APPLICATION...]\n"
"\n"
" -d\t Don't draw client side decorations, when possible\n"
" -D\t Enable debug logging\n"
" -h\t Display this help message\n"
2020-05-31 16:20:59 +02:00
" -m extend Extend the display across all connected outputs (default)\n"
" -m last Use only the last connected output\n"
" -s\t Allow VT switching\n"
2020-01-26 17:49:26 +01:00
" -v\t Show the version number and exit\n"
"\n"
" Use -- when you want to pass arguments to APPLICATION\n",
cage);
}
static bool
parse_args(struct cg_server *server, int argc, char *argv[])
{
int c;
2024-12-23 21:49:50 +08:00
while ((c = getopt(argc, argv, "dDhm:sv")) != -1) {
switch (c) {
case 'd':
server->xdg_decoration = true;
break;
case 'D':
server->log_level = WLR_DEBUG;
break;
case 'h':
usage(stdout, argv[0]);
return false;
2020-05-31 16:20:59 +02:00
case 'm':
if (strcmp(optarg, "last") == 0) {
server->output_mode = CAGE_MULTI_OUTPUT_MODE_LAST;
} else if (strcmp(optarg, "extend") == 0) {
server->output_mode = CAGE_MULTI_OUTPUT_MODE_EXTEND;
}
break;
case 's':
server->allow_vt_switch = true;
break;
2020-01-26 17:49:26 +01:00
case 'v':
fprintf(stdout, "Cage version " CAGE_VERSION "\n");
exit(0);
default:
usage(stderr, argv[0]);
return false;
}
}
return true;
}
2018-11-22 19:59:06 +01:00
int
main(int argc, char *argv[])
{
struct cg_server server = {.log_level = WLR_INFO};
struct wl_event_source *sigchld_source = NULL;
pid_t pid = 0;
int ret = 0, app_ret = 0;
2018-12-14 17:06:36 +01:00
#ifdef DEBUG
server.log_level = WLR_DEBUG;
#endif
if (!parse_args(&server, argc, argv)) {
2018-11-22 19:59:06 +01:00
return 1;
}
wlr_log_init(server.log_level, NULL);
2018-11-22 19:59:06 +01:00
/* Wayland requires XDG_RUNTIME_DIR to be set. */
if (!getenv("XDG_RUNTIME_DIR")) {
wlr_log(WLR_ERROR, "XDG_RUNTIME_DIR is not set in the environment");
return 1;
}
2018-11-22 19:59:06 +01:00
server.wl_display = wl_display_create();
2018-12-14 17:06:36 +01:00
if (!server.wl_display) {
wlr_log(WLR_ERROR, "Cannot allocate a Wayland display");
2018-12-14 17:06:36 +01:00
return 1;
}
server.display_destroy.notify = handle_display_destroy;
wl_display_add_destroy_listener(server.wl_display, &server.display_destroy);
struct wl_event_loop *event_loop = wl_display_get_event_loop(server.wl_display);
struct wl_event_source *sigint_source = wl_event_loop_add_signal(event_loop, SIGINT, handle_signal, &server);
struct wl_event_source *sigterm_source = wl_event_loop_add_signal(event_loop, SIGTERM, handle_signal, &server);
server.backend = wlr_backend_autocreate(event_loop, &server.session);
if (!server.backend) {
2018-12-14 17:06:36 +01:00
wlr_log(WLR_ERROR, "Unable to create the wlroots backend");
ret = 1;
goto end;
}
if (!drop_permissions()) {
ret = 1;
goto end;
}
server.renderer = wlr_renderer_autocreate(server.backend);
if (!server.renderer) {
wlr_log(WLR_ERROR, "Unable to create the wlroots renderer");
ret = 1;
goto end;
}
server.allocator = wlr_allocator_autocreate(server.backend, server.renderer);
if (!server.allocator) {
wlr_log(WLR_ERROR, "Unable to create the wlroots allocator");
ret = 1;
goto end;
}
wlr_renderer_init_wl_display(server.renderer, server.wl_display);
2018-12-14 17:06:36 +01:00
wl_list_init(&server.views);
wl_list_init(&server.outputs);
server.output_layout = wlr_output_layout_create(server.wl_display);
2018-12-14 17:06:36 +01:00
if (!server.output_layout) {
wlr_log(WLR_ERROR, "Unable to create output layout");
ret = 1;
goto end;
}
server.output_layout_change.notify = handle_output_layout_change;
wl_signal_add(&server.output_layout->events.change, &server.output_layout_change);
server.scene = wlr_scene_create();
if (!server.scene) {
wlr_log(WLR_ERROR, "Unable to create scene");
ret = 1;
goto end;
2018-12-14 17:06:36 +01:00
}
2018-11-22 19:59:06 +01:00
server.scene_output_layout = wlr_scene_attach_output_layout(server.scene, server.output_layout);
struct wlr_compositor *compositor = wlr_compositor_create(server.wl_display, 6, server.renderer);
2018-12-14 17:06:36 +01:00
if (!compositor) {
wlr_log(WLR_ERROR, "Unable to create the wlroots compositor");
ret = 1;
goto end;
}
if (!wlr_subcompositor_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the wlroots subcompositor");
ret = 1;
goto end;
}
if (!wlr_data_device_manager_create(server.wl_display)) {
2018-12-14 17:06:36 +01:00
wlr_log(WLR_ERROR, "Unable to create the data device manager");
ret = 1;
goto end;
}
2018-11-22 19:59:06 +01:00
2024-02-24 10:28:31 +01:00
if (!wlr_primary_selection_v1_device_manager_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create primary selection device manager");
ret = 1;
goto end;
}
2018-11-22 19:59:06 +01:00
/* Configure a listener to be notified when new outputs are
* available on the backend. We use this only to detect the
* first output and ignore subsequent outputs. */
server.new_output.notify = handle_new_output;
wl_signal_add(&server.backend->events.new_output, &server.new_output);
2018-11-22 19:59:06 +01:00
server.seat = seat_create(&server, server.backend);
if (!server.seat) {
wlr_log(WLR_ERROR, "Unable to create the seat");
2018-12-14 17:06:36 +01:00
ret = 1;
goto end;
}
2018-11-22 19:59:06 +01:00
server.idle = wlr_idle_notifier_v1_create(server.wl_display);
if (!server.idle) {
wlr_log(WLR_ERROR, "Unable to create the idle tracker");
ret = 1;
goto end;
}
server.idle_inhibit_v1 = wlr_idle_inhibit_v1_create(server.wl_display);
if (!server.idle_inhibit_v1) {
wlr_log(WLR_ERROR, "Cannot create the idle inhibitor");
ret = 1;
goto end;
}
server.new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1_new;
wl_signal_add(&server.idle_inhibit_v1->events.new_inhibitor, &server.new_idle_inhibitor_v1);
wl_list_init(&server.inhibitors);
struct wlr_xdg_shell *xdg_shell = wlr_xdg_shell_create(server.wl_display, 5);
if (!xdg_shell) {
wlr_log(WLR_ERROR, "Unable to create the XDG shell interface");
2018-12-14 17:06:36 +01:00
ret = 1;
goto end;
}
2024-02-16 16:48:25 +01:00
server.new_xdg_toplevel.notify = handle_new_xdg_toplevel;
wl_signal_add(&xdg_shell->events.new_toplevel, &server.new_xdg_toplevel);
server.new_xdg_popup.notify = handle_new_xdg_popup;
wl_signal_add(&xdg_shell->events.new_popup, &server.new_xdg_popup);
2018-11-22 19:59:06 +01:00
struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager =
wlr_xdg_decoration_manager_v1_create(server.wl_display);
if (!xdg_decoration_manager) {
wlr_log(WLR_ERROR, "Unable to create the XDG decoration manager");
ret = 1;
goto end;
}
wl_signal_add(&xdg_decoration_manager->events.new_toplevel_decoration, &server.xdg_toplevel_decoration);
server.xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration;
struct wlr_server_decoration_manager *server_decoration_manager =
wlr_server_decoration_manager_create(server.wl_display);
if (!server_decoration_manager) {
wlr_log(WLR_ERROR, "Unable to create the server decoration manager");
ret = 1;
goto end;
}
2020-02-17 19:49:47 +01:00
wlr_server_decoration_manager_set_default_mode(
server_decoration_manager, server.xdg_decoration ? WLR_SERVER_DECORATION_MANAGER_MODE_SERVER
: WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT);
if (!wlr_viewporter_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the viewporter interface");
ret = 1;
goto end;
}
2025-04-10 17:50:15 +02:00
struct wlr_presentation *presentation = wlr_presentation_create(server.wl_display, server.backend, 2);
if (!presentation) {
wlr_log(WLR_ERROR, "Unable to create the presentation interface");
ret = 1;
goto end;
}
if (!wlr_export_dmabuf_manager_v1_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the export DMABUF manager");
ret = 1;
goto end;
}
if (!wlr_screencopy_manager_v1_create(server.wl_display)) {
2019-08-16 16:46:51 +00:00
wlr_log(WLR_ERROR, "Unable to create the screencopy manager");
ret = 1;
goto end;
}
if (!wlr_single_pixel_buffer_manager_v1_create(server.wl_display)) {
2022-12-16 05:43:10 -05:00
wlr_log(WLR_ERROR, "Unable to create the single pixel buffer manager");
ret = 1;
goto end;
}
if (!wlr_xdg_output_manager_v1_create(server.wl_display, server.output_layout)) {
2019-08-16 17:04:28 +00:00
wlr_log(WLR_ERROR, "Unable to create the output manager");
ret = 1;
goto end;
}
server.output_manager_v1 = wlr_output_manager_v1_create(server.wl_display);
if (!server.output_manager_v1) {
wlr_log(WLR_ERROR, "Unable to create the output manager");
ret = 1;
goto end;
}
server.output_manager_apply.notify = handle_output_manager_apply;
wl_signal_add(&server.output_manager_v1->events.apply, &server.output_manager_apply);
server.output_manager_test.notify = handle_output_manager_test;
wl_signal_add(&server.output_manager_v1->events.test, &server.output_manager_test);
if (!wlr_gamma_control_manager_v1_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the gamma control manager");
ret = 1;
goto end;
}
struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard =
wlr_virtual_keyboard_manager_v1_create(server.wl_display);
if (!virtual_keyboard) {
wlr_log(WLR_ERROR, "Unable to create the virtual keyboard manager");
ret = 1;
goto end;
}
wl_signal_add(&virtual_keyboard->events.new_virtual_keyboard, &server.new_virtual_keyboard);
struct wlr_virtual_pointer_manager_v1 *virtual_pointer =
wlr_virtual_pointer_manager_v1_create(server.wl_display);
if (!virtual_pointer) {
wlr_log(WLR_ERROR, "Unable to create the virtual pointer manager");
ret = 1;
goto end;
}
wl_signal_add(&virtual_pointer->events.new_virtual_pointer, &server.new_virtual_pointer);
server.relative_pointer_manager = wlr_relative_pointer_manager_v1_create(server.wl_display);
if (!server.relative_pointer_manager) {
wlr_log(WLR_ERROR, "Unable to create the relative pointer manager");
ret = 1;
goto end;
}
#if CAGE_HAS_XWAYLAND
struct wlr_xcursor_manager *xcursor_manager = NULL;
struct wlr_xwayland *xwayland = wlr_xwayland_create(server.wl_display, compositor, true);
2019-01-17 22:25:20 +01:00
if (!xwayland) {
wlr_log(WLR_ERROR, "Cannot create XWayland server");
2019-03-23 16:42:31 +01:00
} else {
server.new_xwayland_surface.notify = handle_xwayland_surface_new;
wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface);
xcursor_manager = wlr_xcursor_manager_create(DEFAULT_XCURSOR, XCURSOR_SIZE);
if (!xcursor_manager) {
wlr_log(WLR_ERROR, "Cannot create XWayland XCursor manager");
ret = 1;
goto end;
}
2019-03-23 16:42:31 +01:00
if (setenv("DISPLAY", xwayland->display_name, true) < 0) {
wlr_log_errno(WLR_ERROR,
"Unable to set DISPLAY for XWayland. Clients may not be able to connect");
} else {
wlr_log(WLR_DEBUG, "XWayland is running on display %s", xwayland->display_name);
}
if (!wlr_xcursor_manager_load(xcursor_manager, 1)) {
wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme");
}
struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(xcursor_manager, DEFAULT_XCURSOR, 1);
if (xcursor) {
struct wlr_xcursor_image *image = xcursor->images[0];
wlr_xwayland_set_cursor(xwayland, image->buffer, image->width * 4, image->width, image->height,
image->hotspot_x, image->hotspot_y);
}
}
#endif
2018-11-22 19:59:06 +01:00
const char *socket = wl_display_add_socket_auto(server.wl_display);
if (!socket) {
wlr_log_errno(WLR_ERROR, "Unable to open Wayland socket");
2018-12-14 17:06:36 +01:00
ret = 1;
2020-01-05 13:42:17 +01:00
goto end;
2018-11-22 19:59:06 +01:00
}
if (!wlr_backend_start(server.backend)) {
2018-11-22 19:59:06 +01:00
wlr_log(WLR_ERROR, "Unable to start the wlroots backend");
2018-12-14 17:06:36 +01:00
ret = 1;
goto end;
2018-11-22 19:59:06 +01:00
}
2019-03-23 20:18:13 +01:00
if (setenv("WAYLAND_DISPLAY", socket, true) < 0) {
2020-02-17 19:49:47 +01:00
wlr_log_errno(WLR_ERROR, "Unable to set WAYLAND_DISPLAY. Clients may not be able to connect");
} else {
2020-01-26 17:49:26 +01:00
wlr_log(WLR_DEBUG, "Cage " CAGE_VERSION " is running on Wayland display %s", socket);
2018-12-14 17:06:36 +01:00
}
2018-11-22 19:59:06 +01:00
#if CAGE_HAS_XWAYLAND
if (xwayland) {
wlr_xwayland_set_seat(xwayland, server.seat->seat);
}
#endif
if (optind < argc && !spawn_primary_client(&server, argv + optind, &pid, &sigchld_source)) {
2018-12-16 21:51:48 +01:00
ret = 1;
goto end;
2018-11-22 19:59:06 +01:00
}
seat_center_cursor(server.seat);
2018-11-22 19:59:06 +01:00
wl_display_run(server.wl_display);
#if CAGE_HAS_XWAYLAND
if (xwayland) {
wl_list_remove(&server.new_xwayland_surface.link);
}
wlr_xwayland_destroy(xwayland);
wlr_xcursor_manager_destroy(xcursor_manager);
#endif
2018-11-22 19:59:06 +01:00
wl_display_destroy_clients(server.wl_display);
2018-12-14 17:06:36 +01:00
wl_list_remove(&server.new_virtual_pointer.link);
wl_list_remove(&server.new_virtual_keyboard.link);
wl_list_remove(&server.output_manager_apply.link);
wl_list_remove(&server.output_manager_test.link);
wl_list_remove(&server.xdg_toplevel_decoration.link);
wl_list_remove(&server.new_xdg_toplevel.link);
wl_list_remove(&server.new_xdg_popup.link);
wl_list_remove(&server.new_idle_inhibitor_v1.link);
wl_list_remove(&server.new_output.link);
wl_list_remove(&server.output_layout_change.link);
end:
if (pid != 0)
app_ret = cleanup_primary_client(pid);
if (!ret && server.return_app_code)
ret = app_ret;
2018-12-16 21:51:48 +01:00
wl_event_source_remove(sigint_source);
wl_event_source_remove(sigterm_source);
if (sigchld_source) {
wl_event_source_remove(sigchld_source);
}
seat_destroy(server.seat);
2018-12-14 17:06:36 +01:00
/* This function is not null-safe, but we only ever get here
with a proper wl_display. */
2018-11-22 19:59:06 +01:00
wl_display_destroy(server.wl_display);
2018-12-14 17:06:36 +01:00
return ret;
2018-11-22 19:59:06 +01:00
}