/* * Cagebreak: A Wayland tiling compositor. * * Copyright (C) 2018-2020 Jente Hidskes * * See the LICENSE file accompanying this file. */ #define _POSIX_C_SOURCE 200812L #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if CG_HAS_XWAYLAND #include #endif #include #include #include #include #if CG_HAS_XWAYLAND #include #endif #include "idle_inhibit_v1.h" #include "keybinding.h" #include "message.h" #include "output.h" #include "parse.h" #include "seat.h" #include "server.h" #include "view.h" #include "xdg_shell.h" #if CG_HAS_XWAYLAND #include "xwayland.h" #endif #ifndef WAIT_ANY #define WAIT_ANY -1 #endif void set_sig_handler(int sig, void (*action)(int)) { struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = action; sigemptyset(&act.sa_mask); if(sigaction(sig, &act, NULL)) { wlr_log(WLR_ERROR, "Error setting signal handler: %s\n", strerror(errno)); } } void sig_chld_handler(int signal) { int pid = 1; while(pid > 0) { pid = waitpid(WAIT_ANY, NULL, WNOHANG); } } 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; } static int handle_signal(int signal, void *const data) { struct cg_server *server = data; switch(signal) { case SIGINT: /* Fallthrough */ case SIGTERM: display_terminate(server); return 0; case SIGALRM: { struct cg_output *output; wl_list_for_each(output, &server->outputs, link) { message_clear(output); } return 0; } default: return 0; } } static void usage(FILE *file, const char *const cage) { fprintf(file, "Usage: %s [OPTIONS]\n" "\n" " -r\t Rotate the output 90 degrees clockwise, specify up to three " "times\n" #ifdef DEBUG " -D\t Turn on damage tracking debugging\n" #endif " -h\t Display this help message\n" " -v\t Show the version number and exit\n", cage); } static bool parse_args(struct cg_server *server, int argc, char *argv[]) { int c; #ifdef DEBUG while((c = getopt(argc, argv, "rDhv")) != -1) { #else while((c = getopt(argc, argv, "rhv")) != -1) { #endif switch(c) { case 'r': server->output_transform++; if(server->output_transform > WL_OUTPUT_TRANSFORM_270) { server->output_transform = WL_OUTPUT_TRANSFORM_NORMAL; } break; #ifdef DEBUG case 'D': server->debug_damage_tracking = true; break; #endif case 'h': usage(stdout, argv[0]); return false; case 'v': fprintf(stdout, "Cagebreak version " CG_VERSION "\n"); exit(0); default: usage(stderr, argv[0]); return false; } } if(optind > argc) { usage(stderr, argv[0]); return false; } return true; } /* Parse config file. Lines longer than "max_line_size" are ignored */ int set_configuration(struct cg_server *server, const char *const config_file_path) { FILE *config_file = fopen(config_file_path, "r"); if(config_file == NULL) { wlr_log(WLR_ERROR, "Could not open config file \"%s\"", config_file_path); return -1; } size_t max_line_size = 256; char line[max_line_size * sizeof(char)]; for(unsigned int line_num = 1; fgets(line, max_line_size, config_file) != NULL; ++line_num) { line[strcspn(line, "\n")] = '\0'; if(*line != '\0' && *line != '#') { if(parse_rc_line(server, line) != 0) { wlr_log(WLR_ERROR, "Error in config file \"%s\", line %d\n", config_file_path, line_num); return -1; } } } return 0; } char * get_config_file() { const char *config_home_path = getenv("XDG_CONFIG_HOME"); char *addition = "/cagebreak/config"; if(config_home_path == NULL || config_home_path[0] == '\0') { config_home_path = getenv("HOME"); if(config_home_path == NULL || config_home_path[0] == '\0') { return NULL; } addition = "/.config/cagebreak/config"; } char *config_path = malloc( (strlen(config_home_path) + strlen(addition) + 1) * sizeof(char)); sprintf(config_path, "%s%s", config_home_path, addition); return config_path; } int main(int argc, char *argv[]) { struct cg_server server = {0}; struct wl_event_loop *event_loop = NULL; struct wl_event_source *sigint_source = NULL; struct wl_event_source *sigterm_source = NULL; struct wl_event_source *sigalrm_source = NULL; struct wlr_backend *backend = NULL; struct wlr_renderer *renderer = 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_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; struct wlr_xcursor_manager *xcursor_manager = NULL; #endif int ret = 0; if(!parse_args(&server, argc, argv)) { return 1; } #ifdef DEBUG wlr_log_init(WLR_DEBUG, NULL); #else wlr_log_init(WLR_ERROR, NULL); #endif /* 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; } server.wl_display = wl_display_create(); if(!server.wl_display) { wlr_log(WLR_ERROR, "Cannot allocate a Wayland display"); return 1; } server.running = true; server.modes = malloc(4 * sizeof(char *)); server.modes[0] = strdup("top"); server.modes[1] = strdup("root"); server.modes[2] = strdup("resize"); server.modes[3] = NULL; server.nws = 1; server.message_timeout = 2; event_loop = wl_display_get_event_loop(server.wl_display); sigint_source = wl_event_loop_add_signal(event_loop, SIGINT, handle_signal, &server); sigterm_source = wl_event_loop_add_signal(event_loop, SIGTERM, handle_signal, &server); sigalrm_source = wl_event_loop_add_signal(event_loop, SIGALRM, handle_signal, &server); server.event_loop = event_loop; set_sig_handler(SIGCHLD, sig_chld_handler); backend = wlr_backend_autocreate(server.wl_display, NULL); if(!backend) { wlr_log(WLR_ERROR, "Unable to create the wlroots backend"); ret = 1; goto end; } server.backend = backend; if(!drop_permissions()) { 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; } renderer = wlr_backend_get_renderer(backend); wlr_renderer_init_wl_display(renderer, server.wl_display); server.bg_color = (float[4]){0, 0, 0, 1}; wl_list_init(&server.outputs); server.output_layout = wlr_output_layout_create(); if(!server.output_layout) { wlr_log(WLR_ERROR, "Unable to create output layout"); ret = 1; goto end; } compositor = wlr_compositor_create(server.wl_display, 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; } /* 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); 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); 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; } 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); 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; } 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 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 { wlr_log(WLR_DEBUG, "Cagebreak " CG_VERSION " is running on Wayland display %s", socket); } #if CG_HAS_XWAYLAND wlr_xwayland_set_seat(xwayland, server.seat->seat); #endif { // config_file should only be visible as long as it is valid char *config_file = get_config_file(); if(config_file == NULL) { wlr_log(WLR_ERROR, "Unable to get path to config file"); ret = 1; goto end; } if(set_configuration(&server, config_file) != 0) { free(config_file); ret = 1; goto end; } free(config_file); } /* Place the cursor to the topl left of the output layout. */ wlr_cursor_warp(server.seat->cursor, NULL, 0, 0); wl_display_run(server.wl_display); #if CG_HAS_XWAYLAND wlr_xwayland_destroy(xwayland); wlr_xcursor_manager_destroy(xcursor_manager); #endif wl_display_destroy_clients(server.wl_display); end: for(unsigned int i = 0; server.modes[i] != NULL; ++i) { free(server.modes[i]); } free(server.modes); keybinding_list_free(server.keybindings); wl_event_source_remove(sigint_source); wl_event_source_remove(sigterm_source); wl_event_source_remove(sigalrm_source); seat_destroy(server.seat); /* This function is not null-safe, but we only ever get here with a proper wl_display. */ wl_display_destroy(server.wl_display); wlr_output_layout_destroy(server.output_layout); return ret; }