mirror of
https://cagebreak.project-repo.co/cagebreak.git
synced 2026-08-15 18:19:11 +02:00
This is a major release and includes BREAKING changes! Breaking Changes: * More intuitive switch of focus, next, prev and exchange (#20, 40 in Bugs.md) Cagebreak will probably still feel broadly the same though. * Remove -r flag - This is replaced by output configuration. * Socket disabled by default - The socket is enabled by invoking Cagebreak with the -e flag. * Socket permissions restricted to the user of the Cagebreak process (700) This may require modification of scripts interacting with the socket with different UIDs than the UID of the Cagebreak process. * Rename "Current frame" indicator to "Current tile" Changelog: * Move to wlr_scene * Add events displaying change over the socket * this enables scripting tools * Add support for scaling outputs (thanks to Oliver Friedmann) * Remove -r flag * Add rotation to output configuration * Add cursor enable|disable flag * Add input keyboard configuration * Custom path flag for configuration file * Restrict socket permissions to the user running cagebreak (700) * Fix Issue #31 (resolve some terminology) 38 in Bugs.md * Fix Issue #30 - 39 in Bugs.md * Fix Issue #20 - 40 in Bugs.md * Fix Issue #12 - 41 in Bugs.md (dump + events over socket) * Fix Bugs found via scan-build static analysis (42 - 45 in Bugs.md) * Fix Issue #33 - 46 in Bugs.md * Fix Issue #32 - 47 in Bugs.md * Fix Issue #16 - 48 in Bugs.md * Fix Issue #3 - 49 in Bugs.md (disabling keybinding interpretation for specific keyboards is now possible) * Fix Issue #26 - 50 in Bugs.md * Fix Issue #7 - 51 in Bugs.md (Change cursor to square while Cagebreak is waiting for a key) * Fix config bug for some commands - 52 in Bugs.md * Fix Issue #35 - 53 in Bugs.md (update to wlroots 0.16.1) * Improve FAQ.md (related to some Issues) * Disable outputs when unable to set any mode (crashed previously) * Add Code of Conduct * Print version number on startup
537 lines
15 KiB
C
537 lines
15 KiB
C
// Copyright 2020 - 2023, project-repo and the cagebreak contributors
|
|
// SPDX -License-Identifier: MIT
|
|
|
|
#define _POSIX_C_SOURCE 200812L
|
|
|
|
#include "config.h"
|
|
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include <wayland-server-core.h>
|
|
#include <wlr/backend.h>
|
|
#include <wlr/backend/headless.h>
|
|
#include <wlr/backend/multi.h>
|
|
#include <wlr/render/allocator.h>
|
|
#include <wlr/render/wlr_renderer.h>
|
|
#include <wlr/types/wlr_compositor.h>
|
|
#include <wlr/types/wlr_cursor.h>
|
|
#include <wlr/types/wlr_data_control_v1.h>
|
|
#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.h>
|
|
#include <wlr/types/wlr_idle_inhibit_v1.h>
|
|
#include <wlr/types/wlr_keyboard_group.h>
|
|
#include <wlr/types/wlr_output_damage.h>
|
|
#include <wlr/types/wlr_output_layout.h>
|
|
#include <wlr/types/wlr_presentation_time.h>
|
|
#include <wlr/types/wlr_primary_selection_v1.h>
|
|
#include <wlr/types/wlr_scene.h>
|
|
#include <wlr/types/wlr_screencopy_v1.h>
|
|
#include <wlr/types/wlr_server_decoration.h>
|
|
#include <wlr/types/wlr_viewporter.h>
|
|
#if CG_HAS_XWAYLAND
|
|
#include <wlr/types/wlr_xcursor_manager.h>
|
|
#endif
|
|
#include <wlr/types/wlr_xdg_decoration_v1.h>
|
|
#include <wlr/types/wlr_xdg_output_v1.h>
|
|
#include <wlr/types/wlr_xdg_shell.h>
|
|
#include <wlr/util/log.h>
|
|
#if CG_HAS_XWAYLAND
|
|
#include <wlr/xwayland.h>
|
|
#endif
|
|
|
|
#include "../idle_inhibit_v1.h"
|
|
#include "../input_manager.h"
|
|
#include "../keybinding.h"
|
|
#include "../output.h"
|
|
#include "../parse.h"
|
|
#include "../seat.h"
|
|
#include "../server.h"
|
|
#include "../xdg_shell.h"
|
|
#if CG_HAS_XWAYLAND
|
|
#include "../xwayland.h"
|
|
#endif
|
|
|
|
#include "fuzz-lib.h"
|
|
|
|
struct cg_server server;
|
|
struct wlr_xdg_shell *xdg_shell;
|
|
|
|
struct wlr_xwayland *xwayland;
|
|
#if CG_HAS_XWAYLAND
|
|
struct wlr_xcursor_manager *xcursor_manager;
|
|
#endif
|
|
|
|
static bool
|
|
drop_permissions(void) {
|
|
if(getuid() != geteuid() || getgid() != getegid()) {
|
|
if(setuid(getuid()) != 0 || setgid(getgid()) != 0) {
|
|
wlr_log(WLR_ERROR, "Unable to drop root, refusing to start");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(setuid(0) != -1) {
|
|
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;
|
|
}
|
|
|
|
void
|
|
cleanup() {
|
|
server.running = false;
|
|
#if CG_HAS_XWAYLAND
|
|
if(xwayland != NULL) {
|
|
wlr_xwayland_destroy(xwayland);
|
|
}
|
|
if(xcursor_manager != NULL) {
|
|
wlr_xcursor_manager_destroy(xcursor_manager);
|
|
}
|
|
#endif
|
|
wl_display_destroy_clients(server.wl_display);
|
|
|
|
for(unsigned int i = 0; server.modes[i] != NULL; ++i) {
|
|
free(server.modes[i]);
|
|
}
|
|
free(server.modes);
|
|
|
|
keybinding_list_free(server.keybindings);
|
|
|
|
seat_destroy(server.seat);
|
|
/* This function is not null-safe, but we only ever get here
|
|
with a proper wl_display. */
|
|
wlr_output_layout_destroy(server.output_layout);
|
|
}
|
|
|
|
int
|
|
LLVMFuzzerInitialize(int *argc, char ***argv) {
|
|
struct wlr_backend *backend = NULL;
|
|
struct wlr_compositor *compositor = NULL;
|
|
struct wlr_data_device_manager *data_device_manager = NULL;
|
|
struct wlr_server_decoration_manager *server_decoration_manager = NULL;
|
|
struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager = NULL;
|
|
struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager = NULL;
|
|
struct wlr_screencopy_manager_v1 *screencopy_manager = NULL;
|
|
struct wlr_data_control_manager_v1 *data_control_manager = NULL;
|
|
struct wlr_viewporter *viewporter = NULL;
|
|
struct wlr_presentation *presentation = NULL;
|
|
struct wlr_xdg_output_manager_v1 *output_manager = NULL;
|
|
struct wlr_gamma_control_manager_v1 *gamma_control_manager = NULL;
|
|
struct wlr_xdg_shell *xdg_shell = NULL;
|
|
#if CG_HAS_XWAYLAND
|
|
struct wlr_xwayland *xwayland = NULL;
|
|
#endif
|
|
wl_list_init(&server.input_config);
|
|
wl_list_init(&server.output_config);
|
|
wl_list_init(&server.output_priorities);
|
|
wl_list_init(&server.outputs);
|
|
wl_list_init(&server.disabled_outputs);
|
|
|
|
int ret = 0;
|
|
|
|
#ifdef DEBUG
|
|
wlr_log_init(WLR_DEBUG, NULL);
|
|
#else
|
|
wlr_log_init(WLR_ERROR, NULL);
|
|
#endif
|
|
|
|
server.modes = malloc(4 * sizeof(char *));
|
|
if(!server.modes) {
|
|
wlr_log(WLR_ERROR, "Error allocating mode array");
|
|
goto end;
|
|
}
|
|
|
|
/* Wayland requires XDG_RUNTIME_DIR to be set. */
|
|
if(!getenv("XDG_RUNTIME_DIR")) {
|
|
wlr_log(WLR_INFO, "XDG_RUNTIME_DIR is not set in the environment");
|
|
}
|
|
|
|
server.wl_display = wl_display_create();
|
|
if(!server.wl_display) {
|
|
wlr_log(WLR_ERROR, "Cannot allocate a Wayland display");
|
|
free(server.modes);
|
|
server.modes = NULL;
|
|
goto end;
|
|
}
|
|
|
|
server.xcursor_size = XCURSOR_SIZE;
|
|
const char *env_cursor_size = getenv("XCURSOR_SIZE");
|
|
if(env_cursor_size && strlen(env_cursor_size) > 0) {
|
|
errno = 0;
|
|
char *end;
|
|
unsigned size = strtoul(env_cursor_size, &end, 10);
|
|
if(!*end && errno == 0) {
|
|
server.xcursor_size = size;
|
|
}
|
|
}
|
|
|
|
server.running = true;
|
|
|
|
server.modes[0] = strdup("top");
|
|
server.modes[1] = strdup("root");
|
|
server.modes[2] = strdup("resize");
|
|
server.modes[3] = NULL;
|
|
if(server.modes[0] == NULL || server.modes[1] == NULL ||
|
|
server.modes[2] == NULL) {
|
|
wlr_log(WLR_ERROR, "Error allocating default modes");
|
|
goto end;
|
|
}
|
|
|
|
server.nws = 1;
|
|
server.views_curr_id = 1;
|
|
server.tiles_curr_id = 1;
|
|
server.message_config.fg_color[0] = 0.0;
|
|
server.message_config.fg_color[1] = 0.0;
|
|
server.message_config.fg_color[2] = 0.0;
|
|
server.message_config.fg_color[3] = 1.0;
|
|
|
|
server.message_config.bg_color[0] = 0.9;
|
|
server.message_config.bg_color[1] = 0.85;
|
|
server.message_config.bg_color[2] = 0.85;
|
|
server.message_config.bg_color[3] = 1.0;
|
|
|
|
server.message_config.display_time = 2;
|
|
server.message_config.font = strdup("pango:Monospace 10");
|
|
|
|
backend = wlr_multi_backend_create(server.wl_display);
|
|
if(!backend) {
|
|
wlr_log(WLR_ERROR, "Unable to create the wlroots multi backend");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
server.backend = backend;
|
|
|
|
struct wlr_backend *headless_backend =
|
|
wlr_headless_backend_create(server.wl_display);
|
|
if(!headless_backend) {
|
|
wlr_log(WLR_ERROR, "Unable to create the wlroots headless backend");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
wlr_headless_add_output(headless_backend, 600, 300);
|
|
|
|
if(!wlr_multi_backend_add(backend, headless_backend)) {
|
|
wlr_log(WLR_ERROR,
|
|
"Unable to insert headless backend into multi backend");
|
|
ret = 1;
|
|
goto end;
|
|
};
|
|
|
|
server.keybindings = keybinding_list_init();
|
|
if(server.keybindings == NULL || server.keybindings->keybindings == NULL) {
|
|
wlr_log(WLR_ERROR, "Unable to allocate keybindings");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
server.renderer = wlr_renderer_autocreate(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);
|
|
|
|
server.bg_color = (float[4]){0, 0, 0, 1};
|
|
server.output_layout = wlr_output_layout_create();
|
|
if(!server.output_layout) {
|
|
wlr_log(WLR_ERROR, "Unable to create output layout");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
if(ipc_init(&server) != 0) {
|
|
wlr_log(WLR_ERROR, "Failed to initialize IPC");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
server.scene = wlr_scene_create();
|
|
if(!server.scene) {
|
|
wlr_log(WLR_ERROR, "Unable to create scene");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
wlr_scene_attach_output_layout(server.scene, server.output_layout);
|
|
|
|
compositor = wlr_compositor_create(server.wl_display, server.renderer);
|
|
if(!compositor) {
|
|
wlr_log(WLR_ERROR, "Unable to create the wlroots compositor");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
data_device_manager = wlr_data_device_manager_create(server.wl_display);
|
|
if(!data_device_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the data device manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
server.input = input_manager_create(&server);
|
|
|
|
data_control_manager =
|
|
wlr_data_control_manager_v1_create(server.wl_display);
|
|
if(!data_control_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the data control manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
/* 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(&backend->events.new_output, &server.new_output);
|
|
|
|
server.seat = seat_create(&server, backend);
|
|
if(!server.seat) {
|
|
wlr_log(WLR_ERROR, "Unable to create the seat");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
server.idle = wlr_idle_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);
|
|
|
|
xdg_shell = wlr_xdg_shell_create(server.wl_display, 3);
|
|
if(!xdg_shell) {
|
|
wlr_log(WLR_ERROR, "Unable to create the XDG shell interface");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
server.new_xdg_shell_surface.notify = handle_xdg_shell_surface_new;
|
|
wl_signal_add(&xdg_shell->events.new_surface,
|
|
&server.new_xdg_shell_surface);
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
wlr_server_decoration_manager_set_default_mode(
|
|
server_decoration_manager, WLR_SERVER_DECORATION_MANAGER_MODE_SERVER);
|
|
|
|
viewporter = wlr_viewporter_create(server.wl_display);
|
|
if(!viewporter) {
|
|
wlr_log(WLR_ERROR, "Unable to create the viewporter interface");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
presentation = wlr_presentation_create(server.wl_display, server.backend);
|
|
if(!presentation) {
|
|
wlr_log(WLR_ERROR, "Unable to create the presentation interface");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
wlr_scene_set_presentation(server.scene, presentation);
|
|
|
|
export_dmabuf_manager =
|
|
wlr_export_dmabuf_manager_v1_create(server.wl_display);
|
|
if(!export_dmabuf_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the export DMABUF manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
screencopy_manager = wlr_screencopy_manager_v1_create(server.wl_display);
|
|
if(!screencopy_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the screencopy manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
output_manager = wlr_xdg_output_manager_v1_create(server.wl_display,
|
|
server.output_layout);
|
|
if(!output_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the output manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
if(!wlr_primary_selection_v1_device_manager_create(server.wl_display)) {
|
|
wlr_log(WLR_ERROR,
|
|
"Unable to create the primary selection device manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
gamma_control_manager =
|
|
wlr_gamma_control_manager_v1_create(server.wl_display);
|
|
if(!gamma_control_manager) {
|
|
wlr_log(WLR_ERROR, "Unable to create the gamma control manager");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
#if CG_HAS_XWAYLAND
|
|
xwayland = wlr_xwayland_create(server.wl_display, compositor, true);
|
|
if(!xwayland) {
|
|
wlr_log(WLR_ERROR, "Cannot create XWayland server");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
server.new_xwayland_surface.notify = handle_xwayland_surface_new;
|
|
wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface);
|
|
|
|
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);
|
|
}
|
|
|
|
struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(
|
|
server.seat->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
|
|
|
|
const char *socket = wl_display_add_socket_auto(server.wl_display);
|
|
if(!socket) {
|
|
wlr_log_errno(WLR_ERROR, "Unable to open Wayland socket");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
if(!wlr_backend_start(backend)) {
|
|
wlr_log(WLR_ERROR, "Unable to start the wlroots backend");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
if(setenv("WAYLAND_DISPLAY", socket, true) < 0) {
|
|
wlr_log_errno(WLR_ERROR, "Unable to set WAYLAND_DISPLAY.",
|
|
"Clients may not be able to connect");
|
|
} else {
|
|
fprintf(stderr,
|
|
"Cagebreak " CG_VERSION " is running on Wayland display %s\n",
|
|
socket);
|
|
}
|
|
|
|
#if CG_HAS_XWAYLAND
|
|
wlr_xwayland_set_seat(xwayland, server.seat->seat);
|
|
#endif
|
|
|
|
/* Place the cursor to the top left of the output layout. */
|
|
wlr_cursor_warp(server.seat->cursor, NULL, 0, 0);
|
|
|
|
if(!drop_permissions()) {
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
/* Place the cursor to the topl left of the output layout. */
|
|
wlr_cursor_warp(server.seat->cursor, NULL, 0, 0);
|
|
atexit(cleanup);
|
|
return 0;
|
|
end:
|
|
cleanup();
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
add_output_callback(struct wlr_backend *backend, void *data) {
|
|
long *dims = data;
|
|
wlr_headless_add_output(backend, dims[0], dims[1]);
|
|
}
|
|
|
|
void
|
|
create_output(char *line, struct cg_server *server) {
|
|
char *widthstr = strtok_r(NULL, ";", &line);
|
|
long dims[2] = {600, 200};
|
|
if(widthstr != NULL) {
|
|
dims[0] = strtol(widthstr, NULL, 10);
|
|
if(line[0] != '\0') {
|
|
++line;
|
|
}
|
|
}
|
|
char *heightstr = strtok_r(NULL, ";", &line);
|
|
if(heightstr != NULL) {
|
|
dims[1] = strtol(heightstr, NULL, 10);
|
|
}
|
|
long max_dim = 10000;
|
|
if(dims[0] > max_dim || dims[0] <= 0) {
|
|
wlr_log(WLR_ERROR, "height out of range.");
|
|
return;
|
|
} else if(dims[1] > max_dim || dims[1] <= 0) {
|
|
wlr_log(WLR_ERROR, "width out of range.");
|
|
return;
|
|
}
|
|
wlr_multi_for_each_backend(server->backend, add_output_callback, dims);
|
|
}
|
|
|
|
void
|
|
destroy_output(char *line, struct cg_server *server) {
|
|
if(wl_list_length(&server->outputs) < 2) {
|
|
return;
|
|
}
|
|
char *outpnstr = strtok_r(NULL, ";", &line);
|
|
long outpn = 0;
|
|
if(outpnstr != NULL) {
|
|
outpn = strtol(outpnstr, NULL, 10);
|
|
}
|
|
outpn = outpn % wl_list_length(&server->outputs);
|
|
struct cg_output *it;
|
|
wl_list_for_each(it, &server->outputs, link) {
|
|
if(outpn == 0) {
|
|
break;
|
|
} else {
|
|
--outpn;
|
|
}
|
|
}
|
|
}
|