// Copyright 2020 - 2023, project-repo and the cagebreak contributors // SPDX-License-Identifier: MIT #define _POSIX_C_SOURCE 200809L #include "config.h" #include #include #include #include #include #include #if WLR_HAS_X11_BACKEND #include #endif #include #include #include #include #include #include #include #include #include #if CG_HAS_XWAYLAND #include #endif #include "keybinding.h" #include "message.h" #include "output.h" #include "seat.h" #include "server.h" #include "util.h" #include "view.h" #include "workspace.h" #if CG_HAS_XWAYLAND #include "xwayland.h" #endif void output_clear(struct cg_output *output) { struct cg_server *server = output->server; wlr_scene_output_destroy(output->scene_output); if(server->running && server->curr_output == output && wl_list_length(&server->outputs) > 1) { keybinding_cycle_outputs(server, false, true); } wl_list_remove(&output->link); message_clear(output); struct cg_view *view, *view_tmp; if(server->running) { for(unsigned int i = 0; i < server->nws; ++i) { bool first = true; for(struct cg_tile *tile = output->workspaces[i]->focused_tile; first || output->workspaces[i]->focused_tile != tile; tile = tile->next) { first = false; workspace_tile_update_view(tile, NULL); } struct cg_workspace *ws = server->curr_output ->workspaces[server->curr_output->curr_workspace]; wl_list_for_each_safe(view, view_tmp, &output->workspaces[i]->views, link) { wl_list_remove(&view->link); if(wl_list_empty(&server->outputs)) { view->impl->destroy(view); } else { wl_list_insert(&ws->views, &view->link); wlr_scene_node_reparent(&view->scene_tree->node, ws->scene); view->workspace = ws; view->tile = ws->focused_tile; if(server->seat->focused_view == NULL) { seat_set_focus(server->seat, view); } } } wl_list_for_each_safe( view, view_tmp, &output->workspaces[i]->unmanaged_views, link) { wl_list_remove(&view->link); if(wl_list_empty(&server->outputs)) { view->impl->destroy(view); } else { wl_list_insert(&ws->unmanaged_views, &view->link); wlr_scene_node_reparent(&view->scene_tree->node, ws->scene); view->workspace = ws; view->tile = ws->focused_tile; } } } } } int output_get_num(const struct cg_output *output) { struct cg_output *it; int count = 1; wl_list_for_each(it, &output->server->outputs, link) { if(strcmp(output->wlr_output->name, it->wlr_output->name) == 0) { return count; } ++count; } return -1; } static void output_destroy(struct cg_output *output) { struct cg_server *server = output->server; char *outp_name = strdup(output->wlr_output->name); int outp_num = output_get_num(output); wl_list_remove(&output->destroy.link); wl_list_remove(&output->commit.link); wl_list_remove(&output->frame.link); output_clear(output); /*Important: due to unfortunate events, "workspace" and "output->workspace" * have nothing in common. The former is the workspace of a single output, * whereas the latter is the workspace of the outputs.*/ for(unsigned int i = 0; i < server->nws; ++i) { workspace_free(output->workspaces[i]); } free(output->workspaces); free(output); if(outp_name != NULL) { ipc_send_event(server, "{\"event_name\":\"destroy_output\",\"output\":\"%s\"," "\"output_id\":%d}", outp_name, outp_num); free(outp_name); } else { wlr_log(WLR_ERROR, "Failed to allocate memory for output name in output_destroy"); } if(wl_list_empty(&server->outputs)) { wl_display_terminate(server->wl_display); } } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct cg_output *output = wl_container_of(listener, output, destroy); output_destroy(output); } static void handle_output_frame(struct wl_listener *listener, void *data) { struct cg_output *output = wl_container_of(listener, output, frame); if(!output->wlr_output->enabled) { return; } struct wlr_scene_output *scene_output = wlr_scene_get_scene_output(output->server->scene, output->wlr_output); if(scene_output == NULL) { return; } wlr_scene_output_commit(scene_output, NULL); struct timespec now = {0}; clock_gettime(CLOCK_MONOTONIC, &now); wlr_scene_output_send_frame_done(scene_output, &now); } static int output_set_mode(struct wlr_output *output, int width, int height, float refresh_rate) { int mhz = (int)(refresh_rate * 1000); if(wl_list_empty(&output->modes)) { wlr_log(WLR_DEBUG, "Assigning custom mode to %s", output->name); wlr_output_set_custom_mode(output, width, height, refresh_rate > 0 ? mhz : 0); return 0; } struct wlr_output_mode *mode, *best = NULL; wl_list_for_each(mode, &output->modes, link) { if(mode->width == width && mode->height == height) { if(mode->refresh == mhz) { best = mode; break; } if(best == NULL || mode->refresh > best->refresh) { best = mode; } } } if(!best) { wlr_log(WLR_ERROR, "Configured mode for %s not available", output->name); wlr_log(WLR_INFO, "Picking preferred mode instead"); best = wlr_output_preferred_mode(output); } else { wlr_log(WLR_DEBUG, "Assigning configured mode to %s", output->name); } wlr_output_set_mode(output, best); wlr_output_commit(output); if(!wlr_output_test(output)) { wlr_log(WLR_ERROR, "Unable to assign configured mode to %s, picking arbitrary " "available mode", output->name); struct wlr_output_mode *mode; wl_list_for_each(mode, &output->modes, link) { if(mode == best) { continue; } wlr_output_set_mode(output, mode); wlr_output_commit(output); if(wlr_output_test(output)) { break; } } if(!wlr_output_test(output)) { return 1; } } return 0; } void output_insert(struct cg_server *server, struct cg_output *output) { struct cg_output *it, *prev_it = NULL; bool first = true; wl_list_for_each(it, &server->outputs, link) { if(it->priority < output->priority) { if(first == true) { wl_list_insert(&server->outputs, &output->link); } else { wl_list_insert(it->link.prev, &output->link); } return; } first = false; prev_it = it; } if(prev_it == NULL) { wl_list_insert(&server->outputs, &output->link); } else { wl_list_insert(&prev_it->link, &output->link); } } void output_apply_config(struct cg_server *server, struct cg_output *output, struct cg_output_config *config) { struct wlr_output *wlr_output = output->wlr_output; if(output->wlr_output->enabled) { wlr_output_layout_remove(server->output_layout, wlr_output); } if(config->priority != -1) { output->priority = config->priority; } if(config->angle != -1) { wlr_output_set_transform(wlr_output, config->angle); } if(config->scale != -1) { wlr_log(WLR_INFO, "Setting output scale to %f", config->scale); wlr_output_set_scale(wlr_output, config->scale); } if(config->pos.x != -1) { if(output_set_mode(wlr_output, config->pos.width, config->pos.height, config->refresh_rate) != 0) { wlr_log(WLR_ERROR, "Setting output mode failed, disabling output."); output_clear(output); wl_list_insert(&server->disabled_outputs, &output->link); wlr_output_enable(wlr_output, false); wlr_output_commit(wlr_output); return; } wlr_output_layout_add(server->output_layout, wlr_output, config->pos.x, config->pos.y); /* Since the size of the output may have changed, we * reinitialize all workspaces with a fullscreen layout */ for(unsigned int i = 0; i < output->server->nws; ++i) { output_make_workspace_fullscreen(output, i); } } else { wlr_output_layout_add_auto(server->output_layout, wlr_output); struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); if(preferred_mode) { wlr_output_set_mode(wlr_output, preferred_mode); } } /* Refuse to disable the only output */ if(config->status == OUTPUT_DISABLE && wl_list_length(&server->outputs) > 1) { output_clear(output); wl_list_insert(&server->disabled_outputs, &output->link); wlr_output_enable(wlr_output, false); wlr_output_commit(wlr_output); } else { wl_list_remove(&output->link); output_insert(server, output); wlr_output_enable(wlr_output, true); wlr_output_commit(wlr_output); } if(output->bg != NULL) { wlr_scene_node_destroy(&output->bg->node); output->bg = NULL; } struct wlr_scene_output *scene_output = wlr_scene_get_scene_output(output->server->scene, output->wlr_output); if(scene_output == NULL) { return; } output->bg = wlr_scene_rect_create( &scene_output->scene->tree, output->wlr_output->width, output->wlr_output->height, server->bg_color); struct wlr_box box; wlr_output_layout_get_box(server->output_layout, output->wlr_output, &box); wlr_scene_node_set_position(&output->bg->node, box.x, box.y); wlr_scene_node_lower_to_bottom(&output->bg->node); } struct cg_output_config * empty_output_config(void) { struct cg_output_config *cfg = calloc(1, sizeof(struct cg_output_config)); if(cfg == NULL) { wlr_log(WLR_ERROR, "Could not allocate output configuration."); return NULL; } cfg->status = OUTPUT_DEFAULT; cfg->pos.x = -1; cfg->pos.y = -1; cfg->pos.width = -1; cfg->pos.height = -1; cfg->output_name = NULL; cfg->refresh_rate = 0; cfg->priority = -1; cfg->scale = -1; cfg->angle = -1; return cfg; } /* cfg1 has precedence over cfg2 */ struct cg_output_config * merge_output_configs(struct cg_output_config *cfg1, struct cg_output_config *cfg2) { struct cg_output_config *out_cfg = empty_output_config(); if(cfg1->status == out_cfg->status) { out_cfg->status = cfg2->status; } else { out_cfg->status = cfg1->status; } if(cfg1->pos.x == out_cfg->pos.x) { out_cfg->pos.x = cfg2->pos.x; out_cfg->pos.y = cfg2->pos.y; out_cfg->pos.width = cfg2->pos.width; out_cfg->pos.height = cfg2->pos.height; } else { out_cfg->pos.x = cfg1->pos.x; out_cfg->pos.y = cfg1->pos.y; out_cfg->pos.width = cfg1->pos.width; out_cfg->pos.height = cfg1->pos.height; } if(cfg1->output_name == NULL) { if(cfg2->output_name == NULL) { out_cfg->output_name = NULL; } else { out_cfg->output_name = strdup(cfg2->output_name); } } else { out_cfg->output_name = strdup(cfg1->output_name); } if(cfg1->refresh_rate == out_cfg->refresh_rate) { out_cfg->refresh_rate = cfg2->refresh_rate; } else { out_cfg->refresh_rate = cfg1->refresh_rate; } if(cfg1->priority == out_cfg->priority) { out_cfg->priority = cfg2->priority; } else { out_cfg->priority = cfg1->priority; } if(cfg1->scale == out_cfg->scale) { out_cfg->scale = cfg2->scale; } else { out_cfg->scale = cfg1->scale; } if(cfg1->angle == out_cfg->angle) { out_cfg->angle = cfg2->angle; } else { out_cfg->angle = cfg1->angle; } return out_cfg; } void output_configure(struct cg_server *server, struct cg_output *output) { struct cg_output_config *tot_config = empty_output_config(); struct cg_output_config *config; wl_list_for_each(config, &server->output_config, link) { if(strcmp(config->output_name, output->wlr_output->name) == 0) { if(tot_config == NULL) { return; } struct cg_output_config *prev_config = tot_config; tot_config = merge_output_configs(config, tot_config); if(prev_config->output_name != NULL) { free(prev_config->output_name); } free(prev_config); } } if(tot_config != NULL) { output_apply_config(server, output, tot_config); } free(tot_config->output_name); free(tot_config); } static void handle_output_commit(struct wl_listener *listener, void *data) { struct cg_output *output = wl_container_of(listener, output, commit); struct wlr_output_event_commit *event = data; if(!output->wlr_output->enabled || output->workspaces == NULL) { return; } if(event->state->committed & (WLR_OUTPUT_STATE_TRANSFORM | WLR_OUTPUT_STATE_SCALE | WLR_OUTPUT_STATE_MODE)) { struct cg_view *view; wl_list_for_each( view, &output->workspaces[output->curr_workspace]->views, link) { if(view_is_visible(view)) { view_maximize(view, view->tile); } } } } void output_make_workspace_fullscreen(struct cg_output *output, int ws) { struct cg_server *server = output->server; struct cg_view *current_view = seat_get_focus(server->seat); if(current_view == NULL || ws != output->curr_workspace) { struct cg_view *it = NULL; wl_list_for_each(it, &output->workspaces[ws]->views, link) { if(view_is_visible(it)) { current_view = it; break; } } } workspace_free_tiles(output->workspaces[ws]); if(full_screen_workspace_tiles(server->output_layout, output->wlr_output, output->workspaces[ws], &server->tiles_curr_id) != 0) { wlr_log(WLR_ERROR, "Failed to allocate space for fullscreen workspace"); return; } struct cg_view *it_view; wl_list_for_each(it_view, &output->workspaces[ws]->views, link) { it_view->tile = output->workspaces[ws]->focused_tile; } if(ws == output->curr_workspace) { seat_set_focus(server->seat, current_view); } } #if CG_HAS_FANALYZE #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak" #endif void handle_new_output(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_output); struct wlr_output *wlr_output = data; if(!wlr_output_init_render(wlr_output, server->allocator, server->renderer)) { wlr_log(WLR_ERROR, "Failed to initialize output rendering"); return; } struct cg_output *output = calloc(1, sizeof(struct cg_output)); output->scene_output = wlr_scene_output_create(server->scene, wlr_output); if(!output) { wlr_log(WLR_ERROR, "Failed to allocate output"); return; } output->wlr_output = wlr_output; output->server = server; output->last_scanned_out_view = NULL; struct cg_output_priorities *it; int prio = -1; wl_list_for_each(it, &server->output_priorities, link) { if(strcmp(output->wlr_output->name, it->ident) == 0) { prio = it->priority; } } output->priority = prio; wlr_output_set_transform(wlr_output, WL_OUTPUT_TRANSFORM_NORMAL); output->workspaces = NULL; wl_list_init(&output->messages); if(!wlr_xcursor_manager_load(server->seat->xcursor_manager, wlr_output->scale)) { wlr_log(WLR_ERROR, "Cannot load XCursor theme for output '%s' with scale %f", wlr_output->name, wlr_output->scale); } output_insert(server, output); output_configure(server, output); output->workspaces = malloc(server->nws * sizeof(struct cg_workspace *)); for(unsigned int i = 0; i < server->nws; ++i) { output->workspaces[i] = full_screen_workspace(output); if(!output->workspaces[i]) { wlr_log(WLR_ERROR, "Failed to allocate workspaces for output"); return; } output->workspaces[i]->num = i; wl_list_init(&output->workspaces[i]->views); wl_list_init(&output->workspaces[i]->unmanaged_views); } wlr_scene_node_raise_to_top(&output->workspaces[0]->scene->node); workspace_focus(output, 0); /* We are the first output. Set the current output to this one. */ if(server->curr_output == NULL) { server->curr_output = output; } wlr_cursor_set_xcursor(server->seat->cursor, server->seat->xcursor_manager, DEFAULT_XCURSOR); wlr_cursor_warp(server->seat->cursor, NULL, 0, 0); output->destroy.notify = handle_output_destroy; wl_signal_add(&wlr_output->events.destroy, &output->destroy); output->frame.notify = handle_output_frame; wl_signal_add(&wlr_output->events.frame, &output->frame); output->commit.notify = handle_output_commit; wl_signal_add(&wlr_output->events.commit, &output->commit); ipc_send_event(server, "{\"event_name\":\"new_output\",\"output\":\"%s\",\"output_" "id\":%d,\"priority\":%d}", output->wlr_output->name, output_get_num(output), output->priority); } #if CG_HAS_FANALYZE #pragma GCC diagnostic pop #endif