swayfx/sway/tree/output.c
Brian Ashworth 8fd3f32c79 Fix segfaults on output destruction
This fixes two causes of segfaulting when an output is destroyed.

The first occurred when an output was never enabled. The issue was that
the destroy signal was never initialized so when it was emitted, sway
segfaulted. This was fixed by moving the initialization into
`output_create` since all outputs, regardless of whether they have ever
been enabled, will be destroyed at some point.

The second occurred when the cursor was on an output that was being
destroyed. The sway output would have already been removed, but if there
are other outputs, a cursor rebase would still occur. Since the
wlr_output still existed and the sway output was destroyed, the cursor
could be over nothing, resulting in a segfault when trying to get the
sway output, which was destroyed.
2019-01-10 03:07:36 -05:00

402 lines
11 KiB
C

#define _POSIX_C_SOURCE 200809L
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <wlr/types/wlr_output_damage.h>
#include "sway/ipc-server.h"
#include "sway/layers.h"
#include "sway/output.h"
#include "sway/tree/arrange.h"
#include "sway/tree/output.h"
#include "sway/tree/workspace.h"
#include "log.h"
#include "util.h"
static void restore_workspaces(struct sway_output *output) {
// Workspace output priority
for (int i = 0; i < root->outputs->length; i++) {
struct sway_output *other = root->outputs->items[i];
if (other == output) {
continue;
}
for (int j = 0; j < other->workspaces->length; j++) {
struct sway_workspace *ws = other->workspaces->items[j];
struct sway_output *highest =
workspace_output_get_highest_available(ws, NULL);
if (highest == output) {
workspace_detach(ws);
output_add_workspace(output, ws);
ipc_event_workspace(NULL, ws, "move");
j--;
}
}
if (other->workspaces->length == 0) {
char *next = workspace_next_name(other->wlr_output->name);
struct sway_workspace *ws = workspace_create(other, next);
free(next);
ipc_event_workspace(NULL, ws, "init");
}
}
// Saved workspaces
for (int i = 0; i < root->saved_workspaces->length; ++i) {
struct sway_workspace *ws = root->saved_workspaces->items[i];
output_add_workspace(output, ws);
ipc_event_workspace(NULL, ws, "move");
}
root->saved_workspaces->length = 0;
output_sort_workspaces(output);
}
struct sway_output *output_create(struct wlr_output *wlr_output) {
struct sway_output *output = calloc(1, sizeof(struct sway_output));
node_init(&output->node, N_OUTPUT, output);
output->wlr_output = wlr_output;
wlr_output->data = output;
wl_signal_add(&wlr_output->events.destroy, &output->destroy);
wl_signal_init(&output->events.destroy);
wl_list_insert(&root->all_outputs, &output->link);
output->workspaces = create_list();
output->current.workspaces = create_list();
return output;
}
void output_enable(struct sway_output *output, struct output_config *oc) {
if (!sway_assert(!output->enabled, "output is already enabled")) {
return;
}
struct wlr_output *wlr_output = output->wlr_output;
size_t len = sizeof(output->layers) / sizeof(output->layers[0]);
for (size_t i = 0; i < len; ++i) {
wl_list_init(&output->layers[i]);
}
output->enabled = true;
list_add(root->outputs, output);
output->lx = wlr_output->lx;
output->ly = wlr_output->ly;
wlr_output_transformed_resolution(wlr_output,
&output->width, &output->height);
restore_workspaces(output);
struct sway_workspace *ws = NULL;
if (!output->workspaces->length) {
// Create workspace
char *ws_name = workspace_next_name(wlr_output->name);
wlr_log(WLR_DEBUG, "Creating default workspace %s", ws_name);
ws = workspace_create(output, ws_name);
// Set each seat's focus if not already set
struct sway_seat *seat = NULL;
wl_list_for_each(seat, &server.input->seats, link) {
if (!seat->has_focus) {
seat_set_focus_workspace(seat, ws);
}
}
free(ws_name);
ipc_event_workspace(NULL, ws, "init");
}
apply_output_config(oc, output);
if (ws && config->default_orientation == L_NONE) {
// Since the output transformation and resolution could have changed
// due to applying the output config, the previously set layout for the
// created workspace may not be correct for `default_orientation auto`
ws->layout = output_get_default_layout(output);
}
input_manager_configure_xcursor();
wl_signal_add(&wlr_output->events.mode, &output->mode);
wl_signal_add(&wlr_output->events.transform, &output->transform);
wl_signal_add(&wlr_output->events.scale, &output->scale);
wl_signal_add(&wlr_output->events.present, &output->present);
wl_signal_add(&output->damage->events.frame, &output->damage_frame);
wl_signal_add(&output->damage->events.destroy, &output->damage_destroy);
output_add_listeners(output);
wl_signal_emit(&root->events.new_node, &output->node);
arrange_layers(output);
arrange_root();
}
static void evacuate_sticky(struct sway_workspace *old_ws,
struct sway_output *new_output) {
struct sway_workspace *new_ws = output_get_active_workspace(new_output);
while (old_ws->floating->length) {
struct sway_container *sticky = old_ws->floating->items[0];
container_detach(sticky);
workspace_add_floating(new_ws, sticky);
container_handle_fullscreen_reparent(sticky);
container_floating_move_to_center(sticky);
ipc_event_window(sticky, "move");
}
workspace_detect_urgent(new_ws);
}
static void output_evacuate(struct sway_output *output) {
if (!output->workspaces->length) {
return;
}
struct sway_output *fallback_output = NULL;
if (root->outputs->length > 1) {
fallback_output = root->outputs->items[0];
if (fallback_output == output) {
fallback_output = root->outputs->items[1];
}
}
while (output->workspaces->length) {
struct sway_workspace *workspace = output->workspaces->items[0];
workspace_detach(workspace);
struct sway_output *new_output =
workspace_output_get_highest_available(workspace, output);
if (!new_output) {
new_output = fallback_output;
}
if (workspace_is_empty(workspace)) {
// If floating is not empty, there are sticky containers to move
if (workspace->floating->length) {
evacuate_sticky(workspace, new_output);
}
workspace_begin_destroy(workspace);
continue;
}
if (new_output) {
workspace_output_add_priority(workspace, new_output);
output_add_workspace(new_output, workspace);
output_sort_workspaces(new_output);
ipc_event_workspace(NULL, workspace, "move");
} else {
list_add(root->saved_workspaces, workspace);
}
}
}
void output_destroy(struct sway_output *output) {
if (!sway_assert(output->node.destroying,
"Tried to free output which wasn't marked as destroying")) {
return;
}
if (!sway_assert(output->wlr_output == NULL,
"Tried to free output which still had a wlr_output")) {
return;
}
if (!sway_assert(output->node.ntxnrefs == 0, "Tried to free output "
"which is still referenced by transactions")) {
return;
}
list_free(output->workspaces);
list_free(output->current.workspaces);
free(output);
}
static void untrack_output(struct sway_container *con, void *data) {
struct sway_output *output = data;
int index = list_find(con->outputs, output);
if (index != -1) {
list_del(con->outputs, index);
}
}
void output_disable(struct sway_output *output) {
if (!sway_assert(output->enabled, "Expected an enabled output")) {
return;
}
wlr_log(WLR_DEBUG, "Disabling output '%s'", output->wlr_output->name);
wl_signal_emit(&output->events.destroy, output);
output_evacuate(output);
root_for_each_container(untrack_output, output);
if (output->bg_pid) {
terminate_swaybg(output->bg_pid);
output->bg_pid = 0;
}
int index = list_find(root->outputs, output);
list_del(root->outputs, index);
wl_list_remove(&output->mode.link);
wl_list_remove(&output->transform.link);
wl_list_remove(&output->scale.link);
wl_list_remove(&output->present.link);
wl_list_remove(&output->damage_destroy.link);
wl_list_remove(&output->damage_frame.link);
output->enabled = false;
arrange_root();
}
void output_begin_destroy(struct sway_output *output) {
if (!sway_assert(!output->enabled, "Expected a disabled output")) {
return;
}
wlr_log(WLR_DEBUG, "Destroying output '%s'", output->wlr_output->name);
output->node.destroying = true;
node_set_dirty(&output->node);
wl_list_remove(&output->link);
wl_list_remove(&output->destroy.link);
output->wlr_output->data = NULL;
output->wlr_output = NULL;
}
struct output_config *output_find_config(struct sway_output *output) {
const char *name = output->wlr_output->name;
char identifier[128];
output_get_identifier(identifier, sizeof(identifier), output);
struct output_config *oc = NULL, *all = NULL;
for (int i = 0; i < config->output_configs->length; ++i) {
struct output_config *cur = config->output_configs->items[i];
if (strcasecmp(name, cur->name) == 0 ||
strcasecmp(identifier, cur->name) == 0) {
wlr_log(WLR_DEBUG, "Matched output config for %s", name);
oc = cur;
}
if (strcasecmp("*", cur->name) == 0) {
wlr_log(WLR_DEBUG, "Matched wildcard output config for %s", name);
all = cur;
}
if (oc && all) {
break;
}
}
if (!oc) {
oc = all;
}
return oc;
}
struct sway_output *output_from_wlr_output(struct wlr_output *output) {
return output->data;
}
struct sway_output *output_get_in_direction(struct sway_output *reference,
enum wlr_direction direction) {
if (!sway_assert(direction, "got invalid direction: %d", direction)) {
return NULL;
}
int lx = reference->wlr_output->lx + reference->width / 2;
int ly = reference->wlr_output->ly + reference->height / 2;
struct wlr_output *wlr_adjacent = wlr_output_layout_adjacent_output(
root->output_layout, direction, reference->wlr_output, lx, ly);
if (!wlr_adjacent) {
return NULL;
}
return output_from_wlr_output(wlr_adjacent);
}
void output_add_workspace(struct sway_output *output,
struct sway_workspace *workspace) {
if (workspace->output) {
workspace_detach(workspace);
}
list_add(output->workspaces, workspace);
workspace->output = output;
node_set_dirty(&output->node);
node_set_dirty(&workspace->node);
}
void output_for_each_workspace(struct sway_output *output,
void (*f)(struct sway_workspace *ws, void *data), void *data) {
for (int i = 0; i < output->workspaces->length; ++i) {
struct sway_workspace *workspace = output->workspaces->items[i];
f(workspace, data);
}
}
void output_for_each_container(struct sway_output *output,
void (*f)(struct sway_container *con, void *data), void *data) {
for (int i = 0; i < output->workspaces->length; ++i) {
struct sway_workspace *workspace = output->workspaces->items[i];
workspace_for_each_container(workspace, f, data);
}
}
struct sway_workspace *output_find_workspace(struct sway_output *output,
bool (*test)(struct sway_workspace *ws, void *data), void *data) {
for (int i = 0; i < output->workspaces->length; ++i) {
struct sway_workspace *workspace = output->workspaces->items[i];
if (test(workspace, data)) {
return workspace;
}
}
return NULL;
}
struct sway_container *output_find_container(struct sway_output *output,
bool (*test)(struct sway_container *con, void *data), void *data) {
struct sway_container *result = NULL;
for (int i = 0; i < output->workspaces->length; ++i) {
struct sway_workspace *workspace = output->workspaces->items[i];
if ((result = workspace_find_container(workspace, test, data))) {
return result;
}
}
return NULL;
}
static int sort_workspace_cmp_qsort(const void *_a, const void *_b) {
struct sway_workspace *a = *(void **)_a;
struct sway_workspace *b = *(void **)_b;
if (isdigit(a->name[0]) && isdigit(b->name[0])) {
int a_num = strtol(a->name, NULL, 10);
int b_num = strtol(b->name, NULL, 10);
return (a_num < b_num) ? -1 : (a_num > b_num);
} else if (isdigit(a->name[0])) {
return -1;
} else if (isdigit(b->name[0])) {
return 1;
}
return 0;
}
void output_sort_workspaces(struct sway_output *output) {
list_stable_sort(output->workspaces, sort_workspace_cmp_qsort);
}
void output_get_box(struct sway_output *output, struct wlr_box *box) {
box->x = output->lx;
box->y = output->ly;
box->width = output->width;
box->height = output->height;
}
enum sway_container_layout output_get_default_layout(
struct sway_output *output) {
if (config->default_layout != L_NONE) {
return config->default_layout;
}
if (config->default_orientation != L_NONE) {
return config->default_orientation;
}
if (output->height > output->width) {
return L_VERT;
}
return L_HORIZ;
}